From 20418bb4909c0984bace670a9825f246b7ccc0f9 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 23 Jul 2023 04:42:38 -0500 Subject: [PATCH] add TED to Furnace #855 #873 --- CMakeLists.txt | 10 + papers/format.md | 1 + papers/newIns.md | 2 + src/engine/dispatchContainer.cpp | 4 + src/engine/instrument.cpp | 2 + src/engine/instrument.h | 2 + src/engine/platform/sound/ted-sound.c | 231 +++++++++++++++++ src/engine/platform/sound/ted-sound.h | 81 ++++++ src/engine/platform/ted.cpp | 354 ++++++++++++++++++++++++++ src/engine/platform/ted.h | 78 ++++++ src/engine/song.h | 3 +- src/engine/sysDef.cpp | 10 + src/gui/dataList.cpp | 4 + src/gui/gui.h | 2 + src/gui/guiConst.cpp | 6 + src/gui/insEdit.cpp | 16 +- src/gui/presets.cpp | 11 + src/gui/sysConf.cpp | 34 +++ 18 files changed, 848 insertions(+), 3 deletions(-) create mode 100644 src/engine/platform/sound/ted-sound.c create mode 100644 src/engine/platform/sound/ted-sound.h create mode 100644 src/engine/platform/ted.cpp create mode 100644 src/engine/platform/ted.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9fd842208..1bf22a333 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -283,6 +283,13 @@ if (USE_SDL2) # If you link SDL statically, you also need to define HAVE_LIBC so it builds with the C runtime that your application uses. # This should probably go in a FAQ. set(SDL_LIBC ON CACHE BOOL "Tell SDL that we want it to use our C runtime (required for proper static linking)" FORCE) + + # https://github.com/tildearrow/furnace/issues/1237 + # enabling this will result in SDL finding the Direct3D headers, forcing _WIN32_WINNT to an undesirable value (which makes the Wine headers define GetTickCount64) + if (SUPPORT_XP) + set(SDL_RENDER_D3D OFF CACHE BOOL "Enable the Direct3D render driver" FORCE) + endif() + add_subdirectory(extern/SDL EXCLUDE_FROM_ALL) list(APPEND DEPENDENCIES_DEFINES HAVE_SDL2) list(APPEND DEPENDENCIES_INCLUDE_DIRS extern/SDL/include) @@ -498,6 +505,8 @@ src/engine/platform/sound/sm8521.c src/engine/platform/sound/d65modified.c +src/engine/platform/sound/ted-sound.c + src/engine/platform/oplAInterface.cpp src/engine/platform/ym2608Interface.cpp src/engine/platform/ym2610Interface.cpp @@ -589,6 +598,7 @@ src/engine/platform/ga20.cpp src/engine/platform/sm8521.cpp src/engine/platform/pv1000.cpp src/engine/platform/k053260.cpp +src/engine/platform/ted.cpp src/engine/platform/pcmdac.cpp src/engine/platform/dummy.cpp diff --git a/papers/format.md b/papers/format.md index 499efe480..46bb4c1fa 100644 --- a/papers/format.md +++ b/papers/format.md @@ -218,6 +218,7 @@ size | description | - 0xca: ZX Spectrum (beeper, QuadTone engine) - 5 channels | - 0xcb: Casio PV-1000 - 3 channels | - 0xcc: K053260 - 4 channels + | - 0xcd: TED - 2 channels | - 0xde: YM2610B extended - 19 channels | - 0xe0: QSound - 19 channels | - 0xfc: Pong - 1 channel diff --git a/papers/newIns.md b/papers/newIns.md index bd4daaec6..786a553c3 100644 --- a/papers/newIns.md +++ b/papers/newIns.md @@ -117,6 +117,8 @@ the following instrument types are available: - 47: Pokémon Mini/QuadTone - 48: SM8521 - 49: PV-1000 +- 50: K053260 +- 52: TED the following feature codes are recognized: diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 56c3625d7..2e8532603 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -79,6 +79,7 @@ #include "platform/sm8521.h" #include "platform/pv1000.h" #include "platform/k053260.h" +#include "platform/ted.h" #include "platform/pcmdac.h" #include "platform/dummy.h" #include "../ta-log.h" @@ -507,6 +508,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_K053260: dispatch=new DivPlatformK053260; break; + case DIV_SYSTEM_TED: + dispatch=new DivPlatformTED; + break; case DIV_SYSTEM_PCM_DAC: dispatch=new DivPlatformPCMDAC; break; diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index fe2885eee..9fa7477f2 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -961,6 +961,8 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song) { featureSM=true; featureSL=true; break; + case DIV_INS_TED: + break; case DIV_INS_MAX: break; diff --git a/src/engine/instrument.h b/src/engine/instrument.h index a6d2505f1..ef79a97a3 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -81,6 +81,8 @@ enum DivInstrumentType: unsigned short { DIV_INS_SM8521=48, DIV_INS_PV1000=49, DIV_INS_K053260=50, + // DIV_INS_YMF292=51, + DIV_INS_TED=52, DIV_INS_MAX, DIV_INS_NULL }; diff --git a/src/engine/platform/sound/ted-sound.c b/src/engine/platform/sound/ted-sound.c new file mode 100644 index 000000000..c03662997 --- /dev/null +++ b/src/engine/platform/sound/ted-sound.c @@ -0,0 +1,231 @@ +/* + * ted-sound.c + * + * Written by + * Andreas Boose + * Tibor Biczo + * Marco van den Heuvel + * + * This file is part of VICE, the Versatile Commodore Emulator. + * See README for copyright notice. + * + * 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., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA. + * + */ + +#include +#include +#include + +#include "ted-sound.h" + +/* ------------------------------------------------------------------------- */ + +/* FIXME: Find proper volume multiplier. */ +const int16_t volume_tab[16] = { + 0x0000, 0x0800, 0x1000, 0x1800, 0x2000, 0x2800, 0x3000, 0x3800, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff +}; + +int ted_sound_machine_calculate_samples(struct plus4_sound_s* snd, int16_t *pbuf, int nr, int scc) +{ + int i; + int j; + int16_t volume; + + if (snd->digital) { + for (i = 0; i < nr; i++) { + pbuf[i] = (snd->volume * (snd->voice0_output_enabled + snd->voice1_output_enabled)); + } + } else { + for (i = 0; i < nr; i++) { + snd->sample_position_remainder += snd->sample_length_remainder; + if (snd->sample_position_remainder >= snd->speed) { + snd->sample_position_remainder -= snd->speed; + snd->sample_position_integer++; + } + snd->sample_position_integer += snd->sample_length_integer; + if (snd->sample_position_integer >= 8) { + /* Advance state engine */ + uint32_t ticks = snd->sample_position_integer >> 3; + if (snd->voice0_accu <= ticks) { + uint32_t delay = ticks - snd->voice0_accu; + snd->voice0_sign ^= 1; + snd->voice0_accu = 1023 - snd->voice0_reload; + if (snd->voice0_accu == 0) { + snd->voice0_accu = 1024; + } + if (delay >= snd->voice0_accu) { + snd->voice0_sign = ((delay / snd->voice0_accu) + & 1) ? snd->voice0_sign ^ 1 + : snd->voice0_sign; + snd->voice0_accu = snd->voice0_accu - (delay % snd->voice0_accu); + } else { + snd->voice0_accu -= delay; + } + } else { + snd->voice0_accu -= ticks; + } + + if (snd->voice1_accu <= ticks) { + uint32_t delay = ticks - snd->voice1_accu; + snd->voice1_sign ^= 1; + snd->noise_shift_register + = (snd->noise_shift_register << 1) + + ( 1 ^ ((snd->noise_shift_register >> 7) & 1) ^ + ((snd->noise_shift_register >> 5) & 1) ^ + ((snd->noise_shift_register >> 4) & 1) ^ + ((snd->noise_shift_register >> 1) & 1)); + snd->voice1_accu = 1023 - snd->voice1_reload; + if (snd->voice1_accu == 0) { + snd->voice1_accu = 1024; + } + if (delay >= snd->voice1_accu) { + snd->voice1_sign = ((delay / snd->voice1_accu) + & 1) ? snd->voice1_sign ^ 1 + : snd->voice1_sign; + for (j = 0; j < (int)(delay / snd->voice1_accu); + j++) { + snd->noise_shift_register + = (snd->noise_shift_register << 1) + + ( 1 ^ ((snd->noise_shift_register >> 7) & 1) ^ + ((snd->noise_shift_register >> 5) & 1) ^ + ((snd->noise_shift_register >> 4) & 1) ^ + ((snd->noise_shift_register >> 1) & 1)); + } + snd->voice1_accu = snd->voice1_accu - (delay % snd->voice1_accu); + } else { + snd->voice1_accu -= delay; + } + } else { + snd->voice1_accu -= ticks; + } + } + snd->sample_position_integer = snd->sample_position_integer & 7; + + volume = 0; + + if (snd->voice0_output_enabled && snd->voice0_sign) { + volume += snd->volume; + } + if (snd->voice1_output_enabled && !snd->noise && snd->voice1_sign) { + volume += snd->volume; + } + if (snd->voice1_output_enabled && snd->noise && (!(snd->noise_shift_register & 1))) { + volume += snd->volume; + } + + pbuf[i] = volume; + } + } + return nr; +} + +int ted_sound_machine_init(struct plus4_sound_s* snd, int speed, int cycles_per_sec) +{ + uint8_t val; + memset(snd,0,sizeof(struct plus4_sound_s)); + + snd->speed = speed; + snd->sample_length_integer = cycles_per_sec / speed; + snd->sample_length_remainder = cycles_per_sec % speed; + snd->sample_position_integer = 0; + snd->sample_position_remainder = 0; + snd->noise_shift_register = 0; + + snd->voice0_reload = (snd->plus4_sound_data[0] | (snd->plus4_sound_data[4] << 8)); + snd->voice1_reload = (snd->plus4_sound_data[1] | (snd->plus4_sound_data[2] << 8)); + val = snd->plus4_sound_data[3]; + snd->volume = volume_tab[val & 0x0f]; + snd->voice0_output_enabled = (val & 0x10) ? 1 : 0; + snd->voice1_output_enabled = (val & 0x60) ? 1 : 0; + snd->noise = ((val & 0x60) == 0x40) ? 1 : 0; + snd->digital = val & 0x80; + if (snd->digital) { + snd->voice0_sign = 1; + snd->voice0_accu = 0; + snd->voice1_sign = 1; + snd->voice1_accu = 0; + snd->noise_shift_register = 0; + } + + return 1; +} + +void ted_sound_machine_store(struct plus4_sound_s* snd, uint16_t addr, uint8_t val) +{ + switch (addr) { + case 0x0e: + snd->plus4_sound_data[0] = val; + snd->voice0_reload = (snd->plus4_sound_data[0] | (snd->plus4_sound_data[4] << 8)); + break; + case 0x0f: + snd->plus4_sound_data[1] = val; + snd->voice1_reload = (snd->plus4_sound_data[1] | (snd->plus4_sound_data[2] << 8)); + break; + case 0x10: + snd->plus4_sound_data[2] = val & 3; + snd->voice1_reload = (snd->plus4_sound_data[1] | (snd->plus4_sound_data[2] << 8)); + break; + case 0x11: + snd->volume = volume_tab[val & 0x0f]; + snd->voice0_output_enabled = (val & 0x10) ? 1 : 0; + snd->voice1_output_enabled = (val & 0x60) ? 1 : 0; + snd->noise = ((val & 0x60) == 0x40) ? 1 : 0; + snd->digital = val & 0x80; + if (snd->digital) { + snd->voice0_sign = 1; + snd->voice0_accu = 0; + snd->voice1_sign = 1; + snd->voice1_accu = 0; + snd->noise_shift_register = 0; + } + snd->plus4_sound_data[3] = val; + break; + case 0x12: + snd->plus4_sound_data[4] = val & 3; + snd->voice0_reload = (snd->plus4_sound_data[0] | (snd->plus4_sound_data[4] << 8)); + break; + } +} + +uint8_t ted_sound_machine_read(struct plus4_sound_s* snd, uint16_t addr) +{ + switch (addr) { + case 0x0e: + return snd->plus4_sound_data[0]; + case 0x0f: + return snd->plus4_sound_data[1]; + case 0x10: + return snd->plus4_sound_data[2] | 0xc0; + case 0x11: + return snd->plus4_sound_data[3]; + case 0x12: + return snd->plus4_sound_data[4]; + } + + return 0; +} + +void ted_sound_reset(struct plus4_sound_s* snd) +{ + uint16_t i; + + snd->noise_shift_register = 0; + for (i = 0x0e; i <= 0x12; i++) { + ted_sound_machine_store(snd,i,0); + } +} diff --git a/src/engine/platform/sound/ted-sound.h b/src/engine/platform/sound/ted-sound.h new file mode 100644 index 000000000..d2c6de1c4 --- /dev/null +++ b/src/engine/platform/sound/ted-sound.h @@ -0,0 +1,81 @@ +/* + * ted-sound.h + * + * Written by + * Andreas Boose + * Marco van den Heuvel + * + * This file is part of VICE, the Versatile Commodore Emulator. + * See README for copyright notice. + * + * 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., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA. + * + */ + +#ifndef VICE_TEDSOUND_H +#define VICE_TEDSOUND_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +struct plus4_sound_s { + /* Voice 0 collect number of cycles elapsed */ + uint32_t voice0_accu; + /* Voice 0 toggle sign and reload accu if accu reached 0 */ + uint32_t voice0_reload; + /* Voice 0 sign of the square wave */ + int16_t voice0_sign; + uint8_t voice0_output_enabled; + + /* Voice 1 collect number of cycles elapsed */ + uint32_t voice1_accu; + /* Voice 1 toggle sign and reload accu if accu reached 0 */ + uint32_t voice1_reload; + /* Voice 1 sign of the square wave */ + int16_t voice1_sign; + uint8_t voice1_output_enabled; + + /* Volume multiplier */ + int16_t volume; + /* 8 cycles units per sample */ + uint32_t speed; + uint32_t sample_position_integer; + uint32_t sample_position_remainder; + uint32_t sample_length_integer; + uint32_t sample_length_remainder; + /* Digital output? */ + uint8_t digital; + /* Noise generator active? */ + uint8_t noise; + uint8_t noise_shift_register; + + /* Registers */ + uint8_t plus4_sound_data[5]; +}; + +int ted_sound_machine_init(struct plus4_sound_s* snd, int speed, int cycles_per_sec); +int ted_sound_machine_calculate_samples(struct plus4_sound_s* snd, int16_t *pbuf, int nr, int sound_chip_channels); +void ted_sound_machine_store(struct plus4_sound_s* snd, uint16_t addr, uint8_t val); +uint8_t ted_sound_machine_read(struct plus4_sound_s* snd, uint16_t addr); +void ted_sound_reset(struct plus4_sound_s* snd); + +#ifdef __cplusplus +}; +#endif +#endif diff --git a/src/engine/platform/ted.cpp b/src/engine/platform/ted.cpp new file mode 100644 index 000000000..be5a379e7 --- /dev/null +++ b/src/engine/platform/ted.cpp @@ -0,0 +1,354 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2023 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 "ted.h" +#include "../engine.h" +#include + +//#define rWrite(a,v) pendingWrites[a]=v; +#define rWrite(a,v) if (!skipRegisterWrites) {writes.push(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);} } + +#define CHIP_DIVIDER 8 + +const char* regCheatSheetTED[]={ + "Freq0L", "0e", + "Freq1L", "0f", + "Freq1H", "10", + "Control", "11", + "Freq0H", "12", + NULL +}; + +const char** DivPlatformTED::getRegisterSheet() { + return regCheatSheetTED; +} + +void DivPlatformTED::acquire(short** buf, size_t len) { + for (size_t h=0; hdata[oscBuf[0]->needle++]=(ted.voice0_output_enabled && ted.voice0_sign)?(ted.volume<<1):0; + oscBuf[1]->data[oscBuf[1]->needle++]=(ted.voice1_output_enabled && ((ted.noise && (!(ted.noise_shift_register&1))) || (!ted.noise && ted.voice1_sign)))?(ted.volume<<1):0; + } +} + +void DivPlatformTED::tick(bool sysTick) { + bool resetPhase=false; + + for (int _i=0; _i<2; _i++) { + int i=chanOrder[_i]; + + chan[i].std.next(); + if (chan[i].std.vol.had) { + chan[i].outVol=VOL_SCALE_LINEAR(chan[i].vol,MIN(8,chan[i].std.vol.val),8); + updateCtrl=true; + vol=chan[i].outVol; + } + if (chan[i].std.duty.had) { + chan[i].noise=chan[i].std.duty.val&2; + chan[i].square=chan[i].std.duty.val&1; + chan[i].freqChanged=true; + updateCtrl=true; + } + if (NEW_ARP_STRAT) { + chan[i].handleArp(); + } else if (chan[i].std.arp.had) { + if (!chan[i].inPorta) { + chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val)); + } + chan[i].freqChanged=true; + } + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-32768,32767); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + 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,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER)-1; + if (i==1 && chan[i].noise && !chan[i].square) chan[i].freq>>=4; + if (chan[i].freq<0) chan[i].freq=0; + if (chan[i].freq>1023) chan[i].freq=1023; + + if (i==1) { + rWrite(0x0f,(1022-chan[i].freq)&0xff); + rWrite(0x10,((1022-chan[i].freq)>>8)&0xff); + } else { + rWrite(0x0e,(1022-chan[i].freq)&0xff); + rWrite(0x12,((1022-chan[i].freq)>>8)&0xff); + } + + if (chan[i].keyOn) { + updateCtrl=true; + } + if (chan[i].keyOff) { + updateCtrl=true; + } + if (chan[i].keyOn) chan[i].keyOn=false; + if (chan[i].keyOff) chan[i].keyOff=false; + chan[i].freqChanged=false; + } + if (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1) { + resetPhase=true; + updateCtrl=true; + } + } + + if (resetPhase) { + rWrite(0x11,0x80); + } + + if (updateCtrl) { + updateCtrl=false; + rWrite(0x11,(vol&15)|((chan[0].active && chan[0].square && !isMuted[0])?0x10:0)|((chan[1].active && chan[1].square && !isMuted[1])?0x20:0)|((chan[1].active && chan[1].noise && !isMuted[1])?0x40:0)); + } +} + +int DivPlatformTED::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_TED); + 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].macroInit(ins); + if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + chan[c.chan].outVol=chan[c.chan].vol; + } + chan[c.chan].insChanged=false; + if (keyPriority) { + if (chanOrder[0]==c.chan) { + chanOrder[0]=chanOrder[1]; + chanOrder[1]=c.chan; + } + } + break; + } + case DIV_CMD_NOTE_OFF: + chan[c.chan].active=false; + chan[c.chan].keyOff=true; + chan[c.chan].macroInit(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; + chan[c.chan].insChanged=true; + } + break; + case DIV_CMD_VOLUME: + if (chan[c.chan].vol!=c.value) { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.vol.has) { + chan[c.chan].outVol=c.value; + } + vol=chan[c.chan].outVol; + updateCtrl=true; + } + break; + case DIV_CMD_GET_VOLUME: + if (chan[c.chan].std.vol.has) { + return chan[c.chan].vol; + } + return chan[c.chan].outVol; + break; + case DIV_CMD_PITCH: + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_NOTE_PORTA: { + int destFreq=NOTE_PERIODIC(c.value2); + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value; + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value; + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_STD_NOISE_MODE: + chan[c.chan].noise=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_LEGATO: + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((HACKY_LEGATO_MESS)?(chan[c.chan].std.arp.val):(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].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_TED)); + } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_GET_VOLMAX: + return 8; + break; + case DIV_CMD_MACRO_OFF: + chan[c.chan].std.mask(c.value,true); + break; + case DIV_CMD_MACRO_ON: + chan[c.chan].std.mask(c.value,false); + break; + case DIV_ALWAYS_SET_VOLUME: + return 1; + break; + default: + break; + } + return 1; +} + +void DivPlatformTED::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + updateCtrl=true; +} + +void DivPlatformTED::forceIns() { + for (int i=0; i<2; i++) { + chan[i].freqChanged=true; + } + updateCtrl=true; +} + +void* DivPlatformTED::getChanState(int ch) { + return &chan[ch]; +} + +DivMacroInt* DivPlatformTED::getChanMacroInt(int ch) { + return &chan[ch].std; +} + +DivDispatchOscBuffer* DivPlatformTED::getOscBuffer(int ch) { + return oscBuf[ch]; +} + +unsigned char* DivPlatformTED::getRegisterPool() { + return regPool; +} + +int DivPlatformTED::getRegisterPoolSize() { + return 5; +} + +void DivPlatformTED::reset() { + writes.clear(); + memset(regPool,0,8); + for (int i=0; i<2; i++) { + chan[i]=DivPlatformTED::Channel(); + chan[i].std.setEngine(parent); + } + if (dumpWrites) { + addWrite(0xffffffff,0); + } + ted_sound_machine_init(&ted,1,8); + updateCtrl=true; + vol=15; + + chanOrder[0]=0; + chanOrder[1]=1; +} + +int DivPlatformTED::getOutputCount() { + return 1; +} + +bool DivPlatformTED::keyOffAffectsArp(int ch) { + return true; +} + +void DivPlatformTED::notifyInsDeletion(void* ins) { + for (int i=0; i<2; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformTED::setFlags(const DivConfig& flags) { + if (flags.getInt("clockSel",0)) { + chipClock=COLOR_PAL*2.0/5.0; + } else { + chipClock=COLOR_NTSC/2.0; + } + CHECK_CUSTOM_CLOCK; + rate=chipClock/8; + for (int i=0; i<2; i++) { + oscBuf[i]->rate=rate; + } + keyPriority=flags.getBool("keyPriority",true); +} + +void DivPlatformTED::poke(unsigned int addr, unsigned short val) { + rWrite(addr,val); +} + +void DivPlatformTED::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); +} + +int DivPlatformTED::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + for (int i=0; i<2; i++) { + isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; + } + setFlags(flags); + reset(); + return 2; +} + +void DivPlatformTED::quit() { + for (int i=0; i<2; i++) { + delete oscBuf[i]; + } +} + +DivPlatformTED::~DivPlatformTED() { +} diff --git a/src/engine/platform/ted.h b/src/engine/platform/ted.h new file mode 100644 index 000000000..25c41e3d8 --- /dev/null +++ b/src/engine/platform/ted.h @@ -0,0 +1,78 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2023 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 _TED_H +#define _TED_H + +#include "../dispatch.h" +#include "../fixedQueue.h" +#include "sound/ted-sound.h" + +class DivPlatformTED: public DivDispatch { + struct Channel: public SharedChannel { + bool noise, square; + Channel(): + SharedChannel(8), + noise(false), + square(true) {} + }; + Channel chan[2]; + DivDispatchOscBuffer* oscBuf[2]; + bool isMuted[2]; + struct QueuedWrite { + unsigned char addr; + unsigned char val; + QueuedWrite(): addr(0), val(0) {} + QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {} + }; + FixedQueue writes; + + struct plus4_sound_s ted; + unsigned char vol; + bool updateCtrl, keyPriority; + + unsigned char chanOrder[2]; + unsigned char regPool[8]; + friend void putDispatchChip(void*,int); + friend void putDispatchChan(void*,int,int); + public: + void acquire(short** buf, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); + DivDispatchOscBuffer* getOscBuffer(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void forceIns(); + void tick(bool sysTick=true); + void muteChannel(int ch, bool mute); + int getOutputCount(); + bool keyOffAffectsArp(int ch); + void setFlags(const DivConfig& flags); + void notifyInsDeletion(void* ins); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); + void quit(); + ~DivPlatformTED(); +}; + +#endif diff --git a/src/engine/song.h b/src/engine/song.h index 4be25c436..7fc8dc2a8 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -128,7 +128,8 @@ enum DivSystem { DIV_SYSTEM_YM2608_CSM, DIV_SYSTEM_SM8521, DIV_SYSTEM_PV1000, - DIV_SYSTEM_K053260 + DIV_SYSTEM_K053260, + DIV_SYSTEM_TED }; enum DivEffectType: unsigned short { diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index a911e2df4..e242f0a39 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1863,6 +1863,16 @@ void DivEngine::registerSystems() { } ); + sysDefs[DIV_SYSTEM_TED]=new DivSysDef( + "MOS Technology TED", NULL, 0xcd, 0, 2, false, true, 0, false, 0, + "two square waves (one may be turned into noise). used in the Commodore Plus/4, 16 and 116.", + {"Channel 1", "Channel 2"}, + {"CH1", "CH2"}, + {DIV_CH_PULSE, DIV_CH_PULSE}, + {DIV_INS_TED, DIV_INS_TED}, + {} + ); + sysDefs[DIV_SYSTEM_DUMMY]=new DivSysDef( "Dummy System", NULL, 0xfd, 0, 8, false, true, 0, false, 0, "this is a system designed for testing purposes.", diff --git a/src/gui/dataList.cpp b/src/gui/dataList.cpp index 971030ee7..289584492 100644 --- a/src/gui/dataList.cpp +++ b/src/gui/dataList.cpp @@ -282,6 +282,10 @@ void FurnaceGUI::insListItem(int i, int dir, int asset) { ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_K053260]); name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); break; + case DIV_INS_TED: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_TED]); + name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); + break; default: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_UNKNOWN]); name=fmt::sprintf(ICON_FA_QUESTION "##_INS%d",i); diff --git a/src/gui/gui.h b/src/gui/gui.h index 0979ae068..9976e18d5 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -240,6 +240,8 @@ enum FurnaceGUIColors { GUI_COLOR_INSTR_SM8521, GUI_COLOR_INSTR_PV1000, GUI_COLOR_INSTR_K053260, + GUI_COLOR_INSTR_SCSP, + GUI_COLOR_INSTR_TED, GUI_COLOR_INSTR_UNKNOWN, GUI_COLOR_CHANNEL_BG, diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index f7dcd40b8..001b6a30b 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -132,6 +132,8 @@ const char* insTypes[DIV_INS_MAX+1]={ "SM8521", "PV-1000", "K053260", + "SCSP", + "TED", NULL }; @@ -853,6 +855,8 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={ D(GUI_COLOR_INSTR_SM8521,"",ImVec4(0.5f,0.55f,0.6f,1.0f)), D(GUI_COLOR_INSTR_PV1000,"",ImVec4(0.4f,0.6f,0.7f,1.0f)), D(GUI_COLOR_INSTR_K053260,"",ImVec4(1.0f,0.8f,0.1f,1.0f)), + D(GUI_COLOR_INSTR_SCSP,"",ImVec4(0.5f,0.5f,0.5f,1.0f)), + D(GUI_COLOR_INSTR_TED,"",ImVec4(0.7f,0.6f,1.0f,1.0f)), D(GUI_COLOR_INSTR_UNKNOWN,"",ImVec4(0.3f,0.3f,0.3f,1.0f)), D(GUI_COLOR_CHANNEL_BG,"",ImVec4(0.4f,0.6f,0.8f,1.0f)), @@ -1037,6 +1041,7 @@ const int availableSystems[]={ DIV_SYSTEM_SM8521, DIV_SYSTEM_PV1000, DIV_SYSTEM_K053260, + DIV_SYSTEM_TED, DIV_SYSTEM_PCM_DAC, DIV_SYSTEM_PONG, 0 // don't remove this last one! @@ -1087,6 +1092,7 @@ const int chipsSquare[]={ DIV_SYSTEM_MSM5232, DIV_SYSTEM_T6W28, DIV_SYSTEM_PV1000, + DIV_SYSTEM_TED, 0 // don't remove this last one! }; diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 8d0bf0d0f..e3997bf46 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -267,6 +267,10 @@ const char* msm5232ControlBits[7]={ "16'", "8'", "4'", "2'", "sustain", NULL }; +const char* tedControlBits[3]={ + "square", "noise", NULL +}; + const char* x1_010EnvBits[8]={ "enable", "oneshot", "split L/R", "HinvR", "VinvR", "HinvL", "VinvL", NULL }; @@ -5389,7 +5393,7 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_MSM6258) { volMax=0; } - if (ins->type==DIV_INS_MSM6295) { + if (ins->type==DIV_INS_MSM6295 || ins->type==DIV_INS_TED) { volMax=8; } if (ins->type==DIV_INS_ADPCMA) { @@ -5475,6 +5479,10 @@ void FurnaceGUI::drawInsEdit() { dutyLabel="On/Off"; dutyMax=1; } + if (ins->type==DIV_INS_TED) { + dutyLabel="Square/Noise"; + dutyMax=2; + } if (ins->type==DIV_INS_SWAN) { dutyLabel="Noise"; dutyMax=ins->amiga.useSample?0:8; @@ -5558,6 +5566,7 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_GA20) waveMax=0; if (ins->type==DIV_INS_K053260) waveMax=0; if (ins->type==DIV_INS_POKEMINI) waveMax=0; + if (ins->type==DIV_INS_TED) waveMax=0; if (ins->type==DIV_INS_SU || ins->type==DIV_INS_POKEY) waveMax=7; if (ins->type==DIV_INS_PET) { waveMax=8; @@ -5703,6 +5712,8 @@ void FurnaceGUI::drawInsEdit() { macroList.push_back(FurnaceGUIMacroDesc(dutyLabel,&ins->std.dutyMacro,0,dutyMax,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,mikeyFeedbackBits)); } else if (ins->type==DIV_INS_POKEY) { macroList.push_back(FurnaceGUIMacroDesc(dutyLabel,&ins->std.dutyMacro,0,dutyMax,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,pokeyCtlBits)); + } else if (ins->type==DIV_INS_TED) { + macroList.push_back(FurnaceGUIMacroDesc(dutyLabel,&ins->std.dutyMacro,0,dutyMax,80,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,tedControlBits)); } else if (ins->type==DIV_INS_MSM5232) { macroList.push_back(FurnaceGUIMacroDesc(dutyLabel,&ins->std.dutyMacro,0,dutyMax,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,msm5232ControlBits)); } else if (ins->type==DIV_INS_ES5506) { @@ -5770,7 +5781,8 @@ void FurnaceGUI::drawInsEdit() { (ins->type==DIV_INS_X1_010 && ins->amiga.useSample) || ins->type==DIV_INS_K007232 || ins->type==DIV_INS_GA20 || - ins->type==DIV_INS_K053260) { + ins->type==DIV_INS_K053260 || + ins->type==DIV_INS_TED) { macroList.push_back(FurnaceGUIMacroDesc("Phase Reset",&ins->std.phaseResetMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); } if (ex1Max>0) { diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index fcadc4750..05f5af87a 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -278,6 +278,12 @@ void FurnaceGUI::initSystemPresets() { ENTRY( "Commodore VIC-20", { CH(DIV_SYSTEM_VIC20, 1.0f, 0, "clockSel=1") + }, + "tickRate=50" + ); + ENTRY( + "Commodore Plus/4", { + CH(DIV_SYSTEM_TED, 1.0f, 0, "") } ); ENTRY( @@ -2433,6 +2439,11 @@ void FurnaceGUI::initSystemPresets() { CH(DIV_SYSTEM_PV1000, 1.0f, 0, "") } ); + ENTRY( + "MOS Technology TED", { + CH(DIV_SYSTEM_TED, 1.0f, 0, "clockSel=1") + } + ); CATEGORY_END; CATEGORY_BEGIN("Sample","chips/systems which use PCM or ADPCM samples for sound synthesis."); diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 32e7c35c9..a87f2fa34 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -1902,6 +1902,40 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo } break; } + case DIV_SYSTEM_TED: { + int clockSel=flags.getInt("clockSel",0); + bool keyPriority=flags.getBool("keyPriority",true); + + ImGui::Text("Clock rate:"); + + if (ImGui::RadioButton("NTSC (1.79MHz)",clockSel==0)) { + clockSel=0; + altered=true; + } + if (ImGui::RadioButton("PAL (1.77MHz)",clockSel==1)) { + clockSel=1; + altered=true; + } + + ImGui::Text("Global parameter priority:"); + + if (ImGui::RadioButton("Left to right",!keyPriority)) { + keyPriority=false; + altered=true; + } + if (ImGui::RadioButton("Last used channel",keyPriority)) { + keyPriority=true; + altered=true; + } + + if (altered) { + e->lockSave([&]() { + flags.set("clockSel",clockSel); + flags.set("keyPriority",keyPriority); + }); + } + break; + } case DIV_SYSTEM_SWAN: case DIV_SYSTEM_BUBSYS_WSG: case DIV_SYSTEM_PET: