757 lines
21 KiB
C++
757 lines
21 KiB
C++
/**
|
|
* 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.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_MULTIPCM));
|
|
}
|
|
if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.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::notifySampleChange(int sample) {
|
|
renderInstruments();
|
|
}
|
|
|
|
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";
|
|
|
|
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;
|
|
}
|
|
renderInstruments();
|
|
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;
|
|
}
|