diff --git a/CMakeLists.txt b/CMakeLists.txt index 61b025a90..bd94e9437 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -646,6 +646,8 @@ src/engine/platform/sound/sm8521.c src/engine/platform/sound/supervision.c +src/engine/platform/sound/upd1771c.c + src/engine/platform/sound/d65modified.c src/engine/platform/sound/ted-sound.c @@ -774,6 +776,7 @@ src/engine/platform/k007232.cpp src/engine/platform/ga20.cpp src/engine/platform/sm8521.cpp src/engine/platform/supervision.cpp +src/engine/platform/upd1771c.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 a34838eca..5353c77d1 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -77,6 +77,7 @@ #include "platform/k007232.h" #include "platform/ga20.h" #include "platform/supervision.h" +#include "platform/upd1771c.h" #include "platform/sm8521.h" #include "platform/pv1000.h" #include "platform/k053260.h" @@ -688,6 +689,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_SUPERVISION: dispatch=new DivPlatformSupervision; break; + case DIV_SYSTEM_UPD1771C: + dispatch=new DivPlatformUPD1771c; + break; case DIV_SYSTEM_SM8521: dispatch=new DivPlatformSM8521; if (isRender) { diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index 47f47bb98..40da3337f 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -1141,6 +1141,8 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo featureSM=true; if (amiga.useSample) featureSL=true; break; + case DIV_INS_UPD1771C: + break; case DIV_INS_MAX: break; case DIV_INS_NULL: diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 75a96fd2e..e2aefbdf4 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -95,6 +95,7 @@ enum DivInstrumentType: unsigned short { DIV_INS_BIFURCATOR=62, DIV_INS_SID2=63, // coincidence! DIV_INS_SUPERVISION=64, + DIV_INS_UPD1771C=65, DIV_INS_MAX, DIV_INS_NULL }; diff --git a/src/engine/platform/sound/upd1771c.c b/src/engine/platform/sound/upd1771c.c new file mode 100644 index 000000000..8fd7fd5bb --- /dev/null +++ b/src/engine/platform/sound/upd1771c.c @@ -0,0 +1,142 @@ +#include "upd1771c.h" + +#include + +/* + Each of the 8 waveforms have been extracted from the uPD1771c-017 internal + ROM, from offset 0x1fd (start of first waveform) to offset 0x2fc (end of + last waveform). + (note: given test mode dumping offset non-clarity it may be 0x200-0x2ff) + The waveforms are stored in an 8-bit sign-magnitude format, so if in ROM the + upper bit is 0x80, invert the lower 7 bits to get the 2's complement result + seen here. + Note that only the last 4 waveforms appear to have been intended for use as + waveforms; the first four look as if they're playing back a piece of code as + wave data. +*/ +const signed char WAVEFORMS[8][32]={ +{ 0, 0,-123,-123, -61, -23, 125, 107, 94, 83,-128,-128,-128, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0,-128,-128,-128, 0, 0, 0, 0, 0, 0}, +{ 37, 16, 32, -21, 32, 52, 4, 4, 33, 18, 60, 56, 0, 8, 5, 16, 65, 19, 69, 16, -2, 19, 37, 16, 97, 19, 0, 87, 127, -3, 1, 2}, +{ 0, 8, 1, 52, 4, 0, 0, 77, 81,-109, 47, 97, -83,-109, 38, 97, 0, 52, 4, 0, 1, 4, 1, 22, 2, -46, 33, 97, 0, 8, -85, -99}, +{ 47, 97, 40, 97, -3, 25, 64, 17, 0, 52, 12, 5, 12, 5, 12, 5, 12, 5, 12, 5, 8, 4,-114, 19, 0, 52,-122, 21, 2, 5, 0, 8}, +{ -52, -96,-118,-128,-111, -74, -37, -5, 31, 62, 89, 112, 127, 125, 115, 93, 57, 23, 0, -16, -8, 15, 37, 54, 65, 70, 62, 54, 43, 31, 19, 0}, +{ -81,-128, -61, 13, 65, 93, 127, 47, 41, 44, 52, 55, 56, 58, 58, 34, 0, 68, 76, 72, 61, 108, 55, 29, 32, 39, 43, 49, 50, 51, 51, 0}, +{ -21, -45, -67, -88,-105,-114,-122,-128,-123,-116,-103, -87, -70, -53, -28, -9, 22, 46, 67, 86, 102, 114, 123, 125, 127, 117, 104, 91, 72, 51, 28, 0}, +{ -78,-118,-128,-102, -54, -3, 40, 65, 84, 88, 84, 80, 82, 88, 94, 103, 110, 119, 122, 125, 122, 122, 121, 123, 125, 126, 127, 127, 125, 118, 82, 0} +}; + + +#define NOISE_SIZE 255 + + +const unsigned char noise_tbl[]= +{ + 0x1c,0x86,0x8a,0x8f,0x98,0xa1,0xad,0xbe,0xd9,0x8a,0x66,0x4d,0x40,0x33,0x2b,0x23, + 0x1e,0x8a,0x90,0x97,0xa4,0xae,0xb8,0xd6,0xec,0xe9,0x69,0x4a,0x3e,0x34,0x2d,0x27, + 0x24,0x24,0x89,0x8e,0x93,0x9c,0xa5,0xb0,0xc1,0xdd,0x40,0x36,0x30,0x29,0x27,0x24, + 0x8b,0x90,0x96,0x9e,0xa7,0xb3,0xc4,0xe1,0x25,0x21,0x8a,0x8f,0x93,0x9d,0xa5,0xb2, + 0xc2,0xdd,0xdd,0x98,0xa2,0xaf,0xbf,0xd8,0xfd,0x65,0x4a,0x3c,0x31,0x2b,0x24,0x22, + 0x1e,0x87,0x8c,0x91,0x9a,0xa3,0xaf,0xc0,0xdb,0xbe,0xd9,0x8c,0x66,0x4d,0x40,0x34, + 0x2c,0x24,0x1f,0x88,0x90,0x9a,0xa4,0xb2,0xc2,0xda,0xff,0x67,0x4d,0x3d,0x34,0x2d, + 0x26,0x24,0x20,0x89,0x8e,0x93,0x9c,0xa5,0xb1,0xc2,0xde,0xc1,0xda,0xff,0x67,0x4d, + 0x3d,0x33,0x2d,0x26,0x24,0x20,0x89,0x8e,0x93,0x9c,0xa5,0xb1,0xc2,0xdd,0xa3,0xb0, + 0xc0,0xd9,0xfe,0x66,0x4b,0x3c,0x32,0x2b,0x24,0x23,0x1e,0x88,0x8d,0x92,0x9b,0xa4, + 0xb0,0xc1,0xdc,0xad,0xbe,0xda,0x22,0x20,0x1c,0x85,0x8a,0x8f,0x98,0xa1,0xad,0xbe, + 0xda,0x20,0x1b,0x85,0x8d,0x97,0xa1,0xaf,0xbf,0xd8,0xfd,0x64,0x49,0x3a,0x30,0x2a, + 0x23,0x21,0x1d,0x86,0x8b,0x91,0x9a,0xa2,0xae,0xc0,0xdb,0x33,0x2b,0x24,0x1f,0x88, + 0x90,0x9a,0xa4,0xb2,0xc2,0xda,0xff,0x67,0x4c,0x3e,0x33,0x2d,0x25,0x24,0x1f,0x89, + 0x8e,0x93,0x9c,0xa5,0xb1,0xc2,0xde,0x85,0x8e,0x98,0xa2,0xb0,0xc0,0xd9,0xfe,0x64, + 0x4b,0x3b,0x31,0x2a,0x23,0x22,0x1e,0x88,0x8c,0x91,0x9b,0xa3,0xaf,0xc1,0xdc,0xdc +}; + + +uint8_t upd1771c_packets[16]; +uint8_t upd1771c_mode; +uint32_t upd1771c_pos; +uint8_t upd1771c_off; +uint8_t upd1771c_posc; +uint8_t upd1771c_wave; +uint8_t upd1771c_vol; +uint8_t upd1771c_period; +uint8_t upd1771c_npos; +void upd1771c_reset() { + memset(upd1771c_packets,0,16); + upd1771c_mode = 0; + upd1771c_pos = 0; + upd1771c_posc = 0; + upd1771c_wave = 0; + upd1771c_vol = 0; + upd1771c_period = 0; + upd1771c_off = 0; + upd1771c_npos = 0; +} + +void upd1771c_write_packet(uint8_t ind, uint8_t val) { + upd1771c_packets[ind&15] = val; + switch (upd1771c_packets[0]) { + case 1: + if (ind == 3) { + upd1771c_mode = 1; + upd1771c_wave = (upd1771c_packets[1]&0xe0)>>5; + upd1771c_off = 0; //? + upd1771c_period = upd1771c_packets[2]; + upd1771c_vol = upd1771c_packets[3]&0x1f; + } + break; + case 2: + if (ind == 3) { + upd1771c_mode = 2; + upd1771c_wave = (upd1771c_packets[1]&0xe0)>>5; + upd1771c_off = upd1771c_packets[1]&0x1f; + upd1771c_period = upd1771c_packets[2]<0x20?0x20:upd1771c_packets[2]; + upd1771c_vol = upd1771c_packets[3]&0x1f; + } + break; + default: + case 0: + upd1771c_mode = 0; + break; + } +} + +int upd1771c_repsamp = 0; + +void upd1771c_sound_set_clock(unsigned int clock, unsigned int divi) { + upd1771c_repsamp = divi; +} + +int16_t upd1771c_sound_stream_update() { + int16_t s = 0; + for (int i = 0; i < upd1771c_repsamp; i++) { + s = 0; + switch (upd1771c_mode) { + case 2: + s = ((int16_t)WAVEFORMS[upd1771c_wave][upd1771c_posc])*upd1771c_vol; + upd1771c_pos++; + if (upd1771c_pos >= upd1771c_period) { + upd1771c_pos=0; + upd1771c_posc++; + if (upd1771c_posc == 32) + upd1771c_posc = upd1771c_off; + } + break; + case 1: + upd1771c_pos++; + if (upd1771c_pos >= ((((uint32_t)upd1771c_period) + 1)*128)) { + upd1771c_pos=0; + upd1771c_posc++; + if (upd1771c_posc == NOISE_SIZE) + upd1771c_posc = 0; + } + s = ((int16_t)(noise_tbl[upd1771c_posc]))*upd1771c_vol; + // inaccurate noise mixing :/ + // s |= (upd1771c_npos&128)?127*upd1771c_vol:0; + break; + case 0: + default: + break; + } + } + return s; +} + diff --git a/src/engine/platform/sound/upd1771c.h b/src/engine/platform/sound/upd1771c.h new file mode 100644 index 000000000..5dec4b2eb --- /dev/null +++ b/src/engine/platform/sound/upd1771c.h @@ -0,0 +1,20 @@ +#ifndef __UPD1771C_SOUND_H__ +#define __UPD1771C_SOUND_H__ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include + +void upd1771c_reset(void); +void upd1771c_write_packet(uint8_t ind, uint8_t val); +int16_t upd1771c_sound_stream_update(void); +void upd1771c_sound_set_clock(unsigned int clock, unsigned int divi); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif diff --git a/src/engine/platform/supervision.cpp b/src/engine/platform/supervision.cpp index 140c32265..77352838e 100644 --- a/src/engine/platform/supervision.cpp +++ b/src/engine/platform/supervision.cpp @@ -52,10 +52,10 @@ const char* regCheatSheetSupervision[]={ NULL }; -unsigned char noiseReg[3]; -unsigned char kon[4]; -unsigned char initWrite[4]; -unsigned char initWrite2[4]; +unsigned char supervision_noiseReg[3]; +unsigned char supervision_kon[4]; +unsigned char supervision_initWrite[4]; +unsigned char supervision_initWrite2[4]; const char** DivPlatformSupervision::getRegisterSheet() { return regCheatSheetSupervision; @@ -64,9 +64,9 @@ const char** DivPlatformSupervision::getRegisterSheet() { unsigned char* sampleMem=supervision_dma_mem; -unsigned char duty_swap=0; -unsigned char otherFlags=0; -unsigned int sampleOffset=0; +unsigned char supervision_duty_swap=0; +unsigned char supervision_otherFlags=0; +unsigned int supervision_sampleOffset=0; void DivPlatformSupervision::acquire(short** buf, size_t len) { for (size_t h=0; hcalcFreq(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 (chan[i].freqChanged || initWrite[i]) { + if (chan[i].freqChanged || supervision_initWrite[i]) { rWrite(0x10|(i<<2),chan[i].freq&0xff); rWrite(0x11|(i<<2),(chan[i].freq>>8)&0x7); } - initWrite[i]=0; + supervision_initWrite[i]=0; } else if (i == 3) { int ntPos=chan[i].baseFreq; if (NEW_ARP_STRAT) { @@ -166,9 +166,9 @@ void DivPlatformSupervision::tick(bool sysTick) { chan[i].freq=15-(ntPos&15); unsigned char r=(chan[i].freq<<4)|(chan[i].outVol&0xf); rWrite(0x28,r); - noiseReg[0]=r; + supervision_noiseReg[0]=r; rWrite(0x29,0xc8); - r=((chan[i].duty&1)^duty_swap)|(0x02|0x10)|(chan[i].pan<<2); + r=((chan[i].duty&1)^supervision_duty_swap)|(0x02|0x10)|(chan[i].pan<<2); rWrite(0x2A,r); } if (chan[i].keyOn && i==2) { @@ -188,7 +188,7 @@ void DivPlatformSupervision::tick(bool sysTick) { rWrite(0x1B,chan[i].freq|((chan[i].pan&3)<<2)|((off>>14&7)<<4)); rWrite(0x1C,0x80); } - sampleOffset=chan[i].hasOffset; + supervision_sampleOffset=chan[i].hasOffset; chan[i].hasOffset=0; } } @@ -205,14 +205,14 @@ void DivPlatformSupervision::tick(bool sysTick) { } } } - if (chan[i].keyOn) kon[i]=1; - if (chan[i].keyOff) kon[i]=0; + if (chan[i].keyOn) supervision_kon[i]=1; + if (chan[i].keyOff) supervision_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 (supervision_kon[i]) { if (i < 2) { rWrite(0x12|(i<<2),(chan[i].outVol&0xf)|((chan[i].duty&3)<<4)); rWrite(0x13|(i<<2),0xc8); @@ -228,12 +228,12 @@ void DivPlatformSupervision::tick(bool sysTick) { ntPos+=chan[i].pitch2; chan[i].freq=15-(ntPos&15); unsigned char r=(chan[i].freq<<4)|(chan[i].outVol&0xf); - if (noiseReg[0] != r) rWrite(0x28,r); - noiseReg[0]=r; + if (supervision_noiseReg[0] != r) rWrite(0x28,r); + supervision_noiseReg[0]=r; rWrite(0x29,0xc8); - r=((chan[i].duty&1)^duty_swap)|(0x02|0x10)|(chan[i].pan<<2); - if (noiseReg[2] != r) rWrite(0x2A,r); - noiseReg[2]=r; + r=((chan[i].duty&1)^supervision_duty_swap)|(0x02|0x10)|(chan[i].pan<<2); + if (supervision_noiseReg[2] != r) rWrite(0x2A,r); + supervision_noiseReg[2]=r; } else if (i==2) { if (chan[i].pcm) { int ntPos=chan[i].sampleNote; @@ -242,8 +242,8 @@ void DivPlatformSupervision::tick(bool sysTick) { int sNum=chan[i].sample; DivSample* sample=parent->getSample(sNum); if (sample!=NULL && sNum>=0 && sNumsong.sampleLen) { - unsigned int off=MIN(sampleOff[sNum]+sampleOffset,sampleOff[sNum]+sampleLen[sNum]); - unsigned int len=MAX((((int)sampleLen[sNum])-((int)sampleOffset)),0); + unsigned int off=MIN(sampleOff[sNum]+supervision_sampleOffset,sampleOff[sNum]+sampleLen[sNum]); + unsigned int len=MAX((((int)sampleLen[sNum])-((int)supervision_sampleOffset)),0); if (len) { rWrite(0x1A,MIN(MAX(len>>4,0),255)); if (chan[i].outVol==0) { @@ -262,8 +262,8 @@ void DivPlatformSupervision::tick(bool sysTick) { } else if (i == 3) { rWrite(0x29,0); unsigned char r=0; - if (noiseReg[2] != r) rWrite(0x2A,r); - noiseReg[2]=r; + if (supervision_noiseReg[2] != r) rWrite(0x2A,r); + supervision_noiseReg[2]=r; } } @@ -456,12 +456,12 @@ void DivPlatformSupervision::reset() { supervision_sound_reset(); memset(tempL,0,32*sizeof(int)); memset(tempR,0,32*sizeof(int)); - memset(noiseReg,0,3*sizeof(unsigned char)); - noiseReg[2]=0xff; - memset(kon,0,4*sizeof(unsigned char)); - memset(initWrite,1,4*sizeof(unsigned char)); - memset(initWrite2,1,4*sizeof(unsigned char)); - sampleOffset=0; + memset(supervision_noiseReg,0,3*sizeof(unsigned char)); + supervision_noiseReg[2]=0xff; + memset(supervision_kon,0,4*sizeof(unsigned char)); + memset(supervision_initWrite,1,4*sizeof(unsigned char)); + memset(supervision_initWrite2,1,4*sizeof(unsigned char)); + supervision_sampleOffset=0; } int DivPlatformSupervision::getOutputCount() { @@ -480,13 +480,13 @@ void DivPlatformSupervision::notifyInsDeletion(void* ins) { void DivPlatformSupervision::setFlags(const DivConfig& flags) { if (flags.getInt("swapDuty",true)) { - duty_swap=1; + supervision_duty_swap=1; } else { - duty_swap=0; + supervision_duty_swap=0; } - otherFlags=0; + supervision_otherFlags=0; if (flags.getInt("sqStereo",false)) { - otherFlags |= 1; + supervision_otherFlags |= 1; } chipClock=4000000; CHECK_CUSTOM_CLOCK; @@ -495,7 +495,7 @@ void DivPlatformSupervision::setFlags(const DivConfig& flags) { oscBuf[i]->rate=rate; } supervision_sound_set_clock((unsigned int)chipClock); - supervision_sound_set_flags((unsigned int)otherFlags); + supervision_sound_set_flags((unsigned int)supervision_otherFlags); } void DivPlatformSupervision::poke(unsigned int addr, unsigned short val) { diff --git a/src/engine/platform/upd1771c.cpp b/src/engine/platform/upd1771c.cpp new file mode 100644 index 000000000..9c5eb8da5 --- /dev/null +++ b/src/engine/platform/upd1771c.cpp @@ -0,0 +1,379 @@ +/** + * 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 "upd1771c.h" +#include "../engine.h" +#include "../../ta-log.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* regCheatSheetUPD1771c[]={ + NULL +}; + +unsigned char upd1771c_kon[4]; +unsigned char upd1771c_initWrite[4]; +unsigned char upd1771c_initWrite2[4]; + +const char** DivPlatformUPD1771c::getRegisterSheet() { + return regCheatSheetUPD1771c; +} + +void DivPlatformUPD1771c::acquire(short** buf, size_t len) { + for (size_t h=0; h>i; + //upd1771c_set_mute_mask(mask_bits); + */ + + while (!writes.empty()) { + QueuedWrite w=writes.front(); + upd1771c_write_packet(w.addr&15,w.val); + regPool[w.addr&0xf]=w.val; + writes.pop(); + } + + signed short s = upd1771c_sound_stream_update(); + + for (int i=0; i<1; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=s; + } + + //printf("tempL: %d tempR: %d\n",tempL,tempR); + buf[0][h]=s; + buf[1][h]=s; + } +} + +void DivPlatformUPD1771c::tick(bool sysTick) { + for (int i=0; i<1; i++) { + + chan[i].std.next(); + if (chan[i].std.vol.had) { + chan[i].outVol=VOL_SCALE_LINEAR(chan[i].vol&31,MIN(31,chan[i].std.vol.val),31); + } + 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); + chan[i].baseFreq=NOTE_PERIODIC(f); + } + chan[i].freqChanged=true; + } + if (chan[i].std.wave.had) { + chan[i].wave=chan[i].std.wave.val&7; + } + if (chan[i].std.duty.had) { + chan[i].duty=chan[i].std.duty.val&1; + } + if (chan[i].std.ex1.had) { + chan[i].pos=chan[i].std.ex1.val&31; + } + 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==0) { + 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); + } + if (chan[i].freqChanged || upd1771c_initWrite[i] || chan[i].keyOn) { + if (chan[i].duty == 0) { + rWrite(0,2); + rWrite(1,(chan[i].wave<<5)|chan[i].pos); + rWrite(2,MIN(MAX(chan[i].freq,0),255)); + rWrite(3,chan[i].outVol); + } else if (chan[i].duty == 1) { + rWrite(0,1); + rWrite(1,(chan[i].wave<<5)); + rWrite(2,MIN(MAX(chan[i].freq>>7,0),255)); + rWrite(3,chan[i].outVol); + } else { + rWrite(0,0); + } + upd1771c_initWrite[i]=0; + } + if (chan[i].keyOff) { + rWrite(0,0); + } + if (chan[i].keyOn) upd1771c_kon[i]=1; + if (chan[i].keyOff) upd1771c_kon[i]=0; + if (chan[i].keyOn) chan[i].keyOn=false; + if (chan[i].keyOff) chan[i].keyOff=false; + chan[i].freqChanged=false; + } + + if (upd1771c_kon[i]) { + if (i==0) { + if (chan[i].duty == 0) { + rWrite(0,2); + rWrite(1,(chan[i].wave<<5)|chan[i].pos); + rWrite(2,MIN(MAX(chan[i].freq,0),255)); + rWrite(3,chan[i].outVol); + } else if (chan[i].duty == 1) { + rWrite(0,1); + rWrite(1,(chan[i].wave<<5)); + rWrite(2,MIN(MAX(chan[i].freq>>7,0),255)); + rWrite(3,chan[i].outVol); + } else { + rWrite(0,0); + } + } + } else { + if (i == 0) { + rWrite(0,0); + } + } + + } +} + +int DivPlatformUPD1771c::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_UPD1771C); + 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); + 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].wave=c.value&7; + chan[c.chan].duty=c.value>>4&1; + break; + case DIV_CMD_N163_WAVE_POSITION: + chan[c.chan].pos=c.value; + 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_UPD1771C)); + } + 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 31; + 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 DivPlatformUPD1771c::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; +} + +void DivPlatformUPD1771c::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* DivPlatformUPD1771c::getChanState(int ch) { + return &chan[ch]; +} + +DivMacroInt* DivPlatformUPD1771c::getChanMacroInt(int ch) { + return &chan[ch].std; +} + +DivDispatchOscBuffer* DivPlatformUPD1771c::getOscBuffer(int ch) { + return oscBuf[ch]; +} + +unsigned char* DivPlatformUPD1771c::getRegisterPool() { + return regPool; +} + +int DivPlatformUPD1771c::getRegisterPoolSize() { + return 16; +} + +void DivPlatformUPD1771c::reset() { + writes.clear(); + memset(regPool,0,16); + for (int i=0; i<1; i++) { + chan[i]=DivPlatformUPD1771c::Channel(); + chan[i].std.setEngine(parent); + } + if (dumpWrites) { + addWrite(0xffffffff,0); + } + //upd1771c_sound_reset(); + memset(tempL,0,32*sizeof(int)); + memset(tempR,0,32*sizeof(int)); + memset(upd1771c_kon,0,4*sizeof(unsigned char)); + memset(upd1771c_initWrite,1,4*sizeof(unsigned char)); + memset(upd1771c_initWrite2,1,4*sizeof(unsigned char)); +} + +int DivPlatformUPD1771c::getOutputCount() { + return 2; +} + +bool DivPlatformUPD1771c::keyOffAffectsArp(int ch) { + return true; +} + +void DivPlatformUPD1771c::notifyInsDeletion(void* ins) { + for (int i=0; i<1; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformUPD1771c::setFlags(const DivConfig& flags) { + chipClock=6000000; + CHECK_CUSTOM_CLOCK; + rate=chipClock/32; + for (int i=0; i<1; i++) { + oscBuf[i]->rate=rate; + } + upd1771c_sound_set_clock((unsigned int)chipClock,8); +} + +void DivPlatformUPD1771c::poke(unsigned int addr, unsigned short val) { + rWrite(addr,val); +} + +void DivPlatformUPD1771c::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); +} + +int DivPlatformUPD1771c::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + for (int i=0; i<1; i++) { + isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; + } + setFlags(flags); + reset(); + return 1; +} + +void DivPlatformUPD1771c::quit() { + for (int i=0; i<1; i++) { + delete oscBuf[i]; + } +} + +DivPlatformUPD1771c::~DivPlatformUPD1771c() { +} diff --git a/src/engine/platform/upd1771c.h b/src/engine/platform/upd1771c.h new file mode 100644 index 000000000..b38c78074 --- /dev/null +++ b/src/engine/platform/upd1771c.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 _UPD1771C_H +#define _UPD1771C_H + +#include "../dispatch.h" +#include "../../fixedQueue.h" +#include "sound/upd1771c.h" + +class DivPlatformUPD1771c: public DivDispatch { + struct Channel: public SharedChannel { + unsigned int wave; // pcm is channel 3 ONLY + int pos, duty; + Channel(): + SharedChannel(15), + wave(0), + pos(0), + duty(0) {} + }; + Channel chan[1]; + DivDispatchOscBuffer* oscBuf[1]; + 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[16]; + + 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(); + ~DivPlatformUPD1771c(); +}; + +#endif diff --git a/src/engine/song.h b/src/engine/song.h index 5de17308d..d87c0af3e 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -142,6 +142,7 @@ enum DivSystem { DIV_SYSTEM_BIFURCATOR, DIV_SYSTEM_SID2, DIV_SYSTEM_SUPERVISION, + DIV_SYSTEM_UPD1771C, }; enum DivEffectType: unsigned short { diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 790272187..3484b64d0 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1928,6 +1928,20 @@ void DivEngine::registerSystems() { } ); + sysDefs[DIV_SYSTEM_UPD1771C]=new DivSysDef( + _("NEC uPD1771C"), NULL, 0xe4, 0, 1, false, true, 0, false, 0, 0, 0, + _("this was an SoC with some funky wavetable/noise hardware"), + {_("Wave/Noise")}, + {"W"}, + {DIV_CH_NOISE}, + {DIV_INS_UPD1771C}, + {}, + { + {0x10, {DIV_CMD_STD_NOISE_MODE, _("10xx: Set duty/waveform (bit 0-3: waveform; bit 4: mode)")}}, + {0x12, {DIV_CMD_N163_WAVE_POSITION, _("12xx: Set waveform position (0-31)")}}, + } + ); + 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/guiConst.cpp b/src/gui/guiConst.cpp index a40e2726d..e24f0d1ac 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -185,6 +185,7 @@ const char* insTypes[DIV_INS_MAX+1][3]={ {"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}, + {"NEC uPD1771C",ICON_FA_BAR_CHART,ICON_FUR_INS_UPD1771C}, {NULL,ICON_FA_QUESTION,ICON_FA_QUESTION} }; @@ -1266,6 +1267,7 @@ const int availableSystems[]={ DIV_SYSTEM_BIFURCATOR, DIV_SYSTEM_SID2, DIV_SYSTEM_SUPERVISION, + DIV_SYSTEM_UPD1771C, 0 // don't remove this last one! }; @@ -1362,6 +1364,7 @@ const int chipsSpecial[]={ DIV_SYSTEM_BIFURCATOR, DIV_SYSTEM_SID2, DIV_SYSTEM_SUPERVISION, + DIV_SYSTEM_UPD1771C, 0 // don't remove this last one! }; diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 803b2c721..47fac22b4 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -7558,7 +7558,14 @@ void FurnaceGUI::drawInsEdit() { macroList.push_back(FurnaceGUIMacroDesc(_("Noise Mode"),&ins->std.fmsMacro,0,3,64,uiColors[GUI_COLOR_MACRO_NOISE])); macroList.push_back(FurnaceGUIMacroDesc(_("Wave Mix"),&ins->std.amsMacro,0,3,64,uiColors[GUI_COLOR_MACRO_OTHER])); break; - + case DIV_INS_UPD1771C: + 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)); + macroList.push_back(FurnaceGUIMacroDesc(_("Waveform"),&ins->std.waveMacro,0,7,160,uiColors[GUI_COLOR_MACRO_WAVE],false,NULL,NULL,false,NULL)); + macroList.push_back(FurnaceGUIMacroDesc(_("Wave Pos"),&ins->std.ex1Macro,0,31,160,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc(_("Duty/Mode"),&ins->std.dutyMacro,0,1,160,uiColors[GUI_COLOR_MACRO_NOISE])); + macroList.push_back(FurnaceGUIMacroDesc(_("Pitch"),&ins->std.pitchMacro,-2048,2047,160,uiColors[GUI_COLOR_MACRO_PITCH],true,macroRelativeMode)); + break; case DIV_INS_MAX: case DIV_INS_NULL: break; diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index f5ebe09ee..4b3416bd0 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -3090,6 +3090,11 @@ void FurnaceGUI::initSystemPresets() { CH(DIV_SYSTEM_SUPERVISION, 1.0f, 0, "") } ); + ENTRY( + "NEC uPD1771C", { + CH(DIV_SYSTEM_UPD1771C, 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/sysPartNumber.cpp b/src/gui/sysPartNumber.cpp index 19bfca89c..6fb92bc28 100644 --- a/src/gui/sysPartNumber.cpp +++ b/src/gui/sysPartNumber.cpp @@ -281,6 +281,10 @@ const char* FurnaceGUI::getSystemPartNumber(DivSystem sys, DivConfig& flags) { return "ES1xxx"; case DIV_SYSTEM_SUPERVISION: return "Watara Supervision"; + break; + case DIV_SYSTEM_UPD1771C: + return "uPD1771C"; + break; default: return FurnaceGUI::getSystemName(sys); break; diff --git a/src/icon/furIcons.h b/src/icon/furIcons.h index 1141597fe..71df76fd2 100644 --- a/src/icon/furIcons.h +++ b/src/icon/furIcons.h @@ -76,6 +76,7 @@ #define ICON_FUR_INS_BIFURCATOR u8"\ue160" #define ICON_FUR_INS_SID2 u8"\ue161" #define ICON_FUR_INS_SUPERVISION u8"\ue162" +#define ICON_FUR_INS_UPD1771C u8"\ue163" // sample editor #define ICON_FUR_SAMPLE_APPLY_SILENCE u8"\ue136"