From 9d279ceadb0a9a88b75245c757e9c44819a20fc8 Mon Sep 17 00:00:00 2001 From: AArt1256 Date: Thu, 8 Aug 2024 22:58:01 +0300 Subject: [PATCH] added basic watara supervision support --- CMakeLists.txt | 3 + src/engine/dispatchContainer.cpp | 4 + src/engine/instrument.h | 1 + src/engine/platform/sound/supervision.c | 313 ++++++++++++++++++ src/engine/platform/sound/supervision.h | 36 ++ src/engine/platform/supervision.cpp | 423 ++++++++++++++++++++++++ src/engine/platform/supervision.h | 80 +++++ src/engine/song.h | 1 + src/engine/sysDef.cpp | 13 + src/gui/debug.cpp | 1 + src/gui/guiConst.cpp | 3 + src/gui/insEdit.cpp | 7 + src/gui/presets.cpp | 5 + src/gui/sysConf.cpp | 23 ++ src/gui/sysPartNumber.cpp | 2 + src/icon/furIcons.h | 1 + 16 files changed, 916 insertions(+) create mode 100644 src/engine/platform/sound/supervision.c create mode 100644 src/engine/platform/sound/supervision.h create mode 100644 src/engine/platform/supervision.cpp create mode 100644 src/engine/platform/supervision.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b7bc35433..61b025a90 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -644,6 +644,8 @@ src/engine/platform/sound/ga20/iremga20.cpp src/engine/platform/sound/sm8521.c +src/engine/platform/sound/supervision.c + src/engine/platform/sound/d65modified.c src/engine/platform/sound/ted-sound.c @@ -771,6 +773,7 @@ src/engine/platform/snes.cpp src/engine/platform/k007232.cpp src/engine/platform/ga20.cpp src/engine/platform/sm8521.cpp +src/engine/platform/supervision.cpp src/engine/platform/pv1000.cpp src/engine/platform/k053260.cpp src/engine/platform/ted.cpp diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 65ca2383d..a34838eca 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -76,6 +76,7 @@ #include "platform/vb.h" #include "platform/k007232.h" #include "platform/ga20.h" +#include "platform/supervision.h" #include "platform/sm8521.h" #include "platform/pv1000.h" #include "platform/k053260.h" @@ -684,6 +685,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_GA20: dispatch=new DivPlatformGA20; break; + case DIV_SYSTEM_SUPERVISION: + dispatch=new DivPlatformSupervision; + break; case DIV_SYSTEM_SM8521: dispatch=new DivPlatformSM8521; if (isRender) { diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 49b2991d4..75a96fd2e 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -94,6 +94,7 @@ enum DivInstrumentType: unsigned short { DIV_INS_GBA_MINMOD=61, DIV_INS_BIFURCATOR=62, DIV_INS_SID2=63, // coincidence! + DIV_INS_SUPERVISION=64, DIV_INS_MAX, DIV_INS_NULL }; diff --git a/src/engine/platform/sound/supervision.c b/src/engine/platform/sound/supervision.c new file mode 100644 index 000000000..cc0a4e610 --- /dev/null +++ b/src/engine/platform/sound/supervision.c @@ -0,0 +1,313 @@ +#include "supervision.h" + +#include + +uint32 UNSCALED_CLOCK = 4000000; +#define SV_SAMPLE_RATE ((UNSCALED_CLOCK)/64) +#define SV_DEC_TICK ((SV_SAMPLE_RATE)/60) + +uint32 decrement_tick = 0; + +void supervision_sound_set_clock(uint32 clock) { + UNSCALED_CLOCK = clock; +} + +void supervision_memorymap_registers_write(uint32 Addr, uint8 Value) +{ + switch (Addr & 0x1fff) { + case 0x10: case 0x11: case 0x12: case 0x13: + case 0x14: case 0x15: case 0x16: case 0x17: + supervision_sound_wave_write(((Addr & 0x4) >> 2), Addr & 3, Value); + break; + case 0x18: + case 0x19: + case 0x1a: + case 0x1b: + case 0x1c: + supervision_sound_dma_write(Addr & 0x07, Value); + break; + case 0x28: + case 0x29: + case 0x2a: + supervision_sound_noise_write(Addr & 0x07, Value); + break; + } +} + +uint32 ch_mask = 15; +uint32 flags = 0b00000001; + +void supervision_set_mute_mask(uint32 mask) { + ch_mask = mask; +} + +void supervision_sound_set_flags(uint32 flags_set) +{ + flags = flags_set; +} + +typedef struct { + uint8 reg[4]; + int on; + uint8 waveform, volume; + uint16 pos, size; + uint16 count; +} SVISION_CHANNEL; +SVISION_CHANNEL m_channel[2]; +// For clear sound (no grating), sync with m_channel +SVISION_CHANNEL ch[2]; + +typedef struct { + uint8 reg[3]; + int on, right, left, play; + uint8 type; // 6 - 7-Bit, 14 - 15-Bit + uint16 state; + uint8 value, volume; + uint16 count; + real pos, step; +} SVISION_NOISE; +SVISION_NOISE m_noise; + +typedef struct { + uint8 reg[5]; + int on, right, left; + uint32 ca14to16; + uint16 start; + uint16 size; + real pos, step; +} SVISION_DMA; +SVISION_DMA m_dma; + +void supervision_sound_reset(void) +{ + memset(m_channel, 0, sizeof(m_channel)); + memset(&m_noise, 0, sizeof(m_noise) ); + memset(&m_dma, 0, sizeof(m_dma) ); + + memset(ch, 0, sizeof(ch) ); + decrement_tick = 0; + ch_mask = 15; +} + +void supervision_sound_stream_update(uint8 *stream, uint32 len) +{ + size_t i, j; + SVISION_CHANNEL *channel; + uint8 s = 0; + uint8 *left = stream + 0; + uint8 *right = stream + 1; + uint8 *chout = stream + 2; + + for (i = 0; i < len >> 1; i++, left += 2, right += 2) { + *left = *right = 0; + for (channel = m_channel, j = 0; j < 2; j++, channel++) { + chout[j] = 0; + if (ch[j].size != 0) { + if (ch[j].on || channel->count != 0) { + BOOL on = FALSE; + switch (ch[j].waveform) { + case 0: // 12.5% + on = ch[j].pos < (28 * ch[j].size) >> 5; + break; + case 1: // 25% + on = ch[j].pos < (24 * ch[j].size) >> 5; + break; + case 2: // 50% + on = ch[j].pos < ch[j].size / 2; + break; + case 3: // 75% + on = ch[j].pos < ch[j].size / 4; + // MESS/MAME: <= (9 * ch[j].size) >> 5; + break; + } + s = on ? (ch[j].volume)<<2 : 0; + s = ((ch_mask>>(3-j))&1)?s:0; + if (flags&1) { + if (j == 0) + *right += s; + else + *left += s; + } else { + *left += s; + *right += s; + } + chout[j] = s; + } + ch[j].pos++; + if (ch[j].pos >= ch[j].size) { + ch[j].pos = 0; + // Transition from off to on + if (channel->on) { + memcpy(&ch[j], channel, sizeof(ch[j])); + channel->on = FALSE; + } + } + } + } + + if (m_noise.on && (m_noise.play || m_noise.count != 0)) { + s = (m_noise.value * m_noise.volume) << 2; + s = ch_mask&1?s:0; + chout[3] = 0; + if (m_noise.left) { + *left += s; + chout[3] = s; + } + if (m_noise.right) { + *right += s; + chout[3] = s; + } + m_noise.pos += m_noise.step; + while (m_noise.pos >= 1.0) { // if/while difference - Pacific Battle + // LFSR: x^2 + x + 1 + uint16 feedback; + m_noise.value = m_noise.state & 1; + feedback = ((m_noise.state >> 1) ^ m_noise.state) & 0x0001; + feedback <<= m_noise.type; + m_noise.state = (m_noise.state >> 1) | feedback; + m_noise.pos -= 1.0; + } + } + +/* + if (m_dma.on) { + uint8 sample; + uint16 addr = m_dma.start + (uint16)m_dma.pos / 2; + if (addr >= 0x8000 && addr < 0xc000) { + sample = memorymap_getRomPointer()[(addr & 0x3fff) | m_dma.ca14to16]; + } + else { + sample = Rd6502(addr); + } + if (((uint16)m_dma.pos) & 1) + s = (sample & 0xf); + else + s = (sample & 0xf0) >> 4; + if (m_dma.left) + *left += s; + if (m_dma.right) + *right += s; + m_dma.pos += m_dma.step; + if (m_dma.pos >= m_dma.size) { + m_dma.on = FALSE; + } + } +*/ + if (decrement_tick > SV_DEC_TICK) { + decrement_tick = 0; + supervision_sound_decrement(); + } + decrement_tick++; + } +} + +void supervision_sound_decrement(void) +{ + if (m_channel[0].count > 0) + m_channel[0].count--; + if (m_channel[1].count > 0) + m_channel[1].count--; + if (m_noise.count > 0) + m_noise.count--; +} + +void supervision_sound_wave_write(int which, int offset, uint8 data) +{ + SVISION_CHANNEL *channel = &m_channel[which]; + + channel->reg[offset] = data; + switch (offset) { + case 0: + case 1: { + uint16 size; + size = channel->reg[0] | ((channel->reg[1] & 7) << 8); + // if size == 0 then channel->size == 0 + if (size) + channel->size = (uint16)(((real)SV_SAMPLE_RATE) * ((real)((size + 1) << 5)) / ((real)UNSCALED_CLOCK)); + else + channel->size = 0; + channel->pos = 0; + // Popo Team + if (channel->count != 0 || ch[which].size == 0 || channel->size == 0) { + ch[which].size = channel->size; + if (channel->count == 0) + ch[which].pos = 0; + } + } + break; + case 2: + channel->on = data & 0x40; + channel->waveform = (data & 0x30) >> 4; + channel->volume = data & 0x0f; + if (!channel->on || ch[which].size == 0 || channel->size == 0) { + uint16 pos = ch[which].pos; + memcpy(&ch[which], channel, sizeof(ch[which])); + if (channel->count != 0) // Journey to the West + ch[which].pos = pos; + } + break; + case 3: + channel->count = data + 1; + ch[which].size = channel->size; // Sonny Xpress! + break; + } +} + +void supervision_sound_dma_write(int offset, uint8 data) +{ + m_dma.reg[offset] = data; + switch (offset) { + case 0: + case 1: + m_dma.start = (m_dma.reg[0] | (m_dma.reg[1] << 8)); + break; + case 2: + m_dma.size = (data ? data : 0x100) * 32; // Number of 4-bit samples + break; + case 3: + // Test games: Classic Casino, SSSnake + m_dma.step = ((real)UNSCALED_CLOCK) / ((real)SV_SAMPLE_RATE * (256 << (data & 3))); + // MESS/MAME. Wrong + //m_dma.step = UNSCALED_CLOCK / (256.0 * SV_SAMPLE_RATE * (1 + (data & 3))); + m_dma.right = data & 4; + m_dma.left = data & 8; + m_dma.ca14to16 = ((data & 0x70) >> 4) << 14; + break; + case 4: + m_dma.on = data & 0x80; + if (m_dma.on) { + m_dma.pos = 0.0; + } + break; + } +} + +void supervision_sound_noise_write(int offset, uint8 data) +{ + m_noise.reg[offset] = data; + switch (offset) { + case 0: { + uint32 divisor = 8 << (data >> 4); + if (divisor) + m_noise.step = ((real)UNSCALED_CLOCK) / ((real)SV_SAMPLE_RATE * divisor); + else + m_noise.step = 0; + + m_noise.step = ((real)UNSCALED_CLOCK) / ((real)SV_SAMPLE_RATE * divisor); + m_noise.volume = data & 0xf; + } + break; + case 1: + m_noise.count = data + 1; + break; + case 2: + m_noise.type = (data & 1) ? 14 : 6; + m_noise.play = data & 2; + m_noise.right = data & 4; + m_noise.left = data & 8; + m_noise.on = data & 0x10; /* honey bee start */ + m_noise.state = 1; + break; + } + m_noise.pos = 0.0; +} diff --git a/src/engine/platform/sound/supervision.h b/src/engine/platform/sound/supervision.h new file mode 100644 index 000000000..d18b7317b --- /dev/null +++ b/src/engine/platform/sound/supervision.h @@ -0,0 +1,36 @@ +#ifndef __SUPERVISION_SOUND_H__ +#define __SUPERVISION_SOUND_H__ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include + +typedef uint8_t uint8; +typedef uint16_t uint16; +typedef uint32_t uint32; +typedef float real; +typedef uint8_t BOOL; +enum {FALSE, TRUE}; + +void supervision_sound_reset(void); +void supervision_sound_set_clock(uint32 clock); +void supervision_sound_stream_update(uint8 *stream, uint32 len); +void supervision_sound_decrement(void); +void supervision_sound_wave_write(int which, int offset, uint8 data); +void supervision_sound_dma_write(int offset, uint8 data); +void supervision_sound_noise_write(int offset, uint8 data); +void supervision_sound_noise_write(int offset, uint8 data); +void supervision_memorymap_registers_write(uint32 Addr, uint8 Value); +// 12SN +void supervision_set_mute_mask(uint32 mask); +void supervision_sound_set_flags(uint32 flags_set); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif + diff --git a/src/engine/platform/supervision.cpp b/src/engine/platform/supervision.cpp new file mode 100644 index 000000000..474758929 --- /dev/null +++ b/src/engine/platform/supervision.cpp @@ -0,0 +1,423 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2024 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 "supervision.h" +#include "../engine.h" +#include "furIcons.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 32 + +const char* regCheatSheetSupervision[]={ + "Freq0L", "10", + "Freq0H", "11", + "Control0", "12", + "Len0", "13", + + "Freq1L", "14", + "Freq1H", "15", + "Control1", "16", + "Len1", "17", + + "DMAstartL", "18", + "DMAstartH", "19", + "DMAlen", "1a", + "DMAstep", "1b", + "DMAon", "1c", + + "NoisDiv", "28", + "NoisLen", "29", + "NoisCtrl", "2a", + + NULL +}; + +unsigned char freqLo[2]; +unsigned char freqHi[2]; +unsigned char noiseReg[3]; +unsigned char kon[3]; + +const char** DivPlatformSupervision::getRegisterSheet() { + return regCheatSheetSupervision; +} + +unsigned char duty_swap = 0; +unsigned char otherFlags = 0; + +void DivPlatformSupervision::acquire(short** buf, size_t len) { + for (size_t h=0; hdata[oscBuf[i]->needle++]=CLAMP((((int)s[2+i])-128)*256,-32768,32767); + } + + tempL[0]=(tempL[0]>>1)+(tempL[0]>>2); + tempR[0]=(tempR[0]>>1)+(tempR[0]>>2); + + if (tempL[0]<-32768) tempL[0]=-32768; + if (tempL[0]>32767) tempL[0]=32767; + if (tempR[0]<-32768) tempR[0]=-32768; + if (tempR[0]>32767) tempR[0]=32767; + + //printf("tempL: %d tempR: %d\n",tempL,tempR); + buf[0][h]=tempL[0]; + buf[1][h]=tempR[0]; + } +} + +void DivPlatformSupervision::tick(bool sysTick) { + for (int i=0; i<4; i++) { + + chan[i].std.next(); + if (chan[i].std.vol.had) { + chan[i].outVol=VOL_SCALE_LINEAR(chan[i].vol&15,MIN(15,chan[i].std.vol.val),15); + } + if (NEW_ARP_STRAT) { + chan[i].handleArp(); + } else if (chan[i].std.arp.had) { + if (!chan[i].inPorta) { + int f = parent->calcArp(chan[i].note,chan[i].std.arp.val); + if (i == 3) { + chan[i].baseFreq=f; + //if (chan[i].baseFreq>255) chan[i].baseFreq=255; + if (chan[i].baseFreq<0) chan[i].baseFreq=0; + } else { + chan[i].baseFreq=NOTE_PERIODIC(f); + } + } + chan[i].freqChanged=true; + } + if (chan[i].std.duty.had) { + chan[i].duty=chan[i].std.duty.val; + } + 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) { + //DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_PCE); + if (i < 2) { + 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<<1,CHIP_DIVIDER); + if (chan[i].freq<1) chan[i].freq=1; + if (chan[i].freq>2047) chan[i].freq=2047; + if (freqLo[i] != (chan[i].freq&0xff)) + rWrite(0x10|(i<<2),chan[i].freq&0xff); + freqLo[i] = chan[i].freq&0xff; + if (freqHi[i] != ((chan[i].freq>>8)&0xff)) + rWrite(0x11|(i<<2),(chan[i].freq>>8)&0xff); + freqHi[i] = (chan[i].freq>>8)&0xff; + } else if (i == 3) { + int ntPos=chan[i].baseFreq; + if (NEW_ARP_STRAT) { + if (chan[i].fixedArp) { + ntPos=chan[i].baseNoteOverride; + } else { + ntPos+=chan[i].arpOff; + } + } + ntPos+=chan[i].pitch2; + chan[i].freq=ntPos&15; + unsigned char r = (chan[i].freq<<4)|(chan[i].outVol&0xf); + if (noiseReg[0] != r) rWrite(0x28,r); + noiseReg[0] = r; + } + if (chan[i].keyOn) kon[i] = 1; + if (chan[i].keyOff) kon[i] = 0; + if (chan[i].keyOn) chan[i].keyOn=false; + if (chan[i].keyOff) chan[i].keyOff=false; + chan[i].freqChanged=false; + } + + if (kon[i]) { + if (i < 2) { + rWrite(0x12|(i<<2),(chan[i].outVol&0xf)|((chan[i].duty&3)<<4)); + rWrite(0x13|(i<<2),0xc8); + } else if (i == 3) { + rWrite(0x29,0xc8); + unsigned char r = ((chan[i].duty&1)^duty_swap)|(0x02|0x10)|(4|8); + if (noiseReg[2] != r) rWrite(0x2A,r); + noiseReg[2] = r; + } + } else { + if (i < 2) { + rWrite(0x12|(i<<2),0); + rWrite(0x13|(i<<2),0xc8); + } else if (i == 3) { + rWrite(0x29,0); + unsigned char r = 0; + if (noiseReg[2] != r) rWrite(0x2A,r); + noiseReg[2] = r; + } + } + + } +} + +int DivPlatformSupervision::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_PCE); + + chan[c.chan].sampleNote=DIV_NOTE_NULL; + chan[c.chan].sampleNoteDelta=0; + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=c.chan==3?c.value: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; + //chwrite(c.chan,0x04,0x80|chan[c.chan].vol); + 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; + break; + } + case DIV_CMD_NOTE_OFF: + if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); + 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; + if (chan[c.chan].active) { + //chwrite(c.chan,0x04,0x80|chan[c.chan].outVol); + } + } + } + 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+chan[c.chan].sampleNoteDelta); + 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].duty=c.value; + break; + case DIV_CMD_LEGATO: + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+chan[c.chan].sampleNoteDelta+((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_PCE)); + } + 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 15; + 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_CMD_MACRO_RESTART: + chan[c.chan].std.restart(c.value); + break; + default: + break; + } + return 1; +} + +void DivPlatformSupervision::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + int mask_bits = 0; + for (int i=0; i<4; i++) + mask_bits |= isMuted[i]?0:8>>i; + supervision_set_mute_mask(mask_bits); +} + +void DivPlatformSupervision::forceIns() { + for (int i=0; i<4; i++) { + chan[i].insChanged=true; + chan[i].freqChanged=true; + //chwrite(i,0x05,isMuted[i]?0:chan[i].pan); + } +} + +void* DivPlatformSupervision::getChanState(int ch) { + return &chan[ch]; +} + +DivMacroInt* DivPlatformSupervision::getChanMacroInt(int ch) { + return &chan[ch].std; +} + +DivDispatchOscBuffer* DivPlatformSupervision::getOscBuffer(int ch) { + return oscBuf[ch]; +} + +unsigned char* DivPlatformSupervision::getRegisterPool() { + return regPool; +} + +int DivPlatformSupervision::getRegisterPoolSize() { + return 64; +} + +void DivPlatformSupervision::reset() { + writes.clear(); + memset(regPool,0,64); + for (int i=0; i<4; i++) { + chan[i]=DivPlatformSupervision::Channel(); + chan[i].std.setEngine(parent); + } + if (dumpWrites) { + addWrite(0xffffffff,0); + } + supervision_sound_reset(); + memset(tempL,0,32*sizeof(int)); + memset(tempR,0,32*sizeof(int)); + memset(noiseReg,0,3*sizeof(unsigned char)); + memset(kon,0,3*sizeof(unsigned char)); + memset(freqLo,0,sizeof(unsigned char)); + memset(freqHi,0,sizeof(unsigned char)); +} + +int DivPlatformSupervision::getOutputCount() { + return 2; +} + +bool DivPlatformSupervision::keyOffAffectsArp(int ch) { + return true; +} + +void DivPlatformSupervision::notifyInsDeletion(void* ins) { + for (int i=0; i<4; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformSupervision::setFlags(const DivConfig& flags) { + if (flags.getInt("swapDuty",0)) { + duty_swap = 1; + } else { + duty_swap = 0; + } + otherFlags = 0; + if (flags.getInt("sqStereo",0)) { + otherFlags |= 1; + } + chipClock=4000000; + CHECK_CUSTOM_CLOCK; + rate=chipClock/64; + for (int i=0; i<4; i++) { + oscBuf[i]->rate=rate; + } + supervision_sound_set_clock((unsigned int)chipClock); + supervision_sound_set_flags((unsigned int)otherFlags); +} + +void DivPlatformSupervision::poke(unsigned int addr, unsigned short val) { + rWrite(addr,val); +} + +void DivPlatformSupervision::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); +} + +int DivPlatformSupervision::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + for (int i=0; i<4; i++) { + isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; + } + setFlags(flags); + reset(); + return 4; +} + +void DivPlatformSupervision::quit() { + for (int i=0; i<4; i++) { + delete oscBuf[i]; + } +} + +DivPlatformSupervision::~DivPlatformSupervision() { +} diff --git a/src/engine/platform/supervision.h b/src/engine/platform/supervision.h new file mode 100644 index 000000000..9a670efa0 --- /dev/null +++ b/src/engine/platform/supervision.h @@ -0,0 +1,80 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2024 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 _SUPERVISION_H +#define _SUPERVISION_H + +#include "../dispatch.h" +#include "../../fixedQueue.h" +#include "sound/supervision.h" + +class DivPlatformSupervision: public DivDispatch { + struct Channel: public SharedChannel { + int dacPeriod, dacRate, dacOut; + unsigned int duty, len; + bool setPos; + Channel(): + SharedChannel(31), + duty(0), + len(0x1f), + setPos(false) {} + }; + Channel chan[4]; + DivDispatchOscBuffer* oscBuf[4]; + bool isMuted[4]; + struct QueuedWrite { + unsigned char addr; + unsigned char val; + QueuedWrite(): addr(0), val(9) {} + QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {} + }; + FixedQueue writes; + + int curChan; + int tempL[32]; + int tempR[32]; + int coreQuality; + unsigned char regPool[64]; + 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(); + ~DivPlatformSupervision(); +}; + +#endif diff --git a/src/engine/song.h b/src/engine/song.h index 8912ec4a1..5de17308d 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -141,6 +141,7 @@ enum DivSystem { DIV_SYSTEM_5E01, DIV_SYSTEM_BIFURCATOR, DIV_SYSTEM_SID2, + DIV_SYSTEM_SUPERVISION, }; enum DivEffectType: unsigned short { diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 093369fc2..e4f3e6a13 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1915,6 +1915,19 @@ void DivEngine::registerSystems() { {DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA} ); + sysDefs[DIV_SYSTEM_SUPERVISION]=new DivSysDef( + _("Watara Supervision"), NULL, 0xe3, 0, 4, false, true, 0, false, 0, 0, 0, + _("a handheld that was trying to compete with the Game Boy, but it never succeded."), + {_("Pulse 1"), _("Pulse 2"), _("PCM"), _("Noise")}, + {"S1", "S2", "PCM", "NS"}, + {DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM, DIV_CH_NOISE}, + {DIV_INS_SUPERVISION, DIV_INS_SUPERVISION, DIV_INS_SUPERVISION, DIV_INS_SUPERVISION}, + {DIV_INS_SUPERVISION, DIV_INS_SUPERVISION, DIV_INS_AMIGA, DIV_INS_SUPERVISION}, + { + {0x12, {DIV_CMD_STD_NOISE_MODE, _("12xx: Set duty cycle/noise mode (pulse: 0 to 3; noise: 0 or 1)")}}, + } + ); + sysDefs[DIV_SYSTEM_SM8521]=new DivSysDef( _("Sharp SM8521"), NULL, 0xc8, 0, 3, false, true, 0, false, 0, 32, 16, _("a SoC with wavetable sound hardware."), diff --git a/src/gui/debug.cpp b/src/gui/debug.cpp index 3cb6b4c13..141ce50b2 100644 --- a/src/gui/debug.cpp +++ b/src/gui/debug.cpp @@ -51,6 +51,7 @@ #include "../engine/platform/pcmdac.h" #include "../engine/platform/k007232.h" #include "../engine/platform/ga20.h" +#include "../engine/platform/supervision.h" #include "../engine/platform/sm8521.h" #include "../engine/platform/pv1000.h" #include "../engine/platform/k053260.h" diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 09d0fefbb..0b50125ab 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -184,6 +184,7 @@ const char* insTypes[DIV_INS_MAX+1][3]={ {"GBA MinMod",ICON_FA_VOLUME_UP,ICON_FUR_INS_GBA_MINMOD}, {"Bifurcator",ICON_FA_LINE_CHART,ICON_FUR_INS_BIFURCATOR}, {"SID2",ICON_FA_KEYBOARD_O,ICON_FUR_INS_SID2}, + {"Watara Supervision",ICON_FA_GAMEPAD,ICON_FUR_INS_SUPERVISION}, {NULL,ICON_FA_QUESTION,ICON_FA_QUESTION} }; @@ -1263,6 +1264,7 @@ const int availableSystems[]={ DIV_SYSTEM_5E01, DIV_SYSTEM_BIFURCATOR, DIV_SYSTEM_SID2, + DIV_SYSTEM_SUPERVISION, 0 // don't remove this last one! }; @@ -1358,6 +1360,7 @@ const int chipsSpecial[]={ DIV_SYSTEM_5E01, DIV_SYSTEM_BIFURCATOR, DIV_SYSTEM_SID2, + DIV_SYSTEM_SUPERVISION, 0 // don't remove this last one! }; diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index ebef2402d..92f40e44d 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -7407,6 +7407,13 @@ void FurnaceGUI::drawInsEdit() { macroList.push_back(FurnaceGUIMacroDesc(_("Pulse Width"),&ins->std.dutyMacro,0,255,160,uiColors[GUI_COLOR_MACRO_OTHER])); macroList.push_back(FurnaceGUIMacroDesc(_("Pitch"),&ins->std.pitchMacro,-2048,2047,160,uiColors[GUI_COLOR_MACRO_PITCH],true,macroRelativeMode)); break; + case DIV_INS_SUPERVISION: + macroList.push_back(FurnaceGUIMacroDesc(_("Volume"),&ins->std.volMacro,0,15,160,uiColors[GUI_COLOR_MACRO_VOLUME])); + macroList.push_back(FurnaceGUIMacroDesc(_("Arpeggio"),&ins->std.arpMacro,-120,120,160,uiColors[GUI_COLOR_MACRO_PITCH],true,NULL,macroHoverNote,false,NULL,true,ins->std.arpMacro.val)); + macroList.push_back(FurnaceGUIMacroDesc(_("Duty/Noise"),&ins->std.dutyMacro,0,3,160,uiColors[GUI_COLOR_MACRO_NOISE])); + macroList.push_back(FurnaceGUIMacroDesc(_("Pitch"),&ins->std.pitchMacro,-2048,2047,160,uiColors[GUI_COLOR_MACRO_PITCH],true,macroRelativeMode)); + macroList.push_back(FurnaceGUIMacroDesc(_("Phase Reset"),&ins->std.phaseResetMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); + break; case DIV_INS_SM8521: macroList.push_back(FurnaceGUIMacroDesc(_("Volume"),&ins->std.volMacro,0,31,160,uiColors[GUI_COLOR_MACRO_VOLUME])); macroList.push_back(FurnaceGUIMacroDesc(_("Arpeggio"),&ins->std.arpMacro,-120,120,160,uiColors[GUI_COLOR_MACRO_PITCH],true,NULL,macroHoverNote,false,NULL,true,ins->std.arpMacro.val)); diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 87133ffc8..f5ebe09ee 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -3085,6 +3085,11 @@ void FurnaceGUI::initSystemPresets() { CH(DIV_SYSTEM_SID2, 1.0f, 0, "") } ); + ENTRY( + "Watara Supervision", { + CH(DIV_SYSTEM_SUPERVISION, 1.0f, 0, "") + } + ); CATEGORY_END; CATEGORY_BEGIN("DefleMask-compatible","these configurations are compatible with DefleMask.\nselect this if you need to save as .dmf or work with that program."); diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 0875f25c1..459d17828 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -2317,6 +2317,29 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl } break; } + case DIV_SYSTEM_SUPERVISION: { + bool swapDuty=flags.getInt("swapDuty",true); + + if (ImGui::Checkbox(_("Swap noise duty cycles"),&swapDuty)) { + altered=true; + } + + bool sqStereo=flags.getInt("sqStereo",false); + + if (ImGui::Checkbox(_("Stereo pulse waves"),&sqStereo)) { + altered=true; + } + + if (altered) { + e->lockSave([&]() { + flags.set("swapDuty",(int)swapDuty); + }); + e->lockSave([&]() { + flags.set("sqStereo",(int)sqStereo); + }); + } + break; + } case DIV_SYSTEM_SM8521:/* { bool noAntiClick=flags.getBool("noAntiClick",false); diff --git a/src/gui/sysPartNumber.cpp b/src/gui/sysPartNumber.cpp index 1ce6e2f38..19bfca89c 100644 --- a/src/gui/sysPartNumber.cpp +++ b/src/gui/sysPartNumber.cpp @@ -279,6 +279,8 @@ const char* FurnaceGUI::getSystemPartNumber(DivSystem sys, DivConfig& flags) { break; case DIV_SYSTEM_ESFM: return "ES1xxx"; + case DIV_SYSTEM_SUPERVISION: + return "Watara Supervision"; default: return FurnaceGUI::getSystemName(sys); break; diff --git a/src/icon/furIcons.h b/src/icon/furIcons.h index 9712f5582..74c459511 100644 --- a/src/icon/furIcons.h +++ b/src/icon/furIcons.h @@ -75,6 +75,7 @@ #define ICON_FUR_INS_GBA_MINMOD u8"\ue15f" #define ICON_FUR_INS_BIFURCATOR u8"\ue160" #define ICON_FUR_INS_SID2 u8"\ue161" +#define ICON_FUR_INS_SUPERVISION u8"\ue160" // sample editor #define ICON_FUR_SAMPLE_APPLY_SILENCE u8"\ue136"