From 5fdce33b11e77dfbc6245e2003d6c3708ec9bc25 Mon Sep 17 00:00:00 2001 From: cam900 Date: Wed, 23 Mar 2022 01:48:45 +0900 Subject: [PATCH 01/48] 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 02/48] 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 03/48] 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 04/48] 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 05/48] 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 bd36a4ffdcb85e6b8283de2ac8c749d0cea4f75c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 23 Mar 2022 23:56:59 -0500 Subject: [PATCH 06/48] dev71 - more compatibility flags for .mod --- papers/format.md | 6 ++++- src/engine/engine.cpp | 3 ++- src/engine/engine.h | 8 +++--- src/engine/fileOps.cpp | 27 ++++++++++++++++++-- src/engine/playback.cpp | 56 ++++++++++++++++++++++++----------------- src/engine/song.h | 8 +++++- src/gui/compatFlags.cpp | 12 +++++++++ 7 files changed, 89 insertions(+), 31 deletions(-) diff --git a/papers/format.md b/papers/format.md index af2c4a5c..fc954ba0 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: +- 71: Furnace dev71 - 70: Furnace dev70 - 69: Furnace dev69 - 68: Furnace dev68 @@ -238,7 +239,10 @@ size | description | this is 2.0f for modules before 59 --- | **extended compatibility flags** (>=70) 1 | broken speed selection - 31 | reserved + 1 | no slides on first tick (>=71) or reserved + 1 | next row reset arp pos (>=71) or reserved + 1 | ignore jump at end (>=71) or reserved + 28 | reserved ``` # instrument diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 48f88bfa..2d7b84d9 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -144,7 +144,7 @@ void DivEngine::walkSong(int& loopOrder, int& loopRow, int& loopEnd) { effectVal=pat[k]->data[j][5+(l<<1)]; if (effectVal<0) effectVal=0; if (pat[k]->data[j][4+(l<<1)]==0x0d) { - if (nextOrder==-1 && i24) { @@ -825,6 +828,11 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { if (ds.version<69) { ds.arp0Reset=false; } + if (ds.version<71) { + ds.noSlidesOnFirstTick=false; + ds.rowResetsArpPos=false; + ds.ignoreJumpAtEnd=true; + } ds.isDMF=false; reader.readS(); // reserved @@ -1071,7 +1079,16 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { if (ds.version>=70) { // extended compat flags ds.brokenSpeedSel=reader.readC(); - for (int i=0; i<31; i++) { + if (ds.version>=71) { + song.noSlidesOnFirstTick=reader.readC(); + song.rowResetsArpPos=reader.readC(); + song.ignoreJumpAtEnd=reader.readC(); + } else { + reader.readC(); + reader.readC(); + reader.readC(); + } + for (int i=0; i<28; i++) { reader.readC(); } } @@ -1282,6 +1299,9 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { DivSong ds; ds.tuning=436.0; ds.version=DIV_VERSION_MOD; + ds.noSlidesOnFirstTick=true; + ds.rowResetsArpPos=true; + ds.ignoreJumpAtEnd=false; int insCount=31; bool bypassLimits=false; @@ -1916,7 +1936,10 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { // extended compat flags w->writeC(song.brokenSpeedSel); - for (int i=0; i<31; i++) { + w->writeC(song.noSlidesOnFirstTick); + w->writeC(song.rowResetsArpPos); + w->writeC(song.ignoreJumpAtEnd); + for (int i=0; i<28; i++) { w->writeC(0); } diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 1d7f2417..22e95b02 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -821,7 +821,7 @@ void DivEngine::processRow(int i, bool afterDelay) { } break; case 0x0d: // next order - if (changeOrd<0 && curOrder<(song.ordersLen-1)) { + if (changeOrd<0 && (curOrder<(song.ordersLen-1) || !song.ignoreJumpAtEnd)) { changeOrd=-2; changePos=effectVal; } @@ -1229,6 +1229,7 @@ void DivEngine::nextRow() { } if (haltOn==DIV_HALT_ROW) halted=true; + firstTick=true; } bool DivEngine::nextTick(bool noAccum) { @@ -1281,23 +1282,25 @@ bool DivEngine::nextTick(bool noAccum) { keyHit[i]=true; } } - if (chan[i].volSpeed!=0) { - chan[i].volume=(chan[i].volume&0xff)|(dispatchCmd(DivCommand(DIV_CMD_GET_VOLUME,i))<<8); - chan[i].volume+=chan[i].volSpeed; - if (chan[i].volume>chan[i].volMax) { - chan[i].volume=chan[i].volMax; - chan[i].volSpeed=0; - dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); - } else if (chan[i].volume<0) { - chan[i].volSpeed=0; - if (song.legacyVolumeSlides) { - chan[i].volume=chan[i].volMax+1; + if (!song.noSlidesOnFirstTick || !firstTick) { + if (chan[i].volSpeed!=0) { + chan[i].volume=(chan[i].volume&0xff)|(dispatchCmd(DivCommand(DIV_CMD_GET_VOLUME,i))<<8); + chan[i].volume+=chan[i].volSpeed; + if (chan[i].volume>chan[i].volMax) { + chan[i].volume=chan[i].volMax; + chan[i].volSpeed=0; + dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); + } else if (chan[i].volume<0) { + chan[i].volSpeed=0; + if (song.legacyVolumeSlides) { + chan[i].volume=chan[i].volMax+1; + } else { + chan[i].volume=0; + } + dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); } else { - chan[i].volume=0; + dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); } - dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); - } else { - dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); } } if (chan[i].vibratoDepth>0) { @@ -1315,13 +1318,15 @@ bool DivEngine::nextTick(bool noAccum) { break; } } - if ((chan[i].keyOn || chan[i].keyOff) && chan[i].portaSpeed>0) { - if (dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed,chan[i].portaNote))==2 && chan[i].portaStop && song.targetResetsSlides) { - chan[i].portaSpeed=0; - chan[i].oldNote=chan[i].note; - chan[i].note=chan[i].portaNote; - chan[i].inPorta=false; - dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note)); + if (!song.noSlidesOnFirstTick || !firstTick) { + if ((chan[i].keyOn || chan[i].keyOff) && chan[i].portaSpeed>0) { + if (dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed,chan[i].portaNote))==2 && chan[i].portaStop && song.targetResetsSlides) { + chan[i].portaSpeed=0; + chan[i].oldNote=chan[i].note; + chan[i].note=chan[i].portaNote; + chan[i].inPorta=false; + dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note)); + } } } if (chan[i].cut>0) { @@ -1354,6 +1359,9 @@ bool DivEngine::nextTick(bool noAccum) { dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note)); chan[i].resetArp=false; } + if (song.rowResetsArpPos && firstTick) { + chan[i].arpStage=-1; + } if (chan[i].arp!=0 && !chan[i].arpYield && chan[i].portaSpeed<1) { if (--chan[i].arpTicks<1) { chan[i].arpTicks=song.arpLen; @@ -1377,6 +1385,8 @@ bool DivEngine::nextTick(bool noAccum) { } } + firstTick=false; + // system tick for (int i=0; itick(); diff --git a/src/engine/song.h b/src/engine/song.h index c6d52840..19a89cfa 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -302,6 +302,9 @@ struct DivSong { bool newInsTriggersInPorta; bool arp0Reset; bool brokenSpeedSel; + bool noSlidesOnFirstTick; + bool rowResetsArpPos; + bool ignoreJumpAtEnd; DivOrders orders; std::vector ins; @@ -375,7 +378,10 @@ struct DivSong { oneTickCut(false), newInsTriggersInPorta(true), arp0Reset(true), - brokenSpeedSel(false) { + brokenSpeedSel(false), + noSlidesOnFirstTick(false), + rowResetsArpPos(false), + ignoreJumpAtEnd(false) { for (int i=0; i<32; i++) { system[i]=DIV_SYSTEM_NULL; systemVol[i]=64; diff --git a/src/gui/compatFlags.cpp b/src/gui/compatFlags.cpp index b49c251f..ca0538ea 100644 --- a/src/gui/compatFlags.cpp +++ b/src/gui/compatFlags.cpp @@ -85,6 +85,18 @@ void FurnaceGUI::drawCompatFlags() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("determines next speed based on whether the row is odd/even instead of alternating between speeds."); } + ImGui::Checkbox("Don't slide on the first tick of a row",&e->song.noSlidesOnFirstTick); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("simulates ProTracker's behavior of not applying volume/pitch slides on the first tick of a row."); + } + ImGui::Checkbox("Reset arpeggio position on row change",&e->song.rowResetsArpPos); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("simulates ProTracker's behavior of arpeggio being bound to the current tick of a row."); + } + ImGui::Checkbox("Ignore 0Dxx on the last order",&e->song.ignoreJumpAtEnd); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("if this is on, a jump to next row effect will not take place when it is on the last order of a song."); + } ImGui::Text("Loop modality:"); if (ImGui::RadioButton("Reset channels",e->song.loopModality==0)) { From 7f39ec723abf65c43bfb9b73d581ebe44cb4201e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 24 Mar 2022 01:27:53 -0500 Subject: [PATCH 07/48] SMS: overdrive 2 fixes nice --- src/engine/platform/sms.cpp | 17 ++++++++++++----- src/engine/playback.cpp | 12 ++++++------ 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/engine/platform/sms.cpp b/src/engine/platform/sms.cpp index 114d67b0..08cf568d 100644 --- a/src/engine/platform/sms.cpp +++ b/src/engine/platform/sms.cpp @@ -57,7 +57,10 @@ void DivPlatformSMS::tick() { for (int i=0; i<4; i++) { chan[i].std.next(); if (chan[i].std.hadVol) { - chan[i].outVol=((chan[i].vol&15)*MIN(15,chan[i].std.vol))>>4; + chan[i].outVol=MIN(15,chan[i].std.vol)-(15-(chan[i].vol&15)); + if (chan[i].outVol<0) chan[i].outVol=0; + // old formula + // ((chan[i].vol&15)*MIN(15,chan[i].std.vol))>>4; rWrite(0x90|(i<<5)|(isMuted[i]?15:(15-(chan[i].outVol&15)))); } if (chan[i].std.hadArp) { @@ -66,8 +69,11 @@ void DivPlatformSMS::tick() { chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp); chan[i].actualNote=chan[i].std.arp; } else { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp); - chan[i].actualNote=chan[i].note+chan[i].std.arp; + // TODO: check whether this weird octave boundary thing applies to other systems as well + int areYouSerious=chan[i].note+chan[i].std.arp; + while (areYouSerious>0x60) areYouSerious-=12; + chan[i].baseFreq=NOTE_PERIODIC(areYouSerious); + chan[i].actualNote=areYouSerious; } chan[i].freqChanged=true; } @@ -93,10 +99,11 @@ void DivPlatformSMS::tick() { if (chan[i].actualNote>0x5d) chan[i].freq=0x01; rWrite(0x80|i<<5|(chan[i].freq&15)); rWrite(chan[i].freq>>4); - if (i==2 && snNoiseMode&2) { + // what? + /*if (i==2 && snNoiseMode&2) { chan[3].baseFreq=chan[2].baseFreq; chan[3].actualNote=chan[2].actualNote; - } + }*/ chan[i].freqChanged=false; } } diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 22e95b02..cada57fe 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -742,10 +742,10 @@ void DivEngine::processRow(int i, bool afterDelay) { if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsPorta(dispatchChanOfChan[i])) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; - if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) { + /*if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) { chan[i+1].portaNote=-1; chan[i+1].portaSpeed=-1; - } + }*/ } chan[i].scheduledSlideReset=true; } @@ -763,10 +763,10 @@ void DivEngine::processRow(int i, bool afterDelay) { if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsPorta(dispatchChanOfChan[i])) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; - if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) { + /*if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) { chan[i+1].portaNote=-1; chan[i+1].portaSpeed=-1; - } + }*/ } chan[i].scheduledSlideReset=true; } @@ -1344,10 +1344,10 @@ bool DivEngine::nextTick(bool noAccum) { if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsPorta(dispatchChanOfChan[i])) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; - if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) { + /*if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) { chan[i+1].portaNote=-1; chan[i+1].portaSpeed=-1; - } + }*/ } dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); chan[i].scheduledSlideReset=true; From ea49c760c58b826c5d0a5b70bc08810a12b28d08 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 24 Mar 2022 04:53:09 -0500 Subject: [PATCH 08/48] OPLL: part 1 of fixing drum volumes --- src/engine/platform/opll.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index 4f21ba21..fc19daf6 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -651,6 +651,7 @@ int DivPlatformOPLL::dispatch(DivCommand c) { void DivPlatformOPLL::forceIns() { for (int i=0; i<9; i++) { + if (i>=6 && properDrums) continue; // update custom preset if (chan[i].state.opllPreset==0 && i==lastCustomMemory) { DivInstrumentFM::Operator& mod=chan[i].state.op[0]; @@ -675,7 +676,7 @@ void DivPlatformOPLL::forceIns() { } } } - if (drums) { + if (drums) { // WHAT?! FIX THIS! immWrite(0x16,0x20); immWrite(0x26,0x05); immWrite(0x16,0x20); @@ -687,6 +688,12 @@ void DivPlatformOPLL::forceIns() { immWrite(0x18,0xC0); immWrite(0x28,0x01); } + // UNTESTED + if (properDrums) { + rWrite(0x36,drumVol[0]); + rWrite(0x37,drumVol[1]|(drumVol[4]<<4)); + rWrite(0x38,drumVol[3]|(drumVol[2]<<4)); + } drumState=0; } From d2a78295cebac49f1b84018e94c8312612c5f7d8 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 25 Mar 2022 01:42:04 -0500 Subject: [PATCH 09/48] OPLL: wooooow how did this break --- src/engine/instrument.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index 785c9b19..b87dd133 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -724,6 +724,13 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { std.dutyMacroRel=-1; } + // clear wave macro if OPLL instrument and version<70 + if (version<70 && type==DIV_INS_OPLL) { + std.waveMacroLen=0; + std.waveMacroLoop=-1; + std.waveMacroRel=-1; + } + // sample map if (version>=67) { amiga.useNoteMap=reader.readC(); From 6e35640537598b93ba96a46e43bc585835a62d81 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 25 Mar 2022 02:10:44 -0500 Subject: [PATCH 10/48] GUI: less annoying PET waveform view --- src/gui/insEdit.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index d77a4632..baecd544 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1659,7 +1659,7 @@ void FurnaceGUI::drawInsEdit() { } } if (waveMax>0) { - NORMAL_MACRO(ins->std.waveMacro,ins->std.waveMacroLen,ins->std.waveMacroLoop,ins->std.waveMacroRel,0,waveMax,"wave",waveLabel,bitMode?64:160,ins->std.waveMacroOpen,bitMode,waveNames,false,NULL,0,0,((ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)?1:0),NULL,uiColors[GUI_COLOR_MACRO_WAVE],mmlString[3],0,waveMax,NULL,false); + NORMAL_MACRO(ins->std.waveMacro,ins->std.waveMacroLen,ins->std.waveMacroLoop,ins->std.waveMacroRel,0,waveMax,"wave",waveLabel,(bitMode && ins->type!=DIV_INS_PET)?64:160,ins->std.waveMacroOpen,bitMode,waveNames,false,NULL,0,0,((ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)?1:0),NULL,uiColors[GUI_COLOR_MACRO_WAVE],mmlString[3],0,waveMax,NULL,false); } if (ex1Max>0) { if (ins->type==DIV_INS_C64) { From 03da02711a62cea9f04a28f7e6d377a876730712 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 25 Mar 2022 02:10:57 -0500 Subject: [PATCH 11/48] OPLL: it's tested now. works last thing to do is to restore drum pitches --- src/engine/platform/opll.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index fc19daf6..55a1fa80 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -688,7 +688,7 @@ void DivPlatformOPLL::forceIns() { immWrite(0x18,0xC0); immWrite(0x28,0x01); } - // UNTESTED + // restore drum volumes if (properDrums) { rWrite(0x36,drumVol[0]); rWrite(0x37,drumVol[1]|(drumVol[4]<<4)); From ed857b20c4738692566ef55be2ecdfb9c0bcab11 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 25 Mar 2022 02:52:41 -0500 Subject: [PATCH 12/48] potentially breaking change: better freq formula now using a 4096-entry-long table for calculating final period/frequency see issue #303 --- src/engine/engine.cpp | 12 ++++++++++++ src/engine/engine.h | 2 ++ src/engine/platform/vic20.cpp | 13 ++++++++----- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 2d7b84d9..1000395b 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -832,9 +832,17 @@ double DivEngine::calcBaseFreq(double clock, double divider, int note, bool peri int DivEngine::calcFreq(int base, int pitch, bool period, int octave) { if (song.linearPitch) { + pitch+=2048; + if (pitch<0) pitch=0; + if (pitch>4095) pitch=4095; + return period? + (base*reversePitchTable[pitch])>>10: + (base*pitchTable[pitch])>>10; + /* return period? base*pow(2,-(double)pitch/(12.0*128.0))/(98.0+globalPitch*6.0)*98.0: (base*pow(2,(double)pitch/(12.0*128.0))*(98+globalPitch*6))/98; + */ } return period? base-pitch: @@ -2850,6 +2858,10 @@ bool DivEngine::init() { for (int i=0; i<64; i++) { vibTable[i]=127*sin(((double)i/64.0)*(2*M_PI)); } + for (int i=0; i<4096; i++) { + reversePitchTable[i]=round(1024.0*pow(2.0,(2048.0-(double)i)/(12.0*128.0))); + pitchTable[i]=round(1024.0*pow(2.0,((double)i-2048.0)/(12.0*128.0))); + } for (int i=0; i #define rWrite(a,v) {regPool[(a)]=(v)&0xff; vic_sound_machine_store(vic,a,(v)&0xff);} - -#define CHIP_DIVIDER 32 #define SAMP_DIVIDER 4 +const int chipDividers[4]={ + 128, 64, 32, 64 +}; + const char* regCheatSheetVIC[]={ "CH1_Pitch", "0A", "CH2_Pitch", "0B", @@ -93,6 +95,7 @@ void DivPlatformVIC20::writeOutVol(int ch) { void DivPlatformVIC20::tick() { for (int i=0; i<4; i++) { + int CHIP_DIVIDER=chipDividers[i]; chan[i].std.next(); if (chan[i].std.hadVol) { int env=chan[i].std.vol; @@ -121,10 +124,9 @@ void DivPlatformVIC20::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<3) chan[i].freq>>=(2-i); - else chan[i].freq>>=1; + printf("%d freq: %d\n",i,chan[i].freq); if (chan[i].freq<1) chan[i].freq=1; - if (chan[i].freq>127) chan[i].freq=0; + if (chan[i].freq>127) chan[i].freq=127; if (isMuted[i]) chan[i].keyOn=false; if (chan[i].keyOn) { if (i<3) { @@ -150,6 +152,7 @@ void DivPlatformVIC20::tick() { } int DivPlatformVIC20::dispatch(DivCommand c) { + int CHIP_DIVIDER=chipDividers[c.chan]; switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins); From 0687a6f21777bfaca2a0bc80c56312c57c10a5f8 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 25 Mar 2022 03:18:44 -0500 Subject: [PATCH 13/48] this stupid effect --- src/engine/engine.cpp | 4 ++-- src/engine/playback.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 1000395b..ed21aed0 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -836,8 +836,8 @@ int DivEngine::calcFreq(int base, int pitch, bool period, int octave) { if (pitch<0) pitch=0; if (pitch>4095) pitch=4095; return period? - (base*reversePitchTable[pitch])>>10: - (base*pitchTable[pitch])>>10; + ((base*(reversePitchTable[pitch]))>>10): + (((base*(pitchTable[pitch]))>>10)*(16+globalPitch))/16; /* return period? base*pow(2,-(double)pitch/(12.0*128.0))/(98.0+globalPitch*6.0)*98.0: diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index cada57fe..3a64af7a 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -983,7 +983,7 @@ void DivEngine::processRow(int i, bool afterDelay) { if (chan[i].pitch<-128) chan[i].pitch=-128; if (chan[i].pitch>127) chan[i].pitch=127; } - chan[i].pitch+=globalPitch; + //chan[i].pitch+=globalPitch; dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); break; case 0xea: // legato mode From 5f7078db4206d0f6455154b86d7056f8d028eac3 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 25 Mar 2022 03:41:43 -0500 Subject: [PATCH 14/48] bang bang bang --- src/engine/engine.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index ed21aed0..e9de013a 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -832,17 +832,15 @@ double DivEngine::calcBaseFreq(double clock, double divider, int note, bool peri int DivEngine::calcFreq(int base, int pitch, bool period, int octave) { if (song.linearPitch) { + // global pitch multiplier + int whatTheFuck=(1024+(globalPitch<<6)-(globalPitch<0?globalPitch-6:0)); + if (whatTheFuck<1) whatTheFuck=1; // avoids division by zero but please kill me pitch+=2048; if (pitch<0) pitch=0; if (pitch>4095) pitch=4095; return period? - ((base*(reversePitchTable[pitch]))>>10): - (((base*(pitchTable[pitch]))>>10)*(16+globalPitch))/16; - /* - return period? - base*pow(2,-(double)pitch/(12.0*128.0))/(98.0+globalPitch*6.0)*98.0: - (base*pow(2,(double)pitch/(12.0*128.0))*(98+globalPitch*6))/98; - */ + ((base*(reversePitchTable[pitch]))/whatTheFuck): + (((base*(pitchTable[pitch]))>>10)*whatTheFuck)/1024; } return period? base-pitch: From 26791df58e7e0a37e256a84527e9c5861110c831 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 25 Mar 2022 18:16:47 -0500 Subject: [PATCH 15/48] GUI: redesign FM editor layout, part 1 thanks Raijin for the concept --- src/gui/about.cpp | 1 + src/gui/gui.h | 2 + src/gui/insEdit.cpp | 518 +++++++++++++++++++++++++++++-------------- src/gui/settings.cpp | 17 ++ 4 files changed, 367 insertions(+), 171 deletions(-) diff --git a/src/gui/about.cpp b/src/gui/about.cpp index 4d1322a5..386b01b0 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -47,6 +47,7 @@ const char* aboutLine[]={ "-- graphics/UI design --", "tildearrow", "BlastBrothers", + "Raijin", "", "-- documentation --", "tildearrow", diff --git a/src/gui/gui.h b/src/gui/gui.h index 315ce742..1c646663 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -622,6 +622,7 @@ class FurnaceGUI { int roundedButtons; int roundedMenus; int loadJapanese; + int fmLayout; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -672,6 +673,7 @@ class FurnaceGUI { roundedButtons(1), roundedMenus(0), loadJapanese(0), + fmLayout(0), maxUndoSteps(100), mainFontPath(""), patFontPath(""), diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index baecd544..718eaf01 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1053,207 +1053,383 @@ void FurnaceGUI::drawInsEdit() { ins->fm.op[1].tl&=15; P(ImGui::SliderScalar("Volume##TL",ImGuiDataType_U8,&ins->fm.op[1].tl,&_FIFTEEN,&_ZERO)); rightClickable } - if (willDisplayOps) if (ImGui::BeginTable("FMOperators",2,ImGuiTableFlags_SizingStretchSame)) { - for (int i=0; ifm.op[(opCount==4)?opOrder[i]:i]; - if ((i+1)&1) ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Separator(); - ImGui::PushID(fmt::sprintf("op%d",i).c_str()); - ImGui::Dummy(ImVec2(dpiScale,dpiScale)); - if (ins->type==DIV_INS_OPL && ins->fm.opllPreset==16) { - if (i==1) { - ImGui::Text("Envelope 2 (kick only)"); - } else { - ImGui::Text("Envelope"); - } - } else { - ImGui::Text("OP%d",i+1); - } - - ImGui::SameLine(); - - bool amOn=op.am; - if (ImGui::Checkbox(FM_NAME(FM_AM),&amOn)) { PARAMETER - op.am=amOn; - } - - ImGui::SameLine(); - - int maxTl=127; - if (ins->type==DIV_INS_OPLL) { - if (i==1) { - maxTl=15; - } else { - maxTl=63; - } - } - if (ins->type==DIV_INS_OPL) { - maxTl=63; - } - int maxArDr=(ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ)?31:15; - - bool ssgOn=op.ssgEnv&8; - bool ksrOn=op.ksr; - bool vibOn=op.vib; - bool susOn=op.sus; // don't you make fun of this one - unsigned char ssgEnv=op.ssgEnv&7; - if (ins->type!=DIV_INS_OPL && ins->type!=DIV_INS_OPZ) { - if (ImGui::Checkbox((ins->type==DIV_INS_OPLL)?FM_NAME(FM_EGS):"SSG On",&ssgOn)) { PARAMETER - op.ssgEnv=(op.ssgEnv&7)|(ssgOn<<3); - } - if (ins->type==DIV_INS_FM) { - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Only for Genesis and Neo Geo systems"); - } - } - } - - if (ins->type==DIV_INS_OPL) { - if (ImGui::Checkbox(FM_NAME(FM_SUS),&susOn)) { PARAMETER - op.sus=susOn; - } - } - - //52.0 controls vert scaling; default 96 - drawFMEnv(op.tl&maxTl,op.ar&maxArDr,op.dr&maxArDr,(ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPLL)?((op.rr&15)*2):op.d2r&31,op.rr&15,op.sl&15,maxTl,maxArDr,ImVec2(ImGui::GetContentRegionAvail().x,52.0*dpiScale)); - //P(ImGui::SliderScalar(FM_NAME(FM_AR),ImGuiDataType_U8,&op.ar,&_ZERO,&_THIRTY_ONE)); rightClickable - if (ImGui::BeginTable("opParams",2,ImGuiTableFlags_SizingStretchProp)) { - ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,0.0); \ - ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed,0.0); \ - - ImGui::TableNextRow(); + if (willDisplayOps) { + if (settings.fmLayout==0) { + if (ImGui::BeginTable("FMOperators",14,ImGuiTableFlags_SizingStretchProp|ImGuiTableFlags_Borders)) { + // header + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - op.ar&=maxArDr; - P(ImGui::SliderScalar("##AR",ImGuiDataType_U8,&op.ar,&maxArDr,&_ZERO)); rightClickable - ImGui::TableNextColumn(); - ImGui::Text("%s",FM_NAME(FM_AR)); - ImGui::TableNextRow(); ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - op.dr&=maxArDr; - P(ImGui::SliderScalar("##DR",ImGuiDataType_U8,&op.dr,&maxArDr,&_ZERO)); rightClickable + ImGui::TextUnformatted(FM_NAME(FM_AR)); ImGui::TableNextColumn(); - ImGui::Text("%s",FM_NAME(FM_DR)); - - ImGui::TableNextRow(); + ImGui::TextUnformatted(FM_NAME(FM_DR)); ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - P(ImGui::SliderScalar("##SL",ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); rightClickable - ImGui::TableNextColumn(); - ImGui::Text("%s",FM_NAME(FM_SL)); - + ImGui::TextUnformatted(FM_NAME(FM_SL)); if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { - ImGui::TableNextRow(); ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - P(ImGui::SliderScalar("##D2R",ImGuiDataType_U8,&op.d2r,&_THIRTY_ONE,&_ZERO)); rightClickable - ImGui::TableNextColumn(); - ImGui::Text("%s",FM_NAME(FM_D2R)); + ImGui::TextUnformatted(FM_NAME(FM_D2R)); } - - ImGui::TableNextRow(); ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - P(ImGui::SliderScalar("##RR",ImGuiDataType_U8,&op.rr,&_FIFTEEN,&_ZERO)); rightClickable + ImGui::TextUnformatted(FM_NAME(FM_RR)); ImGui::TableNextColumn(); - ImGui::Text("%s",FM_NAME(FM_RR)); - - ImGui::TableNextRow(); + ImGui::TextUnformatted(FM_NAME(FM_TL)); ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - op.tl&=maxTl; - P(ImGui::SliderScalar("##TL",ImGuiDataType_U8,&op.tl,&maxTl,&_ZERO)); rightClickable - ImGui::TableNextColumn(); - ImGui::Text("%s",FM_NAME(FM_TL)); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Separator(); - ImGui::TableNextColumn(); - ImGui::Separator(); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { - P(ImGui::SliderScalar("##RS",ImGuiDataType_U8,&op.rs,&_ZERO,&_THREE)); rightClickable - ImGui::TableNextColumn(); - ImGui::Text("%s",FM_NAME(FM_RS)); + ImGui::TextUnformatted(FM_NAME(FM_RS)); } else { - P(ImGui::SliderScalar("##KSL",ImGuiDataType_U8,&op.ksl,&_ZERO,&_THREE)); rightClickable - ImGui::TableNextColumn(); - ImGui::Text("%s",FM_NAME(FM_KSL)); + ImGui::TextUnformatted(FM_NAME(FM_KSR)); } - - ImGui::TableNextRow(); ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - P(ImGui::SliderScalar(FM_NAME(FM_MULT),ImGuiDataType_U8,&op.mult,&_ZERO,&_FIFTEEN)); rightClickable + ImGui::TextUnformatted(FM_NAME(FM_MULT)); ImGui::TableNextColumn(); - ImGui::Text("%s",FM_NAME(FM_MULT)); - - if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { - int detune=(op.dt&7)-3; - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::SliderInt("##DT",&detune,-3,4)) { PARAMETER - op.dt=detune+3; - } rightClickable - ImGui::TableNextColumn(); - ImGui::Text("%s",FM_NAME(FM_DT)); + ImGui::TextUnformatted(FM_NAME(FM_DT)); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(FM_NAME(FM_DT2)); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(FM_NAME(FM_SSG)); + ImGui::TableNextColumn(); + ImGui::TextUnformatted("Envelope"); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(FM_NAME(FM_AM)); + float sliderHeight=32.0f*dpiScale; + + for (int i=0; ifm.op[(opCount==4)?opOrder[i]:i]; ImGui::TableNextRow(); ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - P(ImGui::SliderScalar("##DT2",ImGuiDataType_U8,&op.dt2,&_ZERO,&_THREE)); rightClickable - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Only for Arcade system"); + + if (i==0) sliderHeight=(ImGui::GetContentRegionAvail().y/opCount)-ImGui::GetStyle().ItemSpacing.y; + + ImGui::PushID(fmt::sprintf("op%d",i).c_str()); + if (ins->type==DIV_INS_OPL && ins->fm.opllPreset==16) { + if (i==1) { + ImGui::Text("Kick"); + } else { + ImGui::Text("Env"); + } + } else { + ImGui::Text("OP%d",i+1); } - ImGui::TableNextColumn(); - ImGui::Text("%s",FM_NAME(FM_DT2)); - ImGui::TableNextRow(); + int maxTl=127; + if (ins->type==DIV_INS_OPLL) { + if (i==1) { + maxTl=15; + } else { + maxTl=63; + } + } + if (ins->type==DIV_INS_OPL) { + maxTl=63; + } + int maxArDr=(ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ)?31:15; + bool ssgOn=op.ssgEnv&8; + /*bool ksrOn=op.ksr; + bool vibOn=op.vib; + bool susOn=op.sus;*/ + unsigned char ssgEnv=op.ssgEnv&7; + ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::SliderScalar("##SSG",ImGuiDataType_U8,&ssgEnv,&_ZERO,&_SEVEN,ssgEnvTypes[ssgEnv])) { PARAMETER - op.ssgEnv=(op.ssgEnv&8)|(ssgEnv&7); - } rightClickable + op.ar&=maxArDr; + P(ImGui::VSliderScalar("##AR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.ar,&_ZERO,&maxArDr)); + ImGui::TableNextColumn(); - ImGui::Text("%s",FM_NAME(FM_SSG)); + op.dr&=maxArDr; + P(ImGui::VSliderScalar("##DR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dr,&_ZERO,&maxArDr)); + + ImGui::TableNextColumn(); + op.sl&=15; + P(ImGui::VSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); + + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + ImGui::TableNextColumn(); + op.d2r&=31; + P(ImGui::VSliderScalar("##D2R",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.d2r,&_ZERO,&_THIRTY_ONE)); + } + + ImGui::TableNextColumn(); + op.rr&=15; + P(ImGui::VSliderScalar("##RR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rr,&_ZERO,&_FIFTEEN)); + + ImGui::TableNextColumn(); + op.tl&=maxTl; + P(ImGui::VSliderScalar("##TL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.tl,&maxTl,&_ZERO)); + + ImGui::TableNextColumn(); + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + P(ImGui::VSliderScalar("##RS",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rs,&_ZERO,&_THREE)); + } else { + P(ImGui::VSliderScalar("##KSL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.ksl,&_ZERO,&_THREE)); + } + + ImGui::TableNextColumn(); + P(ImGui::VSliderScalar("##MULT",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.mult,&_ZERO,&_FIFTEEN)); + + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + int detune=(op.dt&7)-3; + ImGui::TableNextColumn(); + if (ImGui::VSliderInt("##DT",ImVec2(20.0f*dpiScale,sliderHeight),&detune,-3,4)) { PARAMETER + op.dt=detune+3; + } + + ImGui::TableNextColumn(); + P(ImGui::VSliderScalar("##DT2",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dt2,&_ZERO,&_THREE)); rightClickable + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Only on YM2151 (OPM)"); + } + + ImGui::TableNextColumn(); + ImGui::Text("Preview here"); + + if (ins->type!=DIV_INS_OPL && ins->type!=DIV_INS_OPZ) { + if (ImGui::Checkbox((ins->type==DIV_INS_OPLL)?FM_NAME(FM_EGS):"SSG On",&ssgOn)) { PARAMETER + op.ssgEnv=(op.ssgEnv&7)|(ssgOn<<3); + } + if (ins->type==DIV_INS_FM) { + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Only for OPN family chips"); + } + } + } + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::SliderScalar("##SSG",ImGuiDataType_U8,&ssgEnv,&_ZERO,&_SEVEN,ssgEnvTypes[ssgEnv])) { PARAMETER + op.ssgEnv=(op.ssgEnv&8)|(ssgEnv&7); + } + } + + ImGui::TableNextColumn(); + drawFMEnv(op.tl&maxTl,op.ar&maxArDr,op.dr&maxArDr,(ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPLL)?((op.rr&15)*2):op.d2r&31,op.rr&15,op.sl&15,maxTl,maxArDr,ImVec2(ImGui::GetContentRegionAvail().x,sliderHeight)); + + ImGui::TableNextColumn(); + bool amOn=op.am; + if (ImGui::Checkbox("##AM",&amOn)) { PARAMETER + op.am=amOn; + } + + ImGui::PopID(); } - if (ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPZ) { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - P(ImGui::SliderScalar("##WS",ImGuiDataType_U8,&op.ws,&_ZERO,&_SEVEN,(ins->type==DIV_INS_OPZ)?opzWaveforms[op.ws&7]:oplWaveforms[op.ws&7])); rightClickable - if (ins->type==DIV_INS_OPL && ImGui::IsItemHovered()) { - ImGui::SetTooltip("OPL2/3 only (last 4 waveforms are OPL3 only)"); - } - ImGui::TableNextColumn(); - ImGui::Text("%s",FM_NAME(FM_WS)); - } - ImGui::EndTable(); } - - if (ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPL) { - if (ImGui::Checkbox(FM_NAME(FM_VIB),&vibOn)) { PARAMETER - op.vib=vibOn; - } - ImGui::SameLine(); - if (ImGui::Checkbox(FM_NAME(FM_KSR),&ksrOn)) { PARAMETER - op.ksr=ksrOn; - } + } else { + int columns=2; + switch (settings.fmLayout) { + case 1: // 2x2 + columns=2; + break; + case 2: // 1x4 + columns=1; + break; + case 3: // 4x1 + columns=opCount; + break; } + if (ImGui::BeginTable("FMOperators",columns,ImGuiTableFlags_SizingStretchSame)) { + for (int i=0; ifm.op[(opCount==4)?opOrder[i]:i]; + if ((settings.fmLayout!=3 && ((i+1)&1)) || i==0 || settings.fmLayout==2) ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Separator(); + ImGui::PushID(fmt::sprintf("op%d",i).c_str()); + ImGui::Dummy(ImVec2(dpiScale,dpiScale)); + if (ins->type==DIV_INS_OPL && ins->fm.opllPreset==16) { + if (i==1) { + ImGui::Text("Envelope 2 (kick only)"); + } else { + ImGui::Text("Envelope"); + } + } else { + ImGui::Text("OP%d",i+1); + } - ImGui::PopID(); + ImGui::SameLine(); + + bool amOn=op.am; + if (ImGui::Checkbox(FM_NAME(FM_AM),&amOn)) { PARAMETER + op.am=amOn; + } + + ImGui::SameLine(); + + int maxTl=127; + if (ins->type==DIV_INS_OPLL) { + if (i==1) { + maxTl=15; + } else { + maxTl=63; + } + } + if (ins->type==DIV_INS_OPL) { + maxTl=63; + } + int maxArDr=(ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ)?31:15; + + bool ssgOn=op.ssgEnv&8; + bool ksrOn=op.ksr; + bool vibOn=op.vib; + bool susOn=op.sus; // don't you make fun of this one + unsigned char ssgEnv=op.ssgEnv&7; + if (ins->type!=DIV_INS_OPL && ins->type!=DIV_INS_OPZ) { + if (ImGui::Checkbox((ins->type==DIV_INS_OPLL)?FM_NAME(FM_EGS):"SSG On",&ssgOn)) { PARAMETER + op.ssgEnv=(op.ssgEnv&7)|(ssgOn<<3); + } + if (ins->type==DIV_INS_FM) { + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Only for OPN family chips"); + } + } + } + + if (ins->type==DIV_INS_OPL) { + if (ImGui::Checkbox(FM_NAME(FM_SUS),&susOn)) { PARAMETER + op.sus=susOn; + } + } + + //52.0 controls vert scaling; default 96 + drawFMEnv(op.tl&maxTl,op.ar&maxArDr,op.dr&maxArDr,(ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPLL)?((op.rr&15)*2):op.d2r&31,op.rr&15,op.sl&15,maxTl,maxArDr,ImVec2(ImGui::GetContentRegionAvail().x,52.0*dpiScale)); + //P(ImGui::SliderScalar(FM_NAME(FM_AR),ImGuiDataType_U8,&op.ar,&_ZERO,&_THIRTY_ONE)); rightClickable + if (ImGui::BeginTable("opParams",2,ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,0.0); \ + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed,0.0); \ + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + op.ar&=maxArDr; + P(ImGui::SliderScalar("##AR",ImGuiDataType_U8,&op.ar,&maxArDr,&_ZERO)); rightClickable + ImGui::TableNextColumn(); + ImGui::Text("%s",FM_NAME(FM_AR)); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + op.dr&=maxArDr; + P(ImGui::SliderScalar("##DR",ImGuiDataType_U8,&op.dr,&maxArDr,&_ZERO)); rightClickable + ImGui::TableNextColumn(); + ImGui::Text("%s",FM_NAME(FM_DR)); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + P(ImGui::SliderScalar("##SL",ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); rightClickable + ImGui::TableNextColumn(); + ImGui::Text("%s",FM_NAME(FM_SL)); + + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + P(ImGui::SliderScalar("##D2R",ImGuiDataType_U8,&op.d2r,&_THIRTY_ONE,&_ZERO)); rightClickable + ImGui::TableNextColumn(); + ImGui::Text("%s",FM_NAME(FM_D2R)); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + P(ImGui::SliderScalar("##RR",ImGuiDataType_U8,&op.rr,&_FIFTEEN,&_ZERO)); rightClickable + ImGui::TableNextColumn(); + ImGui::Text("%s",FM_NAME(FM_RR)); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + op.tl&=maxTl; + P(ImGui::SliderScalar("##TL",ImGuiDataType_U8,&op.tl,&maxTl,&_ZERO)); rightClickable + ImGui::TableNextColumn(); + ImGui::Text("%s",FM_NAME(FM_TL)); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Separator(); + ImGui::TableNextColumn(); + ImGui::Separator(); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + P(ImGui::SliderScalar("##RS",ImGuiDataType_U8,&op.rs,&_ZERO,&_THREE)); rightClickable + ImGui::TableNextColumn(); + ImGui::Text("%s",FM_NAME(FM_RS)); + } else { + P(ImGui::SliderScalar("##KSL",ImGuiDataType_U8,&op.ksl,&_ZERO,&_THREE)); rightClickable + ImGui::TableNextColumn(); + ImGui::Text("%s",FM_NAME(FM_KSL)); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + P(ImGui::SliderScalar(FM_NAME(FM_MULT),ImGuiDataType_U8,&op.mult,&_ZERO,&_FIFTEEN)); rightClickable + ImGui::TableNextColumn(); + ImGui::Text("%s",FM_NAME(FM_MULT)); + + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + int detune=(op.dt&7)-3; + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::SliderInt("##DT",&detune,-3,4)) { PARAMETER + op.dt=detune+3; + } rightClickable + ImGui::TableNextColumn(); + ImGui::Text("%s",FM_NAME(FM_DT)); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + P(ImGui::SliderScalar("##DT2",ImGuiDataType_U8,&op.dt2,&_ZERO,&_THREE)); rightClickable + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Only on YM2151 (OPM)"); + } + ImGui::TableNextColumn(); + ImGui::Text("%s",FM_NAME(FM_DT2)); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::SliderScalar("##SSG",ImGuiDataType_U8,&ssgEnv,&_ZERO,&_SEVEN,ssgEnvTypes[ssgEnv])) { PARAMETER + op.ssgEnv=(op.ssgEnv&8)|(ssgEnv&7); + } rightClickable + ImGui::TableNextColumn(); + ImGui::Text("%s",FM_NAME(FM_SSG)); + } + + if (ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPZ) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + P(ImGui::SliderScalar("##WS",ImGuiDataType_U8,&op.ws,&_ZERO,&_SEVEN,(ins->type==DIV_INS_OPZ)?opzWaveforms[op.ws&7]:oplWaveforms[op.ws&7])); rightClickable + if (ins->type==DIV_INS_OPL && ImGui::IsItemHovered()) { + ImGui::SetTooltip("OPL2/3 only (last 4 waveforms are OPL3 only)"); + } + ImGui::TableNextColumn(); + ImGui::Text("%s",FM_NAME(FM_WS)); + } + + ImGui::EndTable(); + } + + if (ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPL) { + if (ImGui::Checkbox(FM_NAME(FM_VIB),&vibOn)) { PARAMETER + op.vib=vibOn; + } + ImGui::SameLine(); + if (ImGui::Checkbox(FM_NAME(FM_KSR),&ksrOn)) { PARAMETER + op.ksr=ksrOn; + } + } + + ImGui::PopID(); + } + ImGui::EndTable(); + } } - ImGui::EndTable(); } ImGui::EndTabItem(); } diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 19e91e89..02ca297b 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -419,6 +419,20 @@ void FurnaceGUI::drawSettings() { settings.controlLayout=3; } + ImGui::Text("FM parameter editor layout:"); + if (ImGui::RadioButton("Modern##fml0",settings.fmLayout==0)) { + settings.fmLayout=0; + } + if (ImGui::RadioButton("Compact (2x2, classic)##fml1",settings.fmLayout==1)) { + settings.fmLayout=1; + } + if (ImGui::RadioButton("Compact (1x4)##fml2",settings.fmLayout==2)) { + settings.fmLayout=2; + } + if (ImGui::RadioButton("Compact (4x1)##fml3",settings.fmLayout==3)) { + settings.fmLayout=3; + } + bool macroViewB=settings.macroView; if (ImGui::Checkbox("Classic macro view (standard macros only; deprecated!)",¯oViewB)) { settings.macroView=macroViewB; @@ -990,6 +1004,7 @@ void FurnaceGUI::syncSettings() { settings.roundedButtons=e->getConfInt("roundedButtons",1); settings.roundedMenus=e->getConfInt("roundedMenus",0); settings.loadJapanese=e->getConfInt("loadJapanese",0); + settings.fmLayout=e->getConfInt("fmLayout",0); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -1034,6 +1049,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.roundedButtons,0,1); clampSetting(settings.roundedMenus,0,1); clampSetting(settings.loadJapanese,0,1); + clampSetting(settings.fmLayout,0,3); // keybinds LOAD_KEYBIND(GUI_ACTION_OPEN,FURKMOD_CMD|SDLK_o); @@ -1268,6 +1284,7 @@ void FurnaceGUI::commitSettings() { e->setConf("roundedButtons",settings.roundedButtons); e->setConf("roundedMenus",settings.roundedMenus); e->setConf("loadJapanese",settings.loadJapanese); + e->setConf("fmLayout",settings.fmLayout); PUT_UI_COLOR(GUI_COLOR_BACKGROUND); PUT_UI_COLOR(GUI_COLOR_FRAME_BACKGROUND); From 248942879564e53a44711c0ed46425f8217a2b90 Mon Sep 17 00:00:00 2001 From: AugiteSoul Date: Sat, 26 Mar 2022 19:37:07 +0100 Subject: [PATCH 16/48] Cleaned up grammar, mostly Might need some changes after this considering some lines were rather confusing - I don't actually know much about how this chip works exactly --- papers/doc/7-systems/x1-010.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/papers/doc/7-systems/x1-010.md b/papers/doc/7-systems/x1-010.md index 1fde55e0..411afb3e 100644 --- a/papers/doc/7-systems/x1-010.md +++ b/papers/doc/7-systems/x1-010.md @@ -1,27 +1,27 @@ # Seta/Allumer X1-010 -One of sound chip originally designed by Seta, mainly used at their arcade hardwares at late-80s to early-2000s. -It has 2 output channels, but no known hardware using this feature for stereo sound. +A sound chip designed by Seta, mainly used in their own arcade hardware from the late 80s to the early 2000s. +It has 2 output channels, but there is no known hardware taking advantage of stereo sound capabilities. Later hardware paired this with external bankswitching logic, but this isn't emulated yet. -Allumer one is just rebadged Seta's thing for use in their arcade hardwares. +Allumer rebadged it for their own arcade hardware. -It has 16 channels, and all channels can be switchable to PCM sample or wavetable playback mode. -Wavetable needs to paired with envelope, this feature is similar as AY PSG, but its shape are stored at RAM: it means it is user-definable. +It has 16 channels, which can all be switched between PCM sample or wavetable playback mode. +Wavetable playback needs to paired with envelope, similar to AY PSG, but shapes are stored in RAM and as such are user-definable. -In furnace, this chip is can be configurable for original arcade mono output or stereo output - it simulates early 'incorrect' emulation on some mono hardware, but it is also based on the assumption that each channel is connected to each output. +In furnace, this chip can be configured for original arcade mono output or stereo output - it simulates early 'incorrect' emulation on some mono hardware, but it is also based on the assumption that each channel is connected to each output. -# waveform type +# waveform types -This chip supports 2 type waveforms, needs to paired external 8 KB RAM for use these features: +This chip supports 2 types of waveforms, needs to be paired to external 8 KB RAM to access these features: -One is signed 8 bit mono waveform, it's operated like other wavetable based sound systems. -These are stored at the bottom half of RAM at common case. +One is a signed 8 bit mono waveform, operated like other wavetable based sound systems. +These are stored at the lower half of RAM at common case. -Another one ("Envelope") is 4 bit stereo waveform, it's multiplied with above and calculates final output, Each nibble is used for each output channels. +The other one ("Envelope") is a 4 bit stereo waveform, multiplied with the above and calculates final output, each nibble is used for each output channel. These are stored at the upper half of RAM at common case. -Both waveforms are 128 byte fixed size, it's freely allocated at each half of RAM except channel register area: each half can be stored total 32/31 waveforms at once. -In furnace, You can set envelope shape split mode. When it sets, its waveform will be split to left half and right half for each outputs. each max size are 128 bytes, total 256 bytes. +Both waveforms are 128 bytes (fixed size), freely allocated at each half of RAM except the channel register area: each half can store total 32/31 waveforms at once. +In furnace, you can enable the envelope shape split mode. When it is set, its waveform will be split to the left and right halves for each output. Each max size is 128 bytes, total 256 bytes. # effects @@ -31,8 +31,8 @@ In furnace, You can set envelope shape split mode. When it sets, its waveform wi - `20xx`: set PCM frequency (1 to FF). - `22xx`: set envelope mode. - bit 0 sets whether envelope will affect this channel. - - bit 1 sets whether envelope one-shot mode. when it sets, channel is halted after envelope cycle is ended. - - bit 2 sets whether envelope shape split mode. when it sets, envelope shape will splitted to left half and right half. + - bit 1 toggles the envelope one-shot mode. when it is set, channel is halted after envelope cycle is finished. + - bit 2 toggles the envelope shape split mode. when it is set, envelope shape will be split to left half and right half. - bit 3/5 sets whether the right/left shape will mirror the original one. - bit 4/6 sets whether the right/left output will mirror the original one. - `23xx`: set envelope period. From d869c21f522d45c55a53faf45fb0274638eb3e09 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 26 Mar 2022 13:47:13 -0500 Subject: [PATCH 17/48] oops I forgot to commit! --- src/engine/platform/vic20.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/engine/platform/vic20.cpp b/src/engine/platform/vic20.cpp index 7c289084..b640f780 100644 --- a/src/engine/platform/vic20.cpp +++ b/src/engine/platform/vic20.cpp @@ -22,11 +22,9 @@ #include #define rWrite(a,v) {regPool[(a)]=(v)&0xff; vic_sound_machine_store(vic,a,(v)&0xff);} -#define SAMP_DIVIDER 4 -const int chipDividers[4]={ - 128, 64, 32, 64 -}; +#define CHIP_DIVIDER 32 +#define SAMP_DIVIDER 4 const char* regCheatSheetVIC[]={ "CH1_Pitch", "0A", @@ -95,7 +93,6 @@ void DivPlatformVIC20::writeOutVol(int ch) { void DivPlatformVIC20::tick() { for (int i=0; i<4; i++) { - int CHIP_DIVIDER=chipDividers[i]; chan[i].std.next(); if (chan[i].std.hadVol) { int env=chan[i].std.vol; @@ -124,9 +121,13 @@ void DivPlatformVIC20::tick() { } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true); - printf("%d freq: %d\n",i,chan[i].freq); + if (i<3) { + chan[i].freq>>=(2-i); + } else { + chan[i].freq>>=1; + } if (chan[i].freq<1) chan[i].freq=1; - if (chan[i].freq>127) chan[i].freq=127; + if (chan[i].freq>127) chan[i].freq=0; if (isMuted[i]) chan[i].keyOn=false; if (chan[i].keyOn) { if (i<3) { @@ -152,7 +153,6 @@ void DivPlatformVIC20::tick() { } int DivPlatformVIC20::dispatch(DivCommand c) { - int CHIP_DIVIDER=chipDividers[c.chan]; switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins); From 1a4290f1c3ef53abd0e3bea218d609ffe824bb91 Mon Sep 17 00:00:00 2001 From: freq-mod <32672779+freq-mod@users.noreply.github.com> Date: Sat, 26 Mar 2022 19:55:20 +0100 Subject: [PATCH 18/48] somewhat improved system descriptions --- papers/doc/7-systems/ay8910.md | 6 +++--- papers/doc/7-systems/nes.md | 4 ++-- papers/doc/7-systems/opl.md | 9 ++++----- papers/doc/7-systems/opll.md | 2 +- papers/doc/7-systems/pce.md | 6 ++++-- papers/doc/7-systems/pcspkr.md | 2 +- papers/doc/7-systems/sms.md | 2 +- papers/doc/7-systems/wonderswan.md | 7 ++----- papers/doc/7-systems/ym2151.md | 2 +- papers/doc/7-systems/ym2610.md | 4 ++-- papers/doc/7-systems/ym2612.md | 2 +- 11 files changed, 22 insertions(+), 24 deletions(-) diff --git a/papers/doc/7-systems/ay8910.md b/papers/doc/7-systems/ay8910.md index 4d13a153..b01d9657 100644 --- a/papers/doc/7-systems/ay8910.md +++ b/papers/doc/7-systems/ay8910.md @@ -1,8 +1,8 @@ # General Instrument AY-3-8910 -this chip was used in several home computers (ZX Spectrum, MSX, Amstrad CPC, Atari ST, etc.), video game consoles (Intellivision and Vectrex), arcade boards and even slot machines! +this chip was used in many home computers (ZX Spectrum, MSX, Amstrad CPC, Atari ST, etc.), video game consoles (Intellivision and Vectrex), arcade boards and even slot machines! -the chip's powerful sound comes from the envelope... +It is a 3-channel PSG sound source. The chip's powerful sound comes from the envelope... AY-3-8914 variant was used in Intellivision, it's basically original AY with 4 level envelope volume per channel and different register format. @@ -39,4 +39,4 @@ AY-3-8914 variant was used in Intellivision, it's basically original AY with 4 l - in this mode the envelope period is set to the channel's notes, multiplied by a fraction. - `x` is the numerator. - `y` is the denominator. - - if `x` or `y` are 0 this will disable auto-envelope mode. \ No newline at end of file + - if `x` or `y` are 0 this will disable auto-envelope mode. diff --git a/papers/doc/7-systems/nes.md b/papers/doc/7-systems/nes.md index dc6eb20a..3de0e307 100644 --- a/papers/doc/7-systems/nes.md +++ b/papers/doc/7-systems/nes.md @@ -2,7 +2,7 @@ the console from Nintendo that plays Super Mario Bros. and helped revive the agonizing video game market in the US during mid-80s. -also known as Famicom. +also known as Famicom. It is a five-channel PSG: first two channels play pulse wave with three different duty cycles, third is a fixed-volume triangle channel, fourth is a noise channel (can work in both pseudo-random and periodic modes) and fifth is a (D)PCM sample channel # effects @@ -15,4 +15,4 @@ also known as Famicom. - `14xy`: setup sweep down. - `x` is the time. - `y` is the shift. - - set to 0 to disable it. \ No newline at end of file + - set to 0 to disable it. diff --git a/papers/doc/7-systems/opl.md b/papers/doc/7-systems/opl.md index 1459eee4..b019e3f9 100644 --- a/papers/doc/7-systems/opl.md +++ b/papers/doc/7-systems/opl.md @@ -5,15 +5,14 @@ a series of FM sound chips which were very popular in DOS land. it was so popula essentially a downgraded version of Yamaha's other FM chips, with only 2 operators per channel. however, it also had a drums mode, and later chips in the series added more waveforms (than just the typical sine) and even a 4-operator mode. -the original OPL was present as an expansion for the Commodore 64 and MSX computers (erm, a variant of it). it only had 9 channels and drums mode. +the original OPL (Yamaha YM3526) was present as an expansion for the Commodore 64 and MSX computers (erm, a variant of it). it only had 9 two-operator channels and drums mode. -its successor, the OPL2, added 3 more waveforms and was one of the more popular chips because it was present on the AdLib card for PC. +its successor, the OPL2 (Yamaha YM3812), added 3 more waveforms and was one of the more popular chips because it was present on the AdLib card for PC. later Creative would borrow the chip to make the Sound Blaster, and totally destroyed AdLib's dominance. -the OPL3 added 9 more channels, 4 more waveforms, 4-operator mode (pairing up to 12 channels to make up to six 4-operator channels), quadraphonic output (sadly Furnace only supports stereo) and some other things. -it was overkill. +the OPL3 (Yamaha YMF262) added 9 more channels, 4 more waveforms, rudimentary 4-operator mode (pairing up to 12 channels to make up to six 4-operator channels), quadraphonic output (sadly Furnace only supports stereo) and some other things. -afterwards everyone moved to Windows and software mixing... +afterwards everyone moved to Windows and software mixed PCM streaming... # effects diff --git a/papers/doc/7-systems/opll.md b/papers/doc/7-systems/opll.md index 880750b3..06f542f1 100644 --- a/papers/doc/7-systems/opll.md +++ b/papers/doc/7-systems/opll.md @@ -1,6 +1,6 @@ # Yamaha YM2413/OPLL -the YM2413, otherwise known as OPLL, is a cost-reduced FM synthesis sound chip manufactured by Yamaha Corporation and based on the Yamaha YM3812 sound chip (OPL2). thought OPL was downgraded enough? :p +the YM2413, otherwise known as OPLL, is a cost-reduced FM synthesis sound chip, based on the Yamaha YM3812 (OPL2). thought OPL was downgraded enough? :p # technical specifications diff --git a/papers/doc/7-systems/pce.md b/papers/doc/7-systems/pce.md index 5034e83f..c061a9b7 100644 --- a/papers/doc/7-systems/pce.md +++ b/papers/doc/7-systems/pce.md @@ -1,9 +1,11 @@ # PC Engine/TurboGrafx-16 -a console from NEC that attempted to enter the fierce battle between Nintendo and Sega, but because its capabilities are a mix of third and fourth generation, it failed to last long. +a console from NEC that, depending on a region: + attempted to enter the fierce battle between Nintendo and Sega, but because its capabilities are a mix of third and fourth generation, it failed to last long. (US and Europe) + was Nintendo's most fearsome rival, completely defeating Sega Mega Drive and defending itself against Super Famicom (Japan) it has 6 wavetable channels and the last two ones also double as noise channels. -furthermore, it has some PCM! +furthermore, it has some PCM and LFO! # effects diff --git a/papers/doc/7-systems/pcspkr.md b/papers/doc/7-systems/pcspkr.md index afaa4f82..6c2e1a94 100644 --- a/papers/doc/7-systems/pcspkr.md +++ b/papers/doc/7-systems/pcspkr.md @@ -1,6 +1,6 @@ # PC Speaker -40 years of one square beep - and still going! +40 years of one square beep - and still going! Single channel, no volume control... # effects diff --git a/papers/doc/7-systems/sms.md b/papers/doc/7-systems/sms.md index 61650ecf..53560a97 100644 --- a/papers/doc/7-systems/sms.md +++ b/papers/doc/7-systems/sms.md @@ -2,7 +2,7 @@ the predecessor to Genesis. -surely had better graphics than NES, but its sound is subject of several jokes. +surely had better graphics than NES, but its sound (fairly weak, 4ch PSG with A-3 is a lowest tone) is subject of several jokes. this console is powered by a derivative of the Texas Instruments SN76489. diff --git a/papers/doc/7-systems/wonderswan.md b/papers/doc/7-systems/wonderswan.md index c5c0bafd..44657dec 100644 --- a/papers/doc/7-systems/wonderswan.md +++ b/papers/doc/7-systems/wonderswan.md @@ -1,11 +1,8 @@ # 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. +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. +It has 4 wavetable channels, channel #2 could play PCM, channel #3 has hardware sweep and channel #4 could play noise. # effects diff --git a/papers/doc/7-systems/ym2151.md b/papers/doc/7-systems/ym2151.md index bf8b0d6b..69eba0fb 100644 --- a/papers/doc/7-systems/ym2151.md +++ b/papers/doc/7-systems/ym2151.md @@ -1,6 +1,6 @@ # Yamaha YM2151 -the sound chip powering several arcade boards and the Sharp X1/X68000. +the sound chip powering several arcade boards and the Sharp X1/X68000. Eight 4-op FM channels, with overpowered LFO and almost unused noise generator. it also was present on several pinball machines and synthesizers of the era, and later surpassed by the YM2414 (OPZ) present in the world-famous TX81Z. diff --git a/papers/doc/7-systems/ym2610.md b/papers/doc/7-systems/ym2610.md index 7eb66eb3..4515fde2 100644 --- a/papers/doc/7-systems/ym2610.md +++ b/papers/doc/7-systems/ym2610.md @@ -2,7 +2,7 @@ originally an arcade board, but SNK shortly adapted it to a rather expensive video game console with the world's biggest cartridges because some people liked the system so much they wanted a home version of it. -its soundchip is a 3-in-1: FM, YM2149 (AY-3-8910 clone) and 2 different format ADPCM in a single package! +its soundchip is a 4-in-1: 4ch 4-op FM, YM2149 (AY-3-8910 clone) and 2 different format ADPCM in a single package! # effects @@ -56,4 +56,4 @@ its soundchip is a 3-in-1: FM, YM2149 (AY-3-8910 clone) and 2 different format A - in this mode the envelope period is set to the channel's notes, multiplied by a fraction. - `x` is the numerator. - `y` is the denominator. - - if `x` or `y` are 0 this will disable auto-envelope mode. \ No newline at end of file + - if `x` or `y` are 0 this will disable auto-envelope mode. diff --git a/papers/doc/7-systems/ym2612.md b/papers/doc/7-systems/ym2612.md index bfe87cf5..b7dd8a16 100644 --- a/papers/doc/7-systems/ym2612.md +++ b/papers/doc/7-systems/ym2612.md @@ -1,6 +1,6 @@ # Yamaha YM2612 -one of two chips that powered the Sega Genesis. +one of two chips that powered the Sega Genesis. It is a six-channel, four-operator FM synthesizer. Channel #6 can be turned into 8-bit PCM player. # effects From 4caa9376bce558d94a6ae4076a2f85302e0ad521 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 26 Mar 2022 17:30:26 -0500 Subject: [PATCH 19/48] GUI: what --- src/gui/insEdit.cpp | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 718eaf01..629fe4c4 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1055,7 +1055,23 @@ void FurnaceGUI::drawInsEdit() { } if (willDisplayOps) { if (settings.fmLayout==0) { - if (ImGui::BeginTable("FMOperators",14,ImGuiTableFlags_SizingStretchProp|ImGuiTableFlags_Borders)) { + if (ImGui::BeginTable("FMOperators",14,ImGuiTableFlags_SizingStretchProp|ImGuiTableFlags_BordersH|ImGuiTableFlags_BordersOuterV)) { + // configure columns + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c4",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c5",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c6",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c7",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c8",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c9",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c10",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c11",ImGuiTableColumnFlags_WidthStretch,0.4f); + ImGui::TableSetupColumn("c12",ImGuiTableColumnFlags_WidthStretch,0.6f); + ImGui::TableSetupColumn("c13",ImGuiTableColumnFlags_WidthFixed); + // header ImGui::TableNextRow(ImGuiTableRowFlags_Headers); ImGui::TableNextColumn(); @@ -1181,10 +1197,8 @@ void FurnaceGUI::drawInsEdit() { } ImGui::TableNextColumn(); - ImGui::Text("Preview here"); - if (ins->type!=DIV_INS_OPL && ins->type!=DIV_INS_OPZ) { - if (ImGui::Checkbox((ins->type==DIV_INS_OPLL)?FM_NAME(FM_EGS):"SSG On",&ssgOn)) { PARAMETER + if (ImGui::Checkbox("##SSGOn",&ssgOn)) { PARAMETER op.ssgEnv=(op.ssgEnv&7)|(ssgOn<<3); } if (ins->type==DIV_INS_FM) { @@ -1192,11 +1206,12 @@ void FurnaceGUI::drawInsEdit() { ImGui::SetTooltip("Only for OPN family chips"); } } - } - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::SliderScalar("##SSG",ImGuiDataType_U8,&ssgEnv,&_ZERO,&_SEVEN,ssgEnvTypes[ssgEnv])) { PARAMETER - op.ssgEnv=(op.ssgEnv&8)|(ssgEnv&7); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::SliderScalar("##SSG",ImGuiDataType_U8,&ssgEnv,&_ZERO,&_SEVEN,ssgEnvTypes[ssgEnv])) { PARAMETER + op.ssgEnv=(op.ssgEnv&8)|(ssgEnv&7); + } } } From 7237e8fb394dadc3db02d692123379ce618369f0 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 26 Mar 2022 18:30:08 -0500 Subject: [PATCH 20/48] GUI: add a space i am lazy --- src/gui/insEdit.cpp | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 629fe4c4..930bcccf 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1055,22 +1055,23 @@ void FurnaceGUI::drawInsEdit() { } if (willDisplayOps) { if (settings.fmLayout==0) { - if (ImGui::BeginTable("FMOperators",14,ImGuiTableFlags_SizingStretchProp|ImGuiTableFlags_BordersH|ImGuiTableFlags_BordersOuterV)) { + if (ImGui::BeginTable("FMOperators",15,ImGuiTableFlags_SizingStretchProp|ImGuiTableFlags_BordersH|ImGuiTableFlags_BordersOuterV)) { // configure columns - ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("c4",ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("c5",ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("c6",ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("c7",ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("c8",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); // op name + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed); // ar + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed); // dr + ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthFixed); // sl + ImGui::TableSetupColumn("c4",ImGuiTableColumnFlags_WidthFixed); // d2r + ImGui::TableSetupColumn("c5",ImGuiTableColumnFlags_WidthFixed); // rr + ImGui::TableSetupColumn("c6",ImGuiTableColumnFlags_WidthFixed); // -separator- + ImGui::TableSetupColumn("c7",ImGuiTableColumnFlags_WidthFixed); // tl + ImGui::TableSetupColumn("c8",ImGuiTableColumnFlags_WidthFixed); // ... ImGui::TableSetupColumn("c9",ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("c10",ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("c11",ImGuiTableColumnFlags_WidthStretch,0.4f); - ImGui::TableSetupColumn("c12",ImGuiTableColumnFlags_WidthStretch,0.6f); - ImGui::TableSetupColumn("c13",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c11",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c12",ImGuiTableColumnFlags_WidthStretch,0.4f); + ImGui::TableSetupColumn("c13",ImGuiTableColumnFlags_WidthStretch,0.6f); + ImGui::TableSetupColumn("c14",ImGuiTableColumnFlags_WidthFixed); // header ImGui::TableNextRow(ImGuiTableRowFlags_Headers); @@ -1089,6 +1090,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); ImGui::TextUnformatted(FM_NAME(FM_RR)); ImGui::TableNextColumn(); + ImGui::TableNextColumn(); ImGui::TextUnformatted(FM_NAME(FM_TL)); ImGui::TableNextColumn(); if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { @@ -1169,6 +1171,9 @@ void FurnaceGUI::drawInsEdit() { op.rr&=15; P(ImGui::VSliderScalar("##RR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rr,&_ZERO,&_FIFTEEN)); + ImGui::TableNextColumn(); + ImGui::Dummy(ImVec2(4.0f*dpiScale,2.0f*dpiScale)); + ImGui::TableNextColumn(); op.tl&=maxTl; P(ImGui::VSliderScalar("##TL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.tl,&maxTl,&_ZERO)); From 1022d64bd0ecb8ae884c5922f9334d2370d1654f Mon Sep 17 00:00:00 2001 From: nicco1690 <78063037+nicco1690@users.noreply.github.com> Date: Sat, 26 Mar 2022 21:32:29 -0400 Subject: [PATCH 21/48] Create VIC-20 documentation funny low-pass sound chip waves go brr --- papers/doc/7-systems/vic20.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 papers/doc/7-systems/vic20.md diff --git a/papers/doc/7-systems/vic20.md b/papers/doc/7-systems/vic20.md new file mode 100644 index 00000000..b221a3cb --- /dev/null +++ b/papers/doc/7-systems/vic20.md @@ -0,0 +1,11 @@ +# Commodore VIC-20 + +The Commodore VIC-20 was Commodore's major attempt at making a personal home computer, and is the percursor to the Commodore 64. The VIC-20 was also known as the VC-20 in Germany, and the VIC-1001 in Japan. + +It has 4 PSG voices that has a limited but wide tuning range, and like the SN76489, the last voice is dedicated to playing pseudo-white noise. Every voice on the VIC-20 has a high-pass and low-pass filter applied to it, which is likely how Rob Yannes got the inspiration to put custom filter modes on the C64's SID. + +The 3 pulse wave channels also have different octaves that they can play notes on (not currently emulated in Furnace version dev71). The first channel is the bass channel, and it can play notes from octaves 2 to octaves 4. The next is the 'mid/chord' channel, and it plays notes from octaves 3 to 5. And rather obviously, the 3rd pulse channel is typically the lead channel, can play notes from octaves 4 to 6. + +## effect commands + + - `10xx` Switch waveform (Only the values 00 though 0F are unique. Everything else is a copy. For example, `1006` is the same as `10f6`.) From 8c6c3f170732521b9d2de8fac98df21f359c7ce9 Mon Sep 17 00:00:00 2001 From: nicco1690 <78063037+nicco1690@users.noreply.github.com> Date: Sat, 26 Mar 2022 21:34:32 -0400 Subject: [PATCH 22/48] Add the VIC-20 to the systems list in README.md --- papers/doc/7-systems/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/papers/doc/7-systems/README.md b/papers/doc/7-systems/README.md index 31931440..5e4e101b 100644 --- a/papers/doc/7-systems/README.md +++ b/papers/doc/7-systems/README.md @@ -27,5 +27,6 @@ this is a list of systems that Furnace supports, including each system's effects - [Yamaha OPL](opl.md) - [PC Speaker](pcspkr.md) - [Commodore PET](pet.md) +- [Commodore VIC-20](vic20.md) Furnace also reads .dmf files with the [Yamaha YMU759](ymu759.md) system, but... From 73536c069104461823120ce933e2780acd5bbda7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 26 Mar 2022 20:55:43 -0500 Subject: [PATCH 23/48] AY: add effects to write to I/O ports --- papers/doc/7-systems/ay8910.md | 4 +++ src/engine/dispatch.h | 2 ++ src/engine/platform/ay.cpp | 57 +++++++++++++++++++++++++++++----- src/engine/platform/ay.h | 4 +++ src/engine/platform/ay8930.cpp | 57 +++++++++++++++++++++++++++++----- src/engine/platform/ay8930.h | 4 +++ src/engine/playback.cpp | 14 +++++++++ 7 files changed, 128 insertions(+), 14 deletions(-) diff --git a/papers/doc/7-systems/ay8910.md b/papers/doc/7-systems/ay8910.md index b01d9657..5df69eb6 100644 --- a/papers/doc/7-systems/ay8910.md +++ b/papers/doc/7-systems/ay8910.md @@ -40,3 +40,7 @@ AY-3-8914 variant was used in Intellivision, it's basically original AY with 4 l - `x` is the numerator. - `y` is the denominator. - if `x` or `y` are 0 this will disable auto-envelope mode. +- `2Exx`: write to I/O port A. + - this changes the port's mode to "write". make sure you have connected something to it. +- `2Fxx`: write to I/O port B. + - this changes the port's mode to "write". make sure you have connected something to it. \ No newline at end of file diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index c6486f26..a3cd1e81 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -101,6 +101,8 @@ enum DivDispatchCmds { DIV_CMD_AY_NOISE_MASK_AND, DIV_CMD_AY_NOISE_MASK_OR, DIV_CMD_AY_AUTO_ENVELOPE, + DIV_CMD_AY_IO_WRITE, + DIV_CMD_AY_AUTO_PWM, DIV_CMD_SAA_ENVELOPE, diff --git a/src/engine/platform/ay.cpp b/src/engine/platform/ay.cpp index 526b3fbd..3e53e8fe 100644 --- a/src/engine/platform/ay.cpp +++ b/src/engine/platform/ay.cpp @@ -19,6 +19,7 @@ #include "ay.h" #include "../engine.h" +#include "../../ta-log.h" #include "sound/ay8910.h" #include #include @@ -98,6 +99,12 @@ const char* DivPlatformAY8910::getEffectName(unsigned char effect) { case 0x29: return "29xy: Set auto-envelope (x: numerator; y: denominator)"; break; + case 0x2e: + return "2Exx: Write to I/O port A"; + break; + case 0x2f: + return "2Fxx: Write to I/O port B"; + break; } return NULL; } @@ -141,6 +148,30 @@ void DivPlatformAY8910::acquire(short* bufL, short* bufR, size_t start, size_t l } } +void DivPlatformAY8910::updateOutSel(bool immediate) { + if (immediate) { + immWrite(0x07, + ~((chan[0].psgMode&1)| + ((chan[1].psgMode&1)<<1)| + ((chan[2].psgMode&1)<<2)| + ((chan[0].psgMode&2)<<2)| + ((chan[1].psgMode&2)<<3)| + ((chan[2].psgMode&2)<<4)| + ((!ioPortA)<<6)| + ((!ioPortB)<<7))); + } else { + rWrite(0x07, + ~((chan[0].psgMode&1)| + ((chan[1].psgMode&1)<<1)| + ((chan[2].psgMode&1)<<2)| + ((chan[0].psgMode&2)<<2)| + ((chan[1].psgMode&2)<<3)| + ((chan[2].psgMode&2)<<4)| + ((!ioPortA)<<6)| + ((!ioPortB)<<7))); + } +} + void DivPlatformAY8910::tick() { // PSG for (int i=0; i<3; i++) { @@ -221,13 +252,7 @@ void DivPlatformAY8910::tick() { } } - rWrite(0x07, - ~((chan[0].psgMode&1)| - ((chan[1].psgMode&1)<<1)| - ((chan[2].psgMode&1)<<2)| - ((chan[0].psgMode&2)<<2)| - ((chan[1].psgMode&2)<<3)| - ((chan[2].psgMode&2)<<4))); + updateOutSel(); if (ayEnvSlide!=0) { ayEnvSlideLow+=ayEnvSlide; @@ -401,6 +426,19 @@ int DivPlatformAY8910::dispatch(DivCommand c) { chan[c.chan].autoEnvDen=c.value&15; chan[c.chan].freqChanged=true; break; + case DIV_CMD_AY_IO_WRITE: + if (c.value) { // port B + ioPortB=true; + portBVal=c.value2; + logI("AY I/O port B write: %x\n",portBVal); + } else { // port A + ioPortA=true; + portAVal=c.value2; + logI("AY I/O port A write: %x\n",portAVal); + } + updateOutSel(true); + immWrite(14+(c.value?1:0),(c.value?portBVal:portAVal)); + break; case DIV_ALWAYS_SET_VOLUME: return 0; break; @@ -486,6 +524,11 @@ void DivPlatformAY8910::reset() { delay=0; extMode=false; + + ioPortA=false; + ioPortB=false; + portAVal=0; + portBVal=0; } bool DivPlatformAY8910::isStereo() { diff --git a/src/engine/platform/ay.h b/src/engine/platform/ay.h index 41c3c404..498c75e6 100644 --- a/src/engine/platform/ay.h +++ b/src/engine/platform/ay.h @@ -65,6 +65,8 @@ class DivPlatformAY8910: public DivDispatch { bool extMode; bool stereo, sunsoft, intellivision; + bool ioPortA, ioPortB; + unsigned char portAVal, portBVal; short oldWrites[16]; short pendingWrites[16]; @@ -75,6 +77,8 @@ class DivPlatformAY8910: public DivDispatch { short* ayBuf[3]; size_t ayBufLen; + void updateOutSel(bool immediate=false); + friend void putDispatchChan(void*,int,int); public: diff --git a/src/engine/platform/ay8930.cpp b/src/engine/platform/ay8930.cpp index d87045a6..3e114e61 100644 --- a/src/engine/platform/ay8930.cpp +++ b/src/engine/platform/ay8930.cpp @@ -19,6 +19,7 @@ #include "ay8930.h" #include "../engine.h" +#include "../../ta-log.h" #include "sound/ay8910.h" #include #include @@ -99,6 +100,12 @@ const char* DivPlatformAY8930::getEffectName(unsigned char effect) { case 0x29: return "29xy: Set auto-envelope (x: numerator; y: denominator)"; break; + case 0x2e: + return "2Exx: Write to I/O port A"; + break; + case 0x2f: + return "2Fxx: Write to I/O port B"; + break; } return NULL; } @@ -141,6 +148,30 @@ void DivPlatformAY8930::acquire(short* bufL, short* bufR, size_t start, size_t l } } +void DivPlatformAY8930::updateOutSel(bool immediate) { + if (immediate) { + immWrite(0x07, + ~((chan[0].psgMode&1)| + ((chan[1].psgMode&1)<<1)| + ((chan[2].psgMode&1)<<2)| + ((chan[0].psgMode&2)<<2)| + ((chan[1].psgMode&2)<<3)| + ((chan[2].psgMode&2)<<4)| + ((!ioPortA)<<6)| + ((!ioPortB)<<7))); + } else { + rWrite(0x07, + ~((chan[0].psgMode&1)| + ((chan[1].psgMode&1)<<1)| + ((chan[2].psgMode&1)<<2)| + ((chan[0].psgMode&2)<<2)| + ((chan[1].psgMode&2)<<3)| + ((chan[2].psgMode&2)<<4)| + ((!ioPortA)<<6)| + ((!ioPortB)<<7))); + } +} + const unsigned char regPeriodL[3]={ 0x0b, 0x10, 0x12 }; @@ -262,13 +293,7 @@ void DivPlatformAY8930::tick() { } } - rWrite(0x07, - ~((chan[0].psgMode&1)| - ((chan[1].psgMode&1)<<1)| - ((chan[2].psgMode&1)<<2)| - ((chan[0].psgMode&2)<<2)| - ((chan[1].psgMode&2)<<3)| - ((chan[2].psgMode&2)<<4))); + updateOutSel(); for (int i=0; i<32; i++) { if (pendingWrites[i]!=oldWrites[i]) { @@ -420,6 +445,19 @@ int DivPlatformAY8930::dispatch(DivCommand c) { chan[c.chan].autoEnvDen=c.value&15; chan[c.chan].freqChanged=true; break; + case DIV_CMD_AY_IO_WRITE: + if (c.value) { // port B + ioPortB=true; + portBVal=c.value2; + logI("AY I/O port B write: %x\n",portBVal); + } else { // port A + ioPortA=true; + portAVal=c.value2; + logI("AY I/O port A write: %x\n",portAVal); + } + updateOutSel(true); + immWrite(14+(c.value?1:0),(c.value?portBVal:portAVal)); + break; case DIV_ALWAYS_SET_VOLUME: return 0; break; @@ -499,6 +537,11 @@ void DivPlatformAY8930::reset() { extMode=false; bank=false; + ioPortA=false; + ioPortB=false; + portAVal=0; + portBVal=0; + immWrite(0x0d,0xa0); immWrite(0x19,2); // and mask immWrite(0x1a,0x00); // or mask diff --git a/src/engine/platform/ay8930.h b/src/engine/platform/ay8930.h index 2568fd7a..6bd51bdc 100644 --- a/src/engine/platform/ay8930.h +++ b/src/engine/platform/ay8930.h @@ -54,6 +54,8 @@ class DivPlatformAY8930: public DivDispatch { int delay; bool extMode, stereo; + bool ioPortA, ioPortB; + unsigned char portAVal, portBVal; short oldWrites[32]; short pendingWrites[32]; @@ -64,6 +66,8 @@ class DivPlatformAY8930: public DivDispatch { short* ayBuf[3]; size_t ayBufLen; + void updateOutSel(bool immediate=false); + friend void putDispatchChan(void*,int,int); public: diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 3a64af7a..aea9e1f8 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -109,6 +109,8 @@ const char* cmdName[DIV_CMD_MAX]={ "AY_NOISE_MASK_AND", "AY_NOISE_MASK_OR", "AY_AUTO_ENVELOPE", + "AY_IO_WRITE", + "AY_AUTO_PWM", "SAA_ENVELOPE", @@ -613,6 +615,12 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char case 0x29: // auto-envelope dispatchCmd(DivCommand(DIV_CMD_AY_AUTO_ENVELOPE,ch,effectVal)); break; + case 0x2e: // I/O port A + dispatchCmd(DivCommand(DIV_CMD_AY_IO_WRITE,ch,0,effectVal)); + break; + case 0x2f: // I/O port B + dispatchCmd(DivCommand(DIV_CMD_AY_IO_WRITE,ch,1,effectVal)); + break; default: return false; } @@ -1031,6 +1039,12 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].inPorta=false; if (!song.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); break; + case 0xf3: // fine volume ramp up + chan[i].volSpeed=effectVal; + break; + case 0xf4: // fine volume ramp down + chan[i].volSpeed=-effectVal; + break; case 0xf8: // single volume ramp up chan[i].volume=MIN(chan[i].volume+effectVal*256,chan[i].volMax); dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); From 3ea9c0360f39a2f411b25d6cbc0b114ec7aed7d9 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 26 Mar 2022 20:58:33 -0500 Subject: [PATCH 24/48] implement F3xx and F4xx for fine vol slides --- papers/doc/3-pattern/effects.md | 2 ++ src/engine/engine.cpp | 4 ++++ src/gui/pattern.cpp | 4 ++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/papers/doc/3-pattern/effects.md b/papers/doc/3-pattern/effects.md index d544c1ea..7a0588eb 100644 --- a/papers/doc/3-pattern/effects.md +++ b/papers/doc/3-pattern/effects.md @@ -54,6 +54,8 @@ however, effects are continuous, which means you only need to type it once and t - `F0xx`: change song Hz by BPM value. - `F1xx`: single tick slide up. - `F2xx`: single tick slide down. +- `F3xx`: fine volume slide up (64x slower than `0Axy`). +- `F4xx`: fine volume slide down (64x slower than `0Axy`). - `F8xx`: single tick volume slide up. - `F9xx`: single tick volume slide down. - `FAxy`: fast volume slide (4x faster than `0Axy`). diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index e9de013a..eefefdec 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -104,6 +104,10 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan) { return "F1xx: Single tick note slide up"; case 0xf2: return "F2xx: Single tick note slide down"; + case 0xf3: + return "F3xx: Fine volume slide up"; + case 0xf4: + return "F4xx: Fine volume slide down"; case 0xf8: return "F8xx: Single tick volume slide up"; case 0xf9: diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index aed8890a..79612827 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -65,8 +65,8 @@ const FurnaceGUIColors extFxColors[32]={ GUI_COLOR_PATTERN_EFFECT_SPEED, // F0 GUI_COLOR_PATTERN_EFFECT_PITCH, // F1 GUI_COLOR_PATTERN_EFFECT_PITCH, // F2 - GUI_COLOR_PATTERN_EFFECT_INVALID, // F3 - GUI_COLOR_PATTERN_EFFECT_INVALID, // F4 + GUI_COLOR_PATTERN_EFFECT_VOLUME, // F3 + GUI_COLOR_PATTERN_EFFECT_VOLUME, // F4 GUI_COLOR_PATTERN_EFFECT_INVALID, // F5 GUI_COLOR_PATTERN_EFFECT_INVALID, // F6 GUI_COLOR_PATTERN_EFFECT_INVALID, // F7 From 00876a461a4a8d0188f5fff9f8e6ba1c41438062 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 26 Mar 2022 21:16:15 -0500 Subject: [PATCH 25/48] update effect list --- papers/doc/3-pattern/effects.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/papers/doc/3-pattern/effects.md b/papers/doc/3-pattern/effects.md index 7a0588eb..773d3037 100644 --- a/papers/doc/3-pattern/effects.md +++ b/papers/doc/3-pattern/effects.md @@ -47,7 +47,7 @@ however, effects are continuous, which means you only need to type it once and t - `ECxx`: note off after `xx` ticks. - `EDxx`: delay note by `xx` ticks. - `EExx`: send external command. - - currently not used, but this eventually will allow you to do special things after I add VGM export. + - this effect is currently incomplete. - `EFxx`: add or subtract global pitch. - this effect is rather weird. use with caution. - `80` is center. From 5c11150b877df97f4a0b28607351ff8132977adc Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 26 Mar 2022 21:43:15 -0500 Subject: [PATCH 26/48] T O D O --- src/engine/platform/gb.cpp | 1 + src/engine/playback.cpp | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index ea6ad771..d25cc211 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -289,6 +289,7 @@ int DivPlatformGB::dispatch(DivCommand c) { chan[c.chan].ins=c.value; if (c.chan!=2) { chan[c.chan].vol=parent->getIns(chan[c.chan].ins)->gb.envVol; + // TODO: also change envelope values } } break; diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index aea9e1f8..8042e2cd 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -905,8 +905,16 @@ void DivEngine::processRow(int i, bool afterDelay) { break; case 0x07: // tremolo // TODO + // this effect is really weird. i thought it would alter the tremolo depth but turns out it's completely different + // this is how it works: + // - 07xy enables tremolo + // - when enabled, a "low" boundary is calculated based on the current volume + // - then a volume slide down starts to the low boundary, and then when this is reached a volume slide up begins + // - this process repeats until 0700 or 0Axy are found + // - note that a volume value does not stop tremolo - instead it glitches this whole thing up break; case 0x0a: // volume ramp + // TODO: non-0x-or-x0 value should be treated as 00 if (effectVal!=0) { if ((effectVal&15)!=0) { chan[i].volSpeed=-(effectVal&15)*64; From 9b6e582f8d97350367bda600dd67793d2599bf21 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 26 Mar 2022 22:15:15 -0500 Subject: [PATCH 27/48] dev72 - two more compat flags --- papers/format.md | 5 ++++- src/engine/engine.h | 4 ++-- src/engine/fileOps.cpp | 25 ++++++++++++++++++++----- src/engine/platform/gb.cpp | 7 +++++-- src/engine/playback.cpp | 2 +- src/engine/song.h | 6 +++++- src/gui/compatFlags.cpp | 8 ++++++++ 7 files changed, 45 insertions(+), 12 deletions(-) diff --git a/papers/format.md b/papers/format.md index fc954ba0..d61e94d4 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: +- 72: Furnace dev72 - 71: Furnace dev71 - 70: Furnace dev70 - 69: Furnace dev69 @@ -242,7 +243,9 @@ size | description 1 | no slides on first tick (>=71) or reserved 1 | next row reset arp pos (>=71) or reserved 1 | ignore jump at end (>=71) or reserved - 28 | reserved + 1 | buggy portamento after slide (>=72) or reserved + 1 | new ins affects envelope (Game Boy) (>=72) or reserved + 26 | reserved ``` # instrument diff --git a/src/engine/engine.h b/src/engine/engine.h index 52d400ca..24c3e135 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 "dev71" -#define DIV_ENGINE_VERSION 71 +#define DIV_VERSION "dev72" +#define DIV_ENGINE_VERSION 72 // for imports #define DIV_VERSION_MOD 0xff01 diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 4697d338..eca105a3 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -148,6 +148,8 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.noSlidesOnFirstTick=false; ds.rowResetsArpPos=false; ds.ignoreJumpAtEnd=true; + ds.buggyPortaAfterSlide=true; + ds.gbInsAffectsEnvelope=true; // 1.1 compat flags if (ds.version>24) { @@ -833,6 +835,10 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { ds.rowResetsArpPos=false; ds.ignoreJumpAtEnd=true; } + if (ds.version<72) { + ds.buggyPortaAfterSlide=true; + ds.gbInsAffectsEnvelope=false; + } ds.isDMF=false; reader.readS(); // reserved @@ -1080,15 +1086,22 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { // extended compat flags ds.brokenSpeedSel=reader.readC(); if (ds.version>=71) { - song.noSlidesOnFirstTick=reader.readC(); - song.rowResetsArpPos=reader.readC(); - song.ignoreJumpAtEnd=reader.readC(); + ds.noSlidesOnFirstTick=reader.readC(); + ds.rowResetsArpPos=reader.readC(); + ds.ignoreJumpAtEnd=reader.readC(); } else { reader.readC(); reader.readC(); reader.readC(); } - for (int i=0; i<28; i++) { + if (ds.version>=72) { + ds.buggyPortaAfterSlide=reader.readC(); + ds.gbInsAffectsEnvelope=reader.readC(); + } else { + reader.readC(); + reader.readC(); + } + for (int i=0; i<26; i++) { reader.readC(); } } @@ -1939,7 +1952,9 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { w->writeC(song.noSlidesOnFirstTick); w->writeC(song.rowResetsArpPos); w->writeC(song.ignoreJumpAtEnd); - for (int i=0; i<28; i++) { + w->writeC(song.buggyPortaAfterSlide); + w->writeC(song.gbInsAffectsEnvelope); + for (int i=0; i<26; i++) { w->writeC(0); } diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index d25cc211..d1d580c8 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -288,8 +288,11 @@ int DivPlatformGB::dispatch(DivCommand c) { if (chan[c.chan].ins!=c.value || c.value2==1) { chan[c.chan].ins=c.value; if (c.chan!=2) { - chan[c.chan].vol=parent->getIns(chan[c.chan].ins)->gb.envVol; - // TODO: also change envelope values + DivInstrument* ins=parent->getIns(chan[c.chan].ins); + chan[c.chan].vol=ins->gb.envVol; + if (parent->song.gbInsAffectsEnvelope) { + rWrite(16+c.chan*5+2,((chan[c.chan].vol<<4))|(ins->gb.envLen&7)|((ins->gb.envDir&1)<<3)); + } } } break; diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 8042e2cd..71fd58dd 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -882,7 +882,7 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].inPorta=false; dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); } else { - if (chan[i].note==chan[i].oldNote && !chan[i].inPorta) { + if (chan[i].note==chan[i].oldNote && !chan[i].inPorta && song.buggyPortaAfterSlide) { chan[i].portaNote=chan[i].note; chan[i].portaSpeed=-1; } else { diff --git a/src/engine/song.h b/src/engine/song.h index 19a89cfa..9142636e 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -305,6 +305,8 @@ struct DivSong { bool noSlidesOnFirstTick; bool rowResetsArpPos; bool ignoreJumpAtEnd; + bool buggyPortaAfterSlide; + bool gbInsAffectsEnvelope; DivOrders orders; std::vector ins; @@ -381,7 +383,9 @@ struct DivSong { brokenSpeedSel(false), noSlidesOnFirstTick(false), rowResetsArpPos(false), - ignoreJumpAtEnd(false) { + ignoreJumpAtEnd(false), + buggyPortaAfterSlide(false), + gbInsAffectsEnvelope(true) { for (int i=0; i<32; i++) { system[i]=DIV_SYSTEM_NULL; systemVol[i]=64; diff --git a/src/gui/compatFlags.cpp b/src/gui/compatFlags.cpp index ca0538ea..91aa34c7 100644 --- a/src/gui/compatFlags.cpp +++ b/src/gui/compatFlags.cpp @@ -97,6 +97,14 @@ void FurnaceGUI::drawCompatFlags() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("if this is on, a jump to next row effect will not take place when it is on the last order of a song."); } + ImGui::Checkbox("Buggy portamento after pitch slide",&e->song.buggyPortaAfterSlide); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("simulates a bug in where portamento does not work after sliding."); + } + ImGui::Checkbox("Apply Game Boy envelope on note-less instrument change",&e->song.gbInsAffectsEnvelope); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("if this is on, an instrument change will also affect the envelope."); + } ImGui::Text("Loop modality:"); if (ImGui::RadioButton("Reset channels",e->song.loopModality==0)) { From b514ee30da4a9a39dd82e3f162d5ae0f5d02a071 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 26 Mar 2022 22:34:11 -0500 Subject: [PATCH 28/48] MOD import: non-linear pitch --- src/engine/engine.cpp | 1 + src/engine/fileOps.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index eefefdec..4973b44f 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -962,6 +962,7 @@ void DivEngine::reset() { chan[i]=DivChannelState(); if (idispatch(DivCommand(DIV_CMD_GET_VOLMAX,dispatchChanOfChan[i]))<<8)|0xff; chan[i].volume=chan[i].volMax; + if (!song.linearPitch) chan[i].vibratoFine=4; } extValue=0; extValuePresent=0; diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index eca105a3..d9806db5 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -1312,6 +1312,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { DivSong ds; ds.tuning=436.0; ds.version=DIV_VERSION_MOD; + ds.linearPitch=false; ds.noSlidesOnFirstTick=true; ds.rowResetsArpPos=true; ds.ignoreJumpAtEnd=false; From 154ef3f9a3aaaf9fbee3e90f713882c88fc4e543 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 26 Mar 2022 23:39:20 -0500 Subject: [PATCH 29/48] Amiga: filter emulation --- papers/doc/7-systems/amiga.md | 2 +- src/engine/dispatch.h | 2 ++ src/engine/fileOps.cpp | 5 +++- src/engine/platform/amiga.cpp | 43 ++++++++++++++++++++++++++++++----- src/engine/platform/amiga.h | 6 +++++ src/engine/playback.cpp | 11 +++++++++ 6 files changed, 61 insertions(+), 8 deletions(-) diff --git a/papers/doc/7-systems/amiga.md b/papers/doc/7-systems/amiga.md index fba62c43..713300c2 100644 --- a/papers/doc/7-systems/amiga.md +++ b/papers/doc/7-systems/amiga.md @@ -6,4 +6,4 @@ in this very computer music trackers were born... # effects -none. as of this moment the Amiga doesn't need any effects in particular, but some may be added in a future. +- `10xx`: toggle low-pass filter. `0` turns it off and `1` turns it on. \ No newline at end of file diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index a3cd1e81..b535df52 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -106,6 +106,8 @@ enum DivDispatchCmds { DIV_CMD_SAA_ENVELOPE, + DIV_CMD_AMIGA_FILTER, + DIV_CMD_LYNX_LFSR_LOAD, DIV_CMD_QSOUND_ECHO_FEEDBACK, diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index d9806db5..0e82b6b7 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -1573,6 +1573,9 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { fxTyp=fxVal>>4; fxVal&=0x0f; switch (fxTyp) { + case 0: + writeFxCol(0x10,!fxVal); + break; case 1: // single note slide up case 2: // single note slide down writeFxCol(fxTyp-1+0xf1,fxVal); @@ -1613,7 +1616,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { ds.systemLen=(chCount+3)/4; for(int i=0; i1 || bypassLimits)?2:0); // PAL } for(int i=0; i=0 && chan[i].samplesong.sampleLen) { chan[i].audSub-=AMIGA_DIVIDER; @@ -102,14 +112,20 @@ void DivPlatformAmiga::acquire(short* bufL, short* bufR, size_t start, size_t le } if (!isMuted[i]) { if (i==0 || i==3) { - bufL[h]+=((chan[i].audDat*chan[i].outVol)*sep1)>>7; - bufR[h]+=((chan[i].audDat*chan[i].outVol)*sep2)>>7; + outL+=((chan[i].audDat*chan[i].outVol)*sep1)>>7; + outR+=((chan[i].audDat*chan[i].outVol)*sep2)>>7; } else { - bufL[h]+=((chan[i].audDat*chan[i].outVol)*sep2)>>7; - bufR[h]+=((chan[i].audDat*chan[i].outVol)*sep1)>>7; + outL+=((chan[i].audDat*chan[i].outVol)*sep2)>>7; + outR+=((chan[i].audDat*chan[i].outVol)*sep1)>>7; } } } + filter[0][0]+=(filtConst*(outL-filter[0][0]))>>12; + filter[0][1]+=(filtConst*(filter[0][0]-filter[0][1]))>>12; + filter[1][0]+=(filtConst*(outR-filter[1][0]))>>12; + filter[1][1]+=(filtConst*(filter[1][0]-filter[1][1]))>>12; + bufL[h]=filter[0][1]; + bufR[h]=filter[1][1]; } } @@ -299,6 +315,10 @@ int DivPlatformAmiga::dispatch(DivCommand c) { chan[c.chan].audPos=c.value; chan[c.chan].setPos=true; break; + case DIV_CMD_AMIGA_FILTER: + filterOn=c.value; + filtConst=filterOn?filtConstOn:filtConstOff; + break; case DIV_CMD_GET_VOLMAX: return 64; break; @@ -332,7 +352,11 @@ void* DivPlatformAmiga::getChanState(int ch) { void DivPlatformAmiga::reset() { for (int i=0; i<4; i++) { chan[i]=DivPlatformAmiga::Channel(); + filter[0][i]=0; + filter[1][i]=0; } + filterOn=false; + filtConst=filterOn?filtConstOn:filtConstOff; } bool DivPlatformAmiga::isStereo() { @@ -372,6 +396,13 @@ void DivPlatformAmiga::setFlags(unsigned int flags) { sep2=127-((flags>>8)&127); amigaModel=flags&2; bypassLimits=flags&4; + if (amigaModel) { + filtConstOff=4000; + filtConstOn=sin(M_PI*8000.0/(double)rate)*4096.0; + } else { + filtConstOff=sin(M_PI*16000.0/(double)rate)*4096.0; + filtConstOn=sin(M_PI*5500.0/(double)rate)*4096.0; + } } int DivPlatformAmiga::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { diff --git a/src/engine/platform/amiga.h b/src/engine/platform/amiga.h index 02dcb3de..d73918e9 100644 --- a/src/engine/platform/amiga.h +++ b/src/engine/platform/amiga.h @@ -68,6 +68,11 @@ class DivPlatformAmiga: public DivDispatch { bool isMuted[4]; bool bypassLimits; bool amigaModel; + bool filterOn; + + int filter[2][4]; + int filtConst; + int filtConstOff, filtConstOn; int sep1, sep2; @@ -88,6 +93,7 @@ class DivPlatformAmiga: public DivDispatch { void notifyWaveChange(int wave); void notifyInsDeletion(void* ins); const char** getRegisterSheet(); + const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); }; diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 71fd58dd..7d089e60 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -113,6 +113,8 @@ const char* cmdName[DIV_CMD_MAX]={ "AY_AUTO_PWM", "SAA_ENVELOPE", + + "AMIGA_FILTER", "LYNX_LFSR_LOAD", @@ -649,6 +651,15 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char return false; } break; + case DIV_SYSTEM_AMIGA: + switch (effect) { + case 0x10: // toggle filter + dispatchCmd(DivCommand(DIV_CMD_AMIGA_FILTER,ch,effectVal)); + break; + default: + return false; + } + break; case DIV_SYSTEM_SEGAPCM: case DIV_SYSTEM_SEGAPCM_COMPAT: switch (effect) { From 08dd693fa0f8a125637cd8bd523e55ecd400eee7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 27 Mar 2022 00:02:17 -0500 Subject: [PATCH 30/48] Amiga: add AM/PM effects --- papers/doc/7-systems/amiga.md | 6 +++++- src/engine/dispatch.h | 2 ++ src/engine/platform/amiga.cpp | 21 +++++++++++++++++++++ src/engine/platform/amiga.h | 4 +++- src/engine/playback.cpp | 8 ++++++++ 5 files changed, 39 insertions(+), 2 deletions(-) diff --git a/papers/doc/7-systems/amiga.md b/papers/doc/7-systems/amiga.md index 713300c2..97c1151f 100644 --- a/papers/doc/7-systems/amiga.md +++ b/papers/doc/7-systems/amiga.md @@ -6,4 +6,8 @@ in this very computer music trackers were born... # effects -- `10xx`: toggle low-pass filter. `0` turns it off and `1` turns it on. \ No newline at end of file +- `10xx`: toggle low-pass filter. `0` turns it off and `1` turns it on. +- `11xx`: toggle amplitude modulation with the next channel. + - does not work on the last channel. +- `12xx`: toggle period (frequency) modulation with the next channel. + - does not work on the last channel. \ No newline at end of file diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index b535df52..15f1cf0d 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -107,6 +107,8 @@ enum DivDispatchCmds { DIV_CMD_SAA_ENVELOPE, DIV_CMD_AMIGA_FILTER, + DIV_CMD_AMIGA_AM, + DIV_CMD_AMIGA_PM, DIV_CMD_LYNX_LFSR_LOAD, diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index a58de52f..b0f25f72 100644 --- a/src/engine/platform/amiga.cpp +++ b/src/engine/platform/amiga.cpp @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#define _USE_MATH_DEFINES #include "amiga.h" #include "../engine.h" #include @@ -68,6 +69,12 @@ const char* DivPlatformAmiga::getEffectName(unsigned char effect) { case 0x10: return "10xx: Toggle filter (0 disables; 1 enables)"; break; + case 0x11: + return "11xx: Toggle AM with next channel"; + break; + case 0x12: + return "12xx: Toggle period modulation with next channel"; + break; } return NULL; } @@ -84,6 +91,14 @@ void DivPlatformAmiga::acquire(short* bufL, short* bufR, size_t start, size_t le DivSample* s=parent->getSample(chan[i].sample); if (s->samples>0) { chan[i].audDat=s->data8[chan[i].audPos++]; + if (i<3 && chan[i].useV) { + chan[i+1].outVol=(unsigned char)chan[i].audDat^0x80; + if (chan[i+1].outVol>64) chan[i+1].outVol=64; + } + if (i<3 && chan[i].useP) { + chan[i+1].freq=(unsigned char)chan[i].audDat^0x80; + if (chan[i+1].freq>AMIGA_DIVIDER) chan[i+1].freq=AMIGA_DIVIDER; + } if (chan[i].audPos>=s->samples || chan[i].audPos>=131071) { if (s->loopStart>=0 && s->loopStart<(int)s->samples) { chan[i].audPos=s->loopStart; @@ -319,6 +334,12 @@ int DivPlatformAmiga::dispatch(DivCommand c) { filterOn=c.value; filtConst=filterOn?filtConstOn:filtConstOff; break; + case DIV_CMD_AMIGA_AM: + chan[c.chan].useV=c.value; + break; + case DIV_CMD_AMIGA_PM: + chan[c.chan].useP=c.value; + break; case DIV_CMD_GET_VOLMAX: return 64; break; diff --git a/src/engine/platform/amiga.h b/src/engine/platform/amiga.h index d73918e9..b5f13701 100644 --- a/src/engine/platform/amiga.h +++ b/src/engine/platform/amiga.h @@ -36,7 +36,7 @@ class DivPlatformAmiga: public DivDispatch { unsigned char ins; int busClock; int note; - bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, useWave, setPos; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, useWave, setPos, useV, useP; signed char vol, outVol; DivMacroInt std; Channel(): @@ -61,6 +61,8 @@ class DivPlatformAmiga: public DivDispatch { inPorta(false), useWave(false), setPos(false), + useV(false), + useP(false), vol(64), outVol(64) {} }; diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 7d089e60..4ab6a517 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -115,6 +115,8 @@ const char* cmdName[DIV_CMD_MAX]={ "SAA_ENVELOPE", "AMIGA_FILTER", + "AMIGA_AM", + "AMIGA_PM", "LYNX_LFSR_LOAD", @@ -656,6 +658,12 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char case 0x10: // toggle filter dispatchCmd(DivCommand(DIV_CMD_AMIGA_FILTER,ch,effectVal)); break; + case 0x11: // toggle AM + dispatchCmd(DivCommand(DIV_CMD_AMIGA_AM,ch,effectVal)); + break; + case 0x12: // toggle PM + dispatchCmd(DivCommand(DIV_CMD_AMIGA_PM,ch,effectVal)); + break; default: return false; } From a58c6da19d97ff1c0ce45cb21c1f0193c2c83496 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 27 Mar 2022 00:29:37 -0500 Subject: [PATCH 31/48] Amiga: oops --- src/engine/platform/amiga.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index b0f25f72..e1bc8394 100644 --- a/src/engine/platform/amiga.cpp +++ b/src/engine/platform/amiga.cpp @@ -97,7 +97,7 @@ void DivPlatformAmiga::acquire(short* bufL, short* bufR, size_t start, size_t le } if (i<3 && chan[i].useP) { chan[i+1].freq=(unsigned char)chan[i].audDat^0x80; - if (chan[i+1].freq>AMIGA_DIVIDER) chan[i+1].freq=AMIGA_DIVIDER; + if (chan[i+1].freq=s->samples || chan[i].audPos>=131071) { if (s->loopStart>=0 && s->loopStart<(int)s->samples) { From 1c98748a88176697d9a9a9d93958898e8aae60b3 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 27 Mar 2022 01:38:24 -0500 Subject: [PATCH 32/48] GUI: redesign FM editor layout, part 2 --- src/gui/insEdit.cpp | 64 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 930bcccf..2e0645d5 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -911,6 +911,12 @@ if (ImGui::BeginTable("MacroSpace",2)) { \ } \ } +#define CENTER_TEXT(text) \ + ImGui::SetCursorPosX(ImGui::GetCursorPosX()+0.5*(ImGui::GetContentRegionAvail().x-ImGui::CalcTextSize(text).x)); + +#define CENTER_VSLIDER \ + ImGui::SetCursorPosX(ImGui::GetCursorPosX()+0.5f*ImGui::GetContentRegionAvail().x-20.0f); + void FurnaceGUI::drawInsEdit() { if (nextWindow==GUI_WINDOW_INS_EDIT) { insEditOpen=true; @@ -1055,60 +1061,76 @@ void FurnaceGUI::drawInsEdit() { } if (willDisplayOps) { if (settings.fmLayout==0) { - if (ImGui::BeginTable("FMOperators",15,ImGuiTableFlags_SizingStretchProp|ImGuiTableFlags_BordersH|ImGuiTableFlags_BordersOuterV)) { + if (ImGui::BeginTable("FMOperators",16,ImGuiTableFlags_SizingStretchProp|ImGuiTableFlags_BordersH|ImGuiTableFlags_BordersOuterV)) { // configure columns ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); // op name - ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed); // ar - ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed); // dr - ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthFixed); // sl - ImGui::TableSetupColumn("c4",ImGuiTableColumnFlags_WidthFixed); // d2r - ImGui::TableSetupColumn("c5",ImGuiTableColumnFlags_WidthFixed); // rr + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.05f); // ar + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.05f); // dr + ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthStretch,0.05f); // sl + ImGui::TableSetupColumn("c4",ImGuiTableColumnFlags_WidthStretch,0.05f); // d2r + ImGui::TableSetupColumn("c5",ImGuiTableColumnFlags_WidthStretch,0.05f); // rr ImGui::TableSetupColumn("c6",ImGuiTableColumnFlags_WidthFixed); // -separator- - ImGui::TableSetupColumn("c7",ImGuiTableColumnFlags_WidthFixed); // tl - ImGui::TableSetupColumn("c8",ImGuiTableColumnFlags_WidthFixed); // ... - ImGui::TableSetupColumn("c9",ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("c10",ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("c11",ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("c12",ImGuiTableColumnFlags_WidthStretch,0.4f); - ImGui::TableSetupColumn("c13",ImGuiTableColumnFlags_WidthStretch,0.6f); - ImGui::TableSetupColumn("c14",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c7",ImGuiTableColumnFlags_WidthStretch,0.05f); // tl + ImGui::TableSetupColumn("c8",ImGuiTableColumnFlags_WidthStretch,0.05f); // rs + ImGui::TableSetupColumn("c9",ImGuiTableColumnFlags_WidthStretch,0.05f); // mult + ImGui::TableSetupColumn("c10",ImGuiTableColumnFlags_WidthStretch,0.05f); // dt + ImGui::TableSetupColumn("c11",ImGuiTableColumnFlags_WidthStretch,0.05f); // dt2 + ImGui::TableSetupColumn("c12",ImGuiTableColumnFlags_WidthFixed); // -separator- + ImGui::TableSetupColumn("c13",ImGuiTableColumnFlags_WidthStretch,0.2f); // ssg + ImGui::TableSetupColumn("c14",ImGuiTableColumnFlags_WidthStretch,0.3f); // env + ImGui::TableSetupColumn("c15",ImGuiTableColumnFlags_WidthFixed); // am // header ImGui::TableNextRow(ImGuiTableRowFlags_Headers); ImGui::TableNextColumn(); ImGui::TableNextColumn(); + CENTER_TEXT(FM_NAME(FM_AR)); ImGui::TextUnformatted(FM_NAME(FM_AR)); ImGui::TableNextColumn(); + CENTER_TEXT(FM_NAME(FM_DR)); ImGui::TextUnformatted(FM_NAME(FM_DR)); ImGui::TableNextColumn(); + CENTER_TEXT(FM_NAME(FM_SL)); ImGui::TextUnformatted(FM_NAME(FM_SL)); if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { ImGui::TableNextColumn(); + CENTER_TEXT(FM_NAME(FM_D2R)); ImGui::TextUnformatted(FM_NAME(FM_D2R)); } ImGui::TableNextColumn(); + CENTER_TEXT(FM_NAME(FM_RR)); ImGui::TextUnformatted(FM_NAME(FM_RR)); ImGui::TableNextColumn(); ImGui::TableNextColumn(); + CENTER_TEXT(FM_NAME(FM_TL)); ImGui::TextUnformatted(FM_NAME(FM_TL)); ImGui::TableNextColumn(); if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + CENTER_TEXT(FM_NAME(FM_RS)); ImGui::TextUnformatted(FM_NAME(FM_RS)); } else { + CENTER_TEXT(FM_NAME(FM_KSR)); ImGui::TextUnformatted(FM_NAME(FM_KSR)); } ImGui::TableNextColumn(); + CENTER_TEXT(FM_NAME(FM_MULT)); ImGui::TextUnformatted(FM_NAME(FM_MULT)); ImGui::TableNextColumn(); + CENTER_TEXT(FM_NAME(FM_DT)); ImGui::TextUnformatted(FM_NAME(FM_DT)); ImGui::TableNextColumn(); + CENTER_TEXT(FM_NAME(FM_DT2)); ImGui::TextUnformatted(FM_NAME(FM_DT2)); ImGui::TableNextColumn(); + ImGui::TableNextColumn(); + CENTER_TEXT(FM_NAME(FM_SSG)); ImGui::TextUnformatted(FM_NAME(FM_SSG)); ImGui::TableNextColumn(); + CENTER_TEXT("Envelope"); ImGui::TextUnformatted("Envelope"); ImGui::TableNextColumn(); + CENTER_TEXT(FM_NAME(FM_AM)); ImGui::TextUnformatted(FM_NAME(FM_AM)); float sliderHeight=32.0f*dpiScale; @@ -1151,24 +1173,29 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); op.ar&=maxArDr; + CENTER_VSLIDER; P(ImGui::VSliderScalar("##AR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.ar,&_ZERO,&maxArDr)); ImGui::TableNextColumn(); op.dr&=maxArDr; + CENTER_VSLIDER; P(ImGui::VSliderScalar("##DR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dr,&_ZERO,&maxArDr)); ImGui::TableNextColumn(); op.sl&=15; + CENTER_VSLIDER; P(ImGui::VSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { ImGui::TableNextColumn(); op.d2r&=31; + CENTER_VSLIDER; P(ImGui::VSliderScalar("##D2R",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.d2r,&_ZERO,&_THIRTY_ONE)); } ImGui::TableNextColumn(); op.rr&=15; + CENTER_VSLIDER; P(ImGui::VSliderScalar("##RR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rr,&_ZERO,&_FIFTEEN)); ImGui::TableNextColumn(); @@ -1176,9 +1203,11 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); op.tl&=maxTl; + CENTER_VSLIDER; P(ImGui::VSliderScalar("##TL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.tl,&maxTl,&_ZERO)); ImGui::TableNextColumn(); + CENTER_VSLIDER; if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { P(ImGui::VSliderScalar("##RS",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rs,&_ZERO,&_THREE)); } else { @@ -1186,21 +1215,27 @@ void FurnaceGUI::drawInsEdit() { } ImGui::TableNextColumn(); + CENTER_VSLIDER; P(ImGui::VSliderScalar("##MULT",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.mult,&_ZERO,&_FIFTEEN)); if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { int detune=(op.dt&7)-3; ImGui::TableNextColumn(); + CENTER_VSLIDER; if (ImGui::VSliderInt("##DT",ImVec2(20.0f*dpiScale,sliderHeight),&detune,-3,4)) { PARAMETER op.dt=detune+3; } ImGui::TableNextColumn(); + CENTER_VSLIDER; P(ImGui::VSliderScalar("##DT2",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dt2,&_ZERO,&_THREE)); rightClickable if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Only on YM2151 (OPM)"); } + ImGui::TableNextColumn(); + ImGui::Dummy(ImVec2(4.0f*dpiScale,2.0f*dpiScale)); + ImGui::TableNextColumn(); if (ins->type!=DIV_INS_OPL && ins->type!=DIV_INS_OPZ) { if (ImGui::Checkbox("##SSGOn",&ssgOn)) { PARAMETER @@ -1225,6 +1260,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); bool amOn=op.am; + ImGui::SetCursorPosY(ImGui::GetCursorPosY()+0.5*(sliderHeight-ImGui::GetFrameHeight())); if (ImGui::Checkbox("##AM",&amOn)) { PARAMETER op.am=amOn; } From 5dac609d92f0c317d9088a00c79b428c5f0eafae Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 27 Mar 2022 01:47:27 -0500 Subject: [PATCH 33/48] Genesis: better DAC write algorithm only write DAC if there aren't too many queued writes --- src/engine/platform/genesis.cpp | 8 ++++++-- src/engine/platform/genesisshared.h | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index 46e24333..10a8762c 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -92,7 +92,9 @@ void DivPlatformGenesis::acquire_nuked(short* bufL, short* bufR, size_t start, s DivSample* s=parent->getSample(dacSample); if (s->samples>0) { if (!isMuted[5]) { - urgentWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80); + if (writes.size()<16) { + urgentWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80); + } } if (++dacPos>=s->samples) { if (s->loopStart>=0 && s->loopStart<(int)s->samples) { @@ -159,7 +161,9 @@ void DivPlatformGenesis::acquire_ymfm(short* bufL, short* bufR, size_t start, si DivSample* s=parent->getSample(dacSample); if (s->samples>0) { if (!isMuted[5]) { - urgentWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80); + if (writes.size()<16) { + urgentWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80); + } } if (++dacPos>=s->samples) { if (s->loopStart>=0 && s->loopStart<(int)s->samples) { diff --git a/src/engine/platform/genesisshared.h b/src/engine/platform/genesisshared.h index 6410ddb2..3d5320f6 100644 --- a/src/engine/platform/genesisshared.h +++ b/src/engine/platform/genesisshared.h @@ -44,6 +44,6 @@ static int orderedOps[4]={ #define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} #define immWrite(a,v) if (!skipRegisterWrites) {writes.push_back(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);} } -#define urgentWrite(a,v) if (!skipRegisterWrites) {if (writes.empty() || writes.front().addrOrVal) {writes.push_back(QueuedWrite(a,v));} else {writes.push_front(QueuedWrite(a,v));}; if (dumpWrites) {addWrite(a,v);} } +#define urgentWrite(a,v) if (!skipRegisterWrites) {if (writes.empty() || writes.size()>16 || writes.front().addrOrVal) {writes.push_back(QueuedWrite(a,v));} else {writes.push_front(QueuedWrite(a,v));}; if (dumpWrites) {addWrite(a,v);} } #include "fmshared_OPN.h" From 062e85af504ae2e2a6017e15d8512a2604bb1137 Mon Sep 17 00:00:00 2001 From: freq-mod <32672779+freq-mod@users.noreply.github.com> Date: Sun, 27 Mar 2022 09:29:53 +0200 Subject: [PATCH 34/48] fix a critical vic20 documentation error --- papers/doc/7-systems/vic20.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/papers/doc/7-systems/vic20.md b/papers/doc/7-systems/vic20.md index b221a3cb..b4dbbffa 100644 --- a/papers/doc/7-systems/vic20.md +++ b/papers/doc/7-systems/vic20.md @@ -4,7 +4,7 @@ The Commodore VIC-20 was Commodore's major attempt at making a personal home com It has 4 PSG voices that has a limited but wide tuning range, and like the SN76489, the last voice is dedicated to playing pseudo-white noise. Every voice on the VIC-20 has a high-pass and low-pass filter applied to it, which is likely how Rob Yannes got the inspiration to put custom filter modes on the C64's SID. -The 3 pulse wave channels also have different octaves that they can play notes on (not currently emulated in Furnace version dev71). The first channel is the bass channel, and it can play notes from octaves 2 to octaves 4. The next is the 'mid/chord' channel, and it plays notes from octaves 3 to 5. And rather obviously, the 3rd pulse channel is typically the lead channel, can play notes from octaves 4 to 6. +The 3 pulse wave channels also have different octaves that they can play notes on. The first channel is the bass channel, and it can play notes from octaves 2 to octaves 4. The next is the 'mid/chord' channel, and it plays notes from octaves 3 to 5. And rather obviously, the 3rd pulse channel is typically the lead channel, can play notes from octaves 4 to 6. ## effect commands From 688190db91f4b5579f05b0a3e21012066429b0f1 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 27 Mar 2022 02:31:03 -0500 Subject: [PATCH 35/48] improve VIC-20 doc --- papers/doc/7-systems/vic20.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/papers/doc/7-systems/vic20.md b/papers/doc/7-systems/vic20.md index b221a3cb..0c818631 100644 --- a/papers/doc/7-systems/vic20.md +++ b/papers/doc/7-systems/vic20.md @@ -2,10 +2,10 @@ The Commodore VIC-20 was Commodore's major attempt at making a personal home computer, and is the percursor to the Commodore 64. The VIC-20 was also known as the VC-20 in Germany, and the VIC-1001 in Japan. -It has 4 PSG voices that has a limited but wide tuning range, and like the SN76489, the last voice is dedicated to playing pseudo-white noise. Every voice on the VIC-20 has a high-pass and low-pass filter applied to it, which is likely how Rob Yannes got the inspiration to put custom filter modes on the C64's SID. +It has 4 PSG voices that has a limited but wide tuning range, and like the SN76489, the last voice is dedicated to playing pseudo-white noise. -The 3 pulse wave channels also have different octaves that they can play notes on (not currently emulated in Furnace version dev71). The first channel is the bass channel, and it can play notes from octaves 2 to octaves 4. The next is the 'mid/chord' channel, and it plays notes from octaves 3 to 5. And rather obviously, the 3rd pulse channel is typically the lead channel, can play notes from octaves 4 to 6. +The 3 pulse wave channels also have different octaves that they can play notes on. The first channel is the bass channel, and it can play notes from octave 1. The next is the 'mid/chord' channel, and it plays notes from octave 2. And rather obviously, the 3rd pulse channel is typically the lead channel, can play notes from octave 3. ## effect commands - - `10xx` Switch waveform (Only the values 00 though 0F are unique. Everything else is a copy. For example, `1006` is the same as `10f6`.) + - `10xx` Switch waveform (`xx` from `00` to `0F`) \ No newline at end of file From f7d7b00e938e1981ffc8e87804aeeec357b41b01 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 27 Mar 2022 03:29:45 -0500 Subject: [PATCH 36/48] GUI: redesign FM editor layout, part 3 --- src/gui/gui.h | 2 + src/gui/insEdit.cpp | 173 ++++++++++++++++++++++++++++++++++++++------ 2 files changed, 151 insertions(+), 24 deletions(-) diff --git a/src/gui/gui.h b/src/gui/gui.h index 1c646663..841b2d3d 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -821,6 +821,8 @@ class FurnaceGUI { float keyHit[DIV_MAX_CHANS]; int lastIns[DIV_MAX_CHANS]; + void drawSSGEnv(unsigned char type, const ImVec2& size); + void drawWaveform(unsigned char type, bool opz, const ImVec2& size); void drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, const ImVec2& size); void drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, unsigned char d2r, unsigned char rr, unsigned char sl, float maxTl, float maxArDr, const ImVec2& size); void drawSysConf(int i); diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 2e0645d5..6fa99feb 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -37,6 +37,12 @@ const char* fmParamNames[3][27]={ {"ALG", "FB", "FMS/PMS", "AMS", "AR", "DR", "D2R", "RR", "SL", "TL", "RS", "MULT", "DT", "DT2", "SSG-EG", "AM", "DAM", "DVB", "EGT", "EGS", "KSL", "SUS", "VIB", "WS", "KSR", "DC", "DM"} }; +const char* fmParamShortNames[3][27]={ + {"ALG", "FB", "FMS", "AMS", "A", "D", "D2", "R", "S", "TL", "RS", "ML", "DT", "DT2", "SSG", "AM", "DAM", "DVB", "SUS", "SUS", "KSL", "SUS", "VIB", "WS", "KSR", "DC", "DM"}, + {"ALG", "FB", "FMS", "AMS", "A", "D", "SR", "R", "S", "TL", "KS", "ML", "DT", "DT2", "SSG", "AM", "AMD", "FMD", "EGT", "EGT", "KSL", "SUS", "VIB", "WS", "KSR", "DC", "DM"}, + {"ALG", "FB", "FMS", "AMS", "A", "D", "D2", "R", "S", "TL", "RS", "ML", "DT", "DT2", "SSG", "AM", "DAM", "DVB", "EGT", "EGS", "KSL", "SUS", "VIB", "WS", "KSR", "DC", "DM"} +}; + const char* opllInsNames[17]={ "User", "Violin", @@ -96,6 +102,7 @@ enum FMParams { }; #define FM_NAME(x) fmParamNames[settings.fmNames][x] +#define FM_SHORT_NAME(x) fmParamShortNames[settings.fmNames][x] const char* c64ShapeBits[5]={ "triangle", "saw", "pulse", "noise", NULL @@ -179,6 +186,121 @@ void addAALine(ImDrawList* dl, const ImVec2& p1, const ImVec2& p2, const ImU32 c dl->AddPolyline(pt,2,color,ImDrawFlags_None,thickness); } +// TODO: don't get drunk tonight +void FurnaceGUI::drawSSGEnv(unsigned char type, const ImVec2& size) { + ImDrawList* dl=ImGui::GetWindowDrawList(); + ImGuiWindow* window=ImGui::GetCurrentWindow(); + + ImVec2 minArea=window->DC.CursorPos; + ImVec2 maxArea=ImVec2( + minArea.x+size.x, + minArea.y+size.y + ); + ImRect rect=ImRect(minArea,maxArea); + ImGuiStyle& style=ImGui::GetStyle(); + ImU32 color=ImGui::GetColorU32(uiColors[GUI_COLOR_TEXT]); + ImGui::ItemSize(size,style.FramePadding.y); + if (ImGui::ItemAdd(rect,ImGui::GetID("ssgEnvDisplay"))) { + ImGui::RenderFrame(rect.Min,rect.Max,ImGui::GetColorU32(ImGuiCol_FrameBg),true,style.FrameRounding); + switch (type) { + case 0: + for (int i=0; i<4; i++) { + ImVec2 pos1=ImLerp(rect.Min,rect.Max,ImVec2((float)i/4.0f,0.2)); + ImVec2 pos2=ImLerp(rect.Min,rect.Max,ImVec2((float)(i+1)/4.0f,0.8)); + addAALine(dl,pos1,pos2,color); + pos1.x=pos2.x; + if (i<3) addAALine(dl,pos1,pos2,color); + } + break; + case 1: { + ImVec2 pos1=ImLerp(rect.Min,rect.Max,ImVec2(0.0,0.2)); + ImVec2 pos2=ImLerp(rect.Min,rect.Max,ImVec2(0.25,0.8)); + addAALine(dl,pos1,pos2,color); + + pos1=ImLerp(rect.Min,rect.Max,ImVec2(1.0,0.8)); + addAALine(dl,pos1,pos2,color); + break; + } + case 2: { + ImVec2 pos1=ImLerp(rect.Min,rect.Max,ImVec2(0.0,0.2)); + ImVec2 pos2=ImLerp(rect.Min,rect.Max,ImVec2(0.25,0.8)); + addAALine(dl,pos1,pos2,color); + + pos1=ImLerp(rect.Min,rect.Max,ImVec2(0.5,0.2)); + addAALine(dl,pos1,pos2,color); + + pos2=ImLerp(rect.Min,rect.Max,ImVec2(0.75,0.8)); + addAALine(dl,pos1,pos2,color); + + pos1=ImLerp(rect.Min,rect.Max,ImVec2(1.0,0.2)); + addAALine(dl,pos1,pos2,color); + break; + } + case 3: { + ImVec2 pos1=ImLerp(rect.Min,rect.Max,ImVec2(0.0,0.2)); + ImVec2 pos2=ImLerp(rect.Min,rect.Max,ImVec2(0.25,0.8)); + addAALine(dl,pos1,pos2,color); + + pos1.x=pos2.x; + addAALine(dl,pos1,pos2,color); + + pos2=ImLerp(rect.Min,rect.Max,ImVec2(1.0,0.2)); + addAALine(dl,pos1,pos2,color); + break; + } + case 4: + for (int i=0; i<4; i++) { + ImVec2 pos1=ImLerp(rect.Min,rect.Max,ImVec2((float)i/4.0f,0.8)); + ImVec2 pos2=ImLerp(rect.Min,rect.Max,ImVec2((float)(i+1)/4.0f,0.2)); + addAALine(dl,pos1,pos2,color); + pos1.x=pos2.x; + if (i<3) addAALine(dl,pos1,pos2,color); + } + break; + case 5: { + ImVec2 pos1=ImLerp(rect.Min,rect.Max,ImVec2(0.0,0.8)); + ImVec2 pos2=ImLerp(rect.Min,rect.Max,ImVec2(0.25,0.2)); + addAALine(dl,pos1,pos2,color); + + pos1=ImLerp(rect.Min,rect.Max,ImVec2(1.0,0.2)); + addAALine(dl,pos1,pos2,color); + break; + } + case 6: { + ImVec2 pos1=ImLerp(rect.Min,rect.Max,ImVec2(0.0,0.8)); + ImVec2 pos2=ImLerp(rect.Min,rect.Max,ImVec2(0.25,0.2)); + addAALine(dl,pos1,pos2,color); + + pos1=ImLerp(rect.Min,rect.Max,ImVec2(0.5,0.8)); + addAALine(dl,pos1,pos2,color); + + pos2=ImLerp(rect.Min,rect.Max,ImVec2(0.75,0.2)); + addAALine(dl,pos1,pos2,color); + + pos1=ImLerp(rect.Min,rect.Max,ImVec2(1.0,0.8)); + addAALine(dl,pos1,pos2,color); + break; + } + case 7: { + ImVec2 pos1=ImLerp(rect.Min,rect.Max,ImVec2(0.0,0.8)); + ImVec2 pos2=ImLerp(rect.Min,rect.Max,ImVec2(0.25,0.2)); + addAALine(dl,pos1,pos2,color); + + pos1.x=pos2.x; + addAALine(dl,pos1,pos2,color); + + pos2=ImLerp(rect.Min,rect.Max,ImVec2(1.0,0.8)); + addAALine(dl,pos1,pos2,color); + break; + } + } + } +} + +void FurnaceGUI::drawWaveform(unsigned char type, bool opz, const ImVec2& size) { + +} + void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, const ImVec2& size) { ImDrawList* dl=ImGui::GetWindowDrawList(); ImGuiWindow* window=ImGui::GetCurrentWindow(); @@ -1085,43 +1207,43 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); ImGui::TableNextColumn(); - CENTER_TEXT(FM_NAME(FM_AR)); - ImGui::TextUnformatted(FM_NAME(FM_AR)); + CENTER_TEXT(FM_SHORT_NAME(FM_AR)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_AR)); ImGui::TableNextColumn(); - CENTER_TEXT(FM_NAME(FM_DR)); - ImGui::TextUnformatted(FM_NAME(FM_DR)); + CENTER_TEXT(FM_SHORT_NAME(FM_DR)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_DR)); ImGui::TableNextColumn(); - CENTER_TEXT(FM_NAME(FM_SL)); - ImGui::TextUnformatted(FM_NAME(FM_SL)); + CENTER_TEXT(FM_SHORT_NAME(FM_SL)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_SL)); if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { ImGui::TableNextColumn(); - CENTER_TEXT(FM_NAME(FM_D2R)); - ImGui::TextUnformatted(FM_NAME(FM_D2R)); + CENTER_TEXT(FM_SHORT_NAME(FM_D2R)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_D2R)); } ImGui::TableNextColumn(); - CENTER_TEXT(FM_NAME(FM_RR)); - ImGui::TextUnformatted(FM_NAME(FM_RR)); + CENTER_TEXT(FM_SHORT_NAME(FM_RR)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_RR)); ImGui::TableNextColumn(); ImGui::TableNextColumn(); - CENTER_TEXT(FM_NAME(FM_TL)); - ImGui::TextUnformatted(FM_NAME(FM_TL)); + CENTER_TEXT(FM_SHORT_NAME(FM_TL)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_TL)); ImGui::TableNextColumn(); if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { - CENTER_TEXT(FM_NAME(FM_RS)); - ImGui::TextUnformatted(FM_NAME(FM_RS)); + CENTER_TEXT(FM_SHORT_NAME(FM_RS)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_RS)); } else { - CENTER_TEXT(FM_NAME(FM_KSR)); - ImGui::TextUnformatted(FM_NAME(FM_KSR)); + CENTER_TEXT(FM_SHORT_NAME(FM_KSR)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_KSR)); } ImGui::TableNextColumn(); - CENTER_TEXT(FM_NAME(FM_MULT)); - ImGui::TextUnformatted(FM_NAME(FM_MULT)); + CENTER_TEXT(FM_SHORT_NAME(FM_MULT)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_MULT)); ImGui::TableNextColumn(); - CENTER_TEXT(FM_NAME(FM_DT)); - ImGui::TextUnformatted(FM_NAME(FM_DT)); + CENTER_TEXT(FM_SHORT_NAME(FM_DT)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_DT)); ImGui::TableNextColumn(); - CENTER_TEXT(FM_NAME(FM_DT2)); - ImGui::TextUnformatted(FM_NAME(FM_DT2)); + CENTER_TEXT(FM_SHORT_NAME(FM_DT2)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_DT2)); ImGui::TableNextColumn(); ImGui::TableNextColumn(); CENTER_TEXT(FM_NAME(FM_SSG)); @@ -1130,8 +1252,8 @@ void FurnaceGUI::drawInsEdit() { CENTER_TEXT("Envelope"); ImGui::TextUnformatted("Envelope"); ImGui::TableNextColumn(); - CENTER_TEXT(FM_NAME(FM_AM)); - ImGui::TextUnformatted(FM_NAME(FM_AM)); + CENTER_TEXT(FM_SHORT_NAME(FM_AM)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_AM)); float sliderHeight=32.0f*dpiScale; @@ -1238,6 +1360,9 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); if (ins->type!=DIV_INS_OPL && ins->type!=DIV_INS_OPZ) { + ImGui::BeginDisabled(!ssgOn); + drawSSGEnv(op.ssgEnv&7,ImVec2(ImGui::GetContentRegionAvail().x,sliderHeight-ImGui::GetFrameHeightWithSpacing())); + ImGui::EndDisabled(); if (ImGui::Checkbox("##SSGOn",&ssgOn)) { PARAMETER op.ssgEnv=(op.ssgEnv&7)|(ssgOn<<3); } From 09b5dd556e7f3dbf88f1165d13c2e48f0f94a308 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 27 Mar 2022 03:38:04 -0500 Subject: [PATCH 37/48] GUI: add setting to change position of SL slider --- src/gui/gui.h | 2 ++ src/gui/insEdit.cpp | 53 +++++++++++++++++++++++++++++++++----------- src/gui/settings.cpp | 11 +++++++++ 3 files changed, 53 insertions(+), 13 deletions(-) diff --git a/src/gui/gui.h b/src/gui/gui.h index 841b2d3d..3b6056f4 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -623,6 +623,7 @@ class FurnaceGUI { int roundedMenus; int loadJapanese; int fmLayout; + int susPosition; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -674,6 +675,7 @@ class FurnaceGUI { roundedMenus(0), loadJapanese(0), fmLayout(0), + susPosition(0), maxUndoSteps(100), mainFontPath(""), patFontPath(""), diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 6fa99feb..837815da 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1212,9 +1212,11 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); CENTER_TEXT(FM_SHORT_NAME(FM_DR)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_DR)); - ImGui::TableNextColumn(); - CENTER_TEXT(FM_SHORT_NAME(FM_SL)); - ImGui::TextUnformatted(FM_SHORT_NAME(FM_SL)); + if (settings.susPosition==0) { + ImGui::TableNextColumn(); + CENTER_TEXT(FM_SHORT_NAME(FM_SL)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_SL)); + } if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { ImGui::TableNextColumn(); CENTER_TEXT(FM_SHORT_NAME(FM_D2R)); @@ -1223,6 +1225,11 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); CENTER_TEXT(FM_SHORT_NAME(FM_RR)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_RR)); + if (settings.susPosition==1) { + ImGui::TableNextColumn(); + CENTER_TEXT(FM_SHORT_NAME(FM_SL)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_SL)); + } ImGui::TableNextColumn(); ImGui::TableNextColumn(); CENTER_TEXT(FM_SHORT_NAME(FM_TL)); @@ -1303,10 +1310,12 @@ void FurnaceGUI::drawInsEdit() { CENTER_VSLIDER; P(ImGui::VSliderScalar("##DR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dr,&_ZERO,&maxArDr)); - ImGui::TableNextColumn(); - op.sl&=15; - CENTER_VSLIDER; - P(ImGui::VSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); + if (settings.susPosition==0) { + ImGui::TableNextColumn(); + op.sl&=15; + CENTER_VSLIDER; + P(ImGui::VSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); + } if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { ImGui::TableNextColumn(); @@ -1320,6 +1329,13 @@ void FurnaceGUI::drawInsEdit() { CENTER_VSLIDER; P(ImGui::VSliderScalar("##RR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rr,&_ZERO,&_FIFTEEN)); + if (settings.susPosition==1) { + ImGui::TableNextColumn(); + op.sl&=15; + CENTER_VSLIDER; + P(ImGui::VSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); + } + ImGui::TableNextColumn(); ImGui::Dummy(ImVec2(4.0f*dpiScale,2.0f*dpiScale)); @@ -1493,12 +1509,14 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); ImGui::Text("%s",FM_NAME(FM_DR)); - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - P(ImGui::SliderScalar("##SL",ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); rightClickable - ImGui::TableNextColumn(); - ImGui::Text("%s",FM_NAME(FM_SL)); + if (settings.susPosition==0) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + P(ImGui::SliderScalar("##SL",ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); rightClickable + ImGui::TableNextColumn(); + ImGui::Text("%s",FM_NAME(FM_SL)); + } if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { ImGui::TableNextRow(); @@ -1516,6 +1534,15 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); ImGui::Text("%s",FM_NAME(FM_RR)); + if (settings.susPosition==1) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + P(ImGui::SliderScalar("##SL",ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); rightClickable + ImGui::TableNextColumn(); + ImGui::Text("%s",FM_NAME(FM_SL)); + } + ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 02ca297b..9484762b 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -433,6 +433,14 @@ void FurnaceGUI::drawSettings() { settings.fmLayout=3; } + ImGui::Text("Position of Sustain in FM editor:"); + if (ImGui::RadioButton("Between Decay and Sustain Rate##susp0",settings.susPosition==0)) { + settings.susPosition=0; + } + if (ImGui::RadioButton("After Release Rate##susp1",settings.susPosition==1)) { + settings.susPosition=1; + } + bool macroViewB=settings.macroView; if (ImGui::Checkbox("Classic macro view (standard macros only; deprecated!)",¯oViewB)) { settings.macroView=macroViewB; @@ -1005,6 +1013,7 @@ void FurnaceGUI::syncSettings() { settings.roundedMenus=e->getConfInt("roundedMenus",0); settings.loadJapanese=e->getConfInt("loadJapanese",0); settings.fmLayout=e->getConfInt("fmLayout",0); + settings.susPosition=e->getConfInt("susPosition",0); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -1050,6 +1059,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.roundedMenus,0,1); clampSetting(settings.loadJapanese,0,1); clampSetting(settings.fmLayout,0,3); + clampSetting(settings.susPosition,0,1); // keybinds LOAD_KEYBIND(GUI_ACTION_OPEN,FURKMOD_CMD|SDLK_o); @@ -1285,6 +1295,7 @@ void FurnaceGUI::commitSettings() { e->setConf("roundedMenus",settings.roundedMenus); e->setConf("loadJapanese",settings.loadJapanese); e->setConf("fmLayout",settings.fmLayout); + e->setConf("susPosition",settings.susPosition); PUT_UI_COLOR(GUI_COLOR_BACKGROUND); PUT_UI_COLOR(GUI_COLOR_FRAME_BACKGROUND); From ef104ce0b0539ed165fbb165755e05e7eece435b Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 28 Mar 2022 01:06:56 +0900 Subject: [PATCH 38/48] Add VRC6 support 2 Pulse channels: - 8 level pulse duty cycle, DAC mode(just ignores duty cycle)/pulse wave mode, 4 bit volume. - Furnace support PCM playback in pulse channels with duty cycle ignore mode. Sawtooth: - nothing but 6 bit volume (8 bit accumulator in technically) and 12 bit frequency (periodic). VRC6 instrument: - 6 bit Volume macro for finer sawtooth volume handling, also 3 bit Duty cycle macro for pulse channels. Duty, PCM mode command and Duty macro affects for pulse channel only. --- CMakeLists.txt | 3 + papers/doc/4-instrument/README.md | 1 + papers/doc/4-instrument/vrc6.md | 7 + papers/doc/7-systems/README.md | 1 + papers/doc/7-systems/vrc6.md | 16 + src/engine/dispatchContainer.cpp | 4 + src/engine/platform/sound/vrcvi/vrcvi.cpp | 307 +++++++++++++ src/engine/platform/sound/vrcvi/vrcvi.hpp | 238 +++++++++++ src/engine/platform/vrc6.cpp | 496 ++++++++++++++++++++++ src/engine/platform/vrc6.h | 100 +++++ src/engine/platform/x1_010.h | 1 - src/engine/playback.cpp | 12 + src/gui/debug.cpp | 28 ++ src/gui/guiConst.cpp | 1 + src/gui/insEdit.cpp | 8 +- src/gui/presets.cpp | 7 + src/gui/sysConf.cpp | 1 + 17 files changed, 1228 insertions(+), 3 deletions(-) create mode 100644 papers/doc/4-instrument/vrc6.md create mode 100644 papers/doc/7-systems/vrc6.md create mode 100644 src/engine/platform/sound/vrcvi/vrcvi.cpp create mode 100644 src/engine/platform/sound/vrcvi/vrcvi.hpp create mode 100644 src/engine/platform/vrc6.cpp create mode 100644 src/engine/platform/vrc6.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b95900c8..8b64ae98 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -275,6 +275,8 @@ src/engine/platform/sound/k005289/k005289.cpp src/engine/platform/sound/vic20sound.c +src/engine/platform/sound/vrcvi/vrcvi.cpp + src/engine/platform/ym2610Interface.cpp src/engine/blip_buf.c @@ -324,6 +326,7 @@ src/engine/platform/vera.cpp src/engine/platform/bubsyswsg.cpp src/engine/platform/pet.cpp src/engine/platform/vic20.cpp +src/engine/platform/vrc6.cpp src/engine/platform/dummy.cpp ) diff --git a/papers/doc/4-instrument/README.md b/papers/doc/4-instrument/README.md index ab03e836..4e72043e 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. +- [Konami VRC6](vrc6.md) - for use with VRC6's PSG sound source. # macros diff --git a/papers/doc/4-instrument/vrc6.md b/papers/doc/4-instrument/vrc6.md new file mode 100644 index 00000000..b7612c2f --- /dev/null +++ b/papers/doc/4-instrument/vrc6.md @@ -0,0 +1,7 @@ +# VRC6 instrument editor + +VRC6 instrument editor consists of only three macros: + +- [Volume] - volume sequence +- [Arpeggio] - pitch sequencr +- [Duty cycle] - spicifies duty cycle for pulse wave channels diff --git a/papers/doc/7-systems/README.md b/papers/doc/7-systems/README.md index 5e4e101b..a76c412b 100644 --- a/papers/doc/7-systems/README.md +++ b/papers/doc/7-systems/README.md @@ -28,5 +28,6 @@ this is a list of systems that Furnace supports, including each system's effects - [PC Speaker](pcspkr.md) - [Commodore PET](pet.md) - [Commodore VIC-20](vic20.md) +- [Konami VRC6](vrc6.md) Furnace also reads .dmf files with the [Yamaha YMU759](ymu759.md) system, but... diff --git a/papers/doc/7-systems/vrc6.md b/papers/doc/7-systems/vrc6.md new file mode 100644 index 00000000..c60ae26f --- /dev/null +++ b/papers/doc/7-systems/vrc6.md @@ -0,0 +1,16 @@ +# Konami VRC6 + +Its one of NES mapper with sound expansion, and one of two VRCs with this feature by Konami. + +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. + +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. + +# effects + +- `12xx`: set duty cycle. (0 to 7) +- `17xx`: toggle PCM mode. + +* All effects are affects at pulse channels only. \ No newline at end of file diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 0f553772..dd13b38e 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -48,6 +48,7 @@ #include "platform/bubsyswsg.h" #include "platform/pet.h" #include "platform/vic20.h" +#include "platform/vrc6.h" #include "platform/dummy.h" #include "../ta-log.h" #include "song.h" @@ -283,6 +284,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_VIC20: dispatch=new DivPlatformVIC20; break; + case DIV_SYSTEM_VRC6: + dispatch=new DivPlatformVRC6; + break; default: logW("this system is not supported yet! using dummy platform.\n"); dispatch=new DivPlatformDummy; diff --git a/src/engine/platform/sound/vrcvi/vrcvi.cpp b/src/engine/platform/sound/vrcvi/vrcvi.cpp new file mode 100644 index 00000000..4754620e --- /dev/null +++ b/src/engine/platform/sound/vrcvi/vrcvi.cpp @@ -0,0 +1,307 @@ +/* + License: BSD-3-Clause + see https://github.com/cam900/vgsound_emu/LICENSE for more details + + Copyright holder(s): cam900 + Konami VRC VI sound emulation core + + It's one of NES mapper with built-in sound chip, and also one of 2 Konami VRCs with this feature. (rest one has OPLL derivatives.) + + It's also DACless like other sound chip and mapper-with-sound manufactured by konami, + the Chips 6 bit digital sound output is needs converted to analog sound output when you it want to make some sounds, or send to sound mixer. + + Its are used for Akumajou Densetsu (Japan release of Castlevania III), Madara, Esper Dream 2. + + The chip is installed in 351951 PCB and 351949A PCB. + + 351951 PCB is used exclusivly for Akumajou Densetsu, Small board has VRC VI, PRG and CHR ROM. + - It's configuration also calls VRC6a, iNES mapper 024. + + 351949A PCB is for Last 2 titles with VRC VI, Bigger board has VRC VI, PRG and CHR ROM, and Battery Backed 8K x 8 bit SRAM. + - Additionally, It's PRG A0 and A1 bit to VRC VI input is swapped, compare to above. + - It's configuration also calls VRC6b, iNES mapper 026. + + The chip itself has 053328, 053329, 053330 Revision, but Its difference between revision is unknown. + + Like other mappers for NES, It has internal timer - Its timer can be sync with scanline like other Konami mapper in this era. + + Register layout (Sound and Timer only; 351951 PCB case, 351949A swaps xxx1 and xxx2): + + Address Bits Description + 7654 3210 + + 9000-9002 Pulse 1 + + 9000 x--- ---- Pulse 1 Duty ignore + -xxx ---- Pulse 1 Duty cycle + ---- xxxx Pulse 1 Volume + 9001 xxxx xxxx Pulse 1 Pitch bit 0-7 + 9002 x--- ---- Pulse 1 Disable + ---- xxxx Pulse 1 Pitch bit 8-11 + + 9003 Sound control + + 9003 ---- -x-- 4 bit Frequency mode + ---- -0x- 8 bit Frequency mode + ---- ---x Halt + + a000-a002 Pulse 2 + + a000 x--- ---- Pulse 2 Duty ignore + -xxx ---- Pulse 2 Duty cycle + ---- xxxx Pulse 2 Volume + a001 xxxx xxxx Pulse 2 Pitch bit 0-7 + a002 x--- ---- Pulse 2 Disable + ---- xxxx Pulse 2 Pitch bit 8-11 + + b000-b002 Sawtooth + + b000 --xx xxxx Sawtooth Accumulate Rate + b001 xxxx xxxx Sawtooth Pitch bit 0-7 + b002 x--- ---- Sawtooth Disable + ---- xxxx Sawtooth Pitch bit 8-11 + + f000-f002 IRQ Timer + + f000 xxxx xxxx IRQ Timer latch + f001 ---- -0-- Sync with scanline + ---- --x- Enable timer + ---- ---x Enable timer after IRQ Acknowledge + f002 ---- ---- IRQ Acknowledge + + Frequency calculations: + + if 4 bit Frequency Mode then + Frequency: Input clock / (bit 8 to 11 of Pitch + 1) + end else if 8 bit Frequency Mode then + Frequency: Input clock / (bit 4 to 11 of Pitch + 1) + end else then + Frequency: Input clock / (Pitch + 1) + end +*/ + +#include "vrcvi.hpp" + +void vrcvi_core::tick() +{ + m_out = 0; + if (!m_control.m_halt) // Halt flag + { + // tick per each clock + for (auto & elem : m_pulse) + { + if (elem.tick()) + m_out += elem.m_control.m_volume; // add 4 bit pulse output + } + if (m_sawtooth.tick()) + m_out += bitfield(m_sawtooth.m_accum, 3, 5); // add 5 bit sawtooth output + } + if (m_timer.tick()) + m_timer.counter_tick(); +} + +void vrcvi_core::reset() +{ + for (auto & elem : m_pulse) + elem.reset(); + + m_sawtooth.reset(); + m_timer.reset(); + m_control.reset(); + m_out = 0; +} + +bool vrcvi_core::alu_t::tick() +{ + if (!m_divider.m_disable) + { + const u16 temp = m_counter; + // post decrement + if (bitfield(m_host.m_control.m_shift, 1)) + { + m_counter = (m_counter & 0x0ff) | (bitfield(bitfield(m_counter, 8, 4) - 1, 0, 4) << 8); + m_counter = (m_counter & 0xf00) | (bitfield(bitfield(m_counter, 0, 8) - 1, 0, 8) << 0); + } + else if (bitfield(m_host.m_control.m_shift, 0)) + { + m_counter = (m_counter & 0x00f) | (bitfield(bitfield(m_counter, 4, 8) - 1, 0, 8) << 4); + m_counter = (m_counter & 0xff0) | (bitfield(bitfield(m_counter, 0, 4) - 1, 0, 4) << 0); + } + else + m_counter = bitfield(bitfield(m_counter, 0, 12) - 1, 0, 12); + + // carry handling + bool carry = bitfield(m_host.m_control.m_shift, 1) ? (bitfield(temp, 8, 4) == 0) : + (bitfield(m_host.m_control.m_shift, 0) ? (bitfield(temp, 4, 8) == 0) : + (bitfield(temp, 0, 12) == 0)); + if (carry) + m_counter = m_divider.m_divider; + + return carry; + } + return false; +} + +bool vrcvi_core::pulse_t::tick() +{ + if (m_divider.m_disable) + return false; + + if (vrcvi_core::alu_t::tick()) + m_cycle = bitfield(m_cycle + 1, 0, 4); + + return m_control.m_mode ? true : ((m_cycle > m_control.m_duty) ? true : false); +} + +bool vrcvi_core::sawtooth_t::tick() +{ + if (m_divider.m_disable) + return false; + + if (vrcvi_core::alu_t::tick()) + { + if (bitfield(m_cycle++, 0)) // Even step only + m_accum += m_rate; + if (m_cycle >= 14) // Reset accumulator at every 14 cycles + { + m_accum = 0; + m_cycle = 0; + } + } + return (m_accum == 0) ? false : true; +} + +void vrcvi_core::alu_t::reset() +{ + m_divider.reset(); + m_counter = 0; + m_cycle = 0; +} + +void vrcvi_core::pulse_t::reset() +{ + vrcvi_core::alu_t::reset(); + m_control.reset(); +} + +void vrcvi_core::sawtooth_t::reset() +{ + vrcvi_core::alu_t::reset(); + m_rate = 0; + m_accum = 0; +} + +bool vrcvi_core::timer_t::tick() +{ + if (m_timer_control.m_enable) + { + if (!m_timer_control.m_sync) // scanline sync mode + { + m_prescaler -= 3; + if (m_prescaler <= 0) + { + m_prescaler += 341; + return true; + } + } + } + return (m_timer_control.m_enable && m_timer_control.m_sync) ? true : false; +} + +void vrcvi_core::timer_t::counter_tick() +{ + if (bitfield(++m_counter, 0, 8) == 0) + { + m_counter = m_counter_latch; + irq_set(); + } +} + +void vrcvi_core::timer_t::reset() +{ + m_timer_control.reset(); + m_prescaler = 341; + m_counter = m_counter_latch = 0; + irq_clear(); +} + +// Accessors + +void vrcvi_core::alu_t::divider_t::write(bool msb, u8 data) +{ + if (msb) + { + m_divider = (m_divider & ~0xf00) | (bitfield(data, 0, 4) << 8); + m_disable = bitfield(data, 7); + } + else + m_divider = (m_divider & ~0x0ff) | data; +} + + +void vrcvi_core::pulse_w(u8 voice, u8 address, u8 data) +{ + pulse_t &v = m_pulse[voice]; + switch (address) + { + case 0x00: // Control - 0x9000 (Pulse 1), 0xa000 (Pulse 2) + v.m_control.m_mode = bitfield(data, 7); + v.m_control.m_duty = bitfield(data, 4, 3); + v.m_control.m_volume = bitfield(data, 0, 4); + break; + case 0x01: // Pitch LSB - 0x9001/0x9002 (Pulse 1), 0xa001/0xa002 (Pulse 2) + v.m_divider.write(false, data); + break; + case 0x02: // Pitch MSB, Enable/Disable - 0x9002/0x9001 (Pulse 1), 0xa002/0xa001 (Pulse 2) + v.m_divider.write(true, data); + break; + } +} + +void vrcvi_core::saw_w(u8 address, u8 data) +{ + switch (address) + { + case 0x00: // Sawtooth Accumulate - 0xb000 + m_sawtooth.m_rate = bitfield(data, 0, 6); + break; + case 0x01: // Pitch LSB - 0xb001/0xb002 (Sawtooth) + m_sawtooth.m_divider.write(false, data); + break; + case 0x02: // Pitch MSB, Enable/Disable - 0xb002/0xb001 (Sawtooth) + m_sawtooth.m_divider.write(true, data); + break; + } +} + +void vrcvi_core::timer_w(u8 address, u8 data) +{ + switch (address) + { + case 0x00: // Timer latch - 0xf000 + m_timer.m_counter_latch = data; + break; + case 0x01: // Timer control - 0xf001/0xf002 + m_timer.m_timer_control.m_sync = bitfield(data, 2); + m_timer.m_timer_control.m_enable = bitfield(data, 1); + m_timer.m_timer_control.m_enable_ack = bitfield(data, 0); + if (m_timer.m_timer_control.m_enable) + { + m_timer.m_counter = m_timer.m_counter_latch; + m_timer.m_prescaler = 341; + } + m_timer.irq_clear(); + break; + case 0x02: // IRQ Acknowledge - 0xf002/0xf001 + m_timer.irq_clear(); + m_timer.m_timer_control.m_enable = m_timer.m_timer_control.m_enable_ack; + break; + } +} + +void vrcvi_core::control_w(u8 data) +{ + // Global control - 0x9003 + m_control.m_halt = bitfield(data, 0); + m_control.m_shift = bitfield(data, 1, 2); +} diff --git a/src/engine/platform/sound/vrcvi/vrcvi.hpp b/src/engine/platform/sound/vrcvi/vrcvi.hpp new file mode 100644 index 00000000..028778b0 --- /dev/null +++ b/src/engine/platform/sound/vrcvi/vrcvi.hpp @@ -0,0 +1,238 @@ +/* + License: BSD-3-Clause + see https://github.com/cam900/vgsound_emu/LICENSE for more details + + Copyright holder(s): cam900 + Konami VRC VI sound emulation core + + See vrcvi.cpp to more infos. +*/ + +#include +#include + +#ifndef _VGSOUND_EMU_VRCVI_HPP +#define _VGSOUND_EMU_VRCVI_HPP + +#pragma once + +namespace vrcvi +{ + typedef unsigned char u8; + typedef unsigned short u16; + typedef unsigned int u32; + typedef signed char s8; + 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); + } +}; + +class vrcvi_intf +{ +public: + virtual void irq_w(bool irq) { } +}; + +using namespace vrcvi; +class vrcvi_core +{ +public: + friend class vrcvi_intf; + // constructor + vrcvi_core(vrcvi_intf &intf) + : m_pulse{*this,*this} + , m_sawtooth(*this) + , m_timer(*this) + , m_intf(intf) + { + } + // accessors, getters, setters + void pulse_w(u8 voice, u8 address, u8 data); + void saw_w(u8 address, u8 data); + void timer_w(u8 address, u8 data); + void control_w(u8 data); + + // internal state + void reset(); + void tick(); + + // 6 bit output + s8 out() { return m_out; } +private: + // Common ALU for sound channels + struct alu_t + { + alu_t(vrcvi_core &host) + : m_host(host) + { }; + + + virtual void reset(); + virtual bool tick(); + + struct divider_t + { + divider_t() + : m_divider(0) + , m_disable(0) + { }; + + void reset() + { + m_divider = 0; + m_disable = 0; + } + + void write(bool msb, u8 data); + + u16 m_divider : 12; // divider (pitch) + u16 m_disable : 1; // channel disable flag + }; + + vrcvi_core &m_host; + divider_t m_divider; + u16 m_counter = 0; // clock counter + u8 m_cycle = 0; // clock cycle + }; + + // 2 Pulse channels + struct pulse_t : alu_t + { + pulse_t(vrcvi_core &host) + : alu_t(host) + { }; + + virtual void reset() override; + virtual bool tick() override; + + // Control bits + struct pulse_control_t + { + pulse_control_t() + : m_mode(0) + , m_duty(0) + , m_volume(0) + { }; + + void reset() + { + m_mode = 0; + m_duty = 0; + m_volume = 0; + } + + u8 m_mode : 1; // duty toggle flag + u8 m_duty : 3; // 3 bit duty cycle + u8 m_volume : 4; // 4 bit volume + }; + + pulse_control_t m_control; + }; + + // 1 Sawtooth channel + struct sawtooth_t : alu_t + { + sawtooth_t(vrcvi_core &host) + : alu_t(host) + { }; + + virtual void reset() override; + virtual bool tick() override; + + u8 m_rate = 0; // sawtooth accumulate rate + u8 m_accum = 0; // sawtooth accumulator, high 5 bit is accumulated to output + }; + + // Internal timer + struct timer_t + { + timer_t(vrcvi_core &host) + : m_host(host) + { }; + + void reset(); + bool tick(); + void counter_tick(); + + // IRQ update + void update() { m_host.m_intf.irq_w(m_timer_control.m_irq_trigger); } + void irq_set() + { + if (!m_timer_control.m_irq_trigger) + { + m_timer_control.m_irq_trigger = 1; + update(); + } + } + void irq_clear() + { + if (m_timer_control.m_irq_trigger) + { + m_timer_control.m_irq_trigger = 0; + update(); + } + } + + // Control bits + struct timer_control_t + { + timer_control_t() + : m_irq_trigger(0) + , m_enable_ack(0) + , m_enable(0) + , m_sync(0) + { }; + + void reset() + { + m_irq_trigger = 0; + m_enable_ack = 0; + m_enable = 0; + m_sync = 0; + } + + u8 m_irq_trigger : 1; + u8 m_enable_ack : 1; + u8 m_enable : 1; + u8 m_sync : 1; + }; + + vrcvi_core &m_host; // host core + timer_control_t m_timer_control; // timer control bits + s16 m_prescaler = 341; // prescaler + u8 m_counter = 0; // clock counter + u8 m_counter_latch = 0; // clock counter latch + }; + + struct global_control_t + { + global_control_t() + : m_halt(0) + , m_shift(0) + { }; + + void reset() + { + m_halt = 0; + m_shift = 0; + } + + u8 m_halt : 1; // halt sound + u8 m_shift : 2; // 4/8 bit right shift + }; + + pulse_t m_pulse[2]; // 2 pulse channels + sawtooth_t m_sawtooth; // sawtooth channel + timer_t m_timer; // internal timer + global_control_t m_control; // control + + vrcvi_intf &m_intf; + + s8 m_out = 0; // 6 bit output +}; + +#endif diff --git a/src/engine/platform/vrc6.cpp b/src/engine/platform/vrc6.cpp new file mode 100644 index 00000000..83fb9c62 --- /dev/null +++ b/src/engine/platform/vrc6.cpp @@ -0,0 +1,496 @@ +/** + * 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 "vrc6.h" +#include "../engine.h" +#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) + +const char* regCheatSheetVRC6[]={ + "S0DutyVol", "9000", + "S0PeriodL", "9001", + "S0PeriodH", "9002", + "GlobalCtl", "9003", + "S1DutyVol", "A000", + "S1PeriodL", "A001", + "S1PeriodH", "A002", + "SawVolume", "B000", + "SawPeriodL", "B001", + "SawPeriodH", "B002", + "TimerLatch", "F000", + "TimerCtl", "F001", + "IRQAck", "F002", + NULL +}; + +const char** DivPlatformVRC6::getRegisterSheet() { + return regCheatSheetVRC6; +} + +const char* DivPlatformVRC6::getEffectName(unsigned char effect) { + switch (effect) { + case 0x12: + return "12xx: Set duty cycle (pulse: 0 to 7)"; + break; + case 0x17: + return "17xx: Toggle PCM mode (pulse channel)"; + break; + } + return NULL; +} + +void DivPlatformVRC6::acquire(short* bufL, short* bufR, size_t start, size_t len) { + for (size_t i=start; irate) { + DivSample* s=parent->getSample(chan[i].dacSample); + if (s->samples<=0) { + chan[i].dacSample=-1; + chWrite(i,0,0); + continue; + } + unsigned char dacData=(((unsigned char)s->data8[chan[i].dacPos]^0x80)>>4); + chan[i].dacOut=MAX(0,MIN(15,(dacData*chan[i].outVol)>>4)); + if (!isMuted[i]) { + chWrite(i,0,0x80|chan[i].dacOut); + } + chan[i].dacPos++; + if (chan[i].dacPos>=s->samples) { + if (s->loopStart>=0 && s->loopStart<(int)s->samples) { + chan[i].dacPos=s->loopStart; + } else { + chan[i].dacSample=-1; + chWrite(i,0,0); + } + } + chan[i].dacPeriod-=rate; + } + } + } + + // VRC6 part + vrc6.tick(); + int sample=vrc6.out()<<9; // scale to 16 bit + if (sample>32767) sample=32767; + if (sample<-32768) sample=-32768; + bufL[i]=bufR[i]=sample; + + // Command part + while (!writes.empty()) { + QueuedWrite w=writes.front(); + switch (w.addr&0xf000) { + case 0x9000: // Pulse 1 + if (w.addr<=0x9003) { + if (w.addr==0x9003) { + vrc6.control_w(w.val); + } else if (w.addr<=0x9002) { + vrc6.pulse_w(0,w.addr&3,w.val); + } + regPool[w.addr-0x9000]=w.val; + } + break; + case 0xa000: // Pulse 2 + if (w.addr<=0xa002) { + vrc6.pulse_w(1,w.addr&3,w.val); + regPool[(w.addr-0xa000)+4]=w.val; + } + break; + case 0xb000: // Sawtooth + if (w.addr<=0xb002) { + vrc6.saw_w(w.addr&3,w.val); + regPool[(w.addr-0xb000)+7]=w.val; + } + break; + case 0xf000: // IRQ/Timer + if (w.addr<=0xf002) { + vrc6.timer_w(w.addr&3,w.val); + regPool[(w.addr-0xf000)+10]=w.val; + } + break; + } + writes.pop(); + } + } +} + +void DivPlatformVRC6::tick() { + for (int i=0; i<3; i++) { + chan[i].std.next(); + if (chan[i].std.hadVol) { + if (i==2) { // sawtooth + chan[i].outVol=((chan[i].vol&63)*MIN(63,chan[i].std.vol))/63; + if (chan[i].outVol<0) chan[i].outVol=0; + if (chan[i].outVol>63) chan[i].outVol=63; + if (!isMuted[i]) { + chWrite(i,0,chan[i].outVol); + } + } else { // pulse + chan[i].outVol=((chan[i].vol&15)*MIN(63,chan[i].std.vol))/63; + if (chan[i].outVol<0) chan[i].outVol=0; + if (chan[i].outVol>15) chan[i].outVol=15; + if ((!isMuted[i]) && (!chan[i].pcm)) { + chWrite(i,0,(chan[i].outVol&0xf)|((chan[i].duty&7)<<4)); + } + } + } + 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.hadDuty) { + chan[i].duty=chan[i].std.duty; + if ((!isMuted[i]) && (i!=2) && (!chan[i].pcm)) { // pulse + chWrite(i,0,(chan[i].outVol&0xf)|((chan[i].duty&7)<<4)); + } + } + 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; + } else { // pulse + chan[i].freq=parent->calcFreq(chan[i].baseFreq/16,chan[i].pitch,true)-1; + if (chan[i].furnaceDac) { + double off=1.0; + if (chan[i].dacSample>=0 && chan[i].dacSamplesong.sampleLen) { + DivSample* s=parent->getSample(chan[i].dacSample); + if (s->centerRate<1) { + off=1.0; + } else { + off=8363.0/(double)s->centerRate; + } + } + chan[i].dacRate=((double)chipClock)/MAX(1,off*chan[i].freq); + if (dumpWrites) addWrite(0xffff0001+(i<<8),chan[i].dacRate); + } + } + 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,0x80); + } else { + chWrite(i,1,chan[i].freq&0xff); + chWrite(i,2,(chan[i].freq>>8)&0xf); + } + if (chan[i].keyOn) chan[i].keyOn=false; + if (chan[i].keyOff) chan[i].keyOff=false; + chan[i].freqChanged=false; + } + } +} + +int DivPlatformVRC6::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: + if (c.chan!=2) { // pulse wave + DivInstrument* ins=parent->getIns(chan[c.chan].ins); + if (ins->type==DIV_INS_AMIGA) { + chan[c.chan].pcm=true; + } else if (chan[c.chan].furnaceDac) { + chan[c.chan].pcm=false; + } + if (chan[c.chan].pcm) { + if (skipRegisterWrites) break; + if (ins->type==DIV_INS_AMIGA) { + chan[c.chan].dacSample=ins->amiga.initSample; + if (chan[c.chan].dacSample<0 || chan[c.chan].dacSample>=parent->song.sampleLen) { + chan[c.chan].dacSample=-1; + if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); + break; + } else { + if (dumpWrites) { + chWrite(c.chan,2,0); + chWrite(c.chan,0,isMuted[c.chan]?0:0x80); + addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dacSample); + } + } + chan[c.chan].dacPos=0; + chan[c.chan].dacPeriod=0; + 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].std.init(ins); + //chan[c.chan].keyOn=true; + chan[c.chan].furnaceDac=true; + } else { + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].note=c.value; + } + chan[c.chan].dacSample=12*sampleBank+chan[c.chan].note%12; + if (chan[c.chan].dacSample>=parent->song.sampleLen) { + chan[c.chan].dacSample=-1; + if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); + break; + } else { + if (dumpWrites) addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dacSample); + } + chan[c.chan].dacPos=0; + chan[c.chan].dacPeriod=0; + chan[c.chan].dacRate=parent->getSample(chan[c.chan].dacSample)->rate; + if (dumpWrites) { + chWrite(c.chan,2,0); + chWrite(c.chan,0,isMuted[c.chan]?0:0x80); + addWrite(0xffff0001+(c.chan<<8),chan[c.chan].dacRate); + } + chan[c.chan].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(parent->getIns(chan[c.chan].ins)); + if (!isMuted[c.chan]) { + if (c.chan==2) { // sawtooth + chWrite(c.chan,0,chan[c.chan].vol); + } else if (!chan[c.chan].pcm) { + chWrite(c.chan,0,(chan[c.chan].vol&0xf)|((chan[c.chan].duty&7)<<4)); + } + } + break; + case DIV_CMD_NOTE_OFF: + chan[c.chan].dacSample=-1; + if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); + chan[c.chan].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) { + chan[c.chan].outVol=c.value; + } + if (!isMuted[c.chan]) { + if (chan[c.chan].active) { + if (c.chan==2) { // sawtooth + chWrite(c.chan,0,chan[c.chan].vol); + } else if (!chan[c.chan].pcm) { + chWrite(c.chan,0,(chan[c.chan].vol&0xf)|((chan[c.chan].duty&7)<<4)); + } + } + } + } + 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_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!=2) && (!chan[c.chan].pcm)) { // pulse + chan[c.chan].duty=c.value; + } + break; + case DIV_CMD_SAMPLE_MODE: + if (c.chan!=2) { // pulse + chan[c.chan].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_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: + if (c.chan==2) return 63; // sawtooth has 6 bit volume + return 15; // pulse has 4 bit volume + break; + case DIV_ALWAYS_SET_VOLUME: + return 1; + break; + default: + break; + } + return 1; +} + +void DivPlatformVRC6::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + if (isMuted[ch]) { + chWrite(ch,0,0); + } else if (chan[ch].active) { + if (ch==2) { // sawtooth + chWrite(ch,0,chan[ch].outVol); + } else { + chWrite(ch,0,chan[ch].pcm?chan[ch].dacOut:((chan[ch].outVol&0xf)|((chan[ch].duty&7)<<4))); + } + } +} + +void DivPlatformVRC6::forceIns() { + for (int i=0; i<3; i++) { + chan[i].insChanged=true; + chan[i].freqChanged=true; + } +} + +void* DivPlatformVRC6::getChanState(int ch) { + return &chan[ch]; +} + +unsigned char* DivPlatformVRC6::getRegisterPool() { + return regPool; +} + +int DivPlatformVRC6::getRegisterPoolSize() { + return 13; +} + +void DivPlatformVRC6::reset() { + for (int i=0; i<3; i++) { + chan[i]=DivPlatformVRC6::Channel(); + } + if (dumpWrites) { + addWrite(0xffffffff,0); + } + + sampleBank=0; + + vrc6.reset(); + // Initialize control register + rWrite(0x9003,0); + // Clear internal IRQ + rWrite(0xf000,0); + rWrite(0xf001,0); + rWrite(0xf002,0); +} + +bool DivPlatformVRC6::keyOffAffectsArp(int ch) { + return true; +} + +void DivPlatformVRC6::setFlags(unsigned int flags) { + if (flags==2) { // Dendy + rate=COLOR_PAL*2.0/5.0; + } else if (flags==1) { // PAL + rate=COLOR_PAL*3.0/8.0; + } else { // NTSC + rate=COLOR_NTSC/2.0; + } + chipClock=rate; +} + +void DivPlatformVRC6::notifyInsDeletion(void* ins) { + for (int i=0; i<3; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformVRC6::poke(unsigned int addr, unsigned short val) { + rWrite(addr,val); +} + +void DivPlatformVRC6::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); +} + +int DivPlatformVRC6::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + for (int i=0; i<3; i++) { + isMuted[i]=false; + } + setFlags(flags); + reset(); + return 3; +} + +void DivPlatformVRC6::quit() { +} + +DivPlatformVRC6::~DivPlatformVRC6() { +} diff --git a/src/engine/platform/vrc6.h b/src/engine/platform/vrc6.h new file mode 100644 index 00000000..05cd8941 --- /dev/null +++ b/src/engine/platform/vrc6.h @@ -0,0 +1,100 @@ +/** + * 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 _VRC6_H +#define _VRC6_H + +#include +#include "../dispatch.h" +#include "../macroInt.h" +#include "sound/vrcvi/vrcvi.hpp" + + +class DivPlatformVRC6: public DivDispatch { + struct Channel { + int freq, baseFreq, pitch, note; + int dacPeriod, dacRate, dacOut; + unsigned int dacPos; + int dacSample; + unsigned char ins, duty; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, pcm, furnaceDac; + signed char vol, outVol; + DivMacroInt std; + Channel(): + freq(0), + baseFreq(0), + pitch(0), + note(0), + dacPeriod(0), + dacRate(0), + dacOut(0), + dacPos(0), + dacSample(-1), + ins(-1), + duty(0), + active(false), + insChanged(true), + freqChanged(false), + keyOn(false), + keyOff(false), + inPorta(false), + pcm(false), + furnaceDac(false), + vol(15), + outVol(15) {} + }; + Channel chan[3]; + bool isMuted[3]; + struct QueuedWrite { + unsigned short addr; + unsigned char val; + QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v) {} + }; + std::queue writes; + unsigned char sampleBank; + vrcvi_intf intf; + vrcvi_core vrc6; + unsigned char regPool[13]; + + 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); + bool keyOffAffectsArp(int ch); + void setFlags(unsigned int flags); + 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(); + DivPlatformVRC6() : vrc6(intf) {}; + ~DivPlatformVRC6(); +}; + +#endif diff --git a/src/engine/platform/x1_010.h b/src/engine/platform/x1_010.h index 73cbaf6b..389fa7bf 100644 --- a/src/engine/platform/x1_010.h +++ b/src/engine/platform/x1_010.h @@ -20,7 +20,6 @@ #ifndef _X1_010_H #define _X1_010_H -#include #include "../dispatch.h" #include "../engine.h" #include "../macroInt.h" diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 4ab6a517..d43f1f46 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -330,6 +330,18 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe return false; } break; + case DIV_SYSTEM_VRC6: + switch (effect) { + case 0x12: // duty or noise mode + dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,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/gui/debug.cpp b/src/gui/debug.cpp index ddbfb5b5..03ecede2 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/vrc6.h" #include "../engine/platform/dummy.h" #define GENESIS_DEBUG \ @@ -274,6 +275,33 @@ void putDispatchChan(void* data, int chanNum, int type) { ImGui::TextColored(ch->env.flag.envVinvL?colorOn:colorOff,">> EnvVinvL"); break; } + case DIV_SYSTEM_VRC6: { + DivPlatformVRC6::Channel* ch=(DivPlatformVRC6::Channel*)data; + ImGui::Text("> VRC6"); + ImGui::Text("* freq: %d",ch->freq); + ImGui::Text(" - base: %d",ch->baseFreq); + ImGui::Text(" - pitch: %d",ch->pitch); + ImGui::Text("- note: %d",ch->note); + ImGui::Text("* DAC:"); + ImGui::Text(" - period: %d",ch->dacPeriod); + ImGui::Text(" - rate: %d",ch->dacRate); + ImGui::Text(" - out: %d",ch->dacOut); + ImGui::Text(" - pos: %d",ch->dacPos); + ImGui::Text(" - sample: %d",ch->dacSample); + ImGui::Text("- ins: %d",ch->ins); + ImGui::Text("- duty: %d",ch->duty); + ImGui::Text("- vol: %.2x",ch->vol); + ImGui::Text("- outVol: %.2x",ch->outVol); + ImGui::TextColored(ch->active?colorOn:colorOff,">> Active"); + ImGui::TextColored(ch->insChanged?colorOn:colorOff,">> InsChanged"); + ImGui::TextColored(ch->freqChanged?colorOn:colorOff,">> FreqChanged"); + ImGui::TextColored(ch->keyOn?colorOn:colorOff,">> KeyOn"); + ImGui::TextColored(ch->keyOff?colorOn:colorOff,">> KeyOff"); + ImGui::TextColored(ch->inPorta?colorOn:colorOff,">> InPorta"); + ImGui::TextColored(ch->pcm?colorOn:colorOff,">> DAC"); + ImGui::TextColored(ch->furnaceDac?colorOn:colorOff,">> FurnaceDAC"); + break; + } default: ImGui::Text("Unknown system! Help!"); break; diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index a25e831e..120ec774 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -165,5 +165,6 @@ const int availableSystems[]={ DIV_SYSTEM_BUBSYS_WSG, DIV_SYSTEM_PET, DIV_SYSTEM_VIC20, + DIV_SYSTEM_VRC6, 0 // don't remove this last one! }; \ No newline at end of file diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 837815da..ef021277 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1924,7 +1924,7 @@ void FurnaceGUI::drawInsEdit() { if ((ins->type==DIV_INS_PCE || ins->type==DIV_INS_AY8930)) { volMax=31; } - if (ins->type==DIV_INS_OPL || ins->type==DIV_INS_VERA) { + if (ins->type==DIV_INS_OPL || ins->type==DIV_INS_VERA || ins->type==DIV_INS_VRC6) { volMax=63; } if (ins->type==DIV_INS_AMIGA) { @@ -1985,6 +1985,10 @@ void FurnaceGUI::drawInsEdit() { dutyLabel="Duty"; dutyMax=63; } + if (ins->type==DIV_INS_VRC6) { + dutyLabel="Duty"; + dutyMax=7; + } bool dutyIsRel=(ins->type==DIV_INS_C64 && !ins->c64.dutyIsAbs); const char* waveLabel="Waveform"; @@ -1993,7 +1997,7 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_C64 || ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_SAA1099) { bitMode=true; } - if (ins->type==DIV_INS_STD) waveMax=0; + if (ins->type==DIV_INS_STD || ins->type==DIV_INS_VRC6) waveMax=0; if (ins->type==DIV_INS_TIA || ins->type==DIV_INS_VIC || ins->type==DIV_INS_OPLL) waveMax=15; if (ins->type==DIV_INS_C64) waveMax=4; if (ins->type==DIV_INS_SAA1099) waveMax=2; diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 0ab7c2ba..835b46fa 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -191,6 +191,13 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "NES with Konami VRC6", { + DIV_SYSTEM_NES, 64, 0, 0, + DIV_SYSTEM_VRC6, 64, 0, 0, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "NES with Konami VRC7", { DIV_SYSTEM_NES, 64, 0, 0, diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index b61245f2..f7bc2e1e 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -147,6 +147,7 @@ void FurnaceGUI::drawSysConf(int i) { } break; case DIV_SYSTEM_NES: + case DIV_SYSTEM_VRC6: if (ImGui::RadioButton("NTSC (1.79MHz)",flags==0)) { e->setSysFlags(i,0,restart); updateWindowTitle(); From c430d24d2f609ae7cdfeeefaa264f938b2584474 Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 28 Mar 2022 01:12:44 +0900 Subject: [PATCH 39/48] VRC6 has internal timer --- papers/doc/7-systems/vrc6.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/papers/doc/7-systems/vrc6.md b/papers/doc/7-systems/vrc6.md index c60ae26f..7dfc2585 100644 --- a/papers/doc/7-systems/vrc6.md +++ b/papers/doc/7-systems/vrc6.md @@ -6,7 +6,7 @@ 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. 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. +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) # effects From 5c5c9199c714ae549fa37054dc1e18e38141a2ee Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 28 Mar 2022 01:22:24 +0900 Subject: [PATCH 40/48] Channel enable bit is inverted --- src/engine/platform/sound/vrcvi/vrcvi.cpp | 14 +++++++------- src/engine/platform/sound/vrcvi/vrcvi.hpp | 6 +++--- src/engine/platform/vrc6.cpp | 8 ++++---- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/engine/platform/sound/vrcvi/vrcvi.cpp b/src/engine/platform/sound/vrcvi/vrcvi.cpp index 4754620e..21c3c5ea 100644 --- a/src/engine/platform/sound/vrcvi/vrcvi.cpp +++ b/src/engine/platform/sound/vrcvi/vrcvi.cpp @@ -36,7 +36,7 @@ -xxx ---- Pulse 1 Duty cycle ---- xxxx Pulse 1 Volume 9001 xxxx xxxx Pulse 1 Pitch bit 0-7 - 9002 x--- ---- Pulse 1 Disable + 9002 x--- ---- Pulse 1 Enable ---- xxxx Pulse 1 Pitch bit 8-11 9003 Sound control @@ -51,14 +51,14 @@ -xxx ---- Pulse 2 Duty cycle ---- xxxx Pulse 2 Volume a001 xxxx xxxx Pulse 2 Pitch bit 0-7 - a002 x--- ---- Pulse 2 Disable + a002 x--- ---- Pulse 2 Enable ---- xxxx Pulse 2 Pitch bit 8-11 b000-b002 Sawtooth b000 --xx xxxx Sawtooth Accumulate Rate b001 xxxx xxxx Sawtooth Pitch bit 0-7 - b002 x--- ---- Sawtooth Disable + b002 x--- ---- Sawtooth Enable ---- xxxx Sawtooth Pitch bit 8-11 f000-f002 IRQ Timer @@ -113,7 +113,7 @@ void vrcvi_core::reset() bool vrcvi_core::alu_t::tick() { - if (!m_divider.m_disable) + if (m_divider.m_enable) { const u16 temp = m_counter; // post decrement @@ -144,7 +144,7 @@ bool vrcvi_core::alu_t::tick() bool vrcvi_core::pulse_t::tick() { - if (m_divider.m_disable) + if (!m_divider.m_enable) return false; if (vrcvi_core::alu_t::tick()) @@ -155,7 +155,7 @@ bool vrcvi_core::pulse_t::tick() bool vrcvi_core::sawtooth_t::tick() { - if (m_divider.m_disable) + if (!m_divider.m_enable) return false; if (vrcvi_core::alu_t::tick()) @@ -232,7 +232,7 @@ void vrcvi_core::alu_t::divider_t::write(bool msb, u8 data) if (msb) { m_divider = (m_divider & ~0xf00) | (bitfield(data, 0, 4) << 8); - m_disable = bitfield(data, 7); + m_enable = bitfield(data, 7); } else m_divider = (m_divider & ~0x0ff) | data; diff --git a/src/engine/platform/sound/vrcvi/vrcvi.hpp b/src/engine/platform/sound/vrcvi/vrcvi.hpp index 028778b0..1d721a87 100644 --- a/src/engine/platform/sound/vrcvi/vrcvi.hpp +++ b/src/engine/platform/sound/vrcvi/vrcvi.hpp @@ -78,19 +78,19 @@ private: { divider_t() : m_divider(0) - , m_disable(0) + , m_enable(0) { }; void reset() { m_divider = 0; - m_disable = 0; + m_enable = 0; } void write(bool msb, u8 data); u16 m_divider : 12; // divider (pitch) - u16 m_disable : 1; // channel disable flag + u16 m_enable : 1; // channel enable flag }; vrcvi_core &m_host; diff --git a/src/engine/platform/vrc6.cpp b/src/engine/platform/vrc6.cpp index 83fb9c62..630d5972 100644 --- a/src/engine/platform/vrc6.cpp +++ b/src/engine/platform/vrc6.cpp @@ -204,10 +204,10 @@ void DivPlatformVRC6::tick() { //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,0x80); + chWrite(i,2,0); } else { chWrite(i,1,chan[i].freq&0xff); - chWrite(i,2,(chan[i].freq>>8)&0xf); + chWrite(i,2,0x80|((chan[i].freq>>8)&0xf)); } if (chan[i].keyOn) chan[i].keyOn=false; if (chan[i].keyOff) chan[i].keyOff=false; @@ -236,7 +236,7 @@ int DivPlatformVRC6::dispatch(DivCommand c) { break; } else { if (dumpWrites) { - chWrite(c.chan,2,0); + chWrite(c.chan,2,0x80); chWrite(c.chan,0,isMuted[c.chan]?0:0x80); addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dacSample); } @@ -268,7 +268,7 @@ int DivPlatformVRC6::dispatch(DivCommand c) { chan[c.chan].dacPeriod=0; chan[c.chan].dacRate=parent->getSample(chan[c.chan].dacSample)->rate; if (dumpWrites) { - chWrite(c.chan,2,0); + chWrite(c.chan,2,0x80); chWrite(c.chan,0,isMuted[c.chan]?0:0x80); addWrite(0xffff0001+(c.chan<<8),chan[c.chan].dacRate); } From 44b4c5c5aa4ba39ff05125591daacb63b355cb39 Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 28 Mar 2022 01:23:52 +0900 Subject: [PATCH 41/48] Spacing --- src/engine/platform/sound/vrcvi/vrcvi.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/sound/vrcvi/vrcvi.hpp b/src/engine/platform/sound/vrcvi/vrcvi.hpp index 1d721a87..40b4245e 100644 --- a/src/engine/platform/sound/vrcvi/vrcvi.hpp +++ b/src/engine/platform/sound/vrcvi/vrcvi.hpp @@ -90,7 +90,7 @@ private: void write(bool msb, u8 data); u16 m_divider : 12; // divider (pitch) - u16 m_enable : 1; // channel enable flag + u16 m_enable : 1; // channel enable flag }; vrcvi_core &m_host; From 5c922a090e369e33ea208260a9a76050a88a869b Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 28 Mar 2022 01:43:43 +0900 Subject: [PATCH 42/48] Fix enable bit correction --- src/engine/platform/sound/vrcvi/vrcvi.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/engine/platform/sound/vrcvi/vrcvi.cpp b/src/engine/platform/sound/vrcvi/vrcvi.cpp index 21c3c5ea..a8a289da 100644 --- a/src/engine/platform/sound/vrcvi/vrcvi.cpp +++ b/src/engine/platform/sound/vrcvi/vrcvi.cpp @@ -145,7 +145,10 @@ bool vrcvi_core::alu_t::tick() bool vrcvi_core::pulse_t::tick() { if (!m_divider.m_enable) + { + m_cycle = 0; return false; + } if (vrcvi_core::alu_t::tick()) m_cycle = bitfield(m_cycle + 1, 0, 4); @@ -156,7 +159,10 @@ bool vrcvi_core::pulse_t::tick() bool vrcvi_core::sawtooth_t::tick() { if (!m_divider.m_enable) + { + m_accum = 0; return false; + } if (vrcvi_core::alu_t::tick()) { From a7647a1d577dfe746ce0cd423f81244e5a08afd5 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 27 Mar 2022 13:38:28 -0500 Subject: [PATCH 43/48] nice troll --- src/engine/platform/genesisshared.h | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/engine/platform/genesisshared.h b/src/engine/platform/genesisshared.h index 3d5320f6..d58d339e 100644 --- a/src/engine/platform/genesisshared.h +++ b/src/engine/platform/genesisshared.h @@ -44,6 +44,17 @@ static int orderedOps[4]={ #define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} #define immWrite(a,v) if (!skipRegisterWrites) {writes.push_back(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);} } -#define urgentWrite(a,v) if (!skipRegisterWrites) {if (writes.empty() || writes.size()>16 || writes.front().addrOrVal) {writes.push_back(QueuedWrite(a,v));} else {writes.push_front(QueuedWrite(a,v));}; if (dumpWrites) {addWrite(a,v);} } +#define urgentWrite(a,v) if (!skipRegisterWrites) { \ + if (writes.empty()) { \ + writes.push_back(QueuedWrite(a,v)); \ + } else if (writes.size()>16 || writes.front().addrOrVal) { \ + writes.push_back(QueuedWrite(a,v)); \ + } else { \ + writes.push_front(QueuedWrite(a,v)); \ + } \ + if (dumpWrites) { \ + addWrite(a,v); \ + } \ +} #include "fmshared_OPN.h" From ef88fc57d86b26731951684788acb4d68cad3427 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 27 Mar 2022 16:35:55 -0500 Subject: [PATCH 44/48] GUI: remove invalid comment --- src/gui/insEdit.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 837815da..8febd9f4 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -186,7 +186,6 @@ void addAALine(ImDrawList* dl, const ImVec2& p1, const ImVec2& p2, const ImU32 c dl->AddPolyline(pt,2,color,ImDrawFlags_None,thickness); } -// TODO: don't get drunk tonight void FurnaceGUI::drawSSGEnv(unsigned char type, const ImVec2& size) { ImDrawList* dl=ImGui::GetWindowDrawList(); ImGuiWindow* window=ImGui::GetCurrentWindow(); From e5ce7c63f867a158c00893bccf5f09f6feb8f1f7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 27 Mar 2022 18:30:46 -0500 Subject: [PATCH 45/48] GUI: redesign FM editor layout, part 4 now with OPL and OPLL --- src/gui/insEdit.cpp | 200 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 169 insertions(+), 31 deletions(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 8febd9f4..68defe8c 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -297,7 +297,83 @@ void FurnaceGUI::drawSSGEnv(unsigned char type, const ImVec2& size) { } void FurnaceGUI::drawWaveform(unsigned char type, bool opz, const ImVec2& size) { + ImDrawList* dl=ImGui::GetWindowDrawList(); + ImGuiWindow* window=ImGui::GetCurrentWindow(); + ImVec2 waveform[65]; + const size_t waveformLen=64; + + ImVec2 minArea=window->DC.CursorPos; + ImVec2 maxArea=ImVec2( + minArea.x+size.x, + minArea.y+size.y + ); + ImRect rect=ImRect(minArea,maxArea); + ImGuiStyle& style=ImGui::GetStyle(); + ImU32 color=ImGui::GetColorU32(uiColors[GUI_COLOR_TEXT]); + ImGui::ItemSize(size,style.FramePadding.y); + if (ImGui::ItemAdd(rect,ImGui::GetID("wsDisplay"))) { + ImGui::RenderFrame(rect.Min,rect.Max,ImGui::GetColorU32(ImGuiCol_FrameBg),true,style.FrameRounding); + switch (type) { + case 0: + for (size_t i=0; i<=waveformLen; i++) { + float x=(float)i/(float)waveformLen; + float y=sin(x*2.0*M_PI); + waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); + } + break; + case 1: + for (size_t i=0; i<=waveformLen; i++) { + float x=(float)i/(float)waveformLen; + float y=MAX(0.0,sin(x*2.0*M_PI)); + waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); + } + break; + case 2: + for (size_t i=0; i<=waveformLen; i++) { + float x=(float)i/(float)waveformLen; + float y=fabs(sin(x*2.0*M_PI)); + waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); + } + break; + case 3: + for (size_t i=0; i<=waveformLen; i++) { + float x=(float)i/(float)waveformLen; + float y=fabs((tan(x*2.0*M_PI)>=0.0)?sin(x*2.0*M_PI):0.0); + waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); + } + break; + case 4: + for (size_t i=0; i<=waveformLen; i++) { + float x=(float)i/(float)waveformLen; + float y=(x>=0.5)?0.0:sin(x*4.0*M_PI); + waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); + } + break; + case 5: + for (size_t i=0; i<=waveformLen; i++) { + float x=(float)i/(float)waveformLen; + float y=(x>=0.5)?0.0:fabs(sin(x*4.0*M_PI)); + waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); + } + break; + case 6: + for (size_t i=0; i<=waveformLen; i++) { + float x=(float)i/(float)waveformLen; + float y=(x>=0.5)?-1.0:1.0; + waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); + } + break; + case 7: + for (size_t i=0; i<=waveformLen; i++) { + float x=(float)i/(float)waveformLen; + float y=pow(2.0*(x-0.5),3.0); + waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); + } + break; + } + dl->AddPolyline(waveform,waveformLen+1,color,ImDrawFlags_None,dpiScale); + } } void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, const ImVec2& size) { @@ -1036,7 +1112,7 @@ if (ImGui::BeginTable("MacroSpace",2)) { \ ImGui::SetCursorPosX(ImGui::GetCursorPosX()+0.5*(ImGui::GetContentRegionAvail().x-ImGui::CalcTextSize(text).x)); #define CENTER_VSLIDER \ - ImGui::SetCursorPosX(ImGui::GetCursorPosX()+0.5f*ImGui::GetContentRegionAvail().x-20.0f); + ImGui::SetCursorPosX(ImGui::GetCursorPosX()+0.5f*ImGui::GetContentRegionAvail().x-10.0f*dpiScale); void FurnaceGUI::drawInsEdit() { if (nextWindow==GUI_WINDOW_INS_EDIT) { @@ -1182,25 +1258,36 @@ void FurnaceGUI::drawInsEdit() { } if (willDisplayOps) { if (settings.fmLayout==0) { - if (ImGui::BeginTable("FMOperators",16,ImGuiTableFlags_SizingStretchProp|ImGuiTableFlags_BordersH|ImGuiTableFlags_BordersOuterV)) { + int numCols=16; + if (ins->type==DIV_INS_OPL) numCols=13; + if (ins->type==DIV_INS_OPLL) numCols=12; + if (ImGui::BeginTable("FMOperators",numCols,ImGuiTableFlags_SizingStretchProp|ImGuiTableFlags_BordersH|ImGuiTableFlags_BordersOuterV)) { // configure columns ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); // op name ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.05f); // ar ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.05f); // dr ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthStretch,0.05f); // sl - ImGui::TableSetupColumn("c4",ImGuiTableColumnFlags_WidthStretch,0.05f); // d2r + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + ImGui::TableSetupColumn("c4",ImGuiTableColumnFlags_WidthStretch,0.05f); // d2r + } ImGui::TableSetupColumn("c5",ImGuiTableColumnFlags_WidthStretch,0.05f); // rr ImGui::TableSetupColumn("c6",ImGuiTableColumnFlags_WidthFixed); // -separator- ImGui::TableSetupColumn("c7",ImGuiTableColumnFlags_WidthStretch,0.05f); // tl - ImGui::TableSetupColumn("c8",ImGuiTableColumnFlags_WidthStretch,0.05f); // rs + ImGui::TableSetupColumn("c8",ImGuiTableColumnFlags_WidthStretch,0.05f); // rs/ksl ImGui::TableSetupColumn("c9",ImGuiTableColumnFlags_WidthStretch,0.05f); // mult - ImGui::TableSetupColumn("c10",ImGuiTableColumnFlags_WidthStretch,0.05f); // dt - ImGui::TableSetupColumn("c11",ImGuiTableColumnFlags_WidthStretch,0.05f); // dt2 - ImGui::TableSetupColumn("c12",ImGuiTableColumnFlags_WidthFixed); // -separator- - ImGui::TableSetupColumn("c13",ImGuiTableColumnFlags_WidthStretch,0.2f); // ssg - ImGui::TableSetupColumn("c14",ImGuiTableColumnFlags_WidthStretch,0.3f); // env + + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + ImGui::TableSetupColumn("c10",ImGuiTableColumnFlags_WidthStretch,0.05f); // dt + ImGui::TableSetupColumn("c11",ImGuiTableColumnFlags_WidthStretch,0.05f); // dt2 + } ImGui::TableSetupColumn("c15",ImGuiTableColumnFlags_WidthFixed); // am + ImGui::TableSetupColumn("c12",ImGuiTableColumnFlags_WidthFixed); // -separator- + if (ins->type!=DIV_INS_OPLL) { + ImGui::TableSetupColumn("c13",ImGuiTableColumnFlags_WidthStretch,0.2f); // ssg/waveform + } + ImGui::TableSetupColumn("c14",ImGuiTableColumnFlags_WidthStretch,0.3f); // env + // header ImGui::TableNextRow(ImGuiTableRowFlags_Headers); ImGui::TableNextColumn(); @@ -1238,28 +1325,41 @@ void FurnaceGUI::drawInsEdit() { CENTER_TEXT(FM_SHORT_NAME(FM_RS)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_RS)); } else { - CENTER_TEXT(FM_SHORT_NAME(FM_KSR)); - ImGui::TextUnformatted(FM_SHORT_NAME(FM_KSR)); + CENTER_TEXT(FM_SHORT_NAME(FM_KSL)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_KSL)); } ImGui::TableNextColumn(); CENTER_TEXT(FM_SHORT_NAME(FM_MULT)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_MULT)); ImGui::TableNextColumn(); - CENTER_TEXT(FM_SHORT_NAME(FM_DT)); - ImGui::TextUnformatted(FM_SHORT_NAME(FM_DT)); + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + CENTER_TEXT(FM_SHORT_NAME(FM_DT)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_DT)); + ImGui::TableNextColumn(); + CENTER_TEXT(FM_SHORT_NAME(FM_DT2)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_DT2)); + ImGui::TableNextColumn(); + } + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + CENTER_TEXT(FM_SHORT_NAME(FM_AM)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_AM)); + } else { + CENTER_TEXT("Other"); + ImGui::TextUnformatted("Other"); + } ImGui::TableNextColumn(); - CENTER_TEXT(FM_SHORT_NAME(FM_DT2)); - ImGui::TextUnformatted(FM_SHORT_NAME(FM_DT2)); - ImGui::TableNextColumn(); - ImGui::TableNextColumn(); - CENTER_TEXT(FM_NAME(FM_SSG)); - ImGui::TextUnformatted(FM_NAME(FM_SSG)); + if (ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPZ) { + ImGui::TableNextColumn(); + CENTER_TEXT(FM_NAME(FM_WS)); + ImGui::TextUnformatted(FM_NAME(FM_WS)); + } else if (ins->type!=DIV_INS_OPLL) { + ImGui::TableNextColumn(); + CENTER_TEXT(FM_NAME(FM_SSG)); + ImGui::TextUnformatted(FM_NAME(FM_SSG)); + } ImGui::TableNextColumn(); CENTER_TEXT("Envelope"); ImGui::TextUnformatted("Envelope"); - ImGui::TableNextColumn(); - CENTER_TEXT(FM_SHORT_NAME(FM_AM)); - ImGui::TextUnformatted(FM_SHORT_NAME(FM_AM)); float sliderHeight=32.0f*dpiScale; @@ -1294,9 +1394,9 @@ void FurnaceGUI::drawInsEdit() { } int maxArDr=(ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ)?31:15; bool ssgOn=op.ssgEnv&8; - /*bool ksrOn=op.ksr; + bool ksrOn=op.ksr; bool vibOn=op.vib; - bool susOn=op.sus;*/ + bool susOn=op.sus; unsigned char ssgEnv=op.ssgEnv&7; ImGui::TableNextColumn(); @@ -1370,6 +1470,13 @@ void FurnaceGUI::drawInsEdit() { ImGui::SetTooltip("Only on YM2151 (OPM)"); } + ImGui::TableNextColumn(); + bool amOn=op.am; + ImGui::SetCursorPosY(ImGui::GetCursorPosY()+0.5*(sliderHeight-ImGui::GetFrameHeight())); + if (ImGui::Checkbox("##AM",&amOn)) { PARAMETER + op.am=amOn; + } + ImGui::TableNextColumn(); ImGui::Dummy(ImVec2(4.0f*dpiScale,2.0f*dpiScale)); @@ -1393,18 +1500,49 @@ void FurnaceGUI::drawInsEdit() { op.ssgEnv=(op.ssgEnv&8)|(ssgEnv&7); } } + } else { + ImGui::TableNextColumn(); + bool amOn=op.am; + ImGui::SetCursorPosY(ImGui::GetCursorPosY()+0.5*(sliderHeight-ImGui::GetFrameHeight()*4.0-ImGui::GetStyle().ItemSpacing.y*3.0)); + if (ImGui::Checkbox(FM_NAME(FM_AM),&amOn)) { PARAMETER + op.am=amOn; + } + if (ImGui::Checkbox(FM_NAME(FM_VIB),&vibOn)) { PARAMETER + op.vib=vibOn; + } + if (ImGui::Checkbox(FM_NAME(FM_KSR),&ksrOn)) { PARAMETER + op.ksr=ksrOn; + } + if (ins->type==DIV_INS_OPL) { + if (ImGui::Checkbox(FM_NAME(FM_SUS),&susOn)) { PARAMETER + op.sus=susOn; + } + } else if (ins->type==DIV_INS_OPLL) { + if (ImGui::Checkbox(FM_NAME(FM_EGS),&ssgOn)) { PARAMETER + op.ssgEnv=(op.ssgEnv&7)|(ssgOn<<3); + } + } + } + + if (ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPZ) { + ImGui::TableNextColumn(); + ImGui::Dummy(ImVec2(4.0f*dpiScale,2.0f*dpiScale)); + ImGui::TableNextColumn(); + + drawWaveform(op.ws&7,ins->type==DIV_INS_OPZ,ImVec2(ImGui::GetContentRegionAvail().x,sliderHeight-ImGui::GetFrameHeightWithSpacing())); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + P(ImGui::SliderScalar("##WS",ImGuiDataType_U8,&op.ws,&_ZERO,&_SEVEN,(ins->type==DIV_INS_OPZ)?opzWaveforms[op.ws&7]:oplWaveforms[op.ws&7])); rightClickable + if (ins->type==DIV_INS_OPL && ImGui::IsItemHovered()) { + ImGui::SetTooltip("OPL2/3 only (last 4 waveforms are OPL3 only)"); + } + } else if (ins->type==DIV_INS_OPLL) { + ImGui::TableNextColumn(); + ImGui::Dummy(ImVec2(4.0f*dpiScale,2.0f*dpiScale)); } ImGui::TableNextColumn(); drawFMEnv(op.tl&maxTl,op.ar&maxArDr,op.dr&maxArDr,(ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPLL)?((op.rr&15)*2):op.d2r&31,op.rr&15,op.sl&15,maxTl,maxArDr,ImVec2(ImGui::GetContentRegionAvail().x,sliderHeight)); - ImGui::TableNextColumn(); - bool amOn=op.am; - ImGui::SetCursorPosY(ImGui::GetCursorPosY()+0.5*(sliderHeight-ImGui::GetFrameHeight())); - if (ImGui::Checkbox("##AM",&amOn)) { PARAMETER - op.am=amOn; - } - ImGui::PopID(); } From 075f758e4dd65d7886d8e5f363cd7a05dfd49231 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 27 Mar 2022 22:04:01 -0500 Subject: [PATCH 46/48] 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 47/48] 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 48/48] 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