From 5ad54dad4df8b4c51c08c65ba5c07cb7e79ca934 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 4 Mar 2022 14:31:29 -0500 Subject: [PATCH 01/32] allocate VERA chip --- papers/format.md | 1 + 1 file changed, 1 insertion(+) diff --git a/papers/format.md b/papers/format.md index 8e8c62e2..4df49ed0 100644 --- a/papers/format.md +++ b/papers/format.md @@ -173,6 +173,7 @@ size | description | - 0xa9: SegaPCM (for Deflemask Compatibility) - 5 channels | - 0xaa: MSM6295 - 4 channels | - 0xab: MSM6258 - 1 channel + | - 0xac: Commander X16 (VERA) - 17 channels | - 0xde: YM2610B extended - 19 channels | - 0xe0: QSound - 19 channels | - (compound!) means that the system is composed of two or more chips, From 837b070aeeeea6aa1e89136be46c53c0113dec0a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 4 Mar 2022 14:52:35 -0500 Subject: [PATCH 02/32] add a pull request template --- .github/pull_request_template.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..6b5659d3 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1 @@ + From 84ba63db24bc640585a8229b85d2c5d7e2189028 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 4 Mar 2022 18:18:43 -0500 Subject: [PATCH 03/32] add PC speaker system haha --- CMakeLists.txt | 1 + src/engine/dispatchContainer.cpp | 4 + src/engine/platform/pcspkr.cpp | 267 +++++++++++++++++++++++++++++++ src/engine/platform/pcspkr.h | 84 ++++++++++ src/gui/gui.cpp | 2 + 5 files changed, 358 insertions(+) create mode 100644 src/engine/platform/pcspkr.cpp create mode 100644 src/engine/platform/pcspkr.h diff --git a/CMakeLists.txt b/CMakeLists.txt index af3e3a7d..8bcb8ce9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -301,6 +301,7 @@ src/engine/platform/ay8930.cpp src/engine/platform/tia.cpp src/engine/platform/saa.cpp src/engine/platform/amiga.cpp +src/engine/platform/pcspkr.cpp src/engine/platform/segapcm.cpp src/engine/platform/qsound.cpp src/engine/platform/dummy.cpp diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 8398ca3d..0682bbd7 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -37,6 +37,7 @@ #include "platform/tia.h" #include "platform/saa.h" #include "platform/amiga.h" +#include "platform/pcspkr.h" #include "platform/segapcm.h" #include "platform/qsound.h" #include "platform/dummy.h" @@ -216,6 +217,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do ((DivPlatformSAA1099*)dispatch)->setCore((DivSAACores)saaCore); break; } + case DIV_SYSTEM_PCSPKR: + dispatch=new DivPlatformPCSpeaker; + break; case DIV_SYSTEM_LYNX: dispatch=new DivPlatformLynx; break; diff --git a/src/engine/platform/pcspkr.cpp b/src/engine/platform/pcspkr.cpp new file mode 100644 index 00000000..8a991387 --- /dev/null +++ b/src/engine/platform/pcspkr.cpp @@ -0,0 +1,267 @@ +/** + * 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 "pcspkr.h" +#include "../engine.h" +#include + +#define PCSPKR_DIVIDER 4 +#define CHIP_DIVIDER 1 + +const char* regCheatSheetPCSpeaker[]={ + "Period", "0", + NULL +}; + +const char** DivPlatformPCSpeaker::getRegisterSheet() { + return regCheatSheetPCSpeaker; +} + +const char* DivPlatformPCSpeaker::getEffectName(unsigned char effect) { + return NULL; +} + +void DivPlatformPCSpeaker::acquire(short* bufL, short* bufR, size_t start, size_t len) { + for (size_t i=start; i>1):((freq+1)>>1); + } + flip=!flip; + } + bufL[i]=(flip && !isMuted[0])?32767:0; + } else { + bufL[i]=0; + } + } +} + +void DivPlatformPCSpeaker::tick() { + for (int i=0; i<1; i++) { + chan[i].std.next(); + if (chan[i].std.hadVol) { + // ok, why are the volumes like that? + chan[i].outVol=chan[i].vol; + } + 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].freqChanged || chan[i].keyOn || chan[i].keyOff) { + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)-1; + if (chan[i].freq<0) chan[i].freq=0; + if (chan[i].freq>65535) chan[i].freq=65535; + if (chan[i].keyOn) { + on=true; + } + if (chan[i].keyOff) { + on=false; + } + freq=chan[i].freq; + if (chan[i].keyOn) chan[i].keyOn=false; + if (chan[i].keyOff) chan[i].keyOff=false; + chan[i].freqChanged=false; + } + } +} + +int DivPlatformPCSpeaker::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: + 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)); + break; + case DIV_CMD_NOTE_OFF: + 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 (chan[c.chan].active) { + on=chan[c.chan].vol; + } + } + 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_LEGATO: + if (c.chan==3) break; + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0))); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + break; + case DIV_CMD_PRE_PORTA: + if (chan[c.chan].active && c.value2) { + if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + } + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_GET_VOLMAX: + return 1; + break; + case DIV_ALWAYS_SET_VOLUME: + return 1; + break; + default: + break; + } + return 1; +} + +void DivPlatformPCSpeaker::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; +} + +void DivPlatformPCSpeaker::forceIns() { + for (int i=0; i<5; i++) { + chan[i].insChanged=true; + } +} + +void* DivPlatformPCSpeaker::getChanState(int ch) { + return &chan[ch]; +} + +unsigned char* DivPlatformPCSpeaker::getRegisterPool() { + return regPool; +} + +int DivPlatformPCSpeaker::getRegisterPoolSize() { + return 2; +} + +void DivPlatformPCSpeaker::reset() { + for (int i=0; i<1; i++) { + chan[i]=DivPlatformPCSpeaker::Channel(); + } + if (dumpWrites) { + addWrite(0xffffffff,0); + } + + on=false; + freq=0; + pos=0; + + memset(regPool,0,2); +} + +bool DivPlatformPCSpeaker::keyOffAffectsArp(int ch) { + return true; +} + +void DivPlatformPCSpeaker::setFlags(unsigned int flags) { + chipClock=COLOR_NTSC/3.0; + rate=chipClock/PCSPKR_DIVIDER; +} + +void DivPlatformPCSpeaker::notifyInsDeletion(void* ins) { + for (int i=0; i<1; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformPCSpeaker::poke(unsigned int addr, unsigned short val) { + // ??? +} + +void DivPlatformPCSpeaker::poke(std::vector& wlist) { + // ??? +} + +int DivPlatformPCSpeaker::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + for (int i=0; i<1; i++) { + isMuted[i]=false; + } + setFlags(flags); + + reset(); + return 5; +} + +void DivPlatformPCSpeaker::quit() { +} + +DivPlatformPCSpeaker::~DivPlatformPCSpeaker() { +} diff --git a/src/engine/platform/pcspkr.h b/src/engine/platform/pcspkr.h new file mode 100644 index 00000000..90e19b8c --- /dev/null +++ b/src/engine/platform/pcspkr.h @@ -0,0 +1,84 @@ +/** + * 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 _PCSPKR_H +#define _PCSPKR_H + +#include "../dispatch.h" +#include "../macroInt.h" + +class DivPlatformPCSpeaker: public DivDispatch { + struct Channel { + int freq, baseFreq, pitch, note; + unsigned char ins, duty, sweep; + bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta, furnaceDac; + signed char vol, outVol, wave; + DivMacroInt std; + Channel(): + freq(0), + baseFreq(0), + pitch(0), + note(0), + ins(-1), + duty(0), + sweep(8), + active(false), + insChanged(true), + freqChanged(false), + sweepChanged(false), + keyOn(false), + keyOff(false), + inPorta(false), + furnaceDac(false), + vol(15), + outVol(15), + wave(-1) {} + }; + Channel chan[1]; + bool isMuted[1]; + bool on, flip; + int pos; + unsigned short freq; + unsigned char regPool[2]; + + 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(); + ~DivPlatformPCSpeaker(); +}; + +#endif diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 58e95003..7ee75fc4 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4589,6 +4589,7 @@ bool FurnaceGUI::loop() { sysAddOption(DIV_SYSTEM_YM2610B_EXT); sysAddOption(DIV_SYSTEM_AY8910); sysAddOption(DIV_SYSTEM_AMIGA); + sysAddOption(DIV_SYSTEM_PCSPKR); sysAddOption(DIV_SYSTEM_OPLL); sysAddOption(DIV_SYSTEM_OPLL_DRUMS); sysAddOption(DIV_SYSTEM_VRC7); @@ -4905,6 +4906,7 @@ bool FurnaceGUI::loop() { sysChangeOption(i,DIV_SYSTEM_YM2610B_EXT); sysChangeOption(i,DIV_SYSTEM_AY8910); sysChangeOption(i,DIV_SYSTEM_AMIGA); + sysChangeOption(i,DIV_SYSTEM_PCSPKR); sysChangeOption(i,DIV_SYSTEM_OPLL); sysChangeOption(i,DIV_SYSTEM_OPLL_DRUMS); sysChangeOption(i,DIV_SYSTEM_VRC7); From ea290a5015c286beb5bff75a693b6b447407258c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 4 Mar 2022 18:37:04 -0500 Subject: [PATCH 04/32] screw ME --- src/engine/platform/pcspkr.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/pcspkr.cpp b/src/engine/platform/pcspkr.cpp index 8a991387..144ec775 100644 --- a/src/engine/platform/pcspkr.cpp +++ b/src/engine/platform/pcspkr.cpp @@ -192,7 +192,7 @@ void DivPlatformPCSpeaker::muteChannel(int ch, bool mute) { } void DivPlatformPCSpeaker::forceIns() { - for (int i=0; i<5; i++) { + for (int i=0; i<1; i++) { chan[i].insChanged=true; } } From b6717fd3143a5317a11fdaba31f6339ccda895a1 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 4 Mar 2022 23:11:34 -0500 Subject: [PATCH 05/32] uninitialized variable --- src/engine/platform/pcspkr.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine/platform/pcspkr.cpp b/src/engine/platform/pcspkr.cpp index 144ec775..76d6612a 100644 --- a/src/engine/platform/pcspkr.cpp +++ b/src/engine/platform/pcspkr.cpp @@ -220,6 +220,7 @@ void DivPlatformPCSpeaker::reset() { on=false; freq=0; pos=0; + flip=false; memset(regPool,0,2); } From 16dfc785d334bdc16da91b6b36d405ba979b9c65 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 5 Mar 2022 00:36:50 -0500 Subject: [PATCH 06/32] PC speaker: improvements --- src/engine/platform/pcspkr.cpp | 75 ++++++++++++++++++++++++++++++++-- src/engine/platform/pcspkr.h | 9 +++- src/engine/song.h | 6 +++ src/gui/gui.cpp | 22 ++++++++++ 4 files changed, 107 insertions(+), 5 deletions(-) diff --git a/src/engine/platform/pcspkr.cpp b/src/engine/platform/pcspkr.cpp index 76d6612a..51353fed 100644 --- a/src/engine/platform/pcspkr.cpp +++ b/src/engine/platform/pcspkr.cpp @@ -37,7 +37,10 @@ const char* DivPlatformPCSpeaker::getEffectName(unsigned char effect) { return NULL; } -void DivPlatformPCSpeaker::acquire(short* bufL, short* bufR, size_t start, size_t len) { +const float cut=0.05; +const float reso=0.06; + +void DivPlatformPCSpeaker::acquire_unfilt(short* bufL, short* bufR, size_t start, size_t len) { for (size_t i=start; i>1):((freq+1)>>1); + pos+=freq; } - flip=!flip; } - bufL[i]=(flip && !isMuted[0])?32767:0; + bufL[i]=(pos>(freq>>1) && !isMuted[0])?32767:0; } else { bufL[i]=0; } } } +void DivPlatformPCSpeaker::acquire_cone(short* bufL, short* bufR, size_t start, size_t len) { + for (size_t i=start; i((freq+16)>>1) && !isMuted[0])?1:0; + low+=0.04*band; + band+=0.04*(next-low-band); + float out=(low+band)*0.75; + if (out>1.0) out=1.0; + if (out<-1.0) out=-1.0; + bufL[i]=out*32767; + } else { + bufL[i]=0; + } + } +} + +void DivPlatformPCSpeaker::acquire_piezo(short* bufL, short* bufR, size_t start, size_t len) { + for (size_t i=start; i((freq+64)>>1) && !isMuted[0])?1:0; + low+=cut*band; + band+=cut*(next-low-(reso*band)); + float out=band*0.15-(next-low)*0.06; + if (out>1.0) out=1.0; + if (out<-1.0) out=-1.0; + bufL[i]=out*32767; + } else { + bufL[i]=0; + } + } +} + +void DivPlatformPCSpeaker::acquire(short* bufL, short* bufR, size_t start, size_t len) { + switch (speakerType) { + case 0: + acquire_unfilt(bufL,bufR,start,len); + break; + case 1: + acquire_cone(bufL,bufR,start,len); + break; + case 2: + acquire_piezo(bufL,bufR,start,len); + break; + } +} + void DivPlatformPCSpeaker::tick() { for (int i=0; i<1; i++) { chan[i].std.next(); @@ -221,6 +285,8 @@ void DivPlatformPCSpeaker::reset() { freq=0; pos=0; flip=false; + low=0; + band=0; memset(regPool,0,2); } @@ -232,6 +298,7 @@ bool DivPlatformPCSpeaker::keyOffAffectsArp(int ch) { void DivPlatformPCSpeaker::setFlags(unsigned int flags) { chipClock=COLOR_NTSC/3.0; rate=chipClock/PCSPKR_DIVIDER; + speakerType=flags&3; } void DivPlatformPCSpeaker::notifyInsDeletion(void* ins) { diff --git a/src/engine/platform/pcspkr.h b/src/engine/platform/pcspkr.h index 90e19b8c..caf6b7f3 100644 --- a/src/engine/platform/pcspkr.h +++ b/src/engine/platform/pcspkr.h @@ -53,12 +53,19 @@ class DivPlatformPCSpeaker: public DivDispatch { Channel chan[1]; bool isMuted[1]; bool on, flip; - int pos; + int pos, speakerType; + float low, band; + float low2, high2, band2; + float low3, band3; unsigned short freq; unsigned char regPool[2]; friend void putDispatchChan(void*,int,int); + void acquire_unfilt(short* bufL, short* bufR, size_t start, size_t len); + void acquire_cone(short* bufL, short* bufR, size_t start, size_t len); + void acquire_piezo(short* bufL, short* bufR, size_t start, size_t len); + public: void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); diff --git a/src/engine/song.h b/src/engine/song.h index fff1065f..d1e255d9 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -215,6 +215,12 @@ struct DivSong { // - 1: Amiga 1200 // - bit 8-14: stereo separation // - 0 is 0% while 127 is 100% + // - PC Speaker: + // - bit 0-1: speaker type + // - 0: unfiltered + // - 1: cone + // - 2: piezo + // - 3: real (TODO) // - QSound: // - bit 12-20: echo feedback // - Valid values are 0-255 diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 7ee75fc4..191f0c21 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4843,6 +4843,28 @@ bool FurnaceGUI::loop() { } break; } + case DIV_SYSTEM_PCSPKR: { + ImGui::Text("Speaker type:"); + if (ImGui::RadioButton("Unfiltered",(flags&3)==0)) { + e->setSysFlags(i,(flags&(~3))|0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("Cone",(flags&3)==1)) { + e->setSysFlags(i,(flags&(~3))|1,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("Piezo",(flags&3)==2)) { + e->setSysFlags(i,(flags&(~3))|2,restart); + updateWindowTitle(); + } + /* + if (ImGui::RadioButton("Use system beeper",(flags&3)==3)) { + e->setSysFlags(i,(flags&(~3))|3,restart); + updateWindowTitle(); + } + */ + break; + } case DIV_SYSTEM_QSOUND: { ImGui::Text("Echo delay:"); int echoBufSize=2725 - (flags & 4095); From e07caddc92cc6ef4edf60366e32bb04504b701b8 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 5 Mar 2022 01:26:59 -0500 Subject: [PATCH 07/32] fix samples being cut at times --- src/engine/fileOps.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 8b5cfad5..ec7cd1cb 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -1112,6 +1112,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { logW("%d: sample depth is wrong! (%d)\n",i,sample->depth); sample->depth=16; } + sample->samples=(double)sample->samples/samplePitches[pitch]; sample->init(sample->samples); unsigned int k=0; From 23431323e2c0eaf52fbdaf8cff3afa37b2adff35 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 5 Mar 2022 02:13:15 -0500 Subject: [PATCH 08/32] GUI: major usability improvement coming click on orders in click-to-edit or select instrument=auto-focus pattern --- src/gui/gui.cpp | 55 ++++++++++++++++++++++++++++++++++++-------- src/gui/gui.h | 5 ++++ src/gui/orders.cpp | 26 +++++++++++++-------- src/gui/settings.cpp | 7 ++++++ 4 files changed, 75 insertions(+), 18 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 191f0c21..8c8831b2 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -623,6 +623,10 @@ void FurnaceGUI::drawEditControls() { e->noteOff(activeNotes[i].chan); } activeNotes.clear(); + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } } ImGui::Text("Edit Step"); @@ -630,6 +634,10 @@ void FurnaceGUI::drawEditControls() { if (ImGui::InputInt("##EditStep",&editStep,1,1)) { if (editStep>=e->song.patLen) editStep=e->song.patLen-1; if (editStep<0) editStep=0; + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } } if (ImGui::Button(ICON_FA_PLAY "##Play")) { @@ -649,9 +657,9 @@ void FurnaceGUI::drawEditControls() { ImGui::Text("Follow"); ImGui::SameLine(); - ImGui::Checkbox("Orders",&followOrders); + unimportant(ImGui::Checkbox("Orders",&followOrders)); ImGui::SameLine(); - ImGui::Checkbox("Pattern",&followPattern); + unimportant(ImGui::Checkbox("Pattern",&followPattern)); bool repeatPattern=e->getRepeatPattern(); if (ImGui::Checkbox("Repeat pattern",&repeatPattern)) { @@ -713,6 +721,10 @@ void FurnaceGUI::drawEditControls() { e->noteOff(activeNotes[i].chan); } activeNotes.clear(); + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } } ImGui::SameLine(); @@ -722,14 +734,18 @@ void FurnaceGUI::drawEditControls() { if (ImGui::InputInt("##EditStep",&editStep,1,1)) { if (editStep>=e->song.patLen) editStep=e->song.patLen-1; if (editStep<0) editStep=0; + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } } ImGui::SameLine(); ImGui::Text("Follow"); ImGui::SameLine(); - ImGui::Checkbox("Orders",&followOrders); + unimportant(ImGui::Checkbox("Orders",&followOrders)); ImGui::SameLine(); - ImGui::Checkbox("Pattern",&followPattern); + unimportant(ImGui::Checkbox("Pattern",&followPattern)); } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; ImGui::End(); @@ -776,6 +792,10 @@ void FurnaceGUI::drawEditControls() { e->noteOff(activeNotes[i].chan); } activeNotes.clear(); + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } } ImGui::Text("Step"); @@ -783,16 +803,20 @@ void FurnaceGUI::drawEditControls() { if (ImGui::InputInt("##EditStep",&editStep,0,0)) { if (editStep>=e->song.patLen) editStep=e->song.patLen-1; if (editStep<0) editStep=0; + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } } ImGui::Text("Foll."); ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(followOrders)?0.6f:0.2f,0.2f,1.0f)); - if (ImGui::SmallButton("Ord##FollowOrders")) { + if (ImGui::SmallButton("Ord##FollowOrders")) { handleUnimportant followOrders=!followOrders; } ImGui::PopStyleColor(); ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(followPattern)?0.6f:0.2f,0.2f,1.0f)); - if (ImGui::SmallButton("Pat##FollowPattern")) { + if (ImGui::SmallButton("Pat##FollowPattern")) { handleUnimportant followPattern=!followPattern; } ImGui::PopStyleColor(); @@ -860,6 +884,10 @@ void FurnaceGUI::drawEditControls() { e->noteOff(activeNotes[i].chan); } activeNotes.clear(); + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } } ImGui::Text("Step"); @@ -869,11 +897,15 @@ void FurnaceGUI::drawEditControls() { if (ImGui::InputInt("##EditStep",&editStep,1,1)) { if (editStep>=e->song.patLen) editStep=e->song.patLen-1; if (editStep<0) editStep=0; + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } } ImGui::NextColumn(); - ImGui::Checkbox("Follow orders",&followOrders); - ImGui::Checkbox("Follow pattern",&followPattern); + unimportant(ImGui::Checkbox("Follow orders",&followOrders)); + unimportant(ImGui::Checkbox("Follow pattern",&followPattern)); } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; ImGui::End(); @@ -1169,10 +1201,15 @@ void FurnaceGUI::drawInsList() { if (ImGui::Selectable(name.c_str(),curIns==i)) { curIns=i; } + if (settings.insFocusesPattern && patternOpen && ImGui::IsItemActivated()) { + nextWindow=GUI_WINDOW_PATTERN; + curIns=i; + } ImGui::PopStyleColor(); if (ImGui::IsItemHovered()) { if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { insEditOpen=true; + nextWindow=GUI_WINDOW_INS_EDIT; } } } @@ -5094,6 +5131,7 @@ bool FurnaceGUI::loop() { ImGui::DockSpaceOverViewport(); + drawPattern(); drawEditControls(); drawSongInfo(); drawOrders(); @@ -5106,7 +5144,6 @@ bool FurnaceGUI::loop() { drawMixer(); drawOsc(); drawVolMeter(); - drawPattern(); drawSettings(); drawDebug(); drawStats(); diff --git a/src/gui/gui.h b/src/gui/gui.h index e58bd17b..7d7f6b4a 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -29,6 +29,9 @@ #define rightClickable if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) ImGui::SetKeyboardFocusHere(-1); +#define handleUnimportant if (settings.insFocusesPattern && patternOpen) {nextWindow=GUI_WINDOW_PATTERN;} +#define unimportant(x) if (x) {handleUnimportant} + enum FurnaceGUIColors { GUI_COLOR_BACKGROUND=0, GUI_COLOR_FRAME_BACKGROUND, @@ -495,6 +498,7 @@ class FurnaceGUI { int viewPrevPattern; int guiColorsBase; int avoidRaisingPattern; + int insFocusesPattern; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -537,6 +541,7 @@ class FurnaceGUI { viewPrevPattern(1), guiColorsBase(0), avoidRaisingPattern(0), + insFocusesPattern(1), maxUndoSteps(100), mainFontPath(""), patFontPath(""), diff --git a/src/gui/orders.cpp b/src/gui/orders.cpp index 94191ec0..22bd7e0d 100644 --- a/src/gui/orders.cpp +++ b/src/gui/orders.cpp @@ -75,6 +75,10 @@ void FurnaceGUI::drawOrders() { e->setOrder(i); curNibble=false; orderCursor=-1; + + if (orderEditMode==0) { + handleUnimportant; + } } ImGui::PopStyleColor(); for (int j=0; jgetTotalChannelCount(); j++) { @@ -111,6 +115,10 @@ void FurnaceGUI::drawOrders() { curNibble=false; } } + + if (orderEditMode==0) { + handleUnimportant; + } } if (!pat->name.empty() && ImGui::IsItemHovered()) { ImGui::SetTooltip("%s",pat->name.c_str()); @@ -148,21 +156,21 @@ void FurnaceGUI::drawOrders() { ImGui::EndTable(); } ImGui::NextColumn(); - if (ImGui::Button(ICON_FA_PLUS)) { + if (ImGui::Button(ICON_FA_PLUS)) { handleUnimportant // add order row (new) doAction(GUI_ACTION_ORDERS_ADD); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Add new order"); } - if (ImGui::Button(ICON_FA_MINUS)) { + if (ImGui::Button(ICON_FA_MINUS)) { handleUnimportant // remove this order row doAction(GUI_ACTION_ORDERS_REMOVE); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Remove order"); - } - if (ImGui::Button(ICON_FA_FILES_O)) { + } + if (ImGui::Button(ICON_FA_FILES_O)) { handleUnimportant // duplicate order row doAction(GUI_ACTION_ORDERS_DUPLICATE); } @@ -172,21 +180,21 @@ void FurnaceGUI::drawOrders() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Duplicate order (right-click to deep clone)"); } - if (ImGui::Button(ICON_FA_ANGLE_UP)) { + if (ImGui::Button(ICON_FA_ANGLE_UP)) { handleUnimportant // move order row up doAction(GUI_ACTION_ORDERS_MOVE_UP); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Move order up"); } - if (ImGui::Button(ICON_FA_ANGLE_DOWN)) { + if (ImGui::Button(ICON_FA_ANGLE_DOWN)) { handleUnimportant // move order row down doAction(GUI_ACTION_ORDERS_MOVE_DOWN); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Move order down"); } - if (ImGui::Button(ICON_FA_ANGLE_DOUBLE_DOWN)) { + if (ImGui::Button(ICON_FA_ANGLE_DOUBLE_DOWN)) { handleUnimportant // duplicate order row at end doAction(GUI_ACTION_ORDERS_DUPLICATE_END); } @@ -196,7 +204,7 @@ void FurnaceGUI::drawOrders() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Duplicate order at end of song (right-click to deep clone)"); } - if (ImGui::Button(changeAllOrders?ICON_FA_LINK"##ChangeAll":ICON_FA_CHAIN_BROKEN"##ChangeAll")) { + if (ImGui::Button(changeAllOrders?ICON_FA_LINK"##ChangeAll":ICON_FA_CHAIN_BROKEN"##ChangeAll")) { handleUnimportant // whether to change one or all orders in a row changeAllOrders=!changeAllOrders; } @@ -217,7 +225,7 @@ void FurnaceGUI::drawOrders() { } else { orderEditModeLabel=ICON_FA_MOUSE_POINTER "##OrderEditMode"; } - if (ImGui::Button(orderEditModeLabel)) { + if (ImGui::Button(orderEditModeLabel)) { handleUnimportant orderEditMode++; if (orderEditMode>3) orderEditMode=0; curNibble=false; diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 304db046..c75d5ba8 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -167,6 +167,11 @@ void FurnaceGUI::drawSettings() { settings.avoidRaisingPattern=avoidRaisingPatternB; } + bool insFocusesPatternB=settings.insFocusesPattern; + if (ImGui::Checkbox("Focus pattern editor when selecting instrument",&insFocusesPatternB)) { + settings.insFocusesPattern=insFocusesPatternB; + } + bool restartOnFlagChangeB=settings.restartOnFlagChange; if (ImGui::Checkbox("Restart song when changing system properties",&restartOnFlagChangeB)) { settings.restartOnFlagChange=restartOnFlagChangeB; @@ -870,6 +875,7 @@ void FurnaceGUI::syncSettings() { settings.viewPrevPattern=e->getConfInt("viewPrevPattern",1); settings.guiColorsBase=e->getConfInt("guiColorsBase",0); settings.avoidRaisingPattern=e->getConfInt("avoidRaisingPattern",0); + settings.insFocusesPattern=e->getConfInt("insFocusesPattern",1); // keybinds LOAD_KEYBIND(GUI_ACTION_OPEN,FURKMOD_CMD|SDLK_o); @@ -1067,6 +1073,7 @@ void FurnaceGUI::commitSettings() { e->setConf("viewPrevPattern",settings.viewPrevPattern); e->setConf("guiColorsBase",settings.guiColorsBase); e->setConf("avoidRaisingPattern",settings.avoidRaisingPattern); + e->setConf("insFocusesPattern",settings.insFocusesPattern); PUT_UI_COLOR(GUI_COLOR_BACKGROUND); PUT_UI_COLOR(GUI_COLOR_FRAME_BACKGROUND); From 227006a1241635daa138f78bcafababc4ae9164e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 5 Mar 2022 02:28:03 -0500 Subject: [PATCH 09/32] GUI: another usability improvement instrument list focused first if it is tabbed when starting program --- src/gui/gui.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 8c8831b2..ed9900e1 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -5135,12 +5135,12 @@ bool FurnaceGUI::loop() { drawEditControls(); drawSongInfo(); drawOrders(); - drawInsList(); - drawInsEdit(); - drawWaveList(); - drawWaveEdit(); drawSampleList(); drawSampleEdit(); + drawWaveList(); + drawWaveEdit(); + drawInsList(); + drawInsEdit(); drawMixer(); drawOsc(); drawVolMeter(); From bf74068c48e01aa4062b5dbbeb6056686ce07ac8 Mon Sep 17 00:00:00 2001 From: freq-mod <32672779+freq-mod@users.noreply.github.com> Date: Sat, 5 Mar 2022 09:37:56 +0100 Subject: [PATCH 10/32] Update README.md with newly added systems and way too frequently asked question about pcm sample playback --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index bed970b1..9c01efe9 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,10 @@ this is a work-in-progress chiptune tracker compatible with DefleMask modules (. - Philips SAA1099 - Amiga - TIA (Atari 2600/7800) + - Yamaha YM2413 (including VRC7) + - Atari Lynx + - QSound + - PC Speaker - multiple sound chips in a single song! - clean-room design (guesswork and ABX tests only, no decompilation involved) - bug/quirk implementation for increased playback accuracy @@ -29,6 +33,7 @@ this is a work-in-progress chiptune tracker compatible with DefleMask modules (. - accurate emulation cores whether possible (Nuked, MAME, SameBoy, Mednafen PCE, puNES, reSID, Stella, SAASound and ymfm) - additional features on top: - FM macros! + - negative octaves - arbitrary pitch samples - sample loop points - SSG envelopes in Neo Geo @@ -186,6 +191,10 @@ also provided are two effects: - `3xxx`: set fine duty. - `4xxx`: set fine cutoff. `xxx` range is 000-7ff. +> how do I use PCM on a PCM-capable system? + +Two possibilities: the recommended way is via creating the "Amiga/Sample" type instrument and assigning sample to it, or via old, Deflemask-compatible method, using `17xx` effect + > my song sounds very odd at a certain point file a bug report. use the Issues page. From fa911a1f1119681d2cb556a2b2bb2eedb4899e6e Mon Sep 17 00:00:00 2001 From: freq-mod <32672779+freq-mod@users.noreply.github.com> Date: Sat, 5 Mar 2022 09:45:42 +0100 Subject: [PATCH 11/32] not yet --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 9c01efe9..e1d97726 100644 --- a/README.md +++ b/README.md @@ -22,10 +22,6 @@ this is a work-in-progress chiptune tracker compatible with DefleMask modules (. - Philips SAA1099 - Amiga - TIA (Atari 2600/7800) - - Yamaha YM2413 (including VRC7) - - Atari Lynx - - QSound - - PC Speaker - multiple sound chips in a single song! - clean-room design (guesswork and ABX tests only, no decompilation involved) - bug/quirk implementation for increased playback accuracy From 9611a4fcc6d2e9c9ed289cbefa832a51ab1d3494 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 5 Mar 2022 04:24:40 -0500 Subject: [PATCH 12/32] OPLL: effect description oops --- src/engine/platform/opll.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index 5ed5b6e3..5159b3ea 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -36,10 +36,10 @@ const char* DivPlatformOPLL::getEffectName(unsigned char effect) { return "11xx: Set feedback (0 to 7)"; break; case 0x12: - return "12xx: Set level of operator 1 (0 highest, 7F lowest)"; + return "12xx: Set level of operator 1 (0 highest, 3F lowest)"; break; case 0x13: - return "13xx: Set level of operator 2 (0 highest, 7F lowest)"; + return "13xx: Set level of operator 2 (0 highest, F lowest)"; break; case 0x16: return "16xy: Set operator multiplier (x: operator from 1 to 2; y: multiplier)"; @@ -50,13 +50,13 @@ const char* DivPlatformOPLL::getEffectName(unsigned char effect) { } break; case 0x19: - return "19xx: Set attack of all operators (0 to 1F)"; + return "19xx: Set attack of all operators (0 to F)"; break; case 0x1a: - return "1Axx: Set attack of operator 1 (0 to 1F)"; + return "1Axx: Set attack of operator 1 (0 to F)"; break; case 0x1b: - return "1Bxx: Set attack of operator 2 (0 to 1F)"; + return "1Bxx: Set attack of operator 2 (0 to F)"; break; } return NULL; From 7745ebb8ec4ef5eee8a2abaae0790ff01e2843d7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 5 Mar 2022 04:49:11 -0500 Subject: [PATCH 13/32] clamp settings to sane values --- src/gui/settings.cpp | 48 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index c75d5ba8..3f0e532d 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -393,7 +393,7 @@ void FurnaceGUI::drawSettings() { } bool macroViewB=settings.macroView; - if (ImGui::Checkbox("Classic macro view (standard macros only)",¯oViewB)) { + if (ImGui::Checkbox("Classic macro view (standard macros only; deprecated!)",¯oViewB)) { settings.macroView=macroViewB; } @@ -834,6 +834,14 @@ void FurnaceGUI::drawSettings() { #define LOAD_KEYBIND(x,y) \ actionKeys[x]=e->getConfInt("keybind_" #x,y); +#define clampSetting(x,minV,maxV) \ + if (xmaxV) { \ + x=maxV; \ + } + void FurnaceGUI::syncSettings() { settings.mainFontSize=e->getConfInt("mainFontSize",18); settings.patFontSize=e->getConfInt("patFontSize",18); @@ -861,7 +869,6 @@ void FurnaceGUI::syncSettings() { settings.allowEditDocking=e->getConfInt("allowEditDocking",0); settings.chipNames=e->getConfInt("chipNames",0); settings.overflowHighlight=e->getConfInt("overflowHighlight",0); - if (settings.fmNames<0 || settings.fmNames>2) settings.fmNames=0; settings.partyTime=e->getConfInt("partyTime",0); settings.germanNotation=e->getConfInt("germanNotation",0); settings.stepOnDelete=e->getConfInt("stepOnDelete",0); @@ -877,6 +884,43 @@ void FurnaceGUI::syncSettings() { settings.avoidRaisingPattern=e->getConfInt("avoidRaisingPattern",0); settings.insFocusesPattern=e->getConfInt("insFocusesPattern",1); + clampSetting(settings.mainFontSize,2,96); + clampSetting(settings.patFontSize,2,96); + clampSetting(settings.iconSize,2,48); + clampSetting(settings.audioEngine,0,1); + clampSetting(settings.audioQuality,0,1); + clampSetting(settings.audioBufSize,32,4096); + clampSetting(settings.audioRate,8000,384000); + clampSetting(settings.arcadeCore,0,1); + clampSetting(settings.ym2612Core,0,1); + clampSetting(settings.saaCore,0,1); + clampSetting(settings.mainFont,0,6); + clampSetting(settings.patFont,0,6); + clampSetting(settings.patRowsBase,0,1); + clampSetting(settings.orderRowsBase,0,1); + clampSetting(settings.soloAction,0,2); + clampSetting(settings.pullDeleteBehavior,0,1); + clampSetting(settings.wrapHorizontal,0,2); + clampSetting(settings.wrapVertical,0,2); + clampSetting(settings.macroView,0,1); + clampSetting(settings.fmNames,0,2); + clampSetting(settings.allowEditDocking,0,1); + clampSetting(settings.chipNames,0,1); + clampSetting(settings.overflowHighlight,0,1); + clampSetting(settings.partyTime,0,1); + clampSetting(settings.germanNotation,0,1); + clampSetting(settings.stepOnDelete,0,1); + clampSetting(settings.scrollStep,0,1); + clampSetting(settings.sysSeparators,0,1); + clampSetting(settings.forceMono,0,1); + clampSetting(settings.controlLayout,0,3); + clampSetting(settings.statusDisplay,0,3); + clampSetting(settings.dpiScale,0.0f,4.0f); + clampSetting(settings.viewPrevPattern,0,1); + clampSetting(settings.guiColorsBase,0,1); + clampSetting(settings.avoidRaisingPattern,0,1); + clampSetting(settings.insFocusesPattern,0,1); + // keybinds LOAD_KEYBIND(GUI_ACTION_OPEN,FURKMOD_CMD|SDLK_o); LOAD_KEYBIND(GUI_ACTION_SAVE,FURKMOD_CMD|SDLK_s); From 6ce2a6743c12c832f97e7037540a7b1f4d03562c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 5 Mar 2022 04:59:05 -0500 Subject: [PATCH 14/32] lynx: fix 3xxx effect not working --- src/engine/playback.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 23cb54e1..f0f43dc1 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -224,14 +224,6 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe return false; } break; - case DIV_SYSTEM_LYNX: - if (effect>=0x30 && effect<0x40) { - int value = ((int)(effect&0x0f)<<8)|effectVal; - dispatchCmd(DivCommand(DIV_CMD_LYNX_LFSR_LOAD,ch,value)); - break; - } - return false; - break; case DIV_SYSTEM_OPLL_DRUMS: switch (effect) { case 0x18: // drum mode toggle @@ -533,6 +525,14 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char return false; } break; + case DIV_SYSTEM_LYNX: + if (effect>=0x30 && effect<0x40) { + int value = ((int)(effect&0x0f)<<8)|effectVal; + dispatchCmd(DivCommand(DIV_CMD_LYNX_LFSR_LOAD,ch,value)); + break; + } + return false; + break; default: return false; } From e3a27cb37cc4b71f35b17c9382fe64a4d044881c Mon Sep 17 00:00:00 2001 From: freq-mod <32672779+freq-mod@users.noreply.github.com> Date: Sat, 5 Mar 2022 19:06:46 +0100 Subject: [PATCH 15/32] Change OPLL friendly param names to be less cringy --- 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 4afa67a5..a58934be 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -59,7 +59,7 @@ const char* ssgEnvTypes[8]={ }; const char* fmParamNames[3][27]={ - {"Algorithm", "Feedback", "LFO > Freq", "LFO > Amp", "Attack", "Decay", "Decay 2", "Release", "Sustain", "Level", "EnvScale", "Multiplier", "Detune", "Detune 2", "SSG-EG", "AM", "AM Depth", "Vibrato Depth", "EnvAlternate", "EnvAlternate", "LevelScale/key", "Sustain", "Vibrato", "Waveform", "EnvScale/key", "OP2 HalfSine", "OP1 HalfSine"}, + {"Algorithm", "Feedback", "LFO > Freq", "LFO > Amp", "Attack", "Decay", "Decay 2", "Release", "Sustain", "Level", "EnvScale", "Multiplier", "Detune", "Detune 2", "SSG-EG", "AM", "AM Depth", "Vibrato Depth", "Sustained Voice", "Sustained Voice", "Level Scaling", "Sustain", "Vibrato", "Waveform", "Key Scale Rate", "OP2 Half Sine", "OP1 Half Sine"}, {"ALG", "FB", "FMS/PMS", "AMS", "AR", "DR", "SR", "RR", "SL", "TL", "KS", "MULT", "DT", "DT2", "SSG-EG", "AM", "AMD", "FMD", "EGT", "EGT", "KSL", "SUS", "VIB", "WS", "KSR", "DC", "DM"}, {"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"} }; From c684107b11b529b00fe9e475a5bf6d7c9d17cbf9 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 5 Mar 2022 13:21:03 -0500 Subject: [PATCH 16/32] not a voice --- 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 a58934be..6ff30896 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -59,7 +59,7 @@ const char* ssgEnvTypes[8]={ }; const char* fmParamNames[3][27]={ - {"Algorithm", "Feedback", "LFO > Freq", "LFO > Amp", "Attack", "Decay", "Decay 2", "Release", "Sustain", "Level", "EnvScale", "Multiplier", "Detune", "Detune 2", "SSG-EG", "AM", "AM Depth", "Vibrato Depth", "Sustained Voice", "Sustained Voice", "Level Scaling", "Sustain", "Vibrato", "Waveform", "Key Scale Rate", "OP2 Half Sine", "OP1 Half Sine"}, + {"Algorithm", "Feedback", "LFO > Freq", "LFO > Amp", "Attack", "Decay", "Decay 2", "Release", "Sustain", "Level", "EnvScale", "Multiplier", "Detune", "Detune 2", "SSG-EG", "AM", "AM Depth", "Vibrato Depth", "Sustained", "Sustained", "Level Scaling", "Sustain", "Vibrato", "Waveform", "Key Scale Rate", "OP2 Half Sine", "OP1 Half Sine"}, {"ALG", "FB", "FMS/PMS", "AMS", "AR", "DR", "SR", "RR", "SL", "TL", "KS", "MULT", "DT", "DT2", "SSG-EG", "AM", "AMD", "FMD", "EGT", "EGT", "KSL", "SUS", "VIB", "WS", "KSR", "DC", "DM"}, {"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"} }; From 6728edbb71d406e0385b689d58f63daff48c2831 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 5 Mar 2022 15:00:19 -0500 Subject: [PATCH 17/32] UNTESTED! DO NOT USE - PC speaker passthrough --- src/engine/platform/pcspkr.cpp | 35 ++++++++++++++++++++++++++++++++++ src/engine/platform/pcspkr.h | 5 +++-- src/gui/gui.cpp | 5 +---- 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/engine/platform/pcspkr.cpp b/src/engine/platform/pcspkr.cpp index 51353fed..eb201c1e 100644 --- a/src/engine/platform/pcspkr.cpp +++ b/src/engine/platform/pcspkr.cpp @@ -21,6 +21,12 @@ #include "../engine.h" #include +#ifdef __linux__ +#include +#include +#include +#endif + #define PCSPKR_DIVIDER 4 #define CHIP_DIVIDER 1 @@ -106,6 +112,19 @@ void DivPlatformPCSpeaker::acquire_piezo(short* bufL, short* bufR, size_t start, } } +void DivPlatformPCSpeaker::acquire_real(short* bufL, short* bufR, size_t start, size_t len) { +#ifdef __linux__ + if (lastOn!=on || lastFreq!=freq) { + lastOn=on; + lastFreq=freq; + ioctl(STDOUT_FILENO,KIOCSOUND,on?freq:0); + } +#endif + for (size_t i=start; i #define _USE_MATH_DEFINES #include "gui.h" #include "util.h" @@ -4894,12 +4893,10 @@ bool FurnaceGUI::loop() { e->setSysFlags(i,(flags&(~3))|2,restart); updateWindowTitle(); } - /* - if (ImGui::RadioButton("Use system beeper",(flags&3)==3)) { + if (ImGui::RadioButton("Use system beeper (Linux only!)",(flags&3)==3)) { e->setSysFlags(i,(flags&(~3))|3,restart); updateWindowTitle(); } - */ break; } case DIV_SYSTEM_QSOUND: { From 8bda9df487f5fa8463f49c761a62056b85507848 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 5 Mar 2022 16:06:41 -0500 Subject: [PATCH 18/32] prepare for OPL systems --- .gitmodules | 3 +++ extern/Nuked-OPL3 | 1 + 2 files changed, 4 insertions(+) create mode 160000 extern/Nuked-OPL3 diff --git a/.gitmodules b/.gitmodules index 435f43c7..d63fd70b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,3 +19,6 @@ [submodule "extern/adpcm"] path = extern/adpcm url = https://github.com/superctr/adpcm +[submodule "extern/Nuked-OPL3"] + path = extern/Nuked-OPL3 + url = https://github.com/nukeykt/Nuked-OPL3.git diff --git a/extern/Nuked-OPL3 b/extern/Nuked-OPL3 new file mode 160000 index 00000000..bb5c8d08 --- /dev/null +++ b/extern/Nuked-OPL3 @@ -0,0 +1 @@ +Subproject commit bb5c8d08a85779c42b75c79d7b84f365a1b93b66 From c34b8325c9e9dcc9caa0b41e8b70087fc7f414de Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 5 Mar 2022 18:18:08 -0500 Subject: [PATCH 19/32] more OPL preparation DOES NOT WORK YET - JUST A PLACEHOLDER --- CMakeLists.txt | 2 + src/engine/dispatchContainer.cpp | 4 + src/engine/platform/opl.cpp | 823 +++++++++++++++++++++++++++++++ src/engine/platform/opl.h | 120 +++++ 4 files changed, 949 insertions(+) create mode 100644 src/engine/platform/opl.cpp create mode 100644 src/engine/platform/opl.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8bcb8ce9..caf08457 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -226,6 +226,7 @@ extern/adpcm/ymz_codec.c extern/Nuked-OPN2/ym3438.c extern/opm/opm.c extern/Nuked-OPLL/opll.c +extern/Nuked-OPL3/opl3.c src/engine/platform/sound/sn76496.cpp src/engine/platform/sound/ay8910.cpp src/engine/platform/sound/saa1099.cpp @@ -298,6 +299,7 @@ src/engine/platform/ym2610b.cpp src/engine/platform/ym2610bext.cpp src/engine/platform/ay.cpp src/engine/platform/ay8930.cpp +src/engine/platform/opl.cpp src/engine/platform/tia.cpp src/engine/platform/saa.cpp src/engine/platform/amiga.cpp diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 0682bbd7..52cc7177 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -34,6 +34,7 @@ #include "platform/ym2610bext.h" #include "platform/ay.h" #include "platform/ay8930.h" +#include "platform/opl.h" #include "platform/tia.h" #include "platform/saa.h" #include "platform/amiga.h" @@ -210,6 +211,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do ((DivPlatformOPLL*)dispatch)->setVRC7(sys==DIV_SYSTEM_VRC7); ((DivPlatformOPLL*)dispatch)->setProperDrums(sys==DIV_SYSTEM_OPLL_DRUMS); break; + case DIV_SYSTEM_OPL3: + dispatch=new DivPlatformOPL; + break; case DIV_SYSTEM_SAA1099: { int saaCore=eng->getConfInt("saaCore",0); if (saaCore<0 || saaCore>2) saaCore=0; diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp new file mode 100644 index 00000000..bb195a47 --- /dev/null +++ b/src/engine/platform/opl.cpp @@ -0,0 +1,823 @@ +/** + * 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 "opl.h" +#include "../engine.h" +#include +#include + +#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} +#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } + +#define CHIP_FREQBASE 4720272 + +// N = invalid +#define N 255 + +/* +const unsigned char slotsOPL2[4][20]={ + {0, 1, 2, 6, 7, 8, 12, 13, 14}, // OP1 + {3, 4, 5, 9, 10, 11, 15, 16, 17}, // OP2 + {N, N, N, N, N, N, N, N, N}, + {N, N, N, N, N, N, N, N, N} +}; + +const unsigned char slotsOPL2Drums[4][20]={ + {0, 1, 2, 6, 7, 8, 12, 16, 14, 17, 13}, // OP1 + {3, 4, 5, 9, 10, 11, 15, N, N, N, N}, // OP2 + {N, N, N, N, N, N, N, N, N, N, N}, + {N, N, N, N, N, N, N, N, N, N, N} +}; + +const unsigned char slotsOPL3[4][20]={ + {0, 6, 1, 7, 2, 8, 18, 24, 19, 25, 20, 26, 30, 31, 32, 12, 13, 14}, // OP1 + {3, 9, 4, 10, 5, 11, 21, 27, 22, 28, 23, 29, 33, 34, 35, 15, 16, 17}, // OP2 + {6, N, 7, N, 8, N, 24, N, 25, N, 26, N, N, N, N, N, N, N}, // OP3 + {9, N, 10, N, 11, N, 27, N, 28, N, 29, N, N, N, N, N, N, N} // OP4 +}; + +const unsigned char slotsOPL3Drums[4][20]={ + {0, 6, 1, 7, 2, 8, 18, 24, 19, 25, 20, 26, 30, 31, 32, 12, 16, 14, 17, 13}, // OP1 + {3, 9, 4, 10, 5, 11, 21, 27, 22, 28, 23, 29, 33, 34, 35, N, N, N, N, N}, // OP2 + {6, N, 7, N, 8, N, 24, N, 25, N, 26, N, N, N, N, N, N, N, N, N}, // OP3 + {9, N, 10, N, 11, N, 27, N, 28, N, 29, N, N, N, N, N, N, N, N, N} // OP4 +}; +*/ + +#undef N + +const char* DivPlatformOPL::getEffectName(unsigned char effect) { + switch (effect) { + case 0x10: + return "10xy: Setup LFO (x: enable; y: speed)"; + break; + case 0x11: + return "11xx: Set feedback (0 to 7)"; + break; + case 0x12: + return "12xx: Set level of operator 1 (0 highest, 3F lowest)"; + break; + case 0x13: + return "13xx: Set level of operator 2 (0 highest, 3F lowest)"; + break; + case 0x14: + return "14xx: Set level of operator 3 (0 highest, 3F lowest; 4-op only)"; + break; + case 0x15: + return "15xx: Set level of operator 4 (0 highest, 3F lowest; 4-op only)"; + break; + case 0x16: + return "16xy: Set operator multiplier (x: operator from 1 to 4; y: multiplier)"; + break; + case 0x17: + return "17xx: Enable channel 6 DAC"; + break; + case 0x18: + return "18xx: Toggle extended channel 3 mode"; + break; + case 0x19: + return "19xx: Set attack of all operators (0 to F)"; + break; + case 0x1a: + return "1Axx: Set attack of operator 1 (0 to F)"; + break; + case 0x1b: + return "1Bxx: Set attack of operator 2 (0 to F)"; + break; + case 0x1c: + return "1Cxx: Set attack of operator 3 (0 to F; 4-op only)"; + break; + case 0x1d: + return "1Dxx: Set attack of operator 4 (0 to F; 4-op only)"; + break; + case 0x20: + return "20xy: Set PSG noise mode (x: preset freq/ch3 freq; y: thin pulse/noise)"; + break; + } + return NULL; +} + +void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_t len) { + static short o[2]; + static int os[2]; + + for (size_t h=start; h>8)<<1),w.val); + //printf("write: %x = %.2x\n",w.addr,w.val); + lastBusy=0; + regPool[w.addr&0x1ff]=w.val; + writes.pop(); + } else { + lastBusy++; + //printf("busycounter: %d\n",lastBusy); + OPL3_WriteReg(&fm,0x0+((w.addr>>8)<<1),w.addr); + w.addrOrVal=true; + } + } + + OPL3_Generate(&fm,o); os[0]+=o[0]; os[1]+=o[1]; + + if (os[0]<-32768) os[0]=-32768; + if (os[0]>32767) os[0]=32767; + + if (os[1]<-32768) os[1]=-32768; + if (os[1]>32767) os[1]=32767; + + bufL[h]=os[0]; + bufR[h]=os[1]; + } +} + +void DivPlatformOPL::acquire(short* bufL, short* bufR, size_t start, size_t len) { + //if (useYMFM) { + // acquire_ymfm(bufL,bufR,start,len); + //} else { + acquire_nuked(bufL,bufR,start,len); + //} +} + +void DivPlatformOPL::tick() { + /* + for (int i=0; i<20; i++) { + if (i==2 && extMode) continue; + chan[i].std.next(); + + if (chan[i].std.hadVol) { + chan[i].outVol=(chan[i].vol*MIN(127,chan[i].std.vol))/127; + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + if (isMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + } + } + + 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.hadAlg) { + chan[i].state.alg=chan[i].std.alg; + rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + if (!parent->song.algMacroBehavior) for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + if (isMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + } + } + if (chan[i].std.hadFb) { + chan[i].state.fb=chan[i].std.fb; + rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + } + if (chan[i].std.hadFms) { + chan[i].state.fms=chan[i].std.fms; + rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + } + if (chan[i].std.hadAms) { + chan[i].state.ams=chan[i].std.ams; + rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + } + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + DivMacroInt::IntOp& m=chan[i].std.op[j]; + if (m.hadAm) { + op.am=m.am; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + if (m.hadAr) { + op.ar=m.ar; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + if (m.hadDr) { + op.dr=m.dr; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + if (m.hadMult) { + op.mult=m.mult; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + if (m.hadRr) { + op.rr=m.rr; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + if (m.hadSl) { + op.sl=m.sl; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + if (m.hadTl) { + op.tl=127-m.tl; + if (isMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + } + if (m.hadRs) { + op.rs=m.rs; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + if (m.hadDt) { + op.dt=m.dt; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + if (m.hadD2r) { + op.d2r=m.d2r; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + if (m.hadSsg) { + op.ssgEnv=m.ssg; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + } + + if (chan[i].keyOn || chan[i].keyOff) { + immWrite(0x28,0x00|konOffs[i]); + chan[i].keyOff=false; + } + } + */ + + for (int i=0; i<512; i++) { + if (pendingWrites[i]!=oldWrites[i]) { + immWrite(i,pendingWrites[i]&0xff); + oldWrites[i]=pendingWrites[i]; + } + } + + /* + for (int i=0; i<20; i++) { + if (chan[i].freqChanged) { + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq)); + if (chan[i].freq>262143) chan[i].freq=262143; + int freqt=toFreq(chan[i].freq); + immWrite(chanOffs[i]+ADDR_FREQH,freqt>>8); + immWrite(chanOffs[i]+ADDR_FREQ,freqt&0xff); + if (chan[i].furnaceDac && dacMode) { + double off=1.0; + if (dacSample>=0 && dacSamplesong.sampleLen) { + DivSample* s=parent->getSample(dacSample); + if (s->centerRate<1) { + off=1.0; + } else { + off=8363.0/(double)s->centerRate; + } + } + dacRate=(1280000*1.25*off)/MAX(1,chan[i].baseFreq); + if (dacRate<1) dacRate=1; + if (dumpWrites) addWrite(0xffff0001,1280000/dacRate); + } + chan[i].freqChanged=false; + } + if (chan[i].keyOn) { + immWrite(0x28,0xf0|konOffs[i]); + chan[i].keyOn=false; + } + } + */ +} + +int DivPlatformOPL::octave(int freq) { + if (freq>=82432) { + return 128; + } else if (freq>=41216) { + return 64; + } else if (freq>=20608) { + return 32; + } else if (freq>=10304) { + return 16; + } else if (freq>=5152) { + return 8; + } else if (freq>=2576) { + return 4; + } else if (freq>=1288) { + return 2; + } else { + return 1; + } + return 1; +} + +int DivPlatformOPL::toFreq(int freq) { + if (freq>=82432) { + return 0x3800|((freq>>7)&0x7ff); + } else if (freq>=41216) { + return 0x3000|((freq>>6)&0x7ff); + } else if (freq>=20608) { + return 0x2800|((freq>>5)&0x7ff); + } else if (freq>=10304) { + return 0x2000|((freq>>4)&0x7ff); + } else if (freq>=5152) { + return 0x1800|((freq>>3)&0x7ff); + } else if (freq>=2576) { + return 0x1000|((freq>>2)&0x7ff); + } else if (freq>=1288) { + return 0x800|((freq>>1)&0x7ff); + } else { + return freq&0x7ff; + } +} + +void DivPlatformOPL::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + /* + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[ch]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[ch].state.op[j]; + if (isMuted[ch]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + if (isOutput[chan[ch].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[ch].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + } + rWrite(chanOffs[ch]+ADDR_LRAF,(isMuted[ch]?0:(chan[ch].pan<<6))|(chan[ch].state.fms&7)|((chan[ch].state.ams&3)<<4)); + */ +} + +int DivPlatformOPL::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].state=ins->fm; + } + + chan[c.chan].std.init(ins); + if (!chan[c.chan].std.willVol) { + chan[c.chan].outVol=chan[c.chan].vol; + } + + /* + for (int i=0; i<4; i++) { + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + if (isMuted[c.chan]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + if (isOutput[chan[c.chan].state.alg][i]) { + if (!chan[c.chan].active || chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } + } else { + if (chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + } + if (chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + } + if (chan[c.chan].insChanged) { + rWrite(chanOffs[c.chan]+ADDR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); + rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4)); + } + */ + chan[c.chan].insChanged=false; + + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); + chan[c.chan].note=c.value; + chan[c.chan].freqChanged=true; + } + chan[c.chan].keyOn=true; + chan[c.chan].active=true; + break; + } + case DIV_CMD_NOTE_OFF: + if (c.chan==5) { + dacSample=-1; + if (dumpWrites) addWrite(0xffff0002,0); + } + chan[c.chan].keyOff=true; + chan[c.chan].keyOn=false; + chan[c.chan].active=false; + break; + case DIV_CMD_NOTE_OFF_ENV: + if (c.chan==5) { + dacSample=-1; + if (dumpWrites) addWrite(0xffff0002,0); + } + chan[c.chan].keyOff=true; + chan[c.chan].keyOn=false; + chan[c.chan].active=false; + chan[c.chan].std.release(); + break; + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_VOLUME: { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.hasVol) { + chan[c.chan].outVol=c.value; + } + /* + for (int i=0; i<4; i++) { + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + if (isMuted[c.chan]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + if (isOutput[chan[c.chan].state.alg][i]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + } + */ + break; + } + case DIV_CMD_GET_VOLUME: { + return chan[c.chan].vol; + 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_PANNING: { + switch (c.value) { + case 0x01: + chan[c.chan].pan=1; + break; + case 0x10: + chan[c.chan].pan=2; + break; + default: + chan[c.chan].pan=3; + break; + } + //rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4)); + 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); + int newFreq; + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + newFreq=chan[c.chan].baseFreq+c.value*octave(chan[c.chan].baseFreq); + if (newFreq>=destFreq) { + newFreq=destFreq; + return2=true; + } + } else { + newFreq=chan[c.chan].baseFreq-c.value*octave(chan[c.chan].baseFreq); + if (newFreq<=destFreq) { + newFreq=destFreq; + return2=true; + } + } + if (!chan[c.chan].portaPause) { + if (octave(chan[c.chan].baseFreq)!=octave(newFreq)) { + chan[c.chan].portaPause=true; + break; + } + } + chan[c.chan].baseFreq=newFreq; + chan[c.chan].portaPause=false; + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_SAMPLE_MODE: { + dacMode=c.value; + rWrite(0x2b,c.value<<7); + break; + } + case DIV_CMD_SAMPLE_BANK: + sampleBank=c.value; + if (sampleBank>(parent->song.sample.size()/12)) { + sampleBank=parent->song.sample.size()/12; + } + break; + case DIV_CMD_LEGATO: { + chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); + chan[c.chan].note=c.value; + chan[c.chan].freqChanged=true; + break; + } + case DIV_CMD_FM_LFO: { + lfoValue=(c.value&7)|((c.value>>4)<<3); + rWrite(0x22,lfoValue); + break; + } + case DIV_CMD_FM_FB: { + chan[c.chan].state.fb=c.value&7; + //rWrite(chanOffs[c.chan]+ADDR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); + break; + } + case DIV_CMD_FM_MULT: { + /* + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.mult=c.value2&15; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + */ + break; + } + case DIV_CMD_FM_TL: { + /* + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.tl=c.value2; + if (isMuted[c.chan]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + if (isOutput[chan[c.chan].state.alg][c.value]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + */ + break; + } + case DIV_CMD_FM_AR: { + /* + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.ar=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + } else { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.ar=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + */ + + break; + } + case DIV_ALWAYS_SET_VOLUME: + return 0; + break; + case DIV_CMD_GET_VOLMAX: + return 127; + break; + case DIV_CMD_PRE_PORTA: + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_PRE_NOTE: + break; + default: + //printf("WARNING: unimplemented command %d\n",c.cmd); + break; + } + return 1; +} + +void DivPlatformOPL::forceIns() { + /* + for (int i=0; i<20; i++) { + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + if (isMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + if (chan[i].active) { + chan[i].keyOn=true; + chan[i].freqChanged=true; + } + } + if (dacMode) { + rWrite(0x2b,0x80); + } + immWrite(0x22,lfoValue); + */ +} + +void DivPlatformOPL::toggleRegisterDump(bool enable) { + DivDispatch::toggleRegisterDump(enable); +} + +void* DivPlatformOPL::getChanState(int ch) { + return &chan[ch]; +} + +unsigned char* DivPlatformOPL::getRegisterPool() { + return regPool; +} + +int DivPlatformOPL::getRegisterPoolSize() { + return 512; +} + +void DivPlatformOPL::reset() { + while (!writes.empty()) writes.pop(); + memset(regPool,0,512); + /* + if (useYMFM) { + fm_ymfm->reset(); + } + */ + OPL3_Reset(&fm,rate); + if (dumpWrites) { + addWrite(0xffffffff,0); + } + for (int i=0; i<20; i++) { + chan[i]=DivPlatformOPL::Channel(); + chan[i].vol=0x3f; + chan[i].outVol=0x3f; + } + + for (int i=0; i<512; i++) { + oldWrites[i]=-1; + pendingWrites[i]=-1; + } + + lastBusy=60; + dacMode=0; + dacPeriod=0; + dacPos=0; + dacRate=0; + dacSample=-1; + sampleBank=0; + lfoValue=8; + + extMode=false; + + // LFO + immWrite(0x22,lfoValue); + + delay=0; +} + +bool DivPlatformOPL::isStereo() { + return true; +} + +bool DivPlatformOPL::keyOffAffectsArp(int ch) { + return (ch>5); +} + +bool DivPlatformOPL::keyOffAffectsPorta(int ch) { + return (ch>5); +} + +void DivPlatformOPL::notifyInsChange(int ins) { + for (int i=0; i<20; i++) { + if (chan[i].ins==ins) { + chan[i].insChanged=true; + } + } +} + +void DivPlatformOPL::notifyInsDeletion(void* ins) { +} + +void DivPlatformOPL::poke(unsigned int addr, unsigned short val) { + immWrite(addr,val); +} + +void DivPlatformOPL::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) immWrite(i.addr,i.val); +} + +int DivPlatformOPL::getPortaFloor(int ch) { + return (ch>5)?12:0; +} + +void DivPlatformOPL::setYMFM(bool use) { + useYMFM=use; +} + +void DivPlatformOPL::setFlags(unsigned int flags) { + /* + if (flags==3) { + chipClock=COLOR_NTSC*12.0/7.0; + } else if (flags==2) { + chipClock=8000000.0; + } else if (flags==1) { + chipClock=COLOR_PAL*12.0/7.0; + } else { + chipClock=COLOR_NTSC*15.0/7.0; + } + ladder=flags&0x80000000; + OPN2_SetChipType(ladder?ym3438_mode_ym2612:0); + if (useYMFM) { + if (fm_ymfm!=NULL) delete fm_ymfm; + if (ladder) { + fm_ymfm=new ymfm::ym2612(iface); + } else { + fm_ymfm=new ymfm::ym3438(iface); + } + rate=chipClock/144; + } else { + rate=chipClock/36; + }*/ + + chipClock=COLOR_NTSC*4.0; + rate=chipClock/32; +} + +int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + dumpWrites=false; + ladder=false; + skipRegisterWrites=false; + for (int i=0; i<20; i++) { + isMuted[i]=false; + } + setFlags(flags); + + reset(); + return 10; +} + +void DivPlatformOPL::quit() { +} + +DivPlatformOPL::~DivPlatformOPL() { +} diff --git a/src/engine/platform/opl.h b/src/engine/platform/opl.h new file mode 100644 index 00000000..c4248192 --- /dev/null +++ b/src/engine/platform/opl.h @@ -0,0 +1,120 @@ +/** + * 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 _OPL_H +#define _OPL_H +#include "../dispatch.h" +#include "../macroInt.h" +#include +#include "../../../extern/Nuked-OPL3/opl3.h" + +class DivPlatformOPL: public DivDispatch { + protected: + struct Channel { + DivInstrumentFM state; + DivMacroInt std; + unsigned char freqH, freqL; + int freq, baseFreq, pitch, note; + unsigned char ins; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta; + int vol, outVol; + unsigned char pan; + Channel(): + freqH(0), + freqL(0), + freq(0), + baseFreq(0), + pitch(0), + note(0), + ins(-1), + active(false), + insChanged(true), + freqChanged(false), + keyOn(false), + keyOff(false), + portaPause(false), + furnaceDac(false), + inPorta(false), + vol(0), + pan(3) {} + }; + Channel chan[20]; + bool isMuted[20]; + struct QueuedWrite { + unsigned short addr; + unsigned char val; + bool addrOrVal; + QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} + }; + std::queue writes; + opl3_chip fm; + int delay; + unsigned char lastBusy; + + unsigned char regPool[512]; + + bool dacMode; + int dacPeriod; + int dacRate; + unsigned int dacPos; + int dacSample; + unsigned char sampleBank; + unsigned char lfoValue; + + bool extMode, useYMFM; + bool ladder; + + short oldWrites[512]; + short pendingWrites[512]; + + int octave(int freq); + int toFreq(int freq); + + friend void putDispatchChan(void*,int,int); + + void acquire_nuked(short* bufL, short* bufR, size_t start, size_t len); + //void acquire_ymfm(short* bufL, short* bufR, size_t start, size_t len); + + 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 isStereo(); + void setYMFM(bool use); + bool keyOffAffectsArp(int ch); + bool keyOffAffectsPorta(int ch); + void toggleRegisterDump(bool enable); + void setFlags(unsigned int flags); + void notifyInsChange(int ins); + void notifyInsDeletion(void* ins); + int getPortaFloor(int ch); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char* getEffectName(unsigned char effect); + int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + void quit(); + ~DivPlatformOPL(); +}; +#endif From 86a71cc6a25e288c097975dfe2e16b9cad07d963 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 6 Mar 2022 03:33:56 -0500 Subject: [PATCH 20/32] PC speaker: use evdev instead of KIOCSOUND still unsupported --- src/engine/platform/pcspkr.cpp | 40 ++++++++++++++++++++++++++++------ src/engine/platform/pcspkr.h | 4 +++- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/engine/platform/pcspkr.cpp b/src/engine/platform/pcspkr.cpp index eb201c1e..54048f90 100644 --- a/src/engine/platform/pcspkr.cpp +++ b/src/engine/platform/pcspkr.cpp @@ -19,10 +19,13 @@ #include "pcspkr.h" #include "../engine.h" +#include #include +#include #ifdef __linux__ #include +#include #include #include #endif @@ -112,14 +115,29 @@ void DivPlatformPCSpeaker::acquire_piezo(short* bufL, short* bufR, size_t start, } } -void DivPlatformPCSpeaker::acquire_real(short* bufL, short* bufR, size_t start, size_t len) { +void DivPlatformPCSpeaker::beepFreq(int freq) { #ifdef __linux__ + static struct input_event ie; + if (beepFD>=0) { + gettimeofday(&ie.time,NULL); + ie.type=EV_SND; + ie.code=SND_TONE; + ie.value=freq; + if (write(beepFD,&ie,sizeof(struct input_event))<0) { + perror("error while writing frequency!"); + } else { + //printf("writing freq: %d\n",freq); + } + } +#endif +} + +void DivPlatformPCSpeaker::acquire_real(short* bufL, short* bufR, size_t start, size_t len) { if (lastOn!=on || lastFreq!=freq) { lastOn=on; lastFreq=freq; - ioctl(STDOUT_FILENO,KIOCSOUND,on?freq:0); + beepFreq((on && !isMuted[0])?freq:0); } -#endif for (size_t i=start; i=0) close(beepFD); +#endif } DivPlatformPCSpeaker::~DivPlatformPCSpeaker() { diff --git a/src/engine/platform/pcspkr.h b/src/engine/platform/pcspkr.h index 6197098a..82e0f461 100644 --- a/src/engine/platform/pcspkr.h +++ b/src/engine/platform/pcspkr.h @@ -53,7 +53,7 @@ class DivPlatformPCSpeaker: public DivDispatch { Channel chan[1]; bool isMuted[1]; bool on, flip, lastOn; - int pos, speakerType; + int pos, speakerType, beepFD; float low, band; float low2, high2, band2; float low3, band3; @@ -62,6 +62,8 @@ class DivPlatformPCSpeaker: public DivDispatch { friend void putDispatchChan(void*,int,int); + void beepFreq(int freq); + void acquire_unfilt(short* bufL, short* bufR, size_t start, size_t len); void acquire_cone(short* bufL, short* bufR, size_t start, size_t len); void acquire_piezo(short* bufL, short* bufR, size_t start, size_t len); From e0eb0ad3f4e8e210f7e447f180374ad969558d82 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 6 Mar 2022 04:52:03 -0500 Subject: [PATCH 21/32] tilde you need to brush up on your header knowledg e. --- src/engine/platform/pcspkr.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/pcspkr.cpp b/src/engine/platform/pcspkr.cpp index 54048f90..5f57f31a 100644 --- a/src/engine/platform/pcspkr.cpp +++ b/src/engine/platform/pcspkr.cpp @@ -19,14 +19,14 @@ #include "pcspkr.h" #include "../engine.h" -#include #include -#include #ifdef __linux__ #include +#include #include #include +#include #include #endif From 2bfb84cd1eab23ff1acc473d639b929a5331de6f Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 6 Mar 2022 14:18:18 -0500 Subject: [PATCH 22/32] AY: add 1.10 and 2.10 rates --- src/engine/platform/ay.cpp | 6 ++++++ src/engine/platform/ay8930.cpp | 6 ++++++ src/engine/song.h | 2 ++ src/gui/gui.cpp | 8 ++++++++ 4 files changed, 22 insertions(+) diff --git a/src/engine/platform/ay.cpp b/src/engine/platform/ay.cpp index 2bcada1e..0777fca8 100644 --- a/src/engine/platform/ay.cpp +++ b/src/engine/platform/ay.cpp @@ -491,6 +491,12 @@ void DivPlatformAY8910::setFlags(unsigned int flags) { case 8: chipClock=COLOR_PAL*3.0/16.0; break; + case 9: + chipClock=COLOR_PAL/4.0; + break; + case 10: + chipClock=2097152; + break; default: chipClock=COLOR_NTSC/2.0; break; diff --git a/src/engine/platform/ay8930.cpp b/src/engine/platform/ay8930.cpp index 040800af..d87045a6 100644 --- a/src/engine/platform/ay8930.cpp +++ b/src/engine/platform/ay8930.cpp @@ -552,6 +552,12 @@ void DivPlatformAY8930::setFlags(unsigned int flags) { case 8: chipClock=COLOR_PAL*3.0/16.0; break; + case 9: + chipClock=COLOR_PAL/4.0; + break; + case 10: + chipClock=2097152; + break; default: chipClock=COLOR_NTSC/2.0; break; diff --git a/src/engine/song.h b/src/engine/song.h index d1e255d9..e4bd428b 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -194,6 +194,8 @@ struct DivSong { // - 6: 0.89MHz (Sunsoft 5B) // - 7: 1.67MHz // - 8: 0.83MHz (Sunsoft 5B on PAL) + // - 9: 1.10MHz (Gamate/VIC-20 PAL) + // - 10: 2.097152MHz (Game Boy) // - bit 4-5: chip type (ignored on AY8930) // - 0: AY-3-8910 or similar // - 1: YM2149 diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index fa890038..fb3e6fc3 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4818,6 +4818,14 @@ bool FurnaceGUI::loop() { e->setSysFlags(i,(flags&(~15))|8,restart); updateWindowTitle(); } + if (ImGui::RadioButton("1.10MHz (Gamate/VIC-20 PAL)",(flags&15)==9)) { + e->setSysFlags(i,(flags&(~15))|9,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("2^21Hz (Game Boy)",(flags&15)==10)) { + e->setSysFlags(i,(flags&(~15))|10,restart); + updateWindowTitle(); + } if (e->song.system[i]==DIV_SYSTEM_AY8910) { ImGui::Text("Chip type:"); if (ImGui::RadioButton("AY-3-8910",(flags&0x30)==0)) { From 3b8388d90cd3f1308e3d508e7b8c318ef149ab4e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 6 Mar 2022 14:39:20 -0500 Subject: [PATCH 23/32] YM2151/2610/2612/Game Boy: fix panning - UNTESTED --- src/engine/platform/arcade.cpp | 4 ++-- src/engine/platform/gb.cpp | 1 + src/engine/platform/genesis.cpp | 14 ++++---------- src/engine/platform/genesisext.cpp | 16 +++++----------- src/engine/platform/ym2610.cpp | 14 ++++---------- src/engine/platform/ym2610b.cpp | 14 ++++---------- src/engine/platform/ym2610bext.cpp | 16 +++++----------- src/engine/platform/ym2610ext.cpp | 16 +++++----------- 8 files changed, 30 insertions(+), 65 deletions(-) diff --git a/src/engine/platform/arcade.cpp b/src/engine/platform/arcade.cpp index 15a25517..1af99eb7 100644 --- a/src/engine/platform/arcade.cpp +++ b/src/engine/platform/arcade.cpp @@ -495,8 +495,8 @@ int DivPlatformArcade::dispatch(DivCommand c) { chan[c.chan].ins=c.value; break; case DIV_CMD_PANNING: { - chan[c.chan].chVolL=((c.value>>4)==1); - chan[c.chan].chVolR=((c.value&15)==1); + chan[c.chan].chVolL=((c.value>>4)>0); + chan[c.chan].chVolR=((c.value&15)>0); if (isMuted[c.chan]) { rWrite(chanOffs[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); } else { diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index f02ea197..a2b53ef7 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -340,6 +340,7 @@ int DivPlatformGB::dispatch(DivCommand c) { case DIV_CMD_PANNING: { lastPan&=~(0x11<0)|(((c.value>>4)>0)<<4) lastPan|=c.value<0)|(((c.value>>4)>0)<<1); } rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4)); break; diff --git a/src/engine/platform/genesisext.cpp b/src/engine/platform/genesisext.cpp index 364505db..02305a9a 100644 --- a/src/engine/platform/genesisext.cpp +++ b/src/engine/platform/genesisext.cpp @@ -106,16 +106,10 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) { opChan[ch].ins=c.value; break; case DIV_CMD_PANNING: { - switch (c.value) { - case 0x01: - opChan[ch].pan=1; - break; - case 0x10: - opChan[ch].pan=2; - break; - default: - opChan[ch].pan=3; - break; + if (c.value==0) { + opChan[ch].pan=3; + } else { + opChan[ch].pan=((c.value&15)>0)|(((c.value>>4)>0)<<1); } // TODO: ??? rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4)); @@ -378,4 +372,4 @@ void DivPlatformGenesisExt::quit() { } DivPlatformGenesisExt::~DivPlatformGenesisExt() { -} \ No newline at end of file +} diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index dd2447da..1bd460fa 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -892,16 +892,10 @@ int DivPlatformYM2610::dispatch(DivCommand c) { chan[c.chan].ins=c.value; break; case DIV_CMD_PANNING: { - switch (c.value) { - case 0x01: - chan[c.chan].pan=1; - break; - case 0x10: - chan[c.chan].pan=2; - break; - default: - chan[c.chan].pan=3; - break; + if (c.value==0) { + chan[c.chan].pan=3; + } else { + chan[c.chan].pan=((c.value&15)>0)|(((c.value>>4)>0)<<1); } if (c.chan>12) { immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index 27bb2afb..a6bcf9ac 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -955,16 +955,10 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { chan[c.chan].ins=c.value; break; case DIV_CMD_PANNING: { - switch (c.value) { - case 0x01: - chan[c.chan].pan=1; - break; - case 0x10: - chan[c.chan].pan=2; - break; - default: - chan[c.chan].pan=3; - break; + if (c.value==0) { + chan[c.chan].pan=3; + } else { + chan[c.chan].pan=((c.value&15)>0)|(((c.value>>4)>0)<<1); } if (c.chan>14) { immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); diff --git a/src/engine/platform/ym2610bext.cpp b/src/engine/platform/ym2610bext.cpp index f48fe19a..5b3c3872 100644 --- a/src/engine/platform/ym2610bext.cpp +++ b/src/engine/platform/ym2610bext.cpp @@ -97,16 +97,10 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { opChan[ch].ins=c.value; break; case DIV_CMD_PANNING: { - switch (c.value) { - case 0x01: - opChan[ch].pan=1; - break; - case 0x10: - opChan[ch].pan=2; - break; - default: - opChan[ch].pan=3; - break; + if (c.value==0) { + opChan[ch].pan=3; + } else { + opChan[ch].pan=((c.value&15)>0)|(((c.value>>4)>0)<<1); } DivInstrument* ins=parent->getIns(opChan[ch].ins); // TODO: ??? @@ -334,4 +328,4 @@ void DivPlatformYM2610BExt::quit() { } DivPlatformYM2610BExt::~DivPlatformYM2610BExt() { -} \ No newline at end of file +} diff --git a/src/engine/platform/ym2610ext.cpp b/src/engine/platform/ym2610ext.cpp index aff4bbf3..5e633eb2 100644 --- a/src/engine/platform/ym2610ext.cpp +++ b/src/engine/platform/ym2610ext.cpp @@ -97,16 +97,10 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { opChan[ch].ins=c.value; break; case DIV_CMD_PANNING: { - switch (c.value) { - case 0x01: - opChan[ch].pan=1; - break; - case 0x10: - opChan[ch].pan=2; - break; - default: - opChan[ch].pan=3; - break; + if (c.value==0) { + opChan[ch].pan=3; + } else { + opChan[ch].pan=((c.value&15)>0)|(((c.value>>4)>0)<<1); } DivInstrument* ins=parent->getIns(opChan[ch].ins); // TODO: ??? @@ -334,4 +328,4 @@ void DivPlatformYM2610Ext::quit() { } DivPlatformYM2610Ext::~DivPlatformYM2610Ext() { -} \ No newline at end of file +} From e10abe08589b3bb4663f759e88f44c00df33fcbf Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 6 Mar 2022 14:41:00 -0500 Subject: [PATCH 24/32] NO --- src/engine/platform/gb.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index a2b53ef7..942032e3 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -340,7 +340,7 @@ int DivPlatformGB::dispatch(DivCommand c) { case DIV_CMD_PANNING: { lastPan&=~(0x11<0)|(((c.value>>4)>0)<<4) + c.value=((c.value&15)>0)|(((c.value>>4)>0)<<4); lastPan|=c.value< Date: Sun, 6 Mar 2022 17:42:51 -0500 Subject: [PATCH 25/32] Amiga: temporarily disable bus limit simulation --- src/engine/platform/amiga.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index 3ee2fd39..c5face68 100644 --- a/src/engine/platform/amiga.cpp +++ b/src/engine/platform/amiga.cpp @@ -84,7 +84,7 @@ void DivPlatformAmiga::acquire(short* bufL, short* bufR, size_t start, size_t le } else { chan[i].sample=-1; } - if (chan[i].freq<124) { + /*if (chan[i].freq<124) { if (++chan[i].busClock>=512) { unsigned int rAmount=(124-chan[i].freq)*2; if (chan[i].audPos>=rAmount) { @@ -92,7 +92,7 @@ void DivPlatformAmiga::acquire(short* bufL, short* bufR, size_t start, size_t le } chan[i].busClock=0; } - } + }*/ chan[i].audSub+=MAX(114,chan[i].freq); } } From 2f9d1e8c0f3fac7f7b8971e4f1d3a7882ddef2b7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 6 Mar 2022 18:10:12 -0500 Subject: [PATCH 26/32] i'll finish this later --- src/engine/platform/opl.cpp | 26 ++++++++++++++++++++++++-- src/engine/platform/opl.h | 7 ++++++- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index bb195a47..ebf7dc92 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -30,7 +30,6 @@ // N = invalid #define N 255 -/* const unsigned char slotsOPL2[4][20]={ {0, 1, 2, 6, 7, 8, 12, 13, 14}, // OP1 {3, 4, 5, 9, 10, 11, 15, 16, 17}, // OP2 @@ -45,6 +44,10 @@ const unsigned char slotsOPL2Drums[4][20]={ {N, N, N, N, N, N, N, N, N, N, N} }; +const unsigned char chanMapOPL2[20]={ + 0, 1, 2, 3, 4, 5, 6, 7, 8, N, N, N, N, N, N, N, N, N, N, N +}; + const unsigned char slotsOPL3[4][20]={ {0, 6, 1, 7, 2, 8, 18, 24, 19, 25, 20, 26, 30, 31, 32, 12, 13, 14}, // OP1 {3, 9, 4, 10, 5, 11, 21, 27, 22, 28, 23, 29, 33, 34, 35, 15, 16, 17}, // OP2 @@ -58,7 +61,10 @@ const unsigned char slotsOPL3Drums[4][20]={ {6, N, 7, N, 8, N, 24, N, 25, N, 26, N, N, N, N, N, N, N, N, N}, // OP3 {9, N, 10, N, 11, N, 27, N, 28, N, 29, N, N, N, N, N, N, N, N, N} // OP4 }; -*/ + +const unsigned char chanMapOPL3[20]={ + 0, 3, 1, 4, 2, 5, 9, 12, 10, 13, 11, 14, 15, 16, 17, 6, 7, 8, N, N +}; #undef N @@ -773,6 +779,22 @@ void DivPlatformOPL::setYMFM(bool use) { useYMFM=use; } +void DivPlatformOPL::setOPLType(int type) { + switch (type) { + case 1: case 2: + slotsNonDrums=(const unsigned char**)slotsOPL2; + slotsDrums=(const unsigned char**)slotsOPL2Drums; + chanMap=chanMapOPL2; + break; + case 3: + slotsNonDrums=(const unsigned char**)slotsOPL3; + slotsDrums=(const unsigned char**)slotsOPL3Drums; + chanMap=chanMapOPL3; + break; + } + oplType=type; +} + void DivPlatformOPL::setFlags(unsigned int flags) { /* if (flags==3) { diff --git a/src/engine/platform/opl.h b/src/engine/platform/opl.h index c4248192..ab6226a4 100644 --- a/src/engine/platform/opl.h +++ b/src/engine/platform/opl.h @@ -64,7 +64,11 @@ class DivPlatformOPL: public DivDispatch { }; std::queue writes; opl3_chip fm; - int delay; + const unsigned char** slotsNonDrums; + const unsigned char** slotsDrums; + const unsigned char** slots; + const unsigned char* chanMap; + int delay, oplType; unsigned char lastBusy; unsigned char regPool[512]; @@ -103,6 +107,7 @@ class DivPlatformOPL: public DivDispatch { void muteChannel(int ch, bool mute); bool isStereo(); void setYMFM(bool use); + void setOPLType(int type); bool keyOffAffectsArp(int ch); bool keyOffAffectsPorta(int ch); void toggleRegisterDump(bool enable); From 72c1116a89a249a9a5541fc57ecb3993523d7d5b Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 6 Mar 2022 22:11:01 -0500 Subject: [PATCH 27/32] PC speaker: correct frequency in real mode damn it --- src/engine/platform/pcspkr.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/engine/platform/pcspkr.cpp b/src/engine/platform/pcspkr.cpp index 5f57f31a..f485bca8 100644 --- a/src/engine/platform/pcspkr.cpp +++ b/src/engine/platform/pcspkr.cpp @@ -122,7 +122,11 @@ void DivPlatformPCSpeaker::beepFreq(int freq) { gettimeofday(&ie.time,NULL); ie.type=EV_SND; ie.code=SND_TONE; - ie.value=freq; + if (freq>0) { + ie.value=chipClock/freq; + } else { + ie.value=0; + } if (write(beepFD,&ie,sizeof(struct input_event))<0) { perror("error while writing frequency!"); } else { From 9333b5bd515320795cae7ba496e1a51d9b98d22d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 6 Mar 2022 22:36:13 -0500 Subject: [PATCH 28/32] prepare for X1-010 --- papers/format.md | 1 + 1 file changed, 1 insertion(+) diff --git a/papers/format.md b/papers/format.md index 4df49ed0..ff068d83 100644 --- a/papers/format.md +++ b/papers/format.md @@ -174,6 +174,7 @@ size | description | - 0xaa: MSM6295 - 4 channels | - 0xab: MSM6258 - 1 channel | - 0xac: Commander X16 (VERA) - 17 channels + | - 0xb0: Seta/Allumer X1-010 - 16 channels | - 0xde: YM2610B extended - 19 channels | - 0xe0: QSound - 19 channels | - (compound!) means that the system is composed of two or more chips, From 177c409e19c902c99567e76b07d16677ca0f5b37 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 6 Mar 2022 22:36:32 -0500 Subject: [PATCH 29/32] add more notes when working with new systems --- src/engine/dispatch.h | 6 ++++++ src/engine/instrument.h | 3 +++ src/engine/playback.cpp | 1 + 3 files changed, 10 insertions(+) diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index 300b0033..e697cccb 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -29,6 +29,12 @@ #define addWrite(a,v) regWrites.push_back(DivRegWrite(a,v)); +// HOW TO ADD A NEW COMMAND: +// add it to this enum. then see playback.cpp. +// there is a const char* cmdName[] array, which contains the command +// names as strings for the commands (and other debug stuff). +// +// if you miss it, the program will crash or misbehave at some point. enum DivDispatchCmds { DIV_CMD_NOTE_ON=0, DIV_CMD_NOTE_OFF, diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 27ce2a4c..a0d37523 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -23,6 +23,9 @@ #include "dataErrors.h" #include "../ta-utils.h" +// NOTICE! +// before adding new instrument types to this struct, please ask me first. +// absolutely zero support granted to conflicting formats. enum DivInstrumentType { DIV_INS_STD=0, DIV_INS_FM=1, diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index f0f43dc1..7114a7c2 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -42,6 +42,7 @@ const char* notes[12]={ "C-", "C#", "D-", "D#", "E-", "F-", "F#", "G-", "G#", "A-", "A#", "B-" }; +// update this when adding new commands. const char* cmdName[DIV_CMD_MAX]={ "NOTE_ON", "NOTE_OFF", From 93d160da5e6e5288cdfa749ffef945211985a341 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 7 Mar 2022 00:24:50 -0500 Subject: [PATCH 30/32] OPLL: but it doesn't have LFOOOOOO --- src/engine/platform/opll.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index 5159b3ea..fd1d61d6 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -29,9 +29,6 @@ const char* DivPlatformOPLL::getEffectName(unsigned char effect) { switch (effect) { - case 0x10: - return "10xy: Setup LFO (x: enable; y: speed)"; - break; case 0x11: return "11xx: Set feedback (0 to 7)"; break; From 8f957baa3e07cbe593debfd9f532fbd96f3b027d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 7 Mar 2022 01:48:48 -0500 Subject: [PATCH 31/32] dispatch: add function to notify playback stopped for the PC Speaker real driver --- src/engine/dispatch.h | 5 +++++ src/engine/engine.cpp | 3 +++ src/engine/platform/abstract.cpp | 4 ++++ src/engine/platform/pcspkr.cpp | 6 ++++++ src/engine/platform/pcspkr.h | 1 + 5 files changed, 19 insertions(+) diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index e697cccb..3c39b03b 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -316,6 +316,11 @@ class DivDispatch { */ virtual void notifyInsDeletion(void* ins); + /** + * notify that playback stopped. + */ + virtual void notifyPlaybackStop(); + /** * force-retrigger instruments. */ diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index a8b023d7..75da62e0 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -828,6 +828,9 @@ void DivEngine::stop() { sPreview.sample=-1; sPreview.wave=-1; sPreview.pos=0; + for (int i=0; inotifyPlaybackStop(); + } isBusy.unlock(); } diff --git a/src/engine/platform/abstract.cpp b/src/engine/platform/abstract.cpp index f61bc5be..7b6115ae 100644 --- a/src/engine/platform/abstract.cpp +++ b/src/engine/platform/abstract.cpp @@ -97,6 +97,10 @@ void DivDispatch::notifyInsDeletion(void* ins) { } +void DivDispatch::notifyPlaybackStop() { + +} + void DivDispatch::forceIns() { } diff --git a/src/engine/platform/pcspkr.cpp b/src/engine/platform/pcspkr.cpp index f485bca8..d8c34681 100644 --- a/src/engine/platform/pcspkr.cpp +++ b/src/engine/platform/pcspkr.cpp @@ -344,6 +344,8 @@ void DivPlatformPCSpeaker::reset() { } #endif beepFreq(0); + } else { + beepFreq(0); } memset(regPool,0,2); @@ -365,6 +367,10 @@ void DivPlatformPCSpeaker::notifyInsDeletion(void* ins) { } } +void DivPlatformPCSpeaker::notifyPlaybackStop() { + beepFreq(0); +} + void DivPlatformPCSpeaker::poke(unsigned int addr, unsigned short val) { // ??? } diff --git a/src/engine/platform/pcspkr.h b/src/engine/platform/pcspkr.h index 82e0f461..17dedf24 100644 --- a/src/engine/platform/pcspkr.h +++ b/src/engine/platform/pcspkr.h @@ -82,6 +82,7 @@ class DivPlatformPCSpeaker: public DivDispatch { bool keyOffAffectsArp(int ch); void setFlags(unsigned int flags); void notifyInsDeletion(void* ins); + void notifyPlaybackStop(); void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); From 165a8a4361e8dad6f0387ecf3139ae78b3eb348a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 7 Mar 2022 01:54:28 -0500 Subject: [PATCH 32/32] PC speaker: register view one register :p --- src/engine/platform/pcspkr.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/engine/platform/pcspkr.cpp b/src/engine/platform/pcspkr.cpp index d8c34681..1ff8ae0c 100644 --- a/src/engine/platform/pcspkr.cpp +++ b/src/engine/platform/pcspkr.cpp @@ -310,6 +310,13 @@ void* DivPlatformPCSpeaker::getChanState(int ch) { } unsigned char* DivPlatformPCSpeaker::getRegisterPool() { + if (on) { + regPool[0]=freq; + regPool[1]=freq>>8; + } else { + regPool[0]=0; + regPool[1]=0; + } return regPool; }