Merge pull request #2666 from tildearrow/hasSampleHeader
Add primary MultiPCM support
This commit is contained in:
commit
6b9313bf25
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
754
src/engine/platform/multipcm.cpp
Normal file
754
src/engine/platform/multipcm.cpp
Normal 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;
|
||||||
|
}
|
141
src/engine/platform/multipcm.h
Normal file
141
src/engine/platform/multipcm.h
Normal 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
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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++) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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!"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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!
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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."));
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in a new issue