diff --git a/CMakeLists.txt b/CMakeLists.txt index 4cc31eae7a..f69bf3cc64 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -602,6 +602,8 @@ src/engine/platform/sound/ted-sound.c src/engine/platform/sound/c140_c219.c +src/engine/platform/sound/es5503.cpp + src/engine/platform/oplAInterface.cpp src/engine/platform/ym2608Interface.cpp src/engine/platform/ym2610Interface.cpp @@ -700,6 +702,7 @@ src/engine/platform/pv1000.cpp src/engine/platform/k053260.cpp src/engine/platform/ted.cpp src/engine/platform/c140.cpp +src/engine/platform/es5503.cpp src/engine/platform/pcmdac.cpp src/engine/platform/dummy.cpp diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index 972daae30c..f547419a9f 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -244,6 +244,12 @@ enum DivDispatchCmds { DIV_CMD_C64_AD, // (value) DIV_CMD_C64_SR, // (value) + DIV_CMD_ES5503_NUM_ENABLED_OSC, // (value); set number of enabled oscillators (more oscs enabled = less sample rate) + DIV_CMD_ES5503_OSC_OUTPUT, // (value) + DIV_CMD_ES5503_WAVE_LENGTH, + DIV_CMD_ES5503_WAVE_POS, + DIV_CMD_ES5503_OSC_MODE, + DIV_ALWAYS_SET_VOLUME, // () -> alwaysSetVol DIV_CMD_MAX diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 4fa2115a2c..60b1bc20be 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -81,6 +81,7 @@ #include "platform/k053260.h" #include "platform/ted.h" #include "platform/c140.h" +#include "platform/es5503.h" #include "platform/pcmdac.h" #include "platform/dummy.h" #include "../ta-log.h" @@ -685,6 +686,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do dispatch=new DivPlatformC140; ((DivPlatformC140*)dispatch)->set219(true); break; + case DIV_SYSTEM_ES5503: + dispatch=new DivPlatformES5503; + break; case DIV_SYSTEM_PCM_DAC: dispatch=new DivPlatformPCMDAC; break; diff --git a/src/engine/engine.h b/src/engine/engine.h index ec8eb06a92..6c6ee89b38 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -382,7 +382,8 @@ enum DivChanTypes { DIV_CH_NOISE=2, DIV_CH_WAVE=3, DIV_CH_PCM=4, - DIV_CH_OP=5 + DIV_CH_OP=5, + DIV_CH_ES5503_VIRT = 6, }; extern const char* cmdName[]; diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index 9248773f95..7870ec7766 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -216,6 +216,14 @@ bool DivInstrumentES5506::operator==(const DivInstrumentES5506& other) { ); } +bool DivInstrumentES5503::operator==(const DivInstrumentES5503& other) { + return ( + _C(initial_osc_mode) && + _C(softpan_virtual_channel) && + _C(phase_reset_on_start) + ); +} + bool DivInstrumentSNES::operator==(const DivInstrumentSNES& other) { return ( _C(useEnv) && @@ -712,6 +720,14 @@ void DivInstrument::writeFeatureNE(SafeWriter* w) { FEATURE_END; } +void DivInstrument::writeFeatureE3(SafeWriter* w) { + FEATURE_BEGIN("E3"); + + w->writeC((es5503.initial_osc_mode << 6)|((uint8_t)es5503.softpan_virtual_channel << 5)|((uint8_t)es5503.phase_reset_on_start << 4)); + + FEATURE_END; +} + void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bool insName) { size_t blockStartSeek=0; size_t blockEndSeek=0; @@ -755,6 +771,7 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo bool featureES=false; bool featureX1=false; bool featureNE=false; + bool featureE3=false; bool checkForWL=false; @@ -968,6 +985,9 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo featureSM=true; featureSL=true; break; + case DIV_INS_ES5503: + featureE3=true; + break; case DIV_INS_MAX: break; @@ -1016,6 +1036,9 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo if (x1_010!=defaultIns.x1_010) { featureX1=true; } + if (es5503!=defaultIns.es5503) { + featureE3=true; + } } // check ins name @@ -1164,6 +1187,9 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo if (featureNE) { writeFeatureNE(w); } + if (featureE3) { + writeFeatureE3(w); + } if (fui && (featureSL || featureWL)) { w->write("EN",2); @@ -1818,6 +1844,18 @@ void DivInstrument::readFeatureNE(SafeReader& reader, short version) { READ_FEAT_END; } +void DivInstrument::readFeatureE3(SafeReader& reader, short version) { + READ_FEAT_BEGIN; + + uint8_t temp = reader.readC(); + + es5503.initial_osc_mode = temp >> 6; + es5503.softpan_virtual_channel = (temp >> 5) & 1; + es5503.phase_reset_on_start = (temp >> 4) & 1; + + READ_FEAT_END; +} + DivDataErrors DivInstrument::readInsDataNew(SafeReader& reader, short version, bool fui, DivSong* song) { unsigned char featCode[2]; bool volIsCutoff=false; @@ -1891,6 +1929,8 @@ DivDataErrors DivInstrument::readInsDataNew(SafeReader& reader, short version, b readFeatureX1(reader,version); } else if (memcmp(featCode,"NE",2)==0) { // NES (DPCM) readFeatureNE(reader,version); + } else if (memcmp(featCode,"E3",2)==0) { // ES5503 + readFeatureE3(reader,version); } else { if (song==NULL && (memcmp(featCode,"SL",2)==0 || (memcmp(featCode,"WL",2)==0))) { // nothing diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 3a7634432d..1726292df8 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -88,6 +88,7 @@ enum DivInstrumentType: unsigned short { DIV_INS_TED=52, DIV_INS_C140=53, DIV_INS_C219=54, + DIV_INS_ES5503=55, DIV_INS_MAX, DIV_INS_NULL }; @@ -820,6 +821,27 @@ struct DivInstrumentES5506 { envelope(Envelope()) {} }; +struct DivInstrumentES5503 { + unsigned char initial_osc_mode; + bool softpan_virtual_channel; + bool phase_reset_on_start; + + enum WaveTableLengths: unsigned char { + DIV_ES5503_WAVE_LENGTH_MAX=8, + }; + + bool operator==(const DivInstrumentES5503& other); + bool operator!=(const DivInstrumentES5503& other) { + return !(*this==other); + } + + DivInstrumentES5503(): + initial_osc_mode(0), softpan_virtual_channel(false), + phase_reset_on_start(true) { + + } +}; + struct DivInstrumentSNES { enum GainMode: unsigned char { GAIN_MODE_DIRECT=0, @@ -871,6 +893,7 @@ struct DivInstrument { DivInstrumentSoundUnit su; DivInstrumentES5506 es5506; DivInstrumentSNES snes; + DivInstrumentES5503 es5503; /** * these are internal functions. @@ -895,6 +918,7 @@ struct DivInstrument { void writeFeatureES(SafeWriter* w); void writeFeatureX1(SafeWriter* w); void writeFeatureNE(SafeWriter* w); + void writeFeatureE3(SafeWriter* w); void readFeatureNA(SafeReader& reader, short version); void readFeatureFM(SafeReader& reader, short version); @@ -915,6 +939,7 @@ struct DivInstrument { void readFeatureES(SafeReader& reader, short version); void readFeatureX1(SafeReader& reader, short version); void readFeatureNE(SafeReader& reader, short version); + void readFeatureE3(SafeReader& reader, short version); DivDataErrors readInsDataOld(SafeReader& reader, short version); DivDataErrors readInsDataNew(SafeReader& reader, short version, bool fui, DivSong* song); diff --git a/src/engine/platform/es5503.cpp b/src/engine/platform/es5503.cpp new file mode 100644 index 0000000000..b794577371 --- /dev/null +++ b/src/engine/platform/es5503.cpp @@ -0,0 +1,930 @@ +/** + * 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 "es5503.h" +#include "../engine.h" +#include "furIcons.h" +#include + +#define my_min(a, b) ((a) >= (b) ? (b) : (a)) + +const int ES5503_wave_lengths[DivInstrumentES5503::DIV_ES5503_WAVE_LENGTH_MAX] = {256, 512, 1024, 2048, 4096, 8192, 16384, 32768}; +uint8_t ES5503_wave_lengths_convert_back(uint32_t len) +{ + if((int)len <= ES5503_wave_lengths[0]) return 0; + + for(int j = 0; j < DivInstrumentES5503::DIV_ES5503_WAVE_LENGTH_MAX - 1; j++) + { + if((int)len > ES5503_wave_lengths[j] && (int)len <= ES5503_wave_lengths[j + 1]) + { + return j + 1; + } + } + + return 0; +} + +int jump_blocks(int actual_size) +{ + for(int i = 0; i < DivInstrumentES5503::DIV_ES5503_WAVE_LENGTH_MAX; i++) + { + if(actual_size < ES5503_wave_lengths[i]) + { + return 1 << i; + } + } + + return 0; +} + +//#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 (double)(chipClock / rate) + +#define CHIP_FREQBASE (894886.0) + +const char* regCheatSheetES5503[]={ + "CHx_FreqL", "x", + "CHx_FreqH", "20+x", + "CHx_Vol", "40+x", + "CHx_CurrSample", "60+x", + "CHx_WaveAddress", "80+x", + "CHx_Control", "A0+x", + "CHx_WaveSize", "C0+x", + "Osc_interrupt", "E0", + "Osc_enable", "E1", + "A/D_conversion_result", "E2", + NULL +}; + + +const char** DivPlatformES5503::getRegisterSheet() { + return regCheatSheetES5503; +} + +void DivPlatformES5503::acquire(short** buf, size_t len) { //the function where we actually fill the fucking audio buffer!!!!! + es5503.fill_audio_buffer(buf, len); + + while (!writes.empty()) { //do register writes + QueuedWrite w=writes.front(); + if(w.addr < 0x100) + { + es5503.write((uint8_t)w.addr,(uint8_t)w.val); + } + + regPool[w.addr]=w.val; + writes.pop(); + } +} + +void DivPlatformES5503::setFlags(const DivConfig& flags) { + chipClock=7159090U; //approx. 894886 * 8 Hz on Apple IIGS + + CHECK_CUSTOM_CLOCK; + + es5503.es5503_core_init(chipClock, this->oscBuf, 32); + + rate=(chipClock / 8) / (es5503.oscsenabled + 2); //26320 Hz for Apple IIGS card with all oscillators enabled + + for (int i=0; i<32; i++) { + oscBuf[i]->rate=rate; + } + + mono=flags.getBool("monoOutput",false); + + es5503.mono = mono; +} + +void DivPlatformES5503::changeNumOscs(uint8_t num_oscs) +{ + es5503.update_num_osc(oscBuf, num_oscs); + rWrite(0xe1, (num_oscs - 1) * 2); + rate=(chipClock / 8) / (es5503.oscsenabled + 2); + + for (int i=0; i<32; i++) { + oscBuf[i]->rate=rate; + } +} + +void DivPlatformES5503::writeSampleMemoryByte(unsigned int address, unsigned char value) +{ + if(es5503.sampleMem) + { + es5503.sampleMem[address] = value; + } +} + +void DivPlatformES5503::updateWave(int ch) +{ + if(!chan[ch].pcm) + { + for (unsigned int i=0; i> 8); + } + + //chan[ch].antiClickWavePos&=31; + if (chan[ch].active) { + if(chan[ch].softpan_channel) + { + uint8_t temp = isMuted[ch] ? 0 : chan[ch].outVol*chan[ch].panleft / 255; + rWrite(0x40+ch,temp); + temp = isMuted[ch] ? 0 : chan[ch].outVol*chan[ch].panright / 255; + rWrite(0x40+ch+1,temp); + } + + else + { + rWrite(0x40+ch,isMuted[ch] ? 0 : chan[ch].outVol); + } + } +} + +void DivPlatformES5503::tick(bool sysTick) { + for(int i = 0; i < 32; i++) + { + chan[i].std.next(); + if (chan[i].std.vol.had) { + chan[i].outVol=VOL_SCALE_LINEAR(chan[i].vol&255,MIN(255,chan[i].std.vol.val),255); + if (chan[i].pcm) { + // ignore for now + } else { + if(chan[i].softpan_channel) + { + uint8_t temp = chan[i].outVol * chan[i].panleft / 255; + rWrite(0x40 + i, isMuted[i] ? 0 : (temp)); + temp = chan[i].outVol * chan[i].panright / 255; + rWrite(0x40 + i + 1, isMuted[i] ? 0 : (temp)); + } + + else + { + rWrite(0x40 + i, isMuted[i] ? 0 : chan[i].outVol); + } + } + } + + if (NEW_ARP_STRAT) { + chan[i].handleArp(); + } else if (chan[i].std.arp.had) { + 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].std.duty.had && !chan[i].pcm) { + chan[i].osc_mode = chan[i].std.duty.val & 3; + if(chan[i].softpan_channel) + { + rWrite(0xA0 + i, (chan[i].osc_mode << 1) | (chan[i].output << 4)); //update osc. mode, do not disturb the osc + } + + else + { + rWrite(0xA0 + i, (chan[i].osc_mode << 1) | (chan[i].output << 4)); + rWrite(0xA0 + i + 1, (chan[i].osc_mode << 1) | (chan[i + 1].output << 4)); + } + } + + if (chan[i].std.wave.had && !chan[i].pcm) { + if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) { + chan[i].wave=chan[i].std.wave.val; + chan[i].ws.changeWave1(chan[i].wave); + if (!chan[i].keyOff) chan[i].keyOn=true; + } + } + + if (chan[i].std.phaseReset.had && !chan[i].pcm && chan[i].std.phaseReset.val == 1) { + if(chan[i].softpan_channel) + { + rWrite(0xA0 + i, (chan[i].osc_mode << 1) | 1 | (chan[i].output << 4)); //writing 1 resets acc + rWrite(0xA0 + i, (chan[i].osc_mode << 1) | (chan[i].output << 4)); //writing 0 forces the reset + rWrite(0xA0 + i + 1, (chan[i].osc_mode << 1) | 1 | (chan[i + 1].output << 4)); //writing 1 resets acc + rWrite(0xA0 + i + 1, (chan[i].osc_mode << 1) | (chan[i + 1].output << 4)); //writing 0 forces the reset + } + + else + { + rWrite(0xA0 + i, (chan[i].osc_mode << 1) | 1 | (chan[i].output << 4)); //writing 1 resets acc + rWrite(0xA0 + i, (chan[i].osc_mode << 1) | (chan[i].output << 4)); //writing 0 forces the reset + } + } + + if (chan[i].std.ex1.had) { + if(chan[i].softpan_channel) + { + rWrite(0x80 + i, chan[i].std.ex1.val); + rWrite(0x80 + i + 1, chan[i].std.ex1.val); + } + + else + { + rWrite(0x80 + i, chan[i].std.ex1.val); + } + } + + if (chan[i].std.ex2.had) { + chan[i].output = chan[i].std.ex2.val; + rWrite(0xa0 + i, (chan[i].osc_mode << 1) | (chan[i].output << 4)); + } + + if (chan[i].std.panL.had && chan[i].softpan_channel) { + chan[i].panleft = chan[i].std.panL.val; + uint8_t temp = chan[i].outVol * chan[i].panleft / 255; + rWrite(0x40 + i, isMuted[i] ? 0 : (temp)); + } + + if (chan[i].std.panR.had && chan[i].softpan_channel) { + chan[i].panright = chan[i].std.panR.val; + uint8_t temp = chan[i].outVol * chan[i].panright / 255; + rWrite(0x40 + i + 1, isMuted[i] ? 0 : (temp)); + } + + if (chan[i].active && !chan[i].pcm) { + if (chan[i].ws.tick()) { + updateWave(i); + } + } + + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { + DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_ES5503); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,0,chan[i].pitch2,(double)chipClock /** (32 + 2) / (es5503.oscsenabled + 2)*/,CHIP_FREQBASE * 130.81 * 2 / 211.0); //TODO: why freq calc is wrong? + if (chan[i].freq<0) chan[i].freq=0; + if (chan[i].freq>0xffff) chan[i].freq=0xffff; + + if(chan[i].softpan_channel) + { + chan[i + 1].freq = chan[i].freq; + rWrite(i, chan[i].freq&0xff); + rWrite(0x20+i, chan[i].freq>>8); + rWrite(i+1, chan[i].freq&0xff); + rWrite(0x20+i+1, chan[i].freq>>8); + } + + else + { + rWrite(i, chan[i].freq&0xff); + rWrite(0x20+i, chan[i].freq>>8); + } + + if (chan[i].keyOn) { + if(chan[i].softpan_channel) + { + uint8_t temp = chan[i].outVol * chan[i].panleft / 255; + rWrite(0x40+i, isMuted[i] ? 0 : temp); //set volume + rWrite(0x80+i, chan[i].wave_pos >> 8); //set wave pos + rWrite(0xa0+i, (chan[i].osc_mode << 1) | (chan[i].output << 4)); + rWrite(0xc0+i, (ES5503_wave_lengths_convert_back(chan[i].wave_size) << 3) | chan[i].address_bus_res); //set wave len + + temp = chan[i].outVol * chan[i].panright / 255; + rWrite(0x40+i+1, isMuted[i] ? 0 : temp); //set volume + rWrite(0x80+i+1, chan[i].wave_pos >> 8); //set wave pos + rWrite(0xa0+i+1, (chan[i].osc_mode << 1) | (chan[i + 1].output << 4)); + rWrite(0xc0+i+1, (ES5503_wave_lengths_convert_back(chan[i].wave_size) << 3) | chan[i].address_bus_res); //set wave len + + if(ins->es5503.phase_reset_on_start) + { + rWrite(0xA0 + i, (chan[i].osc_mode << 1) | 1 | (chan[i].output << 4)); //writing 1 resets acc + rWrite(0xA0 + i, (chan[i].osc_mode << 1) | (chan[i].output << 4)); //writing 0 forces the reset + rWrite(0xA0 + i + 1, (chan[i].osc_mode << 1) | 1 | (chan[i + 1].output << 4)); //writing 1 resets acc + rWrite(0xA0 + i + 1, (chan[i].osc_mode << 1) | (chan[i + 1].output << 4)); //writing 0 forces the reset + } + } + + else + { + rWrite(0x40+i, isMuted[i] ? 0 : chan[i].outVol); //set volume + rWrite(0x80+i, chan[i].wave_pos >> 8); //set wave pos + rWrite(0xa0+i, (chan[i].osc_mode << 1) | (chan[i].output << 4)); + rWrite(0xc0+i, (ES5503_wave_lengths_convert_back(chan[i].wave_size) << 3) | chan[i].address_bus_res); //set wave len + + if(ins->es5503.phase_reset_on_start) + { + rWrite(0xA0 + i, (chan[i].osc_mode << 1) | 1 | (chan[i].output << 4)); //writing 1 resets acc + rWrite(0xA0 + i, (chan[i].osc_mode << 1) | (chan[i].output << 4)); //writing 0 forces the reset + } + } + } + if (chan[i].keyOff) { + if(chan[i].softpan_channel) + { + rWrite(0xA0+i, 1 | (chan[i].output << 4)); //halt oscillator + rWrite(0xA0+i+1, 1 | (chan[i + 1].output << 4)); //halt oscillator + } + + else + { + rWrite(0xA0+i, 1 | (chan[i].output << 4)); //halt oscillator + } + } + + if(chan[i].softpan_channel) + { + if (chan[i].keyOn) chan[i+1].keyOn=false; + if (chan[i].keyOff) chan[i+1].keyOff=false; + } + + if (chan[i].keyOn) chan[i].keyOn=false; + if (chan[i].keyOff) chan[i].keyOff=false; + chan[i].freqChanged=false; + } + } +} + +int DivPlatformES5503::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_ES5503); + chan[c.chan].softpan_channel = ins->es5503.softpan_virtual_channel && !(c.chan & 1); //only works on odd channel + chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:255; + if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) { + chan[c.chan].pcm=true; + } + else + { + chan[c.chan].pcm=false; + chan[c.chan].address_bus_res = 0b000; + } + + chan[c.chan].osc_mode = ins->es5503.initial_osc_mode; + + if (chan[c.chan].pcm) { + if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) { + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].sample=ins->amiga.getSample(c.value); + c.value=ins->amiga.getFreq(c.value); + } + if (chan[c.chan].sample<0) { + chan[c.chan].sample=-1; + break; + } else { + + } + } + + if(chan[c.chan].sample >= 0) + { + chan[c.chan].wave_size = sampleLengths[chan[c.chan].sample]; + chan[c.chan].wave_pos = sampleOffsets[chan[c.chan].sample]; + + if(chan[c.chan].wave_size >= 1024) + { + chan[c.chan].address_bus_res = 0b011; + } + } + + if(chan[c.chan].softpan_channel) + { + chan[c.chan + 1].osc_mode = ins->es5503.initial_osc_mode; + + if(chan[c.chan].sample >= 0) + { + chan[c.chan + 1].wave_size = sampleLengths[chan[c.chan].sample]; + chan[c.chan + 1].wave_pos = sampleOffsets[chan[c.chan].sample]; + + if(chan[c.chan].wave_size >= 1024) + { + chan[c.chan + 1].address_bus_res = 0b011; + } + } + } + } + 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; + + if(chan[c.chan].softpan_channel) + { + chan[c.chan + 1].active=true; + chan[c.chan + 1].keyOn=true; + + chan[c.chan].output = 0; + chan[c.chan + 1].output = 1; //force-reset outputs so softpanned channel works as expected + } + + if (!chan[c.chan].pcm) + { + chan[c.chan].wave_size = 0; + + if(chan[c.chan].softpan_channel) + { + chan[c.chan + 1].wave_size = 0; + chan[c.chan + 1].osc_mode = ins->es5503.initial_osc_mode; + } + } + + chan[c.chan].macroInit(ins); + if (!chan[c.chan].std.vol.will) { + chan[c.chan].outVol=chan[c.chan].vol; + } + + if (!chan[c.chan].pcm) + { + if (chan[c.chan].wave<0) + { + chan[c.chan].wave=0; + chan[c.chan].ws.changeWave1(chan[c.chan].wave); + } + + chan[c.chan].ws.init(ins,256,255,chan[c.chan].insChanged); + chan[c.chan].insChanged=false; + } + + break; + } + case DIV_CMD_NOTE_OFF: + chan[c.chan].sample=-1; + chan[c.chan].pcm=false; //??? + 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 && !chan[c.chan].pcm) { + if(chan[c.chan].softpan_channel) + { + uint8_t temp = chan[c.chan].outVol * chan[c.chan].panleft / 255; + rWrite(0x40 + c.chan, isMuted[c.chan] ? 0 : temp); + temp = chan[c.chan].outVol * chan[c.chan].panright / 255; + rWrite(0x40 + c.chan + 1, isMuted[c.chan] ? 0 : temp); + } + + else + { + rWrite(0x40 + c.chan, isMuted[c.chan] ? 0 : 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_PANNING: { + if(chan[c.chan].softpan_channel) + { + chan[c.chan].panleft = c.value; + chan[c.chan].panright = c.value2; + + uint8_t temp = chan[c.chan].outVol * chan[c.chan].panleft / 255; + rWrite(0x40+c.chan, isMuted[c.chan] ? 0 : temp); + temp = chan[c.chan].outVol * chan[c.chan].panright / 255; + rWrite(0x40+c.chan+1, isMuted[c.chan] ? 0 : temp); + } + break; + } + case DIV_CMD_PITCH: + chan[c.chan].pitch=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_ES5503)); + } + 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_NOTE_PORTA: { + int destFreq=NOTE_FREQUENCY(c.value2); + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value; + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value; + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_ES5503_NUM_ENABLED_OSC: { + if(c.value >= 2 && c.value <= 32) + { + changeNumOscs(c.value); + } + break; + } + case DIV_CMD_ES5503_OSC_OUTPUT: { + if(c.value <= 7) + { + chan[c.chan].output = c.value; + rWrite(0xa0+c.chan, (chan[c.chan].osc_mode << 1) | (chan[c.chan].output << 4)); + } + break; + } + case DIV_CMD_ES5503_WAVE_LENGTH: { + if(c.value <= 7) + { + chan[c.chan].wave_size = ES5503_wave_lengths[c.value]; + rWrite(0xc0+c.chan, (c.value << 3) | chan[c.chan].address_bus_res /*lowest acc resolution*/); //set wave len + + if(chan[c.chan].softpan_channel) + { + chan[c.chan + 1].wave_size = ES5503_wave_lengths[c.value]; + rWrite(0xc0+c.chan+1, (c.value << 3) | chan[c.chan].address_bus_res /*lowest acc resolution*/); //set wave len + } + } + break; + } + case DIV_CMD_ES5503_WAVE_POS: { + chan[c.chan].wave_pos = c.value << 8; + rWrite(0x80+c.chan, c.value); + + if(chan[c.chan].softpan_channel) + { + chan[c.chan + 1].wave_pos = c.value << 8; + rWrite(0x80+c.chan+1, c.value); + } + break; + } + case DIV_CMD_ES5503_OSC_MODE: { + if(c.value <= 3) + { + chan[c.chan].osc_mode = c.value; + rWrite(0xa0+c.chan, (chan[c.chan].osc_mode << 1) | (chan[c.chan].output << 4)); + + if(chan[c.chan].softpan_channel) + { + chan[c.chan+1].osc_mode = c.value; + rWrite(0xa0+c.chan+1, (chan[c.chan].osc_mode << 1) | (chan[c.chan+1].output << 4)); //don't forget to preserve "slave" chan output + } + } + break; + } + case DIV_CMD_GET_VOLMAX: + return 255; + 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 DivPlatformES5503::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; +} + +void DivPlatformES5503::forceIns() { + for (int i=0; i<32; i++) { + chan[i].insChanged=true; + chan[i].freqChanged=true; + updateWave(i); + + if(chan[i].softpan_channel) + { + uint8_t temp = chan[i].outVol * chan[i].panleft / 255; + rWrite(0x40+i,isMuted[i]?0:(temp)); + temp = chan[i].outVol * chan[i].panright / 255; + rWrite(0x40+i+1,isMuted[i]?0:(temp)); + } + + else + { + rWrite(0x40+i,isMuted[i]?0:chan[i].outVol); + } + } +} + +void* DivPlatformES5503::getChanState(int ch) { + return &chan[ch]; +} + +DivMacroInt* DivPlatformES5503::getChanMacroInt(int ch) { + return &chan[ch].std; +} + +const void* DivPlatformES5503::getSampleMem(int index) { + return index == 0 ? es5503.sampleMem : NULL; +} + +size_t DivPlatformES5503::getSampleMemCapacity(int index) +{ + return index == 0 ? 65536 : 0; +} + +bool DivPlatformES5503::isSampleLoaded(int index, int sample) { + if (index!=0) return false; + if (sample<0 || sample>255) return false; + return sampleLoaded[sample]; +} + +int DivPlatformES5503::is_enough_continuous_memory(int actualLength) +{ + int num_blocks = actualLength / 256 + 1; + int result = -1; + + int jumpbl = jump_blocks(actualLength); //restrict possible starting positions to valid ones (depends on sample size) + + for(int i = 0; i < 256; i += jumpbl) + { + result = i; //index of 1st block + + for(int j = 0; j < num_blocks; j++) + { + if(!free_block[i + j]) //block is already occupied! + { + result = -1; + } + } + + if(result != -1) return result; //we found the 1st block + } + + return result; //haven't found continuous memory for this sample so return -1 +} + +int DivPlatformES5503::count_free_blocks() +{ + int count = 0; + + for(int i = 0; i < 256; i++) + { + if(free_block[i]) + { + count++; + } + } + + return count; +} + +size_t DivPlatformES5503::getSampleMemUsage(int index) { + return index == 0 ? (getSampleMemCapacity() - count_free_blocks() * 256) : 0; +} + +void DivPlatformES5503::renderSamples(int sysID) { + memset(es5503.sampleMem,0,getSampleMemCapacity()); + memset(sampleOffsets,0,256*sizeof(uint32_t)); + memset(sampleLoaded,0,256*sizeof(bool)); + memset(free_block,1,256*sizeof(bool)); + memset(sampleLengths,0,256*sizeof(uint32_t)); + + for(int size = 7; size > 0; size--) //first we place the longest samples then descend to shorter ones (bc placement limitations for longer samples) + { + int maxsize = ES5503_wave_lengths[size]; + int minsize = ES5503_wave_lengths[size - 1]; + + for (int i = 0; i < parent->song.sampleLen; i++) + { + DivSample* s = parent->song.sample[i]; + + if (!s->renderOn[0][sysID]) + { + sampleOffsets[i] = 0; + continue; + } + + int length = s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT); + + if(length < 0) break; + + int actualLength = length + 8; //8 more bytes for trailing zeros + int start_pos = 0; + + if (size != 1) + { + if (actualLength < minsize || actualLength >= maxsize) goto end; + } + + else //for samples with <= 256 steps length + { + if (actualLength >= maxsize) goto end; + } + + + start_pos = is_enough_continuous_memory(actualLength); + + if(start_pos == -1) + { + logW("out of ES5503 PCM memory for sample %d!", i); + break; + } + + else + { + int actual_start_pos = start_pos * 256; + + for(int ss = 0; ss < length; ss++) + { + uint8_t val = (uint8_t)s->data8[ss] + 0x80; + if (val == 0) val = 1; + + es5503.sampleMem[actual_start_pos + ss] = val; + //es5503.sampleMem[actual_start_pos + ss] = ((actual_start_pos + ss) & 1) ? 0xff : 0x10; + } + + //memset(&es5503.sampleMem[actual_start_pos + length], 0, 8); //add at least 8 zeros to the end + + int num_blocks = actualLength / 256 + 1; + + sampleLengths[i] = actualLength - 8; + sampleLoaded[i] = true; + sampleOffsets[i] = actual_start_pos; + + logW("sample %d length %d startpos %d", i, sampleLengths[i], sampleOffsets[i]); + + for(int b = start_pos; b < start_pos + num_blocks; b++) + { + free_block[b] = false; + } + } + + end:; + } + } +} + +DivDispatchOscBuffer* DivPlatformES5503::getOscBuffer(int ch) { + return oscBuf[ch]; +} + +unsigned char* DivPlatformES5503::getRegisterPool() { + return regPool; +} + +int DivPlatformES5503::getRegisterPoolSize() { + return 256; +} + +void DivPlatformES5503::reset() { + writes.clear(); + memset(regPool,0,256); + if (dumpWrites) { + addWrite(0xffffffff,0); //do reset + } + //memset(es5503.sampleMem,0,es5503.sampleMemLen); + for (uint8_t i=0; i<32; i++) { + chan[i]=DivPlatformES5503::Channel(); + chan[i].std.setEngine(parent); + chan[i].ws.setEngine(parent); + chan[i].ws.init(NULL,256,255,false); + + chan[i].output = (i & 1) ? 1 : 0; //odd channels are left, even are right + chan[i].softpan_channel = false; + } + + curChan=-1; + // set volume to zero and reset some other shit + for(uint8_t i = 0; i < 0x9f; i++) + { + es5503.write(i,0); //using es5503.write() instead of rWrite() so these aren't included in vgm (instead we use the addWrite(0xffffffff,0) for VGM export routine) + //rWrite(i,0); + } + + for(uint8_t i = 0xc0; i < 0xdf; i++) + { + es5503.write(i,0); + } + + //write to num_osc register and enable all 32 oscillators + es5503.write(0xe1,62); + + for(uint8_t i = 0xa0; i < 0xbf; i++) //odd channels are left, even are right + { + es5503.write(i, (chan[i - 0xa0].output << 4) | 1); //0<<4=left, 1<<4=right, 1=disable oscillator + } +} + +int DivPlatformES5503::getOutputCount() { + return mono ? 1 : 8; +} + +bool DivPlatformES5503::keyOffAffectsArp(int ch) { + return true; +} + +void DivPlatformES5503::notifyWaveChange(int wave) { + for (int i=0; i<32; i++) { + if (chan[i].wave==wave) { + chan[i].ws.changeWave1(wave); + updateWave(i); + } + } +} + +void DivPlatformES5503::notifyInsChange(int ins) { + for (int i=0; i<32; i++) { + if (chan[i].ins==ins) { + chan[i].insChanged=true; + } + } +} + +void DivPlatformES5503::notifyInsDeletion(void* ins) { + for (int i=0; i<32; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformES5503::poke(unsigned int addr, unsigned short val) { + rWrite(addr,val); +} + +void DivPlatformES5503::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); +} + +int DivPlatformES5503::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { + memset(this->regPool, 0, sizeof(this->regPool[0]) * 256); + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + for (int i=0; i<32; i++) { + isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; + } + setFlags(flags); + reset(); + return 32; +} + +DivChannelPair DivPlatformES5503::getPaired(int ch) { + if (chan[ch].softpan_channel && !(ch&1)) + { + return DivChannelPair("SoftPan",ch+1); + } + return DivChannelPair(); +} + +void DivPlatformES5503::quit() { + + es5503.es5503_core_free(); + for (int i=0; i<32; i++) { + delete oscBuf[i]; + } +} diff --git a/src/engine/platform/es5503.h b/src/engine/platform/es5503.h new file mode 100644 index 0000000000..b31cea98dd --- /dev/null +++ b/src/engine/platform/es5503.h @@ -0,0 +1,128 @@ +/** + * 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. + */ + +#pragma once + +#ifndef _ES5503_H +#define _ES5503_H + +#include "../dispatch.h" +#include "../../fixedQueue.h" +#include "../waveSynth.h" +#include "sound/es5503.h" + +class DivPlatformES5503: public DivDispatch { + struct Channel: public SharedChannel { + int sample; + unsigned int panleft, panright; + bool pcm; + int16_t wave; + int macroVolMul; + unsigned int wave_pos, wave_size; + unsigned char osc_mode; + bool softpan_channel; + uint8_t output; //8 outputs on the chip, but Apple IIGS seems to use only two: 0=left, 1=right + uint8_t address_bus_res; //basically octave shifter, can be automatically manipulated to have better frequency range + //e.g. frequency higher than 0xffff => try to use lower acc bits and recalc until freq is lower than 0xffff (lower this number), + //frequency lower than let's say 0x100 => try to use higher acc bits in the same fashion (higher the number until freq is higher than + //0x100; if you can't go higher stop at highest (here 0b111) and recalc frequecny, thus you have the higher precision possible) + DivWaveSynth ws; + int16_t previous_sample; + int32_t previous_sample_pos; //these are needed to reduce unnecessary memory rewrites + Channel(): + SharedChannel(255), + sample(-1), + panleft(255), + panright(255), + pcm(false), + wave(-1), + macroVolMul(255), + wave_pos(0), + wave_size(256), + osc_mode(0), + output(0), + address_bus_res(0b010), + previous_sample(-1), + previous_sample_pos(-1) {} + }; + Channel chan[32]; + DivDispatchOscBuffer* oscBuf[32]; + bool isMuted[32]; + bool antiClickEnabled; + bool mono; + struct QueuedWrite { + unsigned int addr; + unsigned int val; + QueuedWrite(): addr(0), val(0) {} + QueuedWrite(unsigned int a, unsigned int v): addr(a), val(v) {} + }; + FixedQueue writes; + unsigned char lastPan; + + int curChan; + unsigned char sampleBank, lfoMode, lfoSpeed; + + uint32_t sampleOffsets[256]; + bool sampleLoaded[256]; + uint32_t sampleLengths[256]; + uint32_t sampleMemLen; + bool free_block[256]; + + es5503_core es5503; + unsigned char regPool[256]; + + void updateWave(int ch); + int is_enough_continuous_memory(int actualLength); + int count_free_blocks(); + friend void putDispatchChip(void*,int); + friend void putDispatchChan(void*,int,int); + public: + void acquire(short** buf, size_t len); + void changeNumOscs(uint8_t num_oscs); + 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 writeSampleMemoryByte(unsigned int address, unsigned char value); + void tick(bool sysTick=true); + void muteChannel(int ch, bool mute); + int getOutputCount(); + bool keyOffAffectsArp(int ch); + void setFlags(const DivConfig& flags); + DivChannelPair getPaired(int ch); + void notifyWaveChange(int wave); + void notifyInsChange(int ins); + void notifyInsDeletion(void* ins); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); + void quit(); + const void* getSampleMem(int index = 0); + size_t getSampleMemCapacity(int index = 0); + bool isSampleLoaded(int index, int sample); + size_t getSampleMemUsage(int index = 0); + void renderSamples(int chipID); +}; + +#endif diff --git a/src/engine/platform/sound/es5503.cpp b/src/engine/platform/sound/es5503.cpp new file mode 100644 index 0000000000..86bfa9236f --- /dev/null +++ b/src/engine/platform/sound/es5503.cpp @@ -0,0 +1,475 @@ +// license:BSD-3-Clause +// copyright-holders:R. Belmont +/* + + ES5503 - Ensoniq ES5503 "DOC" emulator v2.3 + By R. Belmont. + + Copyright R. Belmont. + + History: the ES5503 was the next design after the famous C64 "SID" by Bob Yannes. + It powered the legendary Mirage sampler (the first affordable pro sampler) as well + as the ESQ-1 synth/sequencer. The ES5505 (used in Taito's F3 System) and 5506 + (used in the "Soundscape" series of ISA PC sound cards) followed on a fundamentally + similar architecture. + + Bugs: On the real silicon, the uppermost enabled oscillator contributes to the output 3 times. + This is likely why the Apple IIgs system software doesn't let you use oscillators 30 and 31. + + Additionally, in "swap" mode, there's one cycle when the switch takes place where the + oscillator's output is 0x80 (centerline) regardless of the sample data. This can + cause audible clicks and a general degradation of audio quality if the correct sample + data at that point isn't 0x80 or very near it. + + Changes: + 0.2 (RB) - improved behavior for volumes > 127, fixes missing notes in Nucleus & missing voices in Thexder + 0.3 (RB) - fixed extraneous clicking, improved timing behavior for e.g. Music Construction Set & Music Studio + 0.4 (RB) - major fixes to IRQ semantics and end-of-sample handling. + 0.5 (RB) - more flexible wave memory hookup (incl. banking) and save state support. + 1.0 (RB) - properly respects the input clock + 2.0 (RB) - C++ conversion, more accurate oscillator IRQ timing + 2.1 (RB) - Corrected phase when looping; synthLAB, Arkanoid, and Arkanoid II no longer go out of tune + 2.1.1 (RB) - Fixed issue introduced in 2.0 where IRQs were delayed + 2.1.2 (RB) - Fixed SoundSmith POLY.SYNTH inst where one-shot on the even oscillator and swap on the odd should loop. + Conversely, the intro voice in FTA Delta Demo has swap on the even and one-shot on the odd and doesn't + want to loop. + 2.1.3 (RB) - Fixed oscillator enable register off-by-1 which caused everything to be half a step sharp. + 2.2 (RB) - More precise one-shot even/swap odd behavior from hardware observations with Ian Brumby's SWAPTEST. + 2.3 (RB) - Sync & AM modes added, emulate the volume glitch for the highest-numbered enabled oscillator. +*/ + +//additional modifications by LTVA for Furnace tracker + +#include "es5503.h" + +#include "../../../ta-log.h" + +// useful constants +static constexpr uint16_t wavesizes[8] = { 256, 512, 1024, 2048, 4096, 8192, 16384, 32768 }; +static constexpr uint32_t wavemasks[8] = { 0x1ff00, 0x1fe00, 0x1fc00, 0x1f800, 0x1f000, 0x1e000, 0x1c000, 0x18000 }; +static constexpr uint32_t accmasks[8] = { 0xff, 0x1ff, 0x3ff, 0x7ff, 0xfff, 0x1fff, 0x3fff, 0x7fff }; +static constexpr int resshifts[8] = { 9, 10, 11, 12, 13, 14, 15, 16 }; + +void es5503_core::es5503_core_init(uint32_t clock, DivDispatchOscBuffer** oscBuf, uint8_t oscsenabled) +{ + memset(this, 0, sizeof(*this)); + // The number here is the number of oscillators to enable -1 times 2. You can never + // have zero oscilllators enabled. So a value of 62 enables all 32 oscillators. + this->oscsenabled = oscsenabled; + this->clock = clock; + output_rate = (clock / 8) / (oscsenabled + 2); + + sampleMemLen = 65536 << 1; + sampleMem = new unsigned char[sampleMemLen]; + memset(sampleMem, 0, sampleMemLen * sizeof(unsigned char)); + + output_channels = 8; //fixed value because real chip seems to have 4 pins for addressing the output to 16 different channels; however, the datasheet suggests to use only 8. + //Thus MSB can be used to switch memory banks, which is used in Ensoniq SQ80 synthesizer. Apple IIGS uses only one bank, though. + + for(int i = 0; i < oscsenabled; i++) + { + this->oscBuf[i] = oscBuf[i]; + } +} + +void es5503_core::es5503_core_free() +{ + if (sampleMem != NULL) + { + delete[] sampleMem; + sampleMem = NULL; + sampleMemLen = 0; + } +} + +void es5503_core::update_num_osc(DivDispatchOscBuffer** oscBuf, uint8_t oscsenabled) +{ + output_rate = (clock / 8) / (oscsenabled + 2); + + for(int i = 0; i < 32; i++) + { + this->oscBuf[i] = oscBuf[i]; + } +} + +uint8_t es5503_core::read(uint8_t offset) +{ + uint8_t retval; + int i; + + //m_stream->update(); + + if (offset < 0xe0) + { + int osc = offset & 0x1f; + + switch(offset & 0xe0) + { + case 0: // freq lo + return (oscillators[osc].freq & 0xff); + + case 0x20: // freq hi + return (oscillators[osc].freq >> 8); + + case 0x40: // volume + return oscillators[osc].vol; + + case 0x60: // data + return oscillators[osc].data; + + case 0x80: // wavetable pointer + return (oscillators[osc].wavetblpointer>>8) & 0xff; + + case 0xa0: // oscillator control + return oscillators[osc].control; + + case 0xc0: // bank select / wavetable size / resolution + retval = 0; + if (oscillators[osc].wavetblpointer & 0x10000) + { + retval |= 0x40; + } + + retval |= (oscillators[osc].wavetblsize<<3); + retval |= oscillators[osc].resolution; + return retval; + } + } + else // global registers + { + switch (offset) + { + case 0xe0: // interrupt status + retval = rege0; + + //m_irq_func(0); + + // scan all oscillators + for (i = 0; i < oscsenabled; i++) + { + if (oscillators[i].irqpend) + { + // signal this oscillator has an interrupt + retval = i<<1; + + rege0 = retval | 0x80; + + // and clear its flag + oscillators[i].irqpend = 0; + break; + } + } + + // if any oscillators still need to be serviced, assert IRQ again immediately + for (i = 0; i < oscsenabled; i++) + { + if (oscillators[i].irqpend) + { + //m_irq_func(1); + break; + } + } + + return retval | 0x41; + + case 0xe1: // oscillator enable + return (oscsenabled - 1) << 1; + + case 0xe2: // A/D converter + return 0;//m_adc_func(); + } + } + + return 0; +} + +void es5503_core::write(uint8_t offset, uint8_t data) +{ + if (offset < 0xe0) + { + int osc = offset & 0x1f; + + switch(offset & 0xe0) + { + case 0: // freq lo + oscillators[osc].freq &= 0xff00; + oscillators[osc].freq |= data; + break; + + case 0x20: // freq hi + oscillators[osc].freq &= 0x00ff; + oscillators[osc].freq |= (data<<8); + break; + + case 0x40: // volume + oscillators[osc].vol = data; + break; + + case 0x60: // data - ignore writes + break; + + case 0x80: // wavetable pointer + oscillators[osc].wavetblpointer = (data<<8); + break; + + case 0xa0: // oscillator control + // key on? + if ((oscillators[osc].control & 1) && (!(data&1))) + { + oscillators[osc].accumulator = 0; + } + oscillators[osc].control = data; + break; + + case 0xc0: // bank select / wavetable size / resolution + if (data & 0x40) // bank select - not used on the Apple IIgs + { + oscillators[osc].wavetblpointer |= 0x10000; + } + else + { + oscillators[osc].wavetblpointer &= 0xffff; + } + + oscillators[osc].wavetblsize = ((data>>3) & 7); + oscillators[osc].wtsize = wavesizes[oscillators[osc].wavetblsize]; + oscillators[osc].resolution = (data & 7); + break; + } + } + + else // global registers + { + switch (offset) + { + case 0xe0: // interrupt status + break; + + case 0xe1: // oscillator enable + // The number here is the number of oscillators to enable -1 times 2. You can never + // have zero oscilllators enabled. So a value of 62 enables all 32 oscillators. + oscsenabled = ((data>>1) & 0x1f) + 1; + //notify_clock_changed(); + break; + + case 0xe2: // A/D converter + break; + } + } +} + +uint8_t es5503_core::read_byte(uint32_t offset) +{ + if (offset < sampleMemLen && sampleMem != NULL) + { + return sampleMem[offset]; + } + + return 0; +} + +// halt_osc: handle halting an oscillator +// onum = oscillator # +// type = 1 for 0 found in sample data, 0 for hit end of table size +void es5503_core::halt_osc(int onum, int type, uint32_t *accumulator, int resshift) +{ + ES5503Osc *pOsc = &oscillators[onum]; + ES5503Osc *pPartner = &oscillators[onum^1]; + int mode = (pOsc->control>>1) & 3; + const int partnerMode = (pPartner->control>>1) & 3; + + // check for sync mode + if (mode == MODE_SYNCAM) + { + if (!(onum & 1)) + { + // we're even, so if the odd oscillator 1 below us is playing, + // restart it. + if (!(oscillators[onum - 1].control & 1)) + { + oscillators[onum - 1].accumulator = 0; + } + } + + // loop this oscillator for both sync and AM + mode = MODE_FREE; + } + + // if 0 found in sample data or mode is not free-run, halt this oscillator + if ((mode != MODE_FREE) || (type != 0)) + { + pOsc->control |= 1; + } + else // preserve the relative phase of the oscillator when looping + { + uint16_t wtsize = pOsc->wtsize - 1; + *accumulator -= (wtsize << resshift); + } + + // if we're in swap mode, start the partner + if (mode == MODE_SWAP) + { + pPartner->control &= ~1; // clear the halt bit + pPartner->accumulator = 0; // and make sure it starts from the top (does this also need phase preservation?) + } + else + { + // if we're not swap and we're the even oscillator of the pair and the partner's swap + // but we aren't, we retrigger (!!!) Verified on IIgs hardware. + if ((partnerMode == MODE_SWAP) && ((onum & 1)==0)) + { + pOsc->control &= ~1; + + // preserve the phase in this case too + uint16_t wtsize = pOsc->wtsize - 1; + *accumulator -= (wtsize << resshift); + } + } + // IRQ enabled for this voice? + if (pOsc->control & 0x08) + { + pOsc->irqpend = 1; + + //m_irq_func(1); + } +} + +void es5503_core::put_in_buffer(int32_t value, uint32_t pos, uint32_t chan, short** buf) +{ + ES5503Osc *pOsc = &oscillators[chan]; + uint8_t output = (pOsc->control >> 4) & (output_channels - 1); + + if (mono) output = 0; + + buf[output][pos] += value / 8; +} + +void es5503_core::fill_audio_buffer(short** buf, size_t len) //fill audio buffer +{ + for(int ii = 0; ii < (mono ? 1 : 8); ii++) + { + memset(buf[ii], 0, len * sizeof(buf[0][0])); + } + + uint32_t osc, snum; + uint32_t ramptr; + uint32_t samples = len; + + for (snum = 0; snum < samples; snum++) + { + for (osc = 0; osc < oscsenabled; osc++) + { + ES5503Osc *pOsc = &oscillators[osc]; + + uint8_t output_channel = (pOsc->control >> 4) & (output_channels - 1); + + if (!(pOsc->control & 1)) + { + uint32_t wtptr = pOsc->wavetblpointer & wavemasks[pOsc->wavetblsize]; + uint32_t altram = 0; + uint32_t acc = pOsc->accumulator; + uint16_t wtsize = pOsc->wtsize - 1; + uint8_t ctrl = pOsc->control; + uint16_t freq = pOsc->freq; + int16_t vol = pOsc->vol; + int8_t data = -128; + int resshift = resshifts[pOsc->resolution] - pOsc->wavetblsize; + uint32_t sizemask = accmasks[pOsc->wavetblsize]; + int mode = (pOsc->control>>1) & 3; + + int32_t curr_sample = 0; + + altram = acc >> resshift; + ramptr = altram & sizemask; + + acc += freq; + + // channel strobe is always valid when reading; this allows potentially banking per voice + m_channel_strobe = (ctrl>>4) & 0xf; + data = (int32_t)read_byte(ramptr + wtptr) ^ 0x80; + + if (read_byte(ramptr + wtptr) == 0x00) + { + halt_osc(osc, 1, &acc, resshift); + } + else + { + if (mode != MODE_SYNCAM) + { + put_in_buffer(data * vol, snum, osc, buf); + + curr_sample += data * vol; + + if (output_channel == (output_channels - 1)) + { + put_in_buffer(data * vol, snum, osc, buf); + put_in_buffer(data * vol, snum, osc, buf); + curr_sample += data * vol; + curr_sample += data * vol; + } + } + else + { + // if we're odd, we play nothing ourselves + if (osc & 1) + { + if (osc < 31) + { + // if the next oscillator up is playing, it's volume becomes our control + if (!(oscillators[osc + 1].control & 1)) + { + oscillators[osc + 1].vol = data ^ 0x80; + } + } + } + else // hard sync, both oscillators play? + { + put_in_buffer(data * vol, snum, osc, buf); + curr_sample += data * vol; + + if (output_channel == (output_channels - 1)) + { + put_in_buffer(data * vol, snum, osc, buf); + put_in_buffer(data * vol, snum, osc, buf); + curr_sample += data * vol; + curr_sample += data * vol; + } + } + } + + if (altram >= wtsize) + { + halt_osc(osc, 0, &acc, resshift); + } + } + + oscBuf[osc]->data[oscBuf[osc]->needle++] = curr_sample / 2; + + // if oscillator halted, we've got no more samples to generate + if (pOsc->control & 1) + { + ctrl |= 1; + break; + } + + pOsc->control = ctrl; + pOsc->accumulator = acc; + pOsc->data = data ^ 0x80; + } + } + } + + for (osc = 0; osc < 32; osc++) + { + ES5503Osc *pOsc = &oscillators[osc]; + + if ((pOsc->control & 1) || osc >= oscsenabled) //zero-fill osc buf if chan not playing or if less numb of osc is enabled + { + for (snum = 0; snum < samples; snum++) + { + //memset(oscBuf[osc]->data, 0, 65536 * sizeof(short)); + oscBuf[osc]->data[oscBuf[osc]->needle++] = 0; + } + } + } +} \ No newline at end of file diff --git a/src/engine/platform/sound/es5503.h b/src/engine/platform/sound/es5503.h new file mode 100644 index 0000000000..b7c12b2326 --- /dev/null +++ b/src/engine/platform/sound/es5503.h @@ -0,0 +1,75 @@ +// license:BSD-3-Clause +// copyright-holders:R. Belmont + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "../../dispatch.h" + +class es5503_core { +public: + // construction/destruction + void es5503_core_init(uint32_t clock, DivDispatchOscBuffer** oscBuf, uint8_t oscsenabled); + void es5503_core_free(); + void update_num_osc(DivDispatchOscBuffer** oscBuf, uint8_t oscsenabled); + + // channels must be a power of two + void set_channels(int channels) { output_channels = channels; } + + uint8_t read(uint8_t offset); + void write(uint8_t offset, uint8_t data); + uint8_t read_byte(uint32_t offset); + void halt_osc(int onum, int type, uint32_t *accumulator, int resshift); + void fill_audio_buffer(short** buf, size_t len); + + unsigned char* sampleMem; + //unsigned char sampleMem[65536 * 2]; + size_t sampleMemLen; + + uint32_t clock; + uint8_t oscsenabled; // # of oscillators enabled + + bool mono; + +private: + enum + { + MODE_FREE = 0, + MODE_ONESHOT = 1, + MODE_SYNCAM = 2, + MODE_SWAP = 3 + }; + + struct ES5503Osc + { + uint16_t freq; + uint16_t wtsize; + uint8_t control; + uint8_t vol; + uint8_t data; + uint32_t wavetblpointer; + uint8_t wavetblsize; + uint8_t resolution; + + uint32_t accumulator; + uint8_t irqpend; + }; + + ES5503Osc oscillators[32]; + int rege0; // contents of register 0xe0 + + uint8_t m_channel_strobe; + + int output_channels; + uint32_t output_rate; + + DivDispatchOscBuffer* oscBuf[32]; + + void put_in_buffer(int32_t value, uint32_t pos, uint32_t chan, short** buf); +}; \ No newline at end of file diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 2ab9ead8eb..7f28ac4feb 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -242,6 +242,12 @@ const char* cmdName[]={ "C64_AD", "C64_SR", + "ES5503_NUM_ENABLED_OSC", + "ES5503_OSC_OUTPUT", + "ES5503_WAVE_LENGTH", + "ES5503_WAVE_POS", + "ES5503_OSC_MODE", + "ALWAYS_SET_VOLUME" }; diff --git a/src/engine/song.h b/src/engine/song.h index 022b65afc4..2d6b82fef8 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -131,7 +131,8 @@ enum DivSystem { DIV_SYSTEM_K053260, DIV_SYSTEM_TED, DIV_SYSTEM_C140, - DIV_SYSTEM_C219 + DIV_SYSTEM_C219, + DIV_SYSTEM_ES5503 }; enum DivEffectType: unsigned short { diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 3c7b378425..a9aea8b88a 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1921,6 +1921,24 @@ void DivEngine::registerSystems() { } ); + sysDefs[DIV_SYSTEM_ES5503]=new DivSysDef( + "Ensoniq ES5503", NULL, 0xff, 0, 32, false, true, 0x171, false, (1U<writeC(0xd5); // set volume to zero and reset some other shit + w->writeC(0); + w->writeC(i); + w->writeC(0); + + w->writeC(0xd5); + w->writeC(0); + w->writeC(0x20+i); + w->writeC(0); + + w->writeC(0xd5); + w->writeC(0); + w->writeC(0x40+i); + w->writeC(0); + + w->writeC(0xd5); + w->writeC(0); + w->writeC(0x60+i); + w->writeC(0); + + w->writeC(0xd5); + w->writeC(0); + w->writeC(0x80+i); + w->writeC(0); + + w->writeC(0xd5); + w->writeC(0); + w->writeC(0xc0+i); + w->writeC(0); + + w->writeC(0xd5); //odd channels are left, even are right + w->writeC(0); + w->writeC(0xa0+i); + w->writeC((i & 1) ? ((1 << 4) | 1) : 1); //0<<4=left, 1<<4=right, 1=disable oscillator + } + + w->writeC(0xd5); //write to num_osc register and enable all 32 oscillators + w->writeC(0); + w->writeC(0xe1); + w->writeC(62); + break; + } case DIV_SYSTEM_OPL: case DIV_SYSTEM_OPL_DRUMS: // disable envelope @@ -640,6 +688,19 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write break; } } + + if (write.addr>=0xfffe0000 && write.addr<0xffff0000 && writeES5503[0] != NULL) { + uint32_t data_size = (write.addr&0xffff); + uint32_t data_offset = ((uint16_t)write.val << 8); + const unsigned char* sample_mem = (const unsigned char*)writeES5503[0]->getSampleMem(); + w->writeC(0x67); + w->writeC(0x66); + w->writeC(0xE1); + w->writeI(data_size + 4); + w->writeI(data_offset); + w->write(sample_mem+data_offset, data_size); + } + if (write.addr>=0xffff0000) { // Furnace special command if (!directStream) { unsigned char streamID=streamOff+((write.addr&0xff00)>>8); @@ -947,6 +1008,12 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeC(write.addr&0xff); w->writeC(write.val&0xff); break; + case DIV_SYSTEM_ES5503: + w->writeC(0xd5); + w->writeC(0); + w->writeC(write.addr&0xff); + w->writeC(write.val&0xff); + break; case DIV_SYSTEM_VBOY: w->writeC(0xc7); w->writeS_BE(baseAddr2S|(write.addr>>2)); @@ -1192,6 +1259,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p int hasGA20=0; int hasLynx=0; + uint8_t es5503_chans = 2; + int howManyChips=0; int chipVolSum=0; int chipAccounting=0; @@ -1257,6 +1326,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p DivDispatch* writeX1010[2]={NULL,NULL}; DivDispatch* writeQSound[2]={NULL,NULL}; DivDispatch* writeES5506[2]={NULL,NULL}; + writeES5503[0] = writeES5503[1] = NULL; DivDispatch* writeZ280[2]={NULL,NULL}; DivDispatch* writeRF5C68[2]={NULL,NULL}; DivDispatch* writeMSM6295[2]={NULL,NULL}; @@ -1632,6 +1702,14 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p howManyChips++; } break; + case DIV_SYSTEM_ES5503: + if (!hasES5503) { + hasES5503=disCont[i].dispatch->chipClock; + willExport[i]=true; + writeES5503[0]=disCont[i].dispatch; + es5503_chans = disCont[i].dispatch->getOutputCount(); + } + break; case DIV_SYSTEM_VBOY: if (!hasVSU) { hasVSU=disCont[i].dispatch->chipClock; @@ -2000,7 +2078,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p w->writeI(hasSAA); w->writeI(hasES5503); w->writeI(hasES5505); - w->writeC(0); // 5503 chans + w->writeC(es5503_chans); // 5503 chans w->writeC(hasES5505?1:0); // 5505 chans w->writeC(0); // C352 clock divider w->writeC(0); // reserved @@ -2272,6 +2350,14 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p w->writeI(0); w->write(writeES5506[i]->getSampleMem(),writeES5506[i]->getSampleMemUsage()); } + if (writeES5503[i]!=NULL && writeES5503[i]->getSampleMemUsage()>0) { + w->writeC(0x67); + w->writeC(0x66); + w->writeC(0xE1); + w->writeI((writeES5503[i]->getSampleMemUsage()+8)|(i*0x80000000)); + w->writeI(0); //start write from offset 0 + w->write(writeES5503[i]->getSampleMem(),writeES5503[i]->getSampleMemUsage()); + } if (writeC140[i]!=NULL && writeC140[i]->getSampleMemUsage()>0) { w->writeC(0x67); w->writeC(0x66); diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index fb03dae67a..a2143ec91f 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -1452,7 +1452,8 @@ void FurnaceGUI::doAction(int what) { i==DIV_INS_K007232 || i==DIV_INS_GA20 || i==DIV_INS_K053260 || - i==DIV_INS_C140) { + i==DIV_INS_C140 || + i==DIV_INS_ES5503) { makeInsTypeList.push_back(i); } } diff --git a/src/gui/gui.h b/src/gui/gui.h index 63c018f49e..24ba03743b 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -290,6 +290,7 @@ enum FurnaceGUIColors { GUI_COLOR_INSTR_TED, GUI_COLOR_INSTR_C140, GUI_COLOR_INSTR_C219, + GUI_COLOR_INSTR_ES5503, GUI_COLOR_INSTR_UNKNOWN, GUI_COLOR_CHANNEL_BG, @@ -300,6 +301,7 @@ enum FurnaceGUIColors { GUI_COLOR_CHANNEL_WAVE, GUI_COLOR_CHANNEL_PCM, GUI_COLOR_CHANNEL_OP, + GUI_COLOR_CHANNEL_ES5503_VIRT, GUI_COLOR_CHANNEL_MUTED, GUI_COLOR_PATTERN_PLAY_HEAD, diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 33b9955d95..5f5b2c232b 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -175,6 +175,7 @@ const char* insTypes[DIV_INS_MAX+1][3]={ {"TED",ICON_FA_BAR_CHART,ICON_FUR_INS_TED}, {"C140",ICON_FA_VOLUME_UP,ICON_FUR_INS_C140}, {"C219",ICON_FA_VOLUME_UP,ICON_FUR_INS_C219}, + {"ES5503",ICON_FA_VOLUME_UP,ICON_FUR_INS_C219}, {NULL,ICON_FA_QUESTION,ICON_FA_QUESTION} }; @@ -984,6 +985,7 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={ D(GUI_COLOR_INSTR_TED,"",ImVec4(0.7f,0.6f,1.0f,1.0f)), D(GUI_COLOR_INSTR_C140,"",ImVec4(1.0f,1.0f,0.0f,1.0f)), D(GUI_COLOR_INSTR_C219,"",ImVec4(1.0f,0.8f,0.0f,1.0f)), + D(GUI_COLOR_INSTR_ES5503,"",ImVec4(1.0f,0.8f,0.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)), @@ -994,6 +996,7 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={ D(GUI_COLOR_CHANNEL_WAVE,"",ImVec4(1.0f,0.5f,0.2f,1.0f)), D(GUI_COLOR_CHANNEL_PCM,"",ImVec4(1.0f,0.9f,0.2f,1.0f)), D(GUI_COLOR_CHANNEL_OP,"",ImVec4(0.2f,0.4f,1.0f,1.0f)), + D(GUI_COLOR_CHANNEL_ES5503_VIRT,"",ImVec4(1.0f,0.7f,0.2f,1.0f)), D(GUI_COLOR_CHANNEL_MUTED,"",ImVec4(0.5f,0.5f,0.5f,1.0f)), D(GUI_COLOR_PATTERN_PLAY_HEAD,"",ImVec4(1.0f,1.0f,1.0f,0.25f)), @@ -1203,6 +1206,7 @@ const int availableSystems[]={ DIV_SYSTEM_TED, DIV_SYSTEM_C140, DIV_SYSTEM_C219, + DIV_SYSTEM_ES5503, DIV_SYSTEM_PCM_DAC, DIV_SYSTEM_PONG, 0 // don't remove this last one! @@ -1316,6 +1320,7 @@ const int chipsSample[]={ DIV_SYSTEM_K053260, DIV_SYSTEM_C140, DIV_SYSTEM_C219, + DIV_SYSTEM_ES5503, 0 // don't remove this last one! }; diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 77ad64cd0e..9ae1ae4b86 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -207,6 +207,13 @@ enum FMParams { FM_AMS2=31 }; +enum ES5503_osc_modes { + OSC_MODE_FREERUN = 0, + OSC_MODE_ONESHOT = 1, + OSC_MODE_SYNC_OR_AM = 2, //for even voice syncs with next odd voice, for odd voice it amplitude modulates next even voice + OSC_MODE_SWAP = 3, //triggers next oscillator after previous finishes; since max wavetable size is 32 KiB allows for 64 KiB wavetables to be played seamlessly +}; + #define FM_NAME(x) fmParamNames[settings.fmNames][x] #define FM_SHORT_NAME(x) fmParamShortNames[settings.fmNames][x] @@ -376,6 +383,8 @@ const int kslMap[4]={ 0, 2, 1, 3 }; +const char* ES5503_wave_lengths_string[DivInstrumentES5503::DIV_ES5503_WAVE_LENGTH_MAX] = {"256", "512", "1024", "2048", "4096", "8192", "16384", "32768"}; + // do not change these! // anything other than a checkbox will look ugly! // @@ -386,6 +395,14 @@ const char* macroRelativeMode="Relative"; const char* macroQSoundMode="QSound"; const char* macroDummyMode="Bug"; +char* int_to_char_array(int num) +{ + static char numberstring[50]; + memset(numberstring, 0, 50); + snprintf(numberstring, 50, "%d", num); + return numberstring; +} + String macroHoverNote(int id, float val, void* u) { int* macroVal=(int*)u; if ((macroVal[id]&0xc0000000)==0x40000000 || (macroVal[id]&0xc0000000)==0x80000000) { @@ -2364,7 +2381,8 @@ void FurnaceGUI::insTabSample(DivInstrument* ins) { ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_VRC6 || - ins->type==DIV_INS_SU) { + ins->type==DIV_INS_SU || + ins->type==DIV_INS_ES5503) { P(ImGui::Checkbox("Use sample",&ins->amiga.useSample)); if (ins->type==DIV_INS_X1_010) { if (ImGui::InputInt("Sample bank slot##BANKSLOT",&ins->x1_010.bankSlot,1,4)) { PARAMETER @@ -5031,6 +5049,55 @@ void FurnaceGUI::drawInsEdit() { P(ImGui::Checkbox("Don't test before new note",&ins->c64.noTest)); ImGui::EndTabItem(); } + if (ins->type==DIV_INS_ES5503) if (ImGui::BeginTabItem("ES5503")) { + ImGui::AlignTextToFramePadding(); + ImGui::Text("Oscillator mode:"); + ImGui::SameLine(); + bool freerun = (ins->es5503.initial_osc_mode == OSC_MODE_FREERUN); + bool oneshot = (ins->es5503.initial_osc_mode == OSC_MODE_ONESHOT); + bool sync_am = (ins->es5503.initial_osc_mode == OSC_MODE_SYNC_OR_AM); + bool swap = (ins->es5503.initial_osc_mode == OSC_MODE_SWAP); + if (ImGui::RadioButton("Freerun",freerun)) { PARAMETER + freerun=true; + oneshot=false; + sync_am=false; + swap=false; + ins->es5503.initial_osc_mode=OSC_MODE_FREERUN; + } + ImGui::SameLine(); + if (ImGui::RadioButton("Oneshot",oneshot)) { PARAMETER + freerun=false; + oneshot=true; + sync_am=false; + swap=false; + ins->es5503.initial_osc_mode=OSC_MODE_ONESHOT; + } + ImGui::SameLine(); + if (ImGui::RadioButton("Sync/AM",sync_am)) { PARAMETER + freerun=false; + oneshot=false; + sync_am=true; + swap=false; + ins->es5503.initial_osc_mode=OSC_MODE_SYNC_OR_AM; + } + ImGui::SameLine(); + if (ImGui::RadioButton("Swap",swap)) { PARAMETER + freerun=false; + oneshot=false; + sync_am=true; + swap=false; + ins->es5503.initial_osc_mode=OSC_MODE_SWAP; + } + + P(ImGui::Checkbox("Virtual softpan channel",&ins->es5503.softpan_virtual_channel)); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Combines odd and next even channel into one virtual channel with 256-step panning.\nInstrument, volume and effects need to be placed on the odd channel (e.g. 1st, 3rd, 5th etc.)"); + } + + P(ImGui::Checkbox("Phase reset on key-on",&ins->es5503.phase_reset_on_start)); + + ImGui::EndTabItem(); + } if (ins->type==DIV_INS_SU) if (ImGui::BeginTabItem("Sound Unit")) { P(ImGui::Checkbox("Switch roles of frequency and phase reset timer",&ins->su.switchRoles)); if (ImGui::BeginChild("HWSeqSU",ImGui::GetContentRegionAvail(),true,ImGuiWindowFlags_MenuBar)) { @@ -5726,6 +5793,7 @@ void FurnaceGUI::drawInsEdit() { ins->type==DIV_INS_FDS || (ins->type==DIV_INS_SWAN && !ins->amiga.useSample) || (ins->type==DIV_INS_PCE && !ins->amiga.useSample) || + (ins->type==DIV_INS_ES5503 && !ins->amiga.useSample) || (ins->type==DIV_INS_VBOY) || ins->type==DIV_INS_SCC || ins->type==DIV_INS_SNES || @@ -5772,6 +5840,10 @@ void FurnaceGUI::drawInsEdit() { wavePreviewLen=ins->amiga.waveLen+1; wavePreviewHeight=15; break; + case DIV_INS_ES5503: + wavePreviewLen=256; + wavePreviewHeight=255; + break; default: wavePreviewLen=32; wavePreviewHeight=31; @@ -6030,7 +6102,7 @@ void FurnaceGUI::drawInsEdit() { volMax=31; } if (ins->type==DIV_INS_ADPCMB || ins->type==DIV_INS_YMZ280B || ins->type==DIV_INS_RF5C68 || - ins->type==DIV_INS_GA20 || ins->type==DIV_INS_C140 || ins->type==DIV_INS_C219) { + ins->type==DIV_INS_GA20 || ins->type==DIV_INS_C140 || ins->type==DIV_INS_C219 || ins->type==DIV_INS_ES5503) { volMax=255; } if (ins->type==DIV_INS_QSOUND) { @@ -6172,6 +6244,10 @@ void FurnaceGUI::drawInsEdit() { dutyLabel="Echo Level"; dutyMax=32767; } + if (ins->type==DIV_INS_ES5503) { + dutyLabel="Osc. mode"; + dutyMax=3; + } const char* waveLabel="Waveform"; int waveMax=(ins->type==DIV_INS_VERA)?3:(MAX(1,e->song.waveLen-1)); @@ -6184,6 +6260,7 @@ void FurnaceGUI::drawInsEdit() { waveMax=0; if (ins->type==DIV_INS_TIA || ins->type==DIV_INS_VIC || ins->type==DIV_INS_OPLL) waveMax=15; if (ins->type==DIV_INS_C64) waveMax=4; + if (ins->type==DIV_INS_ES5503) waveMax=ins->amiga.useSample?0:255; if (ins->type==DIV_INS_SAA1099) waveMax=2; if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPL_DRUMS || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM) waveMax=0; if (ins->type==DIV_INS_MIKEY) waveMax=0; @@ -6219,6 +6296,10 @@ void FurnaceGUI::drawInsEdit() { waveLabel="Patch"; } + if (ins->type==DIV_INS_ES5503) { + waveLabel="Wavetable"; + } + if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930) { waveMax=ins->amiga.useSample?0:3; waveBitMode=ins->amiga.useSample?false:true; @@ -6273,6 +6354,10 @@ void FurnaceGUI::drawInsEdit() { ex1Max=5; ex2Max=11; } + if (ins->type==DIV_INS_ES5503) { + ex1Max=255; + ex2Max=7; + } int panMin=0; int panMax=0; @@ -6343,6 +6428,9 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_ES5506) { panMax=4095; } + if (ins->type==DIV_INS_ES5503 && ins->es5503.softpan_virtual_channel) { + panMax=255; + } if (volMax>0) { macroList.push_back(FurnaceGUIMacroDesc(volumeLabel,ins,DIV_MACRO_VOL,0xff,volMin,volMax,160,uiColors[GUI_COLOR_MACRO_VOLUME])); @@ -6429,7 +6517,8 @@ void FurnaceGUI::drawInsEdit() { ins->type==DIV_INS_K053260 || ins->type==DIV_INS_C140 || ins->type==DIV_INS_C219 || - ins->type==DIV_INS_TED) { + ins->type==DIV_INS_TED || + ins->type==DIV_INS_ES5503) { macroList.push_back(FurnaceGUIMacroDesc("Phase Reset",ins,DIV_MACRO_PHASE_RESET,0xff,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); } if (ex1Max>0) { @@ -6463,6 +6552,8 @@ void FurnaceGUI::drawInsEdit() { macroList.push_back(FurnaceGUIMacroDesc("Special",ins,DIV_MACRO_EX1,0xff,0,ex1Max,96,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,snesModeBits)); } else if (ins->type==DIV_INS_MSM5232) { macroList.push_back(FurnaceGUIMacroDesc("Group Attack",ins,DIV_MACRO_EX1,0xff,0,ex1Max,96,uiColors[GUI_COLOR_MACRO_OTHER])); + } else if (ins->type==DIV_INS_ES5503) { + macroList.push_back(FurnaceGUIMacroDesc("Wave/sample pos.",ins,DIV_MACRO_EX1,0xff,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); } else { macroList.push_back(FurnaceGUIMacroDesc("Duty",ins,DIV_MACRO_EX1,0xff,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); } @@ -6480,8 +6571,9 @@ void FurnaceGUI::drawInsEdit() { macroList.push_back(FurnaceGUIMacroDesc("Echo Length",ins,DIV_MACRO_EX2,0xff,0,ex2Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); } else if (ins->type==DIV_INS_SNES) { macroList.push_back(FurnaceGUIMacroDesc("Gain",ins,DIV_MACRO_EX2,0xff,0,ex2Max,256,uiColors[GUI_COLOR_MACRO_VOLUME],false,NULL,macroHoverGain,false)); - } else if (ins->type==DIV_INS_MSM5232) { macroList.push_back(FurnaceGUIMacroDesc("Group Decay",ins,DIV_MACRO_EX2,0xff,0,ex2Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); + } else if (ins->type==DIV_INS_ES5503) { + macroList.push_back(FurnaceGUIMacroDesc("Osc. output",ins,DIV_MACRO_EX2,0xff,0,ex2Max,64,uiColors[GUI_COLOR_MACRO_OTHER])); } else { macroList.push_back(FurnaceGUIMacroDesc("Envelope",ins,DIV_MACRO_EX2,0xff,0,ex2Max,ex2Bit?64:160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,ex2Bit,ayEnvBits)); } @@ -6538,7 +6630,8 @@ void FurnaceGUI::drawInsEdit() { ins->type==DIV_INS_PCE || ins->type==DIV_INS_X1_010 || ins->type==DIV_INS_SWAN || - ins->type==DIV_INS_VRC6) { + ins->type==DIV_INS_VRC6 || + ins->type==DIV_INS_ES5503) { insTabSample(ins); } if (ins->type>=DIV_INS_MAX) { diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 2dc850cb85..3a5f53561f 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -2985,6 +2985,11 @@ void FurnaceGUI::initSystemPresets() { CH(DIV_SYSTEM_ES5506, 1.0f, 0, "channels=31") } ); + ENTRY( + "Ensoniq ES5503", { + CH(DIV_SYSTEM_ES5503, 1.0f, 0, "") + } + ); ENTRY( "Konami K053260", { CH(DIV_SYSTEM_K053260, 1.0f, 0, "") diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index ac9f15d53c..be74b036b7 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -3422,6 +3422,7 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_CHANNEL_PCM,"PCM"); UI_COLOR_CONFIG(GUI_COLOR_CHANNEL_WAVE,"Wave"); UI_COLOR_CONFIG(GUI_COLOR_CHANNEL_OP,"FM operator"); + UI_COLOR_CONFIG(GUI_COLOR_CHANNEL_ES5503_VIRT,"ES5503 master virtual softpan channel"); UI_COLOR_CONFIG(GUI_COLOR_CHANNEL_MUTED,"Muted"); ImGui::TreePop(); } diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 99baf47e4b..904f7e46a4 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -2276,6 +2276,21 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl supportsCustomRate=false; ImGui::Text("nothing to configure"); break; + case DIV_SYSTEM_ES5503: + { + bool mono = flags.getBool("monoOutput", false); + + if (ImGui::Checkbox("Downmix chip output to mono", &mono)) { + altered = true; + } + + if (altered) { + e->lockSave([&]() { + flags.set("monoOutput", mono); + }); + } + break; + } default: { bool sysPal=flags.getInt("clockSel",0); diff --git a/src/gui/sysPartNumber.cpp b/src/gui/sysPartNumber.cpp index d71e65a984..a59a2ce800 100644 --- a/src/gui/sysPartNumber.cpp +++ b/src/gui/sysPartNumber.cpp @@ -277,6 +277,9 @@ const char* FurnaceGUI::getSystemPartNumber(DivSystem sys, DivConfig& flags) { case DIV_SYSTEM_C219: return "C219"; break; + case DIV_SYSTEM_ES5503: + return "ES5503"; + break; default: return FurnaceGUI::getSystemName(sys); break; diff --git a/src/gui/waveEdit.cpp b/src/gui/waveEdit.cpp index cb4cf04624..65db3a4eb4 100644 --- a/src/gui/waveEdit.cpp +++ b/src/gui/waveEdit.cpp @@ -509,7 +509,7 @@ void FurnaceGUI::drawWaveEdit() { ImGui::SameLine(); ImGui::Text("Height"); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("use a height of:\n- 16 for Game Boy, WonderSwan, Namco WSG, Konami Bubble System, X1-010 Envelope shape and N163\n- 32 for PC Engine\n- 64 for FDS and Virtual Boy\n- 256 for X1-010 and SCC\nany other heights will be scaled during playback."); + ImGui::SetTooltip("use a height of:\n- 16 for Game Boy, WonderSwan, Namco WSG, Konami Bubble System, X1-010 Envelope shape and N163\n- 32 for PC Engine\n- 64 for FDS and Virtual Boy\n- 256 for X1-010, SCC and ES5503\nany other heights will be scaled during playback."); } ImGui::SameLine(); ImGui::SetNextItemWidth(96.0f*dpiScale);