diff --git a/CMakeLists.txt b/CMakeLists.txt index afc9689e2..d8f7b0f1b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -815,6 +815,7 @@ src/engine/platform/nds.cpp src/engine/platform/bifurcator.cpp src/engine/platform/sid2.cpp src/engine/platform/sid3.cpp +src/engine/platform/multipcm.cpp src/engine/platform/pcmdac.cpp src/engine/platform/dummy.cpp diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index ec10500dc..361e1c699 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -917,6 +917,11 @@ class DivDispatch { */ virtual void notifyWaveChange(int wave); + /** + * notify addition of an instrument. + */ + virtual void notifyInsAddition(int sysID); + /** * notify deletion of an instrument. */ @@ -1001,7 +1006,7 @@ class DivDispatch { * @param index the memory index. * @return whether it did. */ - virtual bool hasSampleInstHeader(int index=0); + virtual bool hasSampleInsHeader(int index=0); /** * check whether sample has been loaded in memory. diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 23cbde76b..c5ed67e91 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -93,6 +93,7 @@ #include "platform/bifurcator.h" #include "platform/sid2.h" #include "platform/sid3.h" +#include "platform/multipcm.h" #include "platform/dummy.h" #include "../ta-log.h" #include "song.h" @@ -787,6 +788,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do ((DivPlatformOPL*)dispatch)->setCore(eng->getConfInt("opl4Core",0)); } break; + case DIV_SYSTEM_MULTIPCM: + dispatch=new DivPlatformMultiPCM; + break; case DIV_SYSTEM_DUMMY: dispatch=new DivPlatformDummy; break; diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 192c511db..2fe8abdc4 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -2647,16 +2647,10 @@ int DivEngine::addInstrument(int refChan, DivInstrumentType fallbackType) { song.ins.push_back(ins); song.insLen=insCount+1; checkAssetDir(song.insDir,song.ins.size()); + for (int i=0; inotifyInsAddition(i); + } saveLock.unlock(); - bool hasSampleInst=false; - for (int s=0; shasSampleInstHeader()) { - hasSampleInst=true; - } - } - if (hasSampleInst) { - renderSamplesP(); - } BUSY_END; return insCount; } @@ -2673,16 +2667,10 @@ int DivEngine::addInstrumentPtr(DivInstrument* which) { checkAssetDir(song.insDir,song.ins.size()); checkAssetDir(song.waveDir,song.wave.size()); checkAssetDir(song.sampleDir,song.sample.size()); + for (int i=0; inotifyInsAddition(i); + } saveLock.unlock(); - bool hasSampleInst=false; - for (int s=0; shasSampleInstHeader()) { - hasSampleInst=true; - } - } - if (hasSampleInst) { - renderSamplesP(); - } BUSY_END; return song.insLen; } @@ -2718,13 +2706,13 @@ void DivEngine::delInstrumentUnsafe(int index) { } removeAsset(song.insDir,index); checkAssetDir(song.insDir,song.ins.size()); - bool hasSampleInst=false; + bool hasSampleIns=false; for (int s=0; shasSampleInstHeader()) { - hasSampleInst=true; + if (disCont[s].dispatch->hasSampleInsHeader()) { + hasSampleIns=true; } } - if (hasSampleInst) { + if (hasSampleIns) { renderSamplesP(); } } diff --git a/src/engine/platform/abstract.cpp b/src/engine/platform/abstract.cpp index cc541a841..f42e1ce91 100644 --- a/src/engine/platform/abstract.cpp +++ b/src/engine/platform/abstract.cpp @@ -164,6 +164,10 @@ void DivDispatch::notifyWaveChange(int ins) { } +void DivDispatch::notifyInsAddition(int sysID) { + +} + void DivDispatch::notifyInsDeletion(void* ins) { logE("notifyInsDeletion NOT implemented!"); abort(); @@ -217,7 +221,7 @@ bool DivDispatch::hasSamplePtrHeader(int index) { return false; } -bool DivDispatch::hasSampleInstHeader(int index) { +bool DivDispatch::hasSampleInsHeader(int index) { return false; } diff --git a/src/engine/platform/multipcm.cpp b/src/engine/platform/multipcm.cpp new file mode 100644 index 000000000..47280649e --- /dev/null +++ b/src/engine/platform/multipcm.cpp @@ -0,0 +1,730 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2025 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 "multipcm.h" +#include "../engine.h" +#include "../bsr.h" +#include "../../ta-log.h" +#include +#include + +const unsigned char slotsMPCM[28]={ + 0x00,0x01,0x02,0x03,0x04,0x05,0x06, + 0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e, + 0x10,0x11,0x12,0x13,0x14,0x15,0x16, + 0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e, +}; + +#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} +#define immWrite(a,v) \ + if (!skipRegisterWrites) { \ + writes.push(QueuedWrite(a,v)); \ + if (dumpWrites) { \ + addWrite(1,slotsMPCM[(a>>3)&0x1f]); \ + addWrite(2,a&0x7); \ + addWrite(0,v); \ + } \ + } + +#define chWrite(c,a,v) \ + if (!skipRegisterWrites) { \ + rWrite((c<<3)|(a&0x7),v); \ + } + +#define chImmWrite(c,a,v) \ + if (!skipRegisterWrites) { \ + writes.push(QueuedWrite((c<<3)|(a&0x7),v)); \ + if (dumpWrites) { \ + addWrite(1,slotsMPCM[c&0x1f]); \ + addWrite(2,a&0x7); \ + addWrite(0,v); \ + } \ + } + +#define CHIP_FREQBASE (117440512) + +const char* regCheatSheetMultiPCM[]={ + "Pan", "0", + "SampleL", "1", + "SampleH_FreqL", "2", + "FreqH", "3", + "KeyOn", "4", + "TL_LD", "5", + "LFO_VIB", "6", + "AM", "7", + NULL +}; + +const char** DivPlatformMultiPCM::getRegisterSheet() { + return regCheatSheetMultiPCM; +} + +#define PCM_ADDR_PAN 0 // Panpot +#define PCM_ADDR_WAVE_L 1 // Wavetable number LSB +#define PCM_ADDR_WAVE_H_FN_L 2 // Wavetable number MSB, F-number LSB +#define PCM_ADDR_FN_H_OCT 3 // F-number MSB, Pseudo-reverb, Octave +#define PCM_ADDR_KEY 4 // Key +#define PCM_ADDR_TL 5 // Total level, Level direct + +#define PCM_ADDR_LFO_VIB 6 +#define PCM_ADDR_AM 7 + +void DivPlatformMultiPCM::acquire(short** buf, size_t len) { + thread_local short o[4]; + thread_local int os[2]; + thread_local short pcmBuf[28]; + + for (int i=0; i<28; i++) { + oscBuf[i]->begin(len); + } + + for (size_t h=0; h>3)&0x1f],w.addr&0x7,w.val); + regPool[w.addr]=w.val; + } + writes.pop(); + } + + pcm.generate(o[0],o[1],o[2],o[3],pcmBuf); + // stereo output only + os[0]+=o[0]; + os[1]+=o[1]; + os[0]+=o[2]; + os[1]+=o[3]; + + for (int i=0; i<28; i++) { + oscBuf[i]->putSample(h,CLAMP(pcmBuf[i],-32768,32767)); + } + + if (os[0]<-32768) os[0]=-32768; + if (os[0]>32767) os[0]=32767; + + if (os[1]<-32768) os[1]=-32768; + if (os[1]>32767) os[1]=32767; + + buf[0][h]=os[0]; + buf[1][h]=os[1]; + } + + for (int i=0; i<28; i++) { + oscBuf[i]->end(len); + } +} + +void DivPlatformMultiPCM::tick(bool sysTick) { + for (int i=0; i<28; i++) { + chan[i].std.next(); + if (chan[i].std.vol.had) { + chan[i].outVol=VOL_SCALE_LOG((chan[i].vol&0x7f),(0x7f*chan[i].std.vol.val)/chan[i].macroVolMul,0x7f); + chImmWrite(i,PCM_ADDR_TL,((0x7f-chan[i].outVol)<<1)|(chan[i].levelDirect?1:0)); + } + + if (NEW_ARP_STRAT) { + chan[i].handleArp(); + } else if (chan[i].std.arp.had) { + if (!chan[i].inPorta) { + chan[i].baseFreq=NOTE_FREQUENCY(parent->calcArp(chan[i].note,chan[i].std.arp.val)); + } + chan[i].freqChanged=true; + } + + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-131071,131071); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } + + if (chan[i].std.phaseReset.had) { + if (chan[i].std.phaseReset.val==1 && chan[i].active) { + chan[i].keyOn=true; + chan[i].writeCtrl=true; + } + } + + if (chan[i].std.panL.had) { // panning + chan[i].pan=chan[i].std.panL.val&0xf; + chImmWrite(i,PCM_ADDR_PAN,(isMuted[i]?8:chan[i].pan)<<4); + } + + if (chan[i].std.ex1.had) { + chan[i].lfo=chan[i].std.ex1.val&0x7; + chWrite(i,PCM_ADDR_LFO_VIB,(chan[i].lfo<<3)|(chan[i].vib)); + } + + if (chan[i].std.fms.had) { + chan[i].vib=chan[i].std.fms.val&0x7; + chWrite(i,PCM_ADDR_LFO_VIB,(chan[i].lfo<<3)|(chan[i].vib)); + } + + if (chan[i].std.ams.had) { + chan[i].am=chan[i].std.ams.val&0x7; + chWrite(i,PCM_ADDR_AM,chan[i].am); + } + } + + for (int i=0; i<224; i++) { + if (pendingWrites[i]!=oldWrites[i]) { + immWrite(i,pendingWrites[i]&0xff); + oldWrites[i]=pendingWrites[i]; + } + } + + for (int i=0; i<28; i++) { + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { + DivSample* s=parent->getSample(parent->getIns(chan[i].ins)->amiga.initSample); + unsigned char ctrl=0; + double off=(s->centerRate>=1)?((double)s->centerRate/parent->getCenterRate()):1.0; + chan[i].freq=(int)(off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE)); + if (chan[i].freq<0x400) chan[i].freq=0x400; + chan[i].freqH=0; + if (chan[i].freq>0x3ffffff) { + chan[i].freq=0x3ffffff; + chan[i].freqH=15; + } else if (chan[i].freq>=0x800) { + chan[i].freqH=bsr32(chan[i].freq)-11; + } + chan[i].freqL=(chan[i].freq>>chan[i].freqH)&0x3ff; + chan[i].freqH=8^chan[i].freqH; + ctrl|=chan[i].active?0x80:0; + int waveNum=chan[i].sample; + if (waveNum>=0) { + if (chan[i].keyOn) { + chImmWrite(i,PCM_ADDR_KEY,ctrl&~0x80); // force keyoff first + chImmWrite(i,PCM_ADDR_WAVE_H_FN_L,((chan[i].freqL&0x3f)<<2)|((waveNum>>8)&1)); + chImmWrite(i,PCM_ADDR_WAVE_L,waveNum&0xff); + if (!chan[i].std.vol.had) { + chan[i].outVol=chan[i].vol; + chImmWrite(i,PCM_ADDR_TL,((0x7f-chan[i].outVol)<<1)|(chan[i].levelDirect?1:0)); + } + chan[i].writeCtrl=true; + chan[i].keyOn=false; + } + if (chan[i].keyOff) { + chan[i].writeCtrl=true; + chan[i].keyOff=false; + } + if (chan[i].freqChanged) { + chImmWrite(i,PCM_ADDR_WAVE_H_FN_L,((chan[i].freqL&0x3f)<<2)|((waveNum>>8)&1)); + chImmWrite(i,PCM_ADDR_FN_H_OCT,((chan[i].freqH&0xf)<<4)|((chan[i].freqL>>6)&0xf)); + chan[i].freqChanged=false; + } + if (chan[i].writeCtrl) { + chImmWrite(i,PCM_ADDR_KEY,ctrl); + chan[i].writeCtrl=false; + } + } else { + // cut if we don't have a sample + chImmWrite(i,PCM_ADDR_KEY,ctrl&~0x80); + } + } + } +} + +void DivPlatformMultiPCM::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + chImmWrite(ch,PCM_ADDR_PAN,(isMuted[ch]?8:chan[ch].pan)<<4); +} + +int DivPlatformMultiPCM::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_MULTIPCM); + chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:127; + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].sample=chan[c.chan].ins; + chan[c.chan].sampleNote=c.value; + chan[c.chan].sampleNoteDelta=c.value-chan[c.chan].sampleNote; + } + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); + } + if (chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.insLen) { + chan[c.chan].sample=-1; + } + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + if (chan[c.chan].insChanged) { + if (ins->type==DIV_INS_MULTIPCM) { + chan[c.chan].lfo=ins->multipcm.lfo; + chan[c.chan].vib=ins->multipcm.vib; + chan[c.chan].am=ins->multipcm.am; + } else { + chan[c.chan].lfo=0; + chan[c.chan].vib=0; + chan[c.chan].am=0; + } + chan[c.chan].insChanged=false; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + chan[c.chan].macroInit(ins); + if (!chan[c.chan].std.vol.will) { + chan[c.chan].outVol=chan[c.chan].vol; + } + break; + } + case DIV_CMD_NOTE_OFF: + chan[c.chan].keyOff=true; + chan[c.chan].keyOn=false; + chan[c.chan].active=false; + chan[c.chan].sample=-1; + chan[c.chan].macroInit(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + chan[c.chan].keyOff=true; + chan[c.chan].keyOn=false; + chan[c.chan].active=false; + chan[c.chan].std.release(); + break; + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_VOLUME: { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.vol.has) { + chan[c.chan].outVol=c.value; + } + chImmWrite(c.chan,PCM_ADDR_TL,((0x7f-chan[c.chan].outVol)<<1)|(chan[c.chan].levelDirect?1:0)); + break; + } + case DIV_CMD_GET_VOLUME: + return chan[c.chan].vol; + break; + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value || c.value2==1) { + chan[c.chan].insChanged=true; + } + chan[c.chan].ins=c.value; + break; + case DIV_CMD_PANNING: { + chan[c.chan].pan=8^MIN(parent->convertPanSplitToLinearLR(c.value,c.value2,15)+1,15); + chImmWrite(c.chan,PCM_ADDR_PAN,(isMuted[c.chan]?8:chan[c.chan].pan)<<4); + break; + } + case DIV_CMD_PITCH: { + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + } + case DIV_CMD_NOTE_PORTA: { + int destFreq=NOTE_FREQUENCY(c.value2+chan[c.chan].sampleNoteDelta); + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value; + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value; + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_LEGATO: { + chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+chan[c.chan].sampleNoteDelta+((HACKY_LEGATO_MESS)?(chan[c.chan].std.arp.val-12):(0))); + chan[c.chan].note=c.value; + chan[c.chan].freqChanged=true; + break; + } + case DIV_CMD_MULTIPCM_LFO: + chan[c.chan].lfo=c.value&7; + chWrite(c.chan,PCM_ADDR_LFO_VIB,(chan[c.chan].lfo<<3)|(chan[c.chan].vib)); + break; + case DIV_CMD_MULTIPCM_VIB: + chan[c.chan].vib=c.value&7; + chWrite(c.chan,PCM_ADDR_LFO_VIB,(chan[c.chan].lfo<<3)|(chan[c.chan].vib)); + break; + case DIV_CMD_MULTIPCM_AM: + chan[c.chan].am=c.value&7; + chWrite(c.chan,PCM_ADDR_AM,chan[c.chan].am); + 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; + case DIV_CMD_GET_VOLMAX: + return 127; + 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_MULTIPCM)); + } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) { + chan[c.chan].baseFreq=NOTE_FREQUENCY(chan[c.chan].note); + } + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_PRE_NOTE: + break; + default: + //printf("WARNING: unimplemented command %d\n",c.cmd); + break; + } + return 1; +} + +void DivPlatformMultiPCM::forceIns() { + for (int i=0; i<28; i++) { + chan[i].insChanged=true; + chan[i].freqChanged=true; + } + for (int i=0; i<224; i++) { + oldWrites[i]=-1; + } +} + +void DivPlatformMultiPCM::toggleRegisterDump(bool enable) { + DivDispatch::toggleRegisterDump(enable); +} + +void* DivPlatformMultiPCM::getChanState(int ch) { + return &chan[ch]; +} + +DivMacroInt* DivPlatformMultiPCM::getChanMacroInt(int ch) { + return &chan[ch].std; +} + +unsigned short DivPlatformMultiPCM::getPan(int ch) { + return parent->convertPanLinearToSplit(8^chan[ch].pan,8,15); +} + +DivDispatchOscBuffer* DivPlatformMultiPCM::getOscBuffer(int ch) { + return oscBuf[ch]; +} + +int DivPlatformMultiPCM::mapVelocity(int ch, float vel) { + // -0.375dB per step + // -6: 64: 16 + // -12: 32: 32 + // -18: 16: 48 + // -24: 8: 64 + // -30: 4: 80 + // -36: 2: 96 + // -42: 1: 112 + if (vel==0) return 0; + if (vel>=1.0) return 127; + return CLAMP(round(128.0-(112.0-log2(vel*127.0)*16.0)),0,127); +} + +float DivPlatformMultiPCM::getGain(int ch, int vol) { + if (vol==0) return 0; + return 1.0/pow(10.0,(float)(127-vol)*0.375/20.0); +} + +unsigned char* DivPlatformMultiPCM::getRegisterPool() { + return regPool; +} + +int DivPlatformMultiPCM::getRegisterPoolSize() { + return 224; +} + +void DivPlatformMultiPCM::reset() { + while (!writes.empty()) writes.pop(); + memset(regPool,0,224); + + pcm.reset(); + + for (int i=0; i<28; i++) { + chan[i]=DivPlatformMultiPCM::Channel(); + chan[i].std.setEngine(parent); + } + + for (int i=0; i<224; i++) { + oldWrites[i]=-1; + pendingWrites[i]=-1; + } + + curChan=-1; + curAddr=-1; + + if (dumpWrites) { + addWrite(0xffffffff,0); + } + + delay=0; +} + +int DivPlatformMultiPCM::getOutputCount() { + return 2; +} + +bool DivPlatformMultiPCM::keyOffAffectsArp(int ch) { + return false; +} + +bool DivPlatformMultiPCM::keyOffAffectsPorta(int ch) { + return false; +} + +bool DivPlatformMultiPCM::getLegacyAlwaysSetVolume() { + return false; +} + +void DivPlatformMultiPCM::notifyInsChange(int ins) { + for (int i=0; i<28; i++) { + if (chan[i].ins==ins) { + chan[i].insChanged=true; + } + } +} + +void DivPlatformMultiPCM::notifyInsAddition(int sysID) { + renderSamples(sysID); +} + +void DivPlatformMultiPCM::notifyInsDeletion(void* ins) { + for (int i=0; i<28; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformMultiPCM::poke(unsigned int addr, unsigned short val) { + immWrite(addr,val); +} + +void DivPlatformMultiPCM::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) immWrite(i.addr,i.val); +} + +int DivPlatformMultiPCM::getPortaFloor(int ch) { + return 0; +} + +void DivPlatformMultiPCM::setFlags(const DivConfig& flags) { + chipClock=10000000.0; + CHECK_CUSTOM_CLOCK; + pcm.setClockFrequency(chipClock); + rate=chipClock/224; + + for (int i=0; i<28; i++) { + oscBuf[i]->setRate(rate); + } +} + +const void* DivPlatformMultiPCM::getSampleMem(int index) { + return (index==0)?pcmMem:NULL; +} + +size_t DivPlatformMultiPCM::getSampleMemCapacity(int index) { + return (index==0)?2097152:0; +} + +size_t DivPlatformMultiPCM::getSampleMemUsage(int index) { + return (index==0)?pcmMemLen:0; +} + +bool DivPlatformMultiPCM::hasSamplePtrHeader(int index) { + return (index==0); +} + +bool DivPlatformMultiPCM::hasSampleInsHeader(int index) { + return (index==0); +} + +bool DivPlatformMultiPCM::isSampleLoaded(int index, int sample) { + if (index!=0) return false; + if (sample<0 || sample>32767) return false; + return sampleLoaded[sample]; +} + +const DivMemoryComposition* DivPlatformMultiPCM::getMemCompo(int index) { + if (index!=0) return NULL; + return &memCompo; +} + +void DivPlatformMultiPCM::renderSamples(int sysID) { + memset(pcmMem,0,2097152); + memset(sampleOff,0,32768*sizeof(unsigned int)); + memset(sampleLoaded,0,32768*sizeof(bool)); + + memCompo=DivMemoryComposition(); + memCompo.name="Sample Memory"; + + size_t memPos=0x1800; + int sampleCount=parent->song.sampleLen; + if (sampleCount>512) { + // mark the rest as unavailable + for (int i=512; isong.sample[i]; + if (!s->renderOn[0][sysID]) { + sampleOff[i]=0; + continue; + } + + int length; + int sampleLength; + unsigned char* src=(unsigned char*)s->getCurBuf(); + switch (s->depth) { + case DIV_SAMPLE_DEPTH_8BIT: + sampleLength=s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT); + length=MIN(65535,sampleLength+1); + break; + case DIV_SAMPLE_DEPTH_12BIT: + sampleLength=s->getLoopEndPosition(DIV_SAMPLE_DEPTH_12BIT); + length=MIN(98303,sampleLength+3); + break; + default: + sampleLength=s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT); + length=MIN(65535,sampleLength+1); + src=(unsigned char*)s->data8; + break; + } + if (sampleLength<1) length=0; + int actualLength=MIN((int)(getSampleMemCapacity(0)-memPos),length); + if (actualLength>0) { + for (int i=0, j=0; i=sampleLength && s->depth!=DIV_SAMPLE_DEPTH_12BIT) j=sampleLength-1; + pcmMem[memPos+i]=src[j]; + } + sampleOff[i]=memPos; + memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"Sample",i,memPos,memPos+length)); + memPos+=length; + } + if (actualLengthsong.insLen; + for (int i=0; isong.ins[i]; + DivSample* s=parent->song.sample[ins->amiga.initSample]; + unsigned int insAddr=(i*12); + unsigned char bitDepth; + int startPos=sampleOff[ins->amiga.initSample]; + int endPos=CLAMP(s->isLoopable()?s->loopEnd:(s->samples+1),1,0x10000); + int loop=s->isLoopable()?CLAMP(s->loopStart,0,endPos-2):(endPos-2); + switch (s->depth) { + case DIV_SAMPLE_DEPTH_8BIT: + bitDepth=0; + break; + case DIV_SAMPLE_DEPTH_12BIT: + bitDepth=3; + if (!s->isLoopable()) { + endPos++; + loop++; + } + break; + default: + bitDepth=0; + break; + } + pcmMem[insAddr]=(bitDepth<<6)|((startPos>>16)&0x1f); + pcmMem[1+insAddr]=(startPos>>8)&0xff; + pcmMem[2+insAddr]=(startPos)&0xff; + pcmMem[3+insAddr]=(loop>>8)&0xff; + pcmMem[4+insAddr]=(loop)&0xff; + pcmMem[5+insAddr]=((~(endPos-1))>>8)&0xff; + pcmMem[6+insAddr]=(~(endPos-1))&0xff; + if (ins->type==DIV_INS_MULTIPCM) { + pcmMem[7+insAddr]=(ins->multipcm.lfo<<3)|ins->multipcm.vib; // LFO, VIB + pcmMem[8+insAddr]=(ins->multipcm.ar<<4)|ins->multipcm.d1r; // AR, D1R + pcmMem[9+insAddr]=(ins->multipcm.dl<<4)|ins->multipcm.d2r; // DL, D2R + pcmMem[10+insAddr]=(ins->multipcm.rc<<4)|ins->multipcm.rr; // RC, RR + pcmMem[11+insAddr]=ins->multipcm.am; // AM + } else { + pcmMem[7+insAddr]=0; // LFO, VIB + pcmMem[8+insAddr]=(0xf<<4)|(0xf<<0); // AR, D1R + pcmMem[9+insAddr]=0; // DL, D2R + pcmMem[10+insAddr]=(0xf<<4)|(0xf<<0); // RC, RR + pcmMem[11+insAddr]=0; // AM + } + } + + memCompo.used=pcmMemLen; + } + memCompo.capacity=getSampleMemCapacity(0); +} + +int DivPlatformMultiPCM::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + for (int i=0; i<28; i++) { + isMuted[i]=false; + } + for (int i=0; i<28; i++) { + oscBuf[i]=new DivDispatchOscBuffer; + } + + setFlags(flags); + + pcmMem=new unsigned char[2097152]; + pcmMemLen=0; + pcmMemory.memory=pcmMem; + + reset(); + return 28; +} + +void DivPlatformMultiPCM::quit() { + for (int i=0; i<28; i++) { + delete oscBuf[i]; + } + delete[] pcmMem; +} + +// initialization of important arrays +DivPlatformMultiPCM::DivPlatformMultiPCM(): + pcmMemory(0x200000), + pcm(pcmMemory) { + sampleOff=new unsigned int[32768]; + sampleLoaded=new bool[32768]; +} + +DivPlatformMultiPCM::~DivPlatformMultiPCM() { + delete[] sampleOff; + delete[] sampleLoaded; +} diff --git a/src/engine/platform/multipcm.h b/src/engine/platform/multipcm.h new file mode 100644 index 000000000..42abffa2d --- /dev/null +++ b/src/engine/platform/multipcm.h @@ -0,0 +1,140 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2025 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 _MULTIPCM_H +#define _MULTIPCM_H + +#include "../dispatch.h" +#include "../../fixedQueue.h" +#include "sound/ymf278b/ymf278.h" + +class DivYMW258MemoryInterface: public MemoryInterface { + public: + unsigned char* memory; + DivYMW258MemoryInterface(unsigned size_) : memory(NULL), size(size_) {}; + byte operator[](unsigned address) const override { + if (memory && address { + unsigned int freqH, freqL; + int sample; + bool writeCtrl, levelDirect; + int lfo, vib, am; + int pan; + int macroVolMul; + Channel(): + SharedChannel(0x7f), + freqH(0), + freqL(0), + sample(-1), + writeCtrl(false), + levelDirect(true), + lfo(0), + vib(0), + am(0), + pan(0), + macroVolMul(64) {} + }; + Channel chan[28]; + DivDispatchOscBuffer* oscBuf[28]; + bool isMuted[28]; + struct QueuedWrite { + unsigned int addr; + unsigned char val; + bool addrOrVal; + QueuedWrite(): addr(0), val(0), addrOrVal(false) {} + QueuedWrite(unsigned int a, unsigned char v): addr(a), val(v), addrOrVal(false) {} + }; + FixedQueue writes; + + unsigned char* pcmMem; + size_t pcmMemLen; + DivYMW258MemoryInterface pcmMemory; + unsigned int* sampleOff; + bool* sampleLoaded; + + int delay, curChan, curAddr; + + unsigned char regPool[224]; + + short oldWrites[224]; + short pendingWrites[224]; + + // chips + YMW258 pcm; + + DivMemoryComposition memCompo; + + 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); + unsigned short getPan(int chan); + DivDispatchOscBuffer* getOscBuffer(int chan); + int mapVelocity(int ch, float vel); + float getGain(int ch, int vol); + 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); + bool keyOffAffectsPorta(int ch); + bool getLegacyAlwaysSetVolume(); + void toggleRegisterDump(bool enable); + void setFlags(const DivConfig& flags); + void notifyInsChange(int ins); + void notifyInsAddition(int sysID); + void notifyInsDeletion(void* ins); + int getPortaFloor(int ch); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + const void* getSampleMem(int index); + size_t getSampleMemCapacity(int index); + size_t getSampleMemUsage(int index); + bool hasSamplePtrHeader(int index=0); + bool hasSampleInsHeader(int index=0); + bool isSampleLoaded(int index, int sample); + const DivMemoryComposition* getMemCompo(int index); + void renderSamples(int chipID); + int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); + void quit(); + DivPlatformMultiPCM(); + ~DivPlatformMultiPCM(); +}; +#endif diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 10d28a73a..4179a05b9 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -3248,10 +3248,6 @@ bool DivPlatformOPL::hasSamplePtrHeader(int index) { return (index==0 && pcmChanOffs>=0); } -bool DivPlatformOPL::hasSampleInstHeader(int index) { - return (index==0 && pcmChanOffs>=0); -} - bool DivPlatformOPL::isSampleLoaded(int index, int sample) { if (index!=0) return false; if (sample<0 || sample>32767) return false; diff --git a/src/engine/platform/opl.h b/src/engine/platform/opl.h index 2905b71bf..b93279e98 100644 --- a/src/engine/platform/opl.h +++ b/src/engine/platform/opl.h @@ -219,7 +219,6 @@ class DivPlatformOPL: public DivDispatch { size_t getSampleMemCapacity(int index); size_t getSampleMemUsage(int index); bool hasSamplePtrHeader(int index=0); - bool hasSampleInstHeader(int index=0); bool isSampleLoaded(int index, int sample); const DivMemoryComposition* getMemCompo(int index); void renderSamples(int chipID); diff --git a/src/engine/platform/sound/ymf278b/ymf278.h b/src/engine/platform/sound/ymf278b/ymf278.h index 7e79c10c4..7ada11844 100644 --- a/src/engine/platform/sound/ymf278b/ymf278.h +++ b/src/engine/platform/sound/ymf278b/ymf278.h @@ -68,13 +68,13 @@ public: uint16_t wave; // wavetable number uint16_t FN; // f-number TODO store 'FN | 1024'? int8_t OCT; // octave [-8..+7] - bool PRVB; // pseudo-reverb + bool PRVB = false; // pseudo-reverb uint8_t TLdest; // destination total level uint8_t TL; // total level (goes towards TLdest) uint8_t pan; // panpot 0..15 - bool ch; // channel select + bool ch = false; // channel select bool keyon; // slot keyed on - bool DAMP; + bool DAMP = false; uint8_t lfo; // LFO speed 0..7 uint8_t vib; // vibrato 0..7 uint8_t AM; // AM level 0..7 diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index e3971a2c6..7448d858e 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -623,6 +623,12 @@ void DivEngine::registerSystems() { {0x2f, {DIV_CMD_MULTIPCM_LEVEL_DIRECT, _("2Fxx: PCM Level Direct"), effectValAnd<1>}}, }); + EffectHandlerMap multiPCMPostEffectHandlerMap={ + {0x20, {DIV_CMD_MULTIPCM_LFO, _("20xx: PCM LFO Rate (0 to 7)"), effectValAnd<7>}}, + {0x21, {DIV_CMD_MULTIPCM_VIB, _("21xx: PCM LFO PM Depth (0 to 7)"), effectValAnd<7>}}, + {0x22, {DIV_CMD_MULTIPCM_AM, _("22xx: PCM LFO AM Depth (0 to 7)"), effectValAnd<7>}}, + }; + EffectHandlerMap c64PostEffectHandlerMap={ {0x10, {DIV_CMD_WAVE, _("10xx: Set waveform (bit 0: triangle; bit 1: saw; bit 2: pulse; bit 3: noise)")}}, {0x11, {DIV_CMD_C64_CUTOFF, _("11xx: Set coarse cutoff (not recommended; use 4xxx instead)")}}, @@ -1327,14 +1333,16 @@ void DivEngine::registerSystems() { fmOPLPostEffectHandlerMap ); - // TODO: add 12-bit and 16-bit big endian formats sysDefs[DIV_SYSTEM_MULTIPCM]=new DivSysDef( - _("MultiPCM"), NULL, 0x92, 0, 28, false, true, 0, false, (1U<writeC(0x04); w->writeC(0x00); break; + case DIV_SYSTEM_MULTIPCM: + for (int i=0; i<28; i++) { + w->writeC(0xb5); // set channel + w->writeC(baseAddr2|1); + w->writeC(i); + for (int j=0; j<8; j++) { + w->writeC(0xb5); + w->writeC(baseAddr2|2); + w->writeC(j); + w->writeC(0xb5); // keyoff + w->writeC(baseAddr2|0); + w->writeC(0); + } + } + break; default: break; } @@ -1216,6 +1231,11 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeC(write.addr&0xff); w->writeC(write.val); break; + case DIV_SYSTEM_MULTIPCM: + w->writeC(0xb5); + w->writeC(baseAddr2|(write.addr&0x7f)); + w->writeC(write.val); + break; default: logW("write not handled!"); break; @@ -1398,6 +1418,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p DivDispatch* writeC219[2]={NULL,NULL}; DivDispatch* writeNES[2]={NULL,NULL}; DivDispatch* writePCM_OPL4[2]={NULL,NULL}; + DivDispatch* writeMultiPCM[2]={NULL,NULL}; int writeNESIndex[2]={0,0}; @@ -2018,6 +2039,21 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p howManyChips++; } break; + case DIV_SYSTEM_MULTIPCM: + if (!hasMultiPCM) { + hasMultiPCM=disCont[i].dispatch->rate*180; // for fix pitch in VGM players + CHIP_VOL(13,1.0); + willExport[i]=true; + writeMultiPCM[0]=disCont[i].dispatch; + } else if (!(hasMultiPCM&0x40000000)) { + isSecond[i]=true; + CHIP_VOL_SECOND(13,1.0); + willExport[i]=true; + writeMultiPCM[1]=disCont[i].dispatch; + hasMultiPCM|=0x40000000; + howManyChips++; + } + break; default: break; } @@ -2377,6 +2413,15 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p w->writeI(0); w->write(writePCM_OPL4[i]->getSampleMem(0),writePCM_OPL4[i]->getSampleMemUsage(0)); } + if (writeMultiPCM[i]!=NULL && writeMultiPCM[i]->getSampleMemUsage()>0) { + w->writeC(0x67); + w->writeC(0x66); + w->writeC(0x89); + w->writeI((writeMultiPCM[i]->getSampleMemUsage()+8)|(i*0x80000000)); + w->writeI(writeMultiPCM[i]->getSampleMemCapacity()); + w->writeI(0); + w->write(writeMultiPCM[i]->getSampleMem(),writeMultiPCM[i]->getSampleMemUsage()); + } } for (int i=0; i<2; i++) { diff --git a/src/gui/debug.cpp b/src/gui/debug.cpp index fa8afdba4..9097c2dcf 100644 --- a/src/gui/debug.cpp +++ b/src/gui/debug.cpp @@ -57,6 +57,7 @@ #include "../engine/platform/k053260.h" #include "../engine/platform/c140.h" #include "../engine/platform/msm6295.h" +#include "../engine/platform/multipcm.h" #include "../engine/platform/dummy.h" #define COMMON_CHIP_DEBUG \ @@ -550,6 +551,16 @@ void putDispatchChip(void* data, int type) { COMMON_CHIP_DEBUG_BOOL; break; } + case DIV_SYSTEM_MULTIPCM: { + DivPlatformMultiPCM* ch=(DivPlatformMultiPCM*)data; + ImGui::Text("> MultiPCM"); + COMMON_CHIP_DEBUG; + ImGui::Text("- delay: %d",ch->delay); + ImGui::Text("- curChan: %.2x",ch->curChan); + ImGui::Text("- curAddr: %.2x",ch->curAddr); + COMMON_CHIP_DEBUG_BOOL; + break; + } default: { DivDispatch* ch=(DivDispatch*)data; COMMON_CHIP_DEBUG; @@ -1097,6 +1108,22 @@ void putDispatchChan(void* data, int chanNum, int type) { ImGui::TextColored(ch->setPos?colorOn:colorOff,">> SetPos"); break; } + case DIV_SYSTEM_MULTIPCM: { + DivPlatformMultiPCM::Channel* ch=(DivPlatformMultiPCM::Channel*)data; + ImGui::Text("> MultiPCM"); + COMMON_CHAN_DEBUG; + ImGui::Text("- Sample: %d",ch->sample); + ImGui::Text("- freqHL: %.2x%.2x",ch->freqH,ch->freqL); + ImGui::Text("- lfo: %.2x",ch->lfo); + ImGui::Text("- vib: %.2x",ch->vib); + ImGui::Text("- am: %.2x",ch->am); + ImGui::Text("- pan: %.2x",ch->pan); + ImGui::Text("- macroVolMul: %.2x",ch->macroVolMul); + COMMON_CHAN_DEBUG_BOOL; + ImGui::TextColored(ch->writeCtrl?colorOn:colorOff,">> WriteCtrl"); + ImGui::TextColored(ch->levelDirect?colorOn:colorOff,">> LevelDirect"); + break; + } default: ImGui::Text("Unimplemented chip! Help!"); break; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index e7efc2b3f..7f60d28f7 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1861,13 +1861,13 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { int sampleCountBefore=e->song.sampleLen; std::vector instruments=e->instrumentFromFile(path,false); if (!instruments.empty()) { - int hasSampleInst=false; + int hasSampleIns=false; for (int s=0; ssong.systemLen; s++) { - if (e->getDispatch(s)->hasSampleInstHeader()) { - hasSampleInst=true; + if (e->getDispatch(s)->hasSampleInsHeader()) { + hasSampleIns=true; } } - if ((e->song.sampleLen!=sampleCountBefore) || hasSampleInst) { + if ((e->song.sampleLen!=sampleCountBefore) || hasSampleIns) { e->renderSamplesP(); } if (curFileDialog==GUI_FILE_INS_OPEN_REPLACE) { @@ -3934,13 +3934,13 @@ bool FurnaceGUI::loop() { DivWavetable* droppedWave=NULL; //DivSample* droppedSample=NULL; if (!instruments.empty()) { - bool hasSampleInst=false; + bool hasSampleIns=false; for (int s=0; ssong.systemLen; s++) { - if (e->getDispatch(s)->hasSampleInstHeader()) { - hasSampleInst=true; + if (e->getDispatch(s)->hasSampleInsHeader()) { + hasSampleIns=true; } } - if ((e->song.sampleLen!=sampleCountBefore) || hasSampleInst) { + if ((e->song.sampleLen!=sampleCountBefore) || hasSampleIns) { e->renderSamplesP(); } if (!e->getWarnings().empty()) { @@ -5533,13 +5533,13 @@ bool FurnaceGUI::loop() { instruments.push_back(j); } } - bool hasSampleInst=false; + bool hasSampleIns=false; for (int s=0; ssong.systemLen; s++) { - if (e->getDispatch(s)->hasSampleInstHeader()) { - hasSampleInst=true; + if (e->getDispatch(s)->hasSampleInsHeader()) { + hasSampleIns=true; } } - if ((e->song.sampleLen!=sampleCountBefore) || hasSampleInst) { + if ((e->song.sampleLen!=sampleCountBefore) || hasSampleIns) { e->renderSamplesP(); } if (warn) { @@ -5579,13 +5579,13 @@ bool FurnaceGUI::loop() { int sampleCountBefore=e->song.sampleLen; std::vector instruments=e->instrumentFromFile(copyOfName.c_str(),true,settings.readInsNames); if (!instruments.empty()) { - bool hasSampleInst=false; + bool hasSampleIns=false; for (int s=0; ssong.systemLen; s++) { - if (e->getDispatch(s)->hasSampleInstHeader()) { - hasSampleInst=true; + if (e->getDispatch(s)->hasSampleInsHeader()) { + hasSampleIns=true; } } - if ((e->song.sampleLen!=sampleCountBefore) || hasSampleInst) { + if ((e->song.sampleLen!=sampleCountBefore) || hasSampleIns) { e->renderSamplesP(); } if (!e->getWarnings().empty()) { diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 6e49d2473..608df187f 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -1330,6 +1330,7 @@ const int availableSystems[]={ DIV_SYSTEM_SUPERVISION, DIV_SYSTEM_UPD1771C, DIV_SYSTEM_SID3, + DIV_SYSTEM_MULTIPCM, 0 // don't remove this last one! }; @@ -1462,6 +1463,7 @@ const int chipsSample[]={ DIV_SYSTEM_GBA_MINMOD, DIV_SYSTEM_OPL4, DIV_SYSTEM_OPL4_DRUMS, + DIV_SYSTEM_MULTIPCM, 0 // don't remove this last one! }; diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 547a5c0c1..6c4622662 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1987,19 +1987,30 @@ void FurnaceGUI::drawGBEnv(unsigned char vol, unsigned char len, unsigned char s MARK_MODIFIED; \ e->notifyInsChange(curIns); \ updateFMPreview=true; \ - bool hasSampleInst=false; \ + bool hasSampleIns=false; \ for (int s=0; ssong.systemLen; s++) { \ - if (e->getDispatch(s)->hasSampleInstHeader()) { \ - hasSampleInst=true; \ + if (e->getDispatch(s)->hasSampleInsHeader()) { \ + hasSampleIns=true; \ } \ } \ - if (hasSampleInst) { \ + if (hasSampleIns) { \ e->renderSamplesP(curSample); \ } \ } #define PARAMETER MARK_MODIFIED; e->notifyInsChange(curIns); updateFMPreview=true; +#define REFRESH_INSTRUMENTS \ + bool hasSampleIns=false; \ + for (int s=0; ssong.systemLen; s++) { \ + if (e->getDispatch(s)->hasSampleInsHeader()) { \ + hasSampleIns=true; \ + } \ + } \ + if (hasSampleIns) { \ + e->renderSamplesP(curSample); \ + } + String genericGuide(float value) { return fmt::sprintf("%d",(int)value); } @@ -3494,6 +3505,7 @@ void FurnaceGUI::insTabSample(DivInstrument* ins) { id=fmt::sprintf("%d: %s",i,e->song.sample[i]->name); if (ImGui::Selectable(id.c_str(),ins->amiga.initSample==i)) { PARAMETER ins->amiga.initSample=i; + REFRESH_INSTRUMENTS } } ImGui::EndCombo(); @@ -6768,6 +6780,7 @@ void FurnaceGUI::drawInsEdit() { // reset macro zoom memset(ins->temp.vZoom,-1,sizeof(ins->temp.vZoom)); + REFRESH_INSTRUMENTS } } ImGui::EndCombo(); diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 41a29bd32..972d77e9c 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -2626,6 +2626,63 @@ void FurnaceGUI::initSystemPresets() { ) // 12.5MHz } ); + SUB_ENTRY( + _("Sega System Multi 32"), { + CH(DIV_SYSTEM_YM2612, 1.0f, 0, + "clockSel=2\n" + "chipType=0\n" + ), // discrete 8MHz YM3438 + CH(DIV_SYSTEM_MULTIPCM, 1.0f, 0, "") + } + ); + SUB_ENTRY( + _("Sega System Multi 32 (extended channel 3)"), { + CH(DIV_SYSTEM_YM2612_EXT, 1.0f, 0, + "clockSel=2\n" + "chipType=0\n" + ), // discrete 8MHz YM3438 + CH(DIV_SYSTEM_MULTIPCM, 1.0f, 0, "") + } + ); + SUB_ENTRY( + _("Sega System Multi 32 (CSM)"), { + CH(DIV_SYSTEM_YM2612_CSM, 1.0f, 0, + "clockSel=2\n" + "chipType=0\n" + ), // discrete 8MHz YM3438 + CH(DIV_SYSTEM_MULTIPCM, 1.0f, 0, "") + } + ); + SUB_ENTRY( + _("Sega Model 1/2"), { + CH(DIV_SYSTEM_YM2612, 1.0f, 0, + "clockSel=2\n" + "chipType=0\n" + ), // discrete 8MHz YM3438 + CH(DIV_SYSTEM_MULTIPCM, 1.0f, 0, ""), + CH(DIV_SYSTEM_MULTIPCM, 1.0f, 0, "") + } + ); + SUB_ENTRY( + _("Sega Model 1/2 (extended channel 3)"), { + CH(DIV_SYSTEM_YM2612_EXT, 1.0f, 0, + "clockSel=2\n" + "chipType=0\n" + ), // discrete 8MHz YM3438 + CH(DIV_SYSTEM_MULTIPCM, 1.0f, 0, ""), + CH(DIV_SYSTEM_MULTIPCM, 1.0f, 0, "") + } + ); + SUB_ENTRY( + _("Sega Model 1/2 (CSM)"), { + CH(DIV_SYSTEM_YM2612_CSM, 1.0f, 0, + "clockSel=2\n" + "chipType=0\n" + ), // discrete 8MHz YM3438 + CH(DIV_SYSTEM_MULTIPCM, 1.0f, 0, ""), + CH(DIV_SYSTEM_MULTIPCM, 1.0f, 0, "") + } + ); ENTRY( _("Seta"), {} @@ -3657,6 +3714,11 @@ void FurnaceGUI::initSystemPresets() { CH(DIV_SYSTEM_OPL4_DRUMS, 1.0f, 0, "") } ); + ENTRY( + "Yamaha YMW258-F (MultiPCM)", { + CH(DIV_SYSTEM_MULTIPCM, 1.0f, 0, "") + } + ); CATEGORY_END; CATEGORY_BEGIN(_("Wavetable"),_("chips which use user-specified waveforms to generate sound.")); diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 6b345aa28..f24ad4ec7 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -575,6 +575,11 @@ void FurnaceGUI::drawSampleEdit() { SAMPLE_WARN(warnLength,_("ES5506: maximum sample length is 2097024")); } break; + case DIV_SYSTEM_MULTIPCM: + if (sample->samples>65535) { + SAMPLE_WARN(warnLength,_("MultiPCM: maximum sample length is 65535")); + } + break; default: break; } diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index e8516fa2c..7690a2671 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -2728,6 +2728,7 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl case DIV_SYSTEM_BIFURCATOR: case DIV_SYSTEM_POWERNOISE: case DIV_SYSTEM_UPD1771C: + case DIV_SYSTEM_MULTIPCM: break; case DIV_SYSTEM_YMU759: case DIV_SYSTEM_ESFM: