Merge pull request #2666 from tildearrow/hasSampleHeader

Add primary MultiPCM support
This commit is contained in:
tildearrow 2025-09-14 17:00:03 -05:00 committed by GitHub
commit 6b9313bf25
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 1123 additions and 21 deletions

View file

@ -814,6 +814,7 @@ src/engine/platform/nds.cpp
src/engine/platform/bifurcator.cpp src/engine/platform/bifurcator.cpp
src/engine/platform/sid2.cpp src/engine/platform/sid2.cpp
src/engine/platform/sid3.cpp src/engine/platform/sid3.cpp
src/engine/platform/multipcm.cpp
src/engine/platform/pcmdac.cpp src/engine/platform/pcmdac.cpp
src/engine/platform/dummy.cpp src/engine/platform/dummy.cpp

View file

@ -917,6 +917,11 @@ class DivDispatch {
*/ */
virtual void notifyWaveChange(int wave); virtual void notifyWaveChange(int wave);
/**
* notify addition of an instrument.
*/
virtual void notifyInsAddition(int sysID);
/** /**
* notify deletion of an instrument. * notify deletion of an instrument.
*/ */
@ -966,14 +971,14 @@ class DivDispatch {
* @param index the memory index. * @param index the memory index.
* @return a pointer to sample memory, or NULL. * @return a pointer to sample memory, or NULL.
*/ */
virtual const void* getSampleMem(int index = 0); virtual const void* getSampleMem(int index=0);
/** /**
* Get sample memory capacity. * Get sample memory capacity.
* @param index the memory index. * @param index the memory index.
* @return memory capacity in bytes, or 0 if memory doesn't exist. * @return memory capacity in bytes, or 0 if memory doesn't exist.
*/ */
virtual size_t getSampleMemCapacity(int index = 0); virtual size_t getSampleMemCapacity(int index=0);
/** /**
* get sample memory name. * get sample memory name.
@ -994,7 +999,14 @@ class DivDispatch {
* @param index the memory index. * @param index the memory index.
* @return memory usage in bytes. * @return memory usage in bytes.
*/ */
virtual size_t getSampleMemUsage(int index = 0); virtual size_t getSampleMemUsage(int index=0);
/**
* check whether chip has sample pointer header in sample memory.
* @param index the memory index.
* @return whether it did.
*/
virtual bool hasSamplePtrHeader(int index=0);
/** /**
* check whether sample has been loaded in memory. * check whether sample has been loaded in memory.

View file

@ -93,6 +93,7 @@
#include "platform/bifurcator.h" #include "platform/bifurcator.h"
#include "platform/sid2.h" #include "platform/sid2.h"
#include "platform/sid3.h" #include "platform/sid3.h"
#include "platform/multipcm.h"
#include "platform/dummy.h" #include "platform/dummy.h"
#include "../ta-log.h" #include "../ta-log.h"
#include "song.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)); ((DivPlatformOPL*)dispatch)->setCore(eng->getConfInt("opl4Core",0));
} }
break; break;
case DIV_SYSTEM_MULTIPCM:
dispatch=new DivPlatformMultiPCM;
break;
case DIV_SYSTEM_DUMMY: case DIV_SYSTEM_DUMMY:
dispatch=new DivPlatformDummy; dispatch=new DivPlatformDummy;
break; break;

View file

@ -2647,6 +2647,9 @@ int DivEngine::addInstrument(int refChan, DivInstrumentType fallbackType) {
song.ins.push_back(ins); song.ins.push_back(ins);
song.insLen=insCount+1; song.insLen=insCount+1;
checkAssetDir(song.insDir,song.ins.size()); checkAssetDir(song.insDir,song.ins.size());
for (int i=0; i<song.systemLen; i++) {
disCont[i].dispatch->notifyInsAddition(i);
}
saveLock.unlock(); saveLock.unlock();
BUSY_END; BUSY_END;
return insCount; return insCount;
@ -2664,6 +2667,9 @@ int DivEngine::addInstrumentPtr(DivInstrument* which) {
checkAssetDir(song.insDir,song.ins.size()); checkAssetDir(song.insDir,song.ins.size());
checkAssetDir(song.waveDir,song.wave.size()); checkAssetDir(song.waveDir,song.wave.size());
checkAssetDir(song.sampleDir,song.sample.size()); checkAssetDir(song.sampleDir,song.sample.size());
for (int i=0; i<song.systemLen; i++) {
disCont[i].dispatch->notifyInsAddition(i);
}
saveLock.unlock(); saveLock.unlock();
BUSY_END; BUSY_END;
return song.insLen; return song.insLen;
@ -2675,6 +2681,9 @@ void DivEngine::loadTempIns(DivInstrument* which) {
tempIns=new DivInstrument; tempIns=new DivInstrument;
} }
*tempIns=*which; *tempIns=*which;
for (int i=0; i<song.systemLen; i++) {
disCont[i].dispatch->notifyInsAddition(i);
}
BUSY_END; BUSY_END;
} }

View file

@ -164,6 +164,10 @@ void DivDispatch::notifyWaveChange(int ins) {
} }
void DivDispatch::notifyInsAddition(int sysID) {
}
void DivDispatch::notifyInsDeletion(void* ins) { void DivDispatch::notifyInsDeletion(void* ins) {
logE("notifyInsDeletion NOT implemented!"); logE("notifyInsDeletion NOT implemented!");
abort(); abort();
@ -213,6 +217,10 @@ size_t DivDispatch::getSampleMemUsage(int index) {
return 0; return 0;
} }
bool DivDispatch::hasSamplePtrHeader(int index) {
return false;
}
size_t DivDispatch::getSampleMemOffset(int index) { size_t DivDispatch::getSampleMemOffset(int index) {
return 0; return 0;
} }

View file

@ -0,0 +1,754 @@
/**
* 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 <string.h>
#include <math.h>
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<len; h++) {
os[0]=0; os[1]=0;
if (!writes.empty() && --delay<0) {
QueuedWrite& w=writes.front();
if (w.addr==0xfffffffe) {
delay=w.val;
} else {
delay=1;
pcm.writeReg(slotsMPCM[(w.addr>>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();
renderInstruments();
for (int i=0; i<28; i++) {
chan[i]=DivPlatformMultiPCM::Channel();
chan[i].std.setEngine(parent);
chImmWrite(i,PCM_ADDR_PAN,(isMuted[i]?8:chan[i].pan)<<4);
}
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) {
renderInstruments();
for (int i=0; i<28; i++) {
if (chan[i].ins==ins) {
chan[i].insChanged=true;
}
}
}
void DivPlatformMultiPCM::notifyInsAddition(int sysID) {
renderInstruments();
}
void DivPlatformMultiPCM::notifyInsDeletion(void* ins) {
renderInstruments();
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<DivRegWrite>& 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::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;
}
// this is called on instrument change, reset and/or renderSamples().
// I am not making this part of DivDispatch as this is the only chip with
// instruments in ROM.
void DivPlatformMultiPCM::renderInstruments() {
// instrument table
int insCount=parent->song.insLen;
for (int i=0; i<insCount; i++) {
DivInstrument* ins=parent->song.ins[i];
unsigned int insAddr=(i*12);
short sample=ins->amiga.initSample;
if (sample>=0 && sample<parent->song.sampleLen) {
DivSample* s=parent->song.sample[sample];
unsigned char bitDepth;
int startPos=sampleOff[sample];
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
}
} else {
// fill to dummy instrument
pcmMem[insAddr]=0;
pcmMem[1+insAddr]=0;
pcmMem[2+insAddr]=0;
pcmMem[3+insAddr]=0;
pcmMem[4+insAddr]=0;
pcmMem[5+insAddr]=0xff;
pcmMem[6+insAddr]=0xff;
pcmMem[7+insAddr]=0; // LFO, VIB
pcmMem[8+insAddr]=(0xf<<4)|(0xf<<0); // AR, D1R
pcmMem[9+insAddr]=(0xf<<4)|(0xf<<0); // DL, D2R
pcmMem[10+insAddr]=(0xf<<4)|(0xf<<0); // RC, RR
pcmMem[11+insAddr]=0; // AM
}
}
}
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";
renderInstruments();
size_t memPos=0x1800;
int sampleCount=parent->song.sampleLen;
if (sampleCount>512) {
// mark the rest as unavailable
for (int i=512; i<sampleCount; i++) {
sampleLoaded[i]=false;
}
sampleCount=512;
}
for (int i=0; i<sampleCount; i++) {
DivSample* s=parent->song.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<actualLength; i++, j++) {
if (j>=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 (actualLength<length) {
logW("out of MultiPCM memory for sample %d!",i);
break;
}
sampleLoaded[i]=true;
pcmMemLen=memPos+256;
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;
}

View file

@ -0,0 +1,141 @@
/**
* 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<size) {
return memory[address];
}
return 0;
};
unsigned getSize() const override { return size; };
void write(unsigned address, byte value) override {};
void clear(byte value) override {};
private:
unsigned size;
};
class DivPlatformMultiPCM: public DivDispatch {
protected:
struct Channel: public SharedChannel<int> {
unsigned int freqH, freqL;
int sample;
bool writeCtrl, levelDirect;
int lfo, vib, am;
int pan;
int macroVolMul;
Channel():
SharedChannel<int>(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<QueuedWrite,4096> 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);
void renderInstruments();
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<DivRegWrite>& 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 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

View file

@ -3244,6 +3244,10 @@ size_t DivPlatformOPL::getSampleMemUsage(int index) {
(index==0 && adpcmChan>=0)?adpcmBMemLen:0; (index==0 && adpcmChan>=0)?adpcmBMemLen:0;
} }
bool DivPlatformOPL::hasSamplePtrHeader(int index) {
return (index==0 && pcmChanOffs>=0);
}
size_t DivPlatformOPL::getSampleMemOffset(int index) { size_t DivPlatformOPL::getSampleMemOffset(int index) {
return (index==0 && pcmChanOffs>=0 && ramSize<=0x200000)?0x200000:0; return (index==0 && pcmChanOffs>=0 && ramSize<=0x200000)?0x200000:0;
} }

View file

@ -218,6 +218,7 @@ class DivPlatformOPL: public DivDispatch {
const void* getSampleMem(int index); const void* getSampleMem(int index);
size_t getSampleMemCapacity(int index); size_t getSampleMemCapacity(int index);
size_t getSampleMemUsage(int index); size_t getSampleMemUsage(int index);
bool hasSamplePtrHeader(int index=0);
size_t getSampleMemOffset(int index); size_t getSampleMemOffset(int index);
bool isSampleLoaded(int index, int sample); bool isSampleLoaded(int index, int sample);
const DivMemoryComposition* getMemCompo(int index); const DivMemoryComposition* getMemCompo(int index);

View file

@ -964,6 +964,10 @@ size_t DivPlatformSNES::getSampleMemUsage(int index) {
return index == 0 ? sampleMemLen : 0; return index == 0 ? sampleMemLen : 0;
} }
bool DivPlatformSNES::hasSamplePtrHeader(int index) {
return true;
}
bool DivPlatformSNES::isSampleLoaded(int index, int sample) { bool DivPlatformSNES::isSampleLoaded(int index, int sample) {
if (index!=0) return false; if (index!=0) return false;
if (sample<0 || sample>32767) return false; if (sample<0 || sample>32767) return false;

View file

@ -124,9 +124,10 @@ class DivPlatformSNES: public DivDispatch {
void poke(unsigned int addr, unsigned short val); void poke(unsigned int addr, unsigned short val);
void poke(std::vector<DivRegWrite>& wlist); void poke(std::vector<DivRegWrite>& wlist);
const char** getRegisterSheet(); const char** getRegisterSheet();
const void* getSampleMem(int index = 0); const void* getSampleMem(int index=0);
size_t getSampleMemCapacity(int index = 0); size_t getSampleMemCapacity(int index=0);
size_t getSampleMemUsage(int index = 0); size_t getSampleMemUsage(int index=0);
bool hasSamplePtrHeader(int index=0);
bool isSampleLoaded(int index, int sample); bool isSampleLoaded(int index, int sample);
const DivMemoryComposition* getMemCompo(int index); const DivMemoryComposition* getMemCompo(int index);
void renderSamples(int chipID); void renderSamples(int chipID);

View file

@ -68,13 +68,13 @@ public:
uint16_t wave; // wavetable number uint16_t wave; // wavetable number
uint16_t FN; // f-number TODO store 'FN | 1024'? uint16_t FN; // f-number TODO store 'FN | 1024'?
int8_t OCT; // octave [-8..+7] int8_t OCT; // octave [-8..+7]
bool PRVB; // pseudo-reverb bool PRVB = false; // pseudo-reverb
uint8_t TLdest; // destination total level uint8_t TLdest; // destination total level
uint8_t TL; // total level (goes towards TLdest) uint8_t TL; // total level (goes towards TLdest)
uint8_t pan; // panpot 0..15 uint8_t pan; // panpot 0..15
bool ch; // channel select bool ch = false; // channel select
bool keyon; // slot keyed on bool keyon; // slot keyed on
bool DAMP; bool DAMP = false;
uint8_t lfo; // LFO speed 0..7 uint8_t lfo; // LFO speed 0..7
uint8_t vib; // vibrato 0..7 uint8_t vib; // vibrato 0..7
uint8_t AM; // AM level 0..7 uint8_t AM; // AM level 0..7

View file

@ -623,6 +623,12 @@ void DivEngine::registerSystems() {
{0x2f, {DIV_CMD_MULTIPCM_LEVEL_DIRECT, _("2Fxx: PCM Level Direct"), effectValAnd<1>}}, {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={ EffectHandlerMap c64PostEffectHandlerMap={
{0x10, {DIV_CMD_WAVE, _("10xx: Set waveform (bit 0: triangle; bit 1: saw; bit 2: pulse; bit 3: noise)")}}, {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)")}}, {0x11, {DIV_CMD_C64_CUTOFF, _("11xx: Set coarse cutoff (not recommended; use 4xxx instead)")}},
@ -1327,14 +1333,16 @@ void DivEngine::registerSystems() {
fmOPLPostEffectHandlerMap fmOPLPostEffectHandlerMap
); );
// TODO: add 12-bit and 16-bit big endian formats
sysDefs[DIV_SYSTEM_MULTIPCM]=new DivSysDef( sysDefs[DIV_SYSTEM_MULTIPCM]=new DivSysDef(
_("MultiPCM"), NULL, 0x92, 0, 28, false, true, 0, false, (1U<<DIV_SAMPLE_DEPTH_8BIT)|(1U<<DIV_SAMPLE_DEPTH_16BIT), 0, 0, _("MultiPCM"), NULL, 0x92, 0, 28, false, true, 0x161, false, (1U<<DIV_SAMPLE_DEPTH_8BIT)|(1U<<DIV_SAMPLE_DEPTH_12BIT), 0, 0,
_("how many channels of PCM do you want?\nMultiPCM: yes"), _("how many channels of PCM do you want?\nMultiPCM: yes"),
{_("Channel 1"), _("Channel 2"), _("Channel 3"), _("Channel 4"), _("Channel 5"), _("Channel 6"), _("Channel 7"), _("Channel 8"), _("Channel 9"), _("Channel 10"), _("Channel 11"), _("Channel 12"), _("Channel 13"), _("Channel 14"), _("Channel 15"), _("Channel 16"), _("Channel 17"), _("Channel 18"), _("Channel 19"), _("Channel 20"), _("Channel 21"), _("Channel 22"), _("Channel 23"), _("Channel 24"), _("Channel 25"), _("Channel 26"), _("Channel 27"), _("Channel 28")}, {_("Channel 1"), _("Channel 2"), _("Channel 3"), _("Channel 4"), _("Channel 5"), _("Channel 6"), _("Channel 7"), _("Channel 8"), _("Channel 9"), _("Channel 10"), _("Channel 11"), _("Channel 12"), _("Channel 13"), _("Channel 14"), _("Channel 15"), _("Channel 16"), _("Channel 17"), _("Channel 18"), _("Channel 19"), _("Channel 20"), _("Channel 21"), _("Channel 22"), _("Channel 23"), _("Channel 24"), _("Channel 25"), _("Channel 26"), _("Channel 27"), _("Channel 28")},
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28"}, {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28"},
{DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, {DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
{DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM} {DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM},
{DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA},
{},
multiPCMPostEffectHandlerMap
); );
sysDefs[DIV_SYSTEM_PCSPKR]=new DivSysDef( sysDefs[DIV_SYSTEM_PCSPKR]=new DivSysDef(

View file

@ -708,6 +708,21 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
w->writeC(0x04); w->writeC(0x04);
w->writeC(0x00); w->writeC(0x00);
break; 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: default:
break; break;
} }
@ -1216,6 +1231,11 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
w->writeC(write.addr&0xff); w->writeC(write.addr&0xff);
w->writeC(write.val); w->writeC(write.val);
break; break;
case DIV_SYSTEM_MULTIPCM:
w->writeC(0xb5);
w->writeC(baseAddr2|(write.addr&0x7f));
w->writeC(write.val);
break;
default: default:
logW("write not handled!"); logW("write not handled!");
break; break;
@ -1398,6 +1418,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
DivDispatch* writeC219[2]={NULL,NULL}; DivDispatch* writeC219[2]={NULL,NULL};
DivDispatch* writeNES[2]={NULL,NULL}; DivDispatch* writeNES[2]={NULL,NULL};
DivDispatch* writePCM_OPL4[2]={NULL,NULL}; DivDispatch* writePCM_OPL4[2]={NULL,NULL};
DivDispatch* writeMultiPCM[2]={NULL,NULL};
int writeNESIndex[2]={0,0}; int writeNESIndex[2]={0,0};
@ -2018,6 +2039,21 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
howManyChips++; howManyChips++;
} }
break; 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: default:
break; break;
} }
@ -2381,6 +2417,15 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
w->writeC(mem[i]); w->writeC(mem[i]);
} }
} }
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++) { for (int i=0; i<2; i++) {

View file

@ -57,6 +57,7 @@
#include "../engine/platform/k053260.h" #include "../engine/platform/k053260.h"
#include "../engine/platform/c140.h" #include "../engine/platform/c140.h"
#include "../engine/platform/msm6295.h" #include "../engine/platform/msm6295.h"
#include "../engine/platform/multipcm.h"
#include "../engine/platform/dummy.h" #include "../engine/platform/dummy.h"
#define COMMON_CHIP_DEBUG \ #define COMMON_CHIP_DEBUG \
@ -550,6 +551,16 @@ void putDispatchChip(void* data, int type) {
COMMON_CHIP_DEBUG_BOOL; COMMON_CHIP_DEBUG_BOOL;
break; 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: { default: {
DivDispatch* ch=(DivDispatch*)data; DivDispatch* ch=(DivDispatch*)data;
COMMON_CHIP_DEBUG; COMMON_CHIP_DEBUG;
@ -1097,6 +1108,22 @@ void putDispatchChan(void* data, int chanNum, int type) {
ImGui::TextColored(ch->setPos?colorOn:colorOff,">> SetPos"); ImGui::TextColored(ch->setPos?colorOn:colorOff,">> SetPos");
break; 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: default:
ImGui::Text("Unimplemented chip! Help!"); ImGui::Text("Unimplemented chip! Help!");
break; break;

View file

@ -1870,6 +1870,7 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
} }
if (prevIns>=0 && prevIns<=(int)e->song.ins.size()) { if (prevIns>=0 && prevIns<=(int)e->song.ins.size()) {
*e->song.ins[prevIns]=*instruments[0]; *e->song.ins[prevIns]=*instruments[0];
e->notifyInsChange(prevIns);
} }
} else { } else {
e->loadTempIns(instruments[0]); e->loadTempIns(instruments[0]);
@ -5050,6 +5051,7 @@ bool FurnaceGUI::loop() {
if (prevInsData!=NULL) { if (prevInsData!=NULL) {
if (prevIns>=0 && prevIns<(int)e->song.ins.size()) { if (prevIns>=0 && prevIns<(int)e->song.ins.size()) {
*e->song.ins[prevIns]=*prevInsData; *e->song.ins[prevIns]=*prevInsData;
e->notifyInsChange(prevIns);
} }
} }
} else { } else {
@ -5579,6 +5581,7 @@ bool FurnaceGUI::loop() {
// reset macro zoom // reset macro zoom
memset(e->song.ins[curIns]->temp.vZoom,-1,sizeof(e->song.ins[curIns]->temp.vZoom)); memset(e->song.ins[curIns]->temp.vZoom,-1,sizeof(e->song.ins[curIns]->temp.vZoom));
MARK_MODIFIED; MARK_MODIFIED;
e->notifyInsChange(curIns);
} else { } else {
showError(_("...but you haven't selected an instrument!")); showError(_("...but you haven't selected an instrument!"));
} }
@ -6792,6 +6795,7 @@ bool FurnaceGUI::loop() {
*e->song.ins[curIns]=*i.first; *e->song.ins[curIns]=*i.first;
// reset macro zoom // reset macro zoom
memset(e->song.ins[curIns]->temp.vZoom,-1,sizeof(e->song.ins[curIns]->temp.vZoom)); memset(e->song.ins[curIns]->temp.vZoom,-1,sizeof(e->song.ins[curIns]->temp.vZoom));
e->notifyInsChange(curIns);
} else { } else {
showError(_("...but you haven't selected an instrument!")); showError(_("...but you haven't selected an instrument!"));
} }

View file

@ -1330,6 +1330,7 @@ const int availableSystems[]={
DIV_SYSTEM_SUPERVISION, DIV_SYSTEM_SUPERVISION,
DIV_SYSTEM_UPD1771C, DIV_SYSTEM_UPD1771C,
DIV_SYSTEM_SID3, DIV_SYSTEM_SID3,
DIV_SYSTEM_MULTIPCM,
0 // don't remove this last one! 0 // don't remove this last one!
}; };
@ -1462,6 +1463,7 @@ const int chipsSample[]={
DIV_SYSTEM_GBA_MINMOD, DIV_SYSTEM_GBA_MINMOD,
DIV_SYSTEM_OPL4, DIV_SYSTEM_OPL4,
DIV_SYSTEM_OPL4_DRUMS, DIV_SYSTEM_OPL4_DRUMS,
DIV_SYSTEM_MULTIPCM,
0 // don't remove this last one! 0 // don't remove this last one!
}; };

View file

@ -3512,6 +3512,9 @@ void FurnaceGUI::insTabSample(DivInstrument* ins) {
// Note map // Note map
ImGui::BeginDisabled(ins->amiga.useWave); ImGui::BeginDisabled(ins->amiga.useWave);
P(ImGui::Checkbox(_("Use sample map"),&ins->amiga.useNoteMap)); P(ImGui::Checkbox(_("Use sample map"),&ins->amiga.useNoteMap));
if ((ins->type==DIV_INS_MULTIPCM) && ImGui::IsItemHovered()) {
ImGui::SetTooltip(_("Only for OPL4 PCM."));
}
if (ins->amiga.useNoteMap) { if (ins->amiga.useNoteMap) {
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) && ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows)) sampleMapFocused=false; if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) && ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows)) sampleMapFocused=false;
if (curWindowLast!=GUI_WINDOW_INS_EDIT) sampleMapFocused=false; if (curWindowLast!=GUI_WINDOW_INS_EDIT) sampleMapFocused=false;

View file

@ -2626,6 +2626,63 @@ void FurnaceGUI::initSystemPresets() {
) // 12.5MHz ) // 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( ENTRY(
_("Seta"), {} _("Seta"), {}
@ -3657,6 +3714,11 @@ void FurnaceGUI::initSystemPresets() {
CH(DIV_SYSTEM_OPL4_DRUMS, 1.0f, 0, "") CH(DIV_SYSTEM_OPL4_DRUMS, 1.0f, 0, "")
} }
); );
ENTRY(
"Yamaha YMW258-F (MultiPCM)", {
CH(DIV_SYSTEM_MULTIPCM, 1.0f, 0, "")
}
);
CATEGORY_END; CATEGORY_END;
CATEGORY_BEGIN(_("Wavetable"),_("chips which use user-specified waveforms to generate sound.")); CATEGORY_BEGIN(_("Wavetable"),_("chips which use user-specified waveforms to generate sound."));

View file

@ -60,6 +60,18 @@ const double timeMultipliers[13]={
_x+=_text; \ _x+=_text; \
} }
// with sample pointer header in sample memory
#define REFRESH_SAMPLE \
bool hasSamplePtr=false; \
for (int s=0; s<e->song.systemLen; s++) { \
if (e->getDispatch(s)->hasSamplePtrHeader()) { \
hasSamplePtr=true; \
} \
} \
if (hasSamplePtr) { \
e->renderSamplesP(curSample); \
}
#define MAX_RATE(_name,_x) \ #define MAX_RATE(_name,_x) \
if (e->isPreviewingSample()) { \ if (e->isPreviewingSample()) { \
if ((int)e->getSamplePreviewRate()>(int)(_x)) { \ if ((int)e->getSamplePreviewRate()>(int)(_x)) { \
@ -563,6 +575,11 @@ void FurnaceGUI::drawSampleEdit() {
SAMPLE_WARN(warnLength,_("ES5506: maximum sample length is 2097024")); SAMPLE_WARN(warnLength,_("ES5506: maximum sample length is 2097024"));
} }
break; break;
case DIV_SYSTEM_MULTIPCM:
if (sample->samples>65535) {
SAMPLE_WARN(warnLength,_("MultiPCM: maximum sample length is 65535"));
}
break;
default: default:
break; break;
} }
@ -638,9 +655,7 @@ void FurnaceGUI::drawSampleEdit() {
sample->loopEnd=sample->samples;*/ sample->loopEnd=sample->samples;*/
} }
updateSampleTex=true; updateSampleTex=true;
if (e->getSampleFormatMask()&(1U<<DIV_SAMPLE_DEPTH_BRR)) { REFRESH_SAMPLE
e->renderSamplesP(curSample);
}
} }
popWarningColor(); popWarningColor();
if (ImGui::IsItemHovered() && (!warnLoop.empty() || sample->depth==DIV_SAMPLE_DEPTH_BRR)) { if (ImGui::IsItemHovered() && (!warnLoop.empty() || sample->depth==DIV_SAMPLE_DEPTH_BRR)) {
@ -877,9 +892,7 @@ void FurnaceGUI::drawSampleEdit() {
sample->loopStart=sample->loopEnd; sample->loopStart=sample->loopEnd;
} }
updateSampleTex=true; updateSampleTex=true;
if (e->getSampleFormatMask()&(1U<<DIV_SAMPLE_DEPTH_BRR)) { REFRESH_SAMPLE
e->renderSamplesP(curSample);
}
} }
if (ImGui::IsItemActive()) { if (ImGui::IsItemActive()) {
keepLoopAlive=true; keepLoopAlive=true;
@ -920,9 +933,7 @@ void FurnaceGUI::drawSampleEdit() {
sample->loopEnd=sample->samples; sample->loopEnd=sample->samples;
} }
updateSampleTex=true; updateSampleTex=true;
if (e->getSampleFormatMask()&(1U<<DIV_SAMPLE_DEPTH_BRR)) { REFRESH_SAMPLE
e->renderSamplesP(curSample);
}
} }
if (ImGui::IsItemActive()) { if (ImGui::IsItemActive()) {
keepLoopAlive=true; keepLoopAlive=true;

View file

@ -2728,6 +2728,7 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl
case DIV_SYSTEM_BIFURCATOR: case DIV_SYSTEM_BIFURCATOR:
case DIV_SYSTEM_POWERNOISE: case DIV_SYSTEM_POWERNOISE:
case DIV_SYSTEM_UPD1771C: case DIV_SYSTEM_UPD1771C:
case DIV_SYSTEM_MULTIPCM:
break; break;
case DIV_SYSTEM_YMU759: case DIV_SYSTEM_YMU759:
case DIV_SYSTEM_ESFM: case DIV_SYSTEM_ESFM: