Merge branch 'master' of https://github.com/tildearrow/furnace into es5506_alt
* 'master' of https://github.com/tildearrow/furnace: (64 commits) finish wave synth! update contributing guidelines Namco WSG: make non-linear slides faster wave synth work SoundUnit: fix order of filter bits GUI: add Namco arcade presets implement some dual wave synth effects Y8950: fix ADPCM VGM export Namco WSG: finish it up Namco WSG: 8 channel WSG (CUS30) now works Namco WSG: muting Namco WSG: 8 channel WSG (15xx) now works Namco WSG: 3 channel WSG now works YM2612: change key on/off strategy GUI: fix scrollbars not working Please enter the commit message for your changes. shhhhhhhhhhhh update to-do list Whoops it doesn't work (yet) ... # Conflicts: # src/engine/dispatch.h # src/engine/platform/su.cpp # src/engine/playback.cpp # src/engine/sample.cpp # src/engine/sample.h # src/engine/song.h # src/engine/vgmOps.cpp # src/gui/presets.cpp
This commit is contained in:
commit
17881837ab
156 changed files with 86578 additions and 714 deletions
|
|
@ -59,6 +59,8 @@ enum DivDispatchCmds {
|
|||
DIV_CMD_SAMPLE_FREQ, // (frequency)
|
||||
DIV_CMD_SAMPLE_BANK, // (bank)
|
||||
DIV_CMD_SAMPLE_POS, // (pos)
|
||||
DIV_CMD_SAMPLE_TRANSWAVE_SLICE_MODE, // (enabled)
|
||||
DIV_CMD_SAMPLE_TRANSWAVE_SLICE_POS, // (slice)
|
||||
|
||||
DIV_CMD_FM_HARD_RESET, // (enabled)
|
||||
DIV_CMD_FM_LFO, // (speed)
|
||||
|
|
@ -181,6 +183,13 @@ enum DivDispatchCmds {
|
|||
DIV_CMD_ES5506_ENVELOPE_K2RAMP, // (ramp, slowdown)
|
||||
DIV_CMD_ES5506_PAUSE, // (value)
|
||||
|
||||
DIV_CMD_SU_SWEEP_PERIOD_LOW, // (which, val)
|
||||
DIV_CMD_SU_SWEEP_PERIOD_HIGH, // (which, val)
|
||||
DIV_CMD_SU_SWEEP_BOUND, // (which, val)
|
||||
DIV_CMD_SU_SWEEP_ENABLE, // (which, val)
|
||||
DIV_CMD_SU_SYNC_PERIOD_LOW,
|
||||
DIV_CMD_SU_SYNC_PERIOD_HIGH,
|
||||
|
||||
DIV_ALWAYS_SET_VOLUME, // () -> alwaysSetVol
|
||||
|
||||
DIV_CMD_MAX
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
#include "engine.h"
|
||||
#include "platform/genesis.h"
|
||||
#include "platform/genesisext.h"
|
||||
#include "platform/namcowsg.h"
|
||||
#include "platform/sms.h"
|
||||
#include "platform/opll.h"
|
||||
#include "platform/gb.h"
|
||||
|
|
@ -32,6 +33,7 @@
|
|||
#include "platform/ym2203.h"
|
||||
#include "platform/ym2203ext.h"
|
||||
#include "platform/ym2608.h"
|
||||
#include "platform/ym2608ext.h"
|
||||
#include "platform/ym2610.h"
|
||||
#include "platform/ym2610ext.h"
|
||||
#include "platform/ym2610b.h"
|
||||
|
|
@ -59,6 +61,8 @@
|
|||
#include "platform/mmc5.h"
|
||||
#include "platform/es5506.h"
|
||||
#include "platform/scc.h"
|
||||
#include "platform/ymz280b.h"
|
||||
#include "platform/rf5c68.h"
|
||||
#include "platform/dummy.h"
|
||||
#include "../ta-log.h"
|
||||
#include "platform/zxbeeper.h"
|
||||
|
|
@ -248,6 +252,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
|||
case DIV_SYSTEM_PC98:
|
||||
dispatch=new DivPlatformYM2608;
|
||||
break;
|
||||
case DIV_SYSTEM_PC98_EXT:
|
||||
dispatch=new DivPlatformYM2608Ext;
|
||||
break;
|
||||
case DIV_SYSTEM_OPLL:
|
||||
case DIV_SYSTEM_OPLL_DRUMS:
|
||||
case DIV_SYSTEM_VRC7:
|
||||
|
|
@ -351,9 +358,29 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
|||
dispatch=new DivPlatformSCC;
|
||||
((DivPlatformSCC*)dispatch)->setChipModel(true);
|
||||
break;
|
||||
case DIV_SYSTEM_YMZ280B:
|
||||
dispatch=new DivPlatformYMZ280B;
|
||||
((DivPlatformYMZ280B*)dispatch)->setChipModel(280);
|
||||
break;
|
||||
case DIV_SYSTEM_RF5C68:
|
||||
dispatch=new DivPlatformRF5C68;
|
||||
break;
|
||||
case DIV_SYSTEM_SOUND_UNIT:
|
||||
dispatch=new DivPlatformSoundUnit;
|
||||
break;
|
||||
case DIV_SYSTEM_NAMCO:
|
||||
dispatch=new DivPlatformNamcoWSG;
|
||||
// Pac-Man (TODO: support Pole Position?)
|
||||
((DivPlatformNamcoWSG*)dispatch)->setDeviceType(1);
|
||||
break;
|
||||
case DIV_SYSTEM_NAMCO_15XX:
|
||||
dispatch=new DivPlatformNamcoWSG;
|
||||
((DivPlatformNamcoWSG*)dispatch)->setDeviceType(15);
|
||||
break;
|
||||
case DIV_SYSTEM_NAMCO_CUS30:
|
||||
dispatch=new DivPlatformNamcoWSG;
|
||||
((DivPlatformNamcoWSG*)dispatch)->setDeviceType(30);
|
||||
break;
|
||||
case DIV_SYSTEM_DUMMY:
|
||||
dispatch=new DivPlatformDummy;
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -1069,6 +1069,7 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) {
|
|||
endOfSong=false;
|
||||
} else {
|
||||
ticks=1;
|
||||
tempoAccum=0;
|
||||
totalTicks=0;
|
||||
totalSeconds=0;
|
||||
totalTicksR=0;
|
||||
|
|
@ -2683,6 +2684,7 @@ void DivEngine::quitDispatch() {
|
|||
speedAB=false;
|
||||
endOfSong=false;
|
||||
ticks=0;
|
||||
tempoAccum=0;
|
||||
curRow=0;
|
||||
curOrder=0;
|
||||
nextSpeed=3;
|
||||
|
|
|
|||
|
|
@ -45,8 +45,8 @@
|
|||
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
|
||||
#define BUSY_END isBusy.unlock(); softLocked=false;
|
||||
|
||||
#define DIV_VERSION "dev95"
|
||||
#define DIV_ENGINE_VERSION 95
|
||||
#define DIV_VERSION "dev96"
|
||||
#define DIV_ENGINE_VERSION 96
|
||||
|
||||
// for imports
|
||||
#define DIV_VERSION_MOD 0xff01
|
||||
|
|
@ -312,6 +312,7 @@ class DivEngine {
|
|||
int changeOrd, changePos, totalSeconds, totalTicks, totalTicksR, totalCmds, lastCmds, cmdsPerSecond, globalPitch;
|
||||
unsigned char extValue;
|
||||
unsigned char speed1, speed2;
|
||||
short tempoAccum;
|
||||
DivStatusView view;
|
||||
DivHaltPositions haltOn;
|
||||
DivChannelState chan[DIV_MAX_CHANS];
|
||||
|
|
@ -944,6 +945,7 @@ class DivEngine {
|
|||
extValue(0),
|
||||
speed1(3),
|
||||
speed2(3),
|
||||
tempoAccum(0),
|
||||
view(DIV_STATUS_NOTHING),
|
||||
haltOn(DIV_HALT_NONE),
|
||||
audioEngine(DIV_AUDIO_NULL),
|
||||
|
|
|
|||
|
|
@ -1404,11 +1404,19 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
|||
} else {
|
||||
reader.readC();
|
||||
}
|
||||
for (int i=0; i<18; i++) {
|
||||
for (int i=0; i<14; i++) {
|
||||
reader.readC();
|
||||
}
|
||||
}
|
||||
|
||||
// first song virtual tempo
|
||||
if (ds.version>=96) {
|
||||
subSong->virtualTempoN=reader.readS();
|
||||
subSong->virtualTempoD=reader.readS();
|
||||
} else {
|
||||
reader.readI();
|
||||
}
|
||||
|
||||
// subsongs
|
||||
if (ds.version>=95) {
|
||||
subSong->name=reader.readString();
|
||||
|
|
@ -1457,7 +1465,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
|||
subSong->hilightA=reader.readC();
|
||||
subSong->hilightB=reader.readC();
|
||||
|
||||
reader.readI(); // reserved
|
||||
if (ds.version>=96) {
|
||||
subSong->virtualTempoN=reader.readS();
|
||||
subSong->virtualTempoD=reader.readS();
|
||||
} else {
|
||||
reader.readI();
|
||||
}
|
||||
|
||||
subSong->name=reader.readString();
|
||||
subSong->notes=reader.readString();
|
||||
|
|
@ -2871,9 +2884,13 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
|
|||
w->writeC(song.snDutyReset);
|
||||
w->writeC(song.pitchMacroIsLinear);
|
||||
w->writeC(song.pitchSlideSpeed);
|
||||
for (int i=0; i<18; i++) {
|
||||
for (int i=0; i<14; i++) {
|
||||
w->writeC(0);
|
||||
}
|
||||
|
||||
// first subsong virtual tempo
|
||||
w->writeS(subSong->virtualTempoN);
|
||||
w->writeS(subSong->virtualTempoD);
|
||||
|
||||
// subsong list
|
||||
w->writeString(subSong->name,false);
|
||||
|
|
@ -2904,7 +2921,8 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
|
|||
w->writeS(subSong->ordersLen);
|
||||
w->writeC(subSong->hilightA);
|
||||
w->writeC(subSong->hilightB);
|
||||
w->writeI(0); // reserved
|
||||
w->writeS(subSong->virtualTempoN);
|
||||
w->writeS(subSong->virtualTempoD);
|
||||
|
||||
w->writeString(subSong->name,false);
|
||||
w->writeString(subSong->notes,false);
|
||||
|
|
@ -3184,6 +3202,14 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
|
|||
addWarning("only the currently selected subsong will be saved");
|
||||
}
|
||||
|
||||
if (curSubSong->virtualTempoD!=curSubSong->virtualTempoN) {
|
||||
addWarning(".dmf format does not support virtual tempo");
|
||||
}
|
||||
|
||||
if (song.tuning<439.99 && song.tuning>440.01) {
|
||||
addWarning(".dmf format does not support tuning");
|
||||
}
|
||||
|
||||
if (sys==DIV_SYSTEM_C64_6581 || sys==DIV_SYSTEM_C64_8580) {
|
||||
addWarning("absolute duty/cutoff macro not available in .dmf!");
|
||||
addWarning("duty precision will be lost");
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ enum DivInstrumentType: unsigned short {
|
|||
DIV_INS_MULTIPCM=28,
|
||||
DIV_INS_SNES=29,
|
||||
DIV_INS_SU=30,
|
||||
DIV_INS_NAMCO=31,
|
||||
DIV_INS_MAX,
|
||||
DIV_INS_NULL
|
||||
};
|
||||
|
|
@ -512,6 +513,7 @@ enum DivWaveSynthEffects {
|
|||
DIV_WS_SUBTRACT,
|
||||
DIV_WS_AVERAGE,
|
||||
DIV_WS_PHASE,
|
||||
DIV_WS_CHORUS,
|
||||
|
||||
DIV_WS_SINGLE_MAX,
|
||||
|
||||
|
|
@ -522,7 +524,9 @@ enum DivWaveSynthEffects {
|
|||
DIV_WS_PING_PONG,
|
||||
DIV_WS_OVERLAY,
|
||||
DIV_WS_NEGATIVE_OVERLAY,
|
||||
DIV_WS_PHASE_DUAL,
|
||||
DIV_WS_SLIDE,
|
||||
DIV_WS_MIX,
|
||||
DIV_WS_PHASE_MOD,
|
||||
|
||||
DIV_WS_DUAL_MAX
|
||||
};
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ const char** DivPlatformES5506::getRegisterSheet() {
|
|||
const char* DivPlatformES5506::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xx: Change waveform";
|
||||
return "10xx: Change waveform or sample, transwave index";
|
||||
break;
|
||||
case 0x11:
|
||||
return "11xx: Set filter mode (00 to 03)";
|
||||
|
|
@ -123,6 +123,9 @@ const char* DivPlatformES5506::getEffectName(unsigned char effect) {
|
|||
case 0x12:
|
||||
return "120x: Set pause (bit 0)";
|
||||
break;
|
||||
case 0x13:
|
||||
return "130x: Set transwave slice mode (bit 0)";
|
||||
break;
|
||||
case 0x14:
|
||||
return "14xx: Set filter coefficient K1 low byte";
|
||||
break;
|
||||
|
|
@ -284,12 +287,10 @@ void DivPlatformES5506::e_pin(bool state)
|
|||
DivInstrument* ins=parent->getIns(chan[i].ins);
|
||||
if (!ins->amiga.useNoteMap && ins->amiga.transWave.enable) {
|
||||
const int next=chan[ch].pcm.next;
|
||||
bool sampleVaild=false;
|
||||
if (next>=0 && next<ins->amiga.transWaveMap.size()) {
|
||||
DivInstrumentAmiga::TransWaveMap& transWaveInd=ins->amiga.transWaveMap[next];
|
||||
int sample=transWaveInd.ind;
|
||||
if (sample>=0 && sample<parent->song.sampleLen) {
|
||||
sampleVaild=true;
|
||||
chan[ch].pcm.index=sample;
|
||||
chan[ch].transWave.ind=next;
|
||||
DivSample* s=parent->getSample(sample);
|
||||
|
|
@ -328,8 +329,8 @@ void DivPlatformES5506::e_pin(bool state)
|
|||
const unsigned int start=s->offES5506<<10;
|
||||
const unsigned int length=s->samples-1;
|
||||
const unsigned int end=start+(length<<11);
|
||||
const double nextFreqOffs=PITCH_OFFSET*off;
|
||||
chan[ch].pcm.loopMode=loopMode;
|
||||
chan[ch].pcm.nextFreqOffs=PITCH_OFFSET*off;
|
||||
chan[ch].pcm.reversed=reversed;
|
||||
chan[ch].pcm.bank=(s->offES5506>>22)&3;
|
||||
chan[ch].pcm.start=start;
|
||||
|
|
@ -359,11 +360,12 @@ void DivPlatformES5506::e_pin(bool state)
|
|||
}
|
||||
// Set loop mode & Bank
|
||||
pageWriteMask(0x00|ch,0x5f,0x00,loopFlag,0xfcfc);
|
||||
if (chan[ch].pcm.nextFreqOffs!=nextFreqOffs) {
|
||||
chan[ch].pcm.nextFreqOffs=nextFreqOffs;
|
||||
chan[ch].noteChanged.offs=1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sampleVaild) {
|
||||
chan[ch].noteChanged.offs=1;
|
||||
}
|
||||
chan[ch].pcmChanged.changed=0;
|
||||
}
|
||||
}
|
||||
|
|
@ -641,7 +643,6 @@ void DivPlatformES5506::tick(bool sysTick) {
|
|||
if (!chan[i].isTransWave) {
|
||||
if (chan[i].pcmChanged.transwaveInd && (!ins->amiga.useNoteMap && ins->amiga.transWave.enable)) {
|
||||
const int next=chan[i].pcm.next;
|
||||
bool sampleVaild=false;
|
||||
if (next>=0 && next<ins->amiga.transWaveMap.size()) {
|
||||
DivInstrumentAmiga::TransWaveMap& transWaveInd=ins->amiga.transWaveMap[next];
|
||||
int sample=transWaveInd.ind;
|
||||
|
|
@ -676,10 +677,10 @@ void DivPlatformES5506::tick(bool sysTick) {
|
|||
if (transWaveInd.reversed!=2) {
|
||||
reversed=transWaveInd.reversed;
|
||||
}
|
||||
chan[i].pcm.loopMode=loopMode;
|
||||
chan[i].pcm.reversed=reversed;
|
||||
if (sampleVaild) {
|
||||
chan[i].pcmChanged.slice=1;
|
||||
chan[i].pcmChanged.slice=1;
|
||||
if ((chan[i].pcm.loopMode!=loopMode) || (chan[i].pcm.reversed!=reversed)) {
|
||||
chan[i].pcm.loopMode=loopMode;
|
||||
chan[i].pcm.reversed=reversed;
|
||||
chan[i].pcmChanged.loopBank=1;
|
||||
}
|
||||
}
|
||||
|
|
@ -752,20 +753,31 @@ void DivPlatformES5506::tick(bool sysTick) {
|
|||
const unsigned int start=s->offES5506<<10;
|
||||
const unsigned int length=s->samples-1;
|
||||
const unsigned int end=start+(length<<11);
|
||||
const unsigned int nextBank=(s->offES5506>>22)&3;
|
||||
const double nextFreqOffs=PITCH_OFFSET*off;
|
||||
chan[i].pcm.loopMode=loopMode;
|
||||
chan[i].pcm.nextFreqOffs=PITCH_OFFSET*off;
|
||||
chan[i].pcm.reversed=reversed;
|
||||
chan[i].pcm.bank=(s->offES5506>>22)&3;
|
||||
chan[i].pcm.bank=nextBank;
|
||||
chan[i].pcm.start=start;
|
||||
chan[i].pcm.end=end;
|
||||
chan[i].pcm.length=length;
|
||||
chan[i].keyOn=true;
|
||||
if ((chan[i].pcm.loopMode!=loopMode) || (chan[i].pcm.reversed!=reversed) || (chan[i].pcm.bank!=nextBank)) {
|
||||
chan[i].pcm.loopMode=loopMode;
|
||||
chan[i].pcm.reversed=reversed;
|
||||
chan[i].pcm.bank=nextBank;
|
||||
chan[i].pcmChanged.loopBank=1;
|
||||
}
|
||||
if (chan[i].pcm.nextFreqOffs!=nextFreqOffs) {
|
||||
chan[i].pcm.nextFreqOffs=nextFreqOffs;
|
||||
chan[i].noteChanged.offs=1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sampleVaild) {
|
||||
if (!chan[i].keyOn) {
|
||||
pageWrite(0x20|i,0x03,(chan[i].pcm.reversed)?chan[i].pcm.end:chan[i].pcm.start);
|
||||
}
|
||||
chan[i].pcmChanged.slice=1;
|
||||
chan[i].pcmChanged.loopBank=1;
|
||||
chan[i].noteChanged.offs=1;
|
||||
}
|
||||
chan[i].pcmChanged.index=0;
|
||||
}
|
||||
|
|
@ -783,9 +795,13 @@ void DivPlatformES5506::tick(bool sysTick) {
|
|||
loopEnd=chan[i].transWave.sliceEnd;
|
||||
}
|
||||
const unsigned int start=s->offES5506<<10;
|
||||
chan[i].pcm.loopStart=(start+(unsigned int)(loopStart*2048.0))&0xfffff800;
|
||||
chan[i].pcm.loopEnd=(start+(unsigned int)((loopEnd-1.0)*2048.0))&0xffffff80;
|
||||
chan[i].pcmChanged.position=1;
|
||||
const unsigned int nextLoopStart=(start+(unsigned int)(loopStart*2048.0))&0xfffff800;
|
||||
const unsigned int nextLoopEnd=(start+(unsigned int)((loopEnd-1.0)*2048.0))&0xffffff80;
|
||||
if ((chan[i].pcm.loopStart!=nextLoopStart) || (chan[i].pcm.loopEnd!=nextLoopEnd)) {
|
||||
chan[i].pcm.loopStart=(start+(unsigned int)(loopStart*2048.0))&0xfffff800;
|
||||
chan[i].pcm.loopEnd=(start+(unsigned int)((loopEnd-1.0)*2048.0))&0xffffff80;
|
||||
chan[i].pcmChanged.position=1;
|
||||
}
|
||||
}
|
||||
}
|
||||
chan[i].pcmChanged.slice=0;
|
||||
|
|
@ -867,7 +883,7 @@ void DivPlatformES5506::tick(bool sysTick) {
|
|||
}
|
||||
chan[i].envChanged.changed=0;
|
||||
}
|
||||
if (chan[i].noteChanged.changed) {
|
||||
if (chan[i].noteChanged.changed) { // note value changed or frequency offset is changed
|
||||
if (chan[i].noteChanged.offs) {
|
||||
if (chan[i].pcm.freqOffs!=chan[i].pcm.nextFreqOffs) {
|
||||
chan[i].pcm.freqOffs=chan[i].pcm.nextFreqOffs;
|
||||
|
|
@ -879,7 +895,7 @@ void DivPlatformES5506::tick(bool sysTick) {
|
|||
}
|
||||
chan[i].noteChanged.offs=0;
|
||||
}
|
||||
if (chan[i].noteChanged.note) { // note value changed or frequency offset is changed
|
||||
if (chan[i].noteChanged.note) {
|
||||
if (chan[i].prevNote!=chan[i].nextNote) {
|
||||
chan[i].prevNote=chan[i].nextNote;
|
||||
const int nextFreq=NOTE_ES5506(i,chan[i].nextNote);
|
||||
|
|
@ -1001,7 +1017,7 @@ int DivPlatformES5506::dispatch(DivCommand c) {
|
|||
}
|
||||
if (sample>=0 && sample<parent->song.sampleLen) {
|
||||
sampleVaild=true;
|
||||
chan[c.chan].pcm.index=sample;
|
||||
chan[c.chan].pcm.index=chan[c.chan].pcm.next=sample;
|
||||
chan[c.chan].pcm.pause=(chan[c.chan].std.alg.will)?(chan[c.chan].std.alg.val&1):false;
|
||||
chan[c.chan].pcm.isNoteMap=ins->amiga.useNoteMap && !ins->amiga.transWave.enable;
|
||||
chan[c.chan].transWave.enable=!ins->amiga.useNoteMap && ins->amiga.transWave.enable;
|
||||
|
|
@ -1067,7 +1083,7 @@ int DivPlatformES5506::dispatch(DivCommand c) {
|
|||
}
|
||||
}
|
||||
if (!sampleVaild) {
|
||||
chan[c.chan].pcm.index=-1;
|
||||
chan[c.chan].pcm.index=chan[c.chan].pcm.next=-1;
|
||||
chan[c.chan].filter=DivInstrumentES5506::Filter();
|
||||
chan[c.chan].envelope=DivInstrumentES5506::Envelope();
|
||||
}
|
||||
|
|
@ -1153,9 +1169,36 @@ int DivPlatformES5506::dispatch(DivCommand c) {
|
|||
chan[c.chan].pitch=c.value;
|
||||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
// sample commands
|
||||
case DIV_CMD_WAVE:
|
||||
if (!chan[c.chan].useWave) {
|
||||
if (chan[c.chan].active) {
|
||||
if (((ins->amiga.useNoteMap && !ins->amiga.transWave.enable) && (c.value>=0 && c.value<120)) ||
|
||||
((!ins->amiga.useNoteMap && ins->amiga.transWave.enable) && (c.value>=0 && c.value<ins->amiga.transWaveMap.size())) ||
|
||||
((!ins->amiga.useNoteMap && !ins->amiga.transWave.enable) && (c.value>=0 && c.value<parent->song.sampleLen))) {
|
||||
chan[c.chan].pcm.next=c.value;
|
||||
if (!ins->amiga.useNoteMap && ins->amiga.transWave.enable) {
|
||||
chan[c.chan].pcmChanged.transwaveInd=1;
|
||||
} else {
|
||||
chan[c.chan].pcmChanged.index=1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// reserved for useWave
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_TRANSWAVE_SLICE_MODE:
|
||||
if (chan[c.chan].transWave.sliceEnable!=(bool)(c.value&1)) {
|
||||
chan[c.chan].transWave.sliceEnable=c.value&1;
|
||||
chan[c.chan].pcmChanged.slice=1;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_TRANSWAVE_SLICE_POS:
|
||||
if (chan[c.chan].transWave.sliceEnable && (chan[c.chan].transWave.slice!=(unsigned short)(c.value&0xfff))) {
|
||||
chan[c.chan].transWave.slice=c.value&0xfff;
|
||||
chan[c.chan].pcmChanged.slice=1;
|
||||
}
|
||||
break;
|
||||
// Filter commands
|
||||
case DIV_CMD_ES5506_FILTER_MODE:
|
||||
chan[c.chan].filter.mode=DivInstrumentES5506::Filter::FilterMode(c.value&3);
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf {
|
|||
changed(0) {}
|
||||
} envChanged;
|
||||
|
||||
struct PcmChanged {
|
||||
struct PCMChanged {
|
||||
union {
|
||||
struct {
|
||||
unsigned char index: 1; // sample index
|
||||
|
|
@ -136,7 +136,7 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf {
|
|||
unsigned char position: 1; // sample position in memory
|
||||
unsigned char loopBank: 1; // Loop mode and Bank
|
||||
unsigned char transwaveInd: 1; // transwave index
|
||||
unsigned char dummy: 4; // dummy for bit padding
|
||||
unsigned char dummy: 3; // dummy for bit padding
|
||||
};
|
||||
unsigned char changed;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -438,7 +438,17 @@ void DivPlatformGenesis::tick(bool sysTick) {
|
|||
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i=0; i<512; i++) {
|
||||
if (pendingWrites[i]!=oldWrites[i]) {
|
||||
immWrite(i,pendingWrites[i]&0xff);
|
||||
oldWrites[i]=pendingWrites[i];
|
||||
}
|
||||
}
|
||||
|
||||
for (int i=0; i<6; i++) {
|
||||
if (i==2 && extMode) continue;
|
||||
if (chan[i].keyOn || chan[i].keyOff) {
|
||||
if (chan[i].hardReset && chan[i].keyOn) {
|
||||
for (int j=0; j<4; j++) {
|
||||
|
|
@ -463,20 +473,14 @@ void DivPlatformGenesis::tick(bool sysTick) {
|
|||
}
|
||||
}
|
||||
|
||||
for (int i=0; i<512; i++) {
|
||||
if (pendingWrites[i]!=oldWrites[i]) {
|
||||
immWrite(i,pendingWrites[i]&0xff);
|
||||
oldWrites[i]=pendingWrites[i];
|
||||
}
|
||||
}
|
||||
|
||||
for (int i=0; i<6; i++) {
|
||||
if (i==2 && extMode) continue;
|
||||
if (chan[i].freqChanged) {
|
||||
if (parent->song.linearPitch==2) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,4,chan[i].pitch2,chipClock,CHIP_FREQBASE,11);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE,11);
|
||||
} else {
|
||||
int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,false,4,chan[i].pitch2,chipClock,CHIP_FREQBASE,11);
|
||||
int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE,11);
|
||||
int block=(chan[i].baseFreq&0xf800)>>11;
|
||||
if (fNum<0) fNum=0;
|
||||
if (fNum>2047) {
|
||||
|
|
@ -501,7 +505,7 @@ void DivPlatformGenesis::tick(bool sysTick) {
|
|||
off=(double)s->centerRate/8363.0;
|
||||
}
|
||||
}
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,4,chan[i].pitch2,1,1);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,1,1);
|
||||
dacRate=chan[i].freq*off;
|
||||
if (dacRate<1) dacRate=1;
|
||||
if (dumpWrites) addWrite(0xffff0001,dacRate);
|
||||
|
|
@ -1152,14 +1156,12 @@ void DivPlatformGenesis::setYMFM(bool use) {
|
|||
}
|
||||
|
||||
void DivPlatformGenesis::setFlags(unsigned int flags) {
|
||||
if (flags==3) {
|
||||
chipClock=COLOR_NTSC*12.0/7.0;
|
||||
} else if (flags==2) {
|
||||
chipClock=8000000.0;
|
||||
} else if (flags==1) {
|
||||
chipClock=COLOR_PAL*12.0/7.0;
|
||||
} else {
|
||||
chipClock=COLOR_NTSC*15.0/7.0;
|
||||
switch (flags) {
|
||||
case 1: chipClock=COLOR_PAL*12.0/7.0; break;
|
||||
case 2: chipClock=8000000.0; break;
|
||||
case 3: chipClock=COLOR_NTSC*12.0/7.0; break;
|
||||
case 4: chipClock=COLOR_NTSC*9.0/4.0; break;
|
||||
default: chipClock=COLOR_NTSC*15.0/7.0; break;
|
||||
}
|
||||
ladder=flags&0x80000000;
|
||||
OPN2_SetChipType(ladder?ym3438_mode_ym2612:0);
|
||||
|
|
|
|||
|
|
@ -461,9 +461,9 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
|
|||
if (extMode) for (int i=0; i<4; i++) {
|
||||
if (opChan[i].freqChanged) {
|
||||
if (parent->song.linearPitch==2) {
|
||||
opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,false,4,opChan[i].pitch2,chipClock,CHIP_FREQBASE,11);
|
||||
opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,false,2,opChan[i].pitch2,chipClock,CHIP_FREQBASE,11);
|
||||
} else {
|
||||
int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,false,4,opChan[i].pitch2);
|
||||
int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,false,2,opChan[i].pitch2);
|
||||
int block=(opChan[i].baseFreq&0xf800)>>11;
|
||||
if (fNum<0) fNum=0;
|
||||
if (fNum>2047) {
|
||||
|
|
|
|||
|
|
@ -358,7 +358,7 @@ void DivPlatformN163::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
// TODO: what is this mess?
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,0,chan[i].pitch2,chipClock,CHIP_FREQBASE);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE);
|
||||
chan[i].freq=(((chan[i].freq*chan[i].waveLen)*(chanMax+1))/16);
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
if (chan[i].freq>0x3ffff) chan[i].freq=0x3ffff;
|
||||
|
|
|
|||
590
src/engine/platform/namcowsg.cpp
Normal file
590
src/engine/platform/namcowsg.cpp
Normal file
|
|
@ -0,0 +1,590 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2022 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 "namcowsg.h"
|
||||
#include "../engine.h"
|
||||
#include <math.h>
|
||||
|
||||
//#define rWrite(a,v) pendingWrites[a]=v;
|
||||
#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} }
|
||||
|
||||
#define CHIP_FREQBASE 4194304
|
||||
|
||||
const char* regCheatSheetNamcoWSG[]={
|
||||
"WaveSel0", "05",
|
||||
"WaveSel1", "0A",
|
||||
"WaveSel2", "0F",
|
||||
"FreqS0", "10",
|
||||
"FreqL0", "11",
|
||||
"FreqM0", "12",
|
||||
"FreqH0", "13",
|
||||
"FreqX0", "14",
|
||||
"Volume0", "15",
|
||||
"FreqL1", "16",
|
||||
"FreqM1", "17",
|
||||
"FreqH1", "18",
|
||||
"FreqX1", "19",
|
||||
"Volume1", "1A",
|
||||
"FreqL2", "1B",
|
||||
"FreqM2", "1C",
|
||||
"FreqH2", "1D",
|
||||
"FreqX2", "1E",
|
||||
"Volume2", "1F",
|
||||
NULL
|
||||
};
|
||||
|
||||
const char* regCheatSheetNamco15XX[]={
|
||||
"Volume0", "03",
|
||||
"FreqL0", "04",
|
||||
"FreqH0", "05",
|
||||
"WaveSel0", "06",
|
||||
|
||||
"Volume1", "0B",
|
||||
"FreqL1", "0C",
|
||||
"FreqH1", "0D",
|
||||
"WaveSel1", "0E",
|
||||
|
||||
"Volume2", "13",
|
||||
"FreqL2", "14",
|
||||
"FreqH2", "15",
|
||||
"WaveSel2", "16",
|
||||
|
||||
"Volume3", "1B",
|
||||
"FreqL3", "1C",
|
||||
"FreqH3", "1D",
|
||||
"WaveSel3", "1E",
|
||||
|
||||
"Volume4", "23",
|
||||
"FreqL4", "24",
|
||||
"FreqH4", "25",
|
||||
"WaveSel4", "26",
|
||||
|
||||
"Volume5", "2B",
|
||||
"FreqL5", "2C",
|
||||
"FreqH5", "2D",
|
||||
"WaveSel5", "2E",
|
||||
|
||||
"Volume6", "33",
|
||||
"FreqL6", "34",
|
||||
"FreqH6", "35",
|
||||
"WaveSel6", "36",
|
||||
|
||||
"Volume7", "3B",
|
||||
"FreqL7", "3C",
|
||||
"FreqH7", "3D",
|
||||
"WaveSel7", "3E",
|
||||
|
||||
NULL
|
||||
};
|
||||
|
||||
const char* regCheatSheetNamcoCUS30[]={
|
||||
"VolumeL0", "00",
|
||||
"WaveSel0", "01",
|
||||
"FreqH0", "02",
|
||||
"FreqL0", "03",
|
||||
"VolumeR0", "04",
|
||||
|
||||
"VolumeL1", "08",
|
||||
"WaveSel1", "09",
|
||||
"FreqH1", "0A",
|
||||
"FreqL1", "0B",
|
||||
"VolumeR1", "0C",
|
||||
|
||||
"VolumeL2", "10",
|
||||
"WaveSel2", "11",
|
||||
"FreqH2", "12",
|
||||
"FreqL2", "13",
|
||||
"VolumeR2", "14",
|
||||
|
||||
"VolumeL3", "18",
|
||||
"WaveSel3", "19",
|
||||
"FreqH3", "1A",
|
||||
"FreqL3", "1B",
|
||||
"VolumeR3", "1C",
|
||||
|
||||
"VolumeL4", "20",
|
||||
"WaveSel4", "21",
|
||||
"FreqH4", "22",
|
||||
"FreqL4", "23",
|
||||
"VolumeR4", "24",
|
||||
|
||||
"VolumeL5", "28",
|
||||
"WaveSel5", "29",
|
||||
"FreqH5", "2A",
|
||||
"FreqL5", "2B",
|
||||
"VolumeR5", "2C",
|
||||
|
||||
"VolumeL6", "30",
|
||||
"WaveSel6", "31",
|
||||
"FreqH6", "32",
|
||||
"FreqL6", "33",
|
||||
"VolumeR6", "34",
|
||||
|
||||
"VolumeL7", "38",
|
||||
"WaveSel7", "39",
|
||||
"FreqH7", "3A",
|
||||
"FreqL7", "3B",
|
||||
"VolumeR7", "3C",
|
||||
|
||||
NULL
|
||||
};
|
||||
|
||||
const char** DivPlatformNamcoWSG::getRegisterSheet() {
|
||||
if (devType==30) return regCheatSheetNamcoCUS30;
|
||||
if (devType==15) return regCheatSheetNamco15XX;
|
||||
return regCheatSheetNamcoWSG;
|
||||
}
|
||||
|
||||
const char* DivPlatformNamcoWSG::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xx: Change waveform";
|
||||
break;
|
||||
case 0x11:
|
||||
return "11xx: Toggle noise mode";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformNamcoWSG::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
short* buf[2]={
|
||||
bufL+start, bufR+start
|
||||
};
|
||||
while (!writes.empty()) {
|
||||
QueuedWrite w=writes.front();
|
||||
switch (devType) {
|
||||
case 1:
|
||||
((namco_device*)namco)->pacman_sound_w(w.addr,w.val);
|
||||
break;
|
||||
case 2:
|
||||
((namco_device*)namco)->polepos_sound_w(w.addr,w.val);
|
||||
break;
|
||||
case 15:
|
||||
((namco_15xx_device*)namco)->sharedram_w(w.addr,w.val);
|
||||
break;
|
||||
case 30:
|
||||
((namco_cus30_device*)namco)->namcos1_cus30_w(w.addr,w.val);
|
||||
break;
|
||||
}
|
||||
regPool[w.addr&0x3f]=w.val;
|
||||
writes.pop();
|
||||
}
|
||||
namco->sound_stream_update(buf,len);
|
||||
}
|
||||
|
||||
void DivPlatformNamcoWSG::updateWave(int ch) {
|
||||
if (devType==30) {
|
||||
for (int i=0; i<32; i++) {
|
||||
((namco_cus30_device*)namco)->namcos1_cus30_w(i+ch*32,chan[ch].ws.output[i]);
|
||||
}
|
||||
} else {
|
||||
for (int i=0; i<32; i++) {
|
||||
namco->update_namco_waveform(i+ch*32,chan[ch].ws.output[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformNamcoWSG::tick(bool sysTick) {
|
||||
for (int i=0; i<chans; i++) {
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.vol.had) {
|
||||
chan[i].outVol=((chan[i].vol&15)*MIN(15,chan[i].std.vol.val))>>4;
|
||||
}
|
||||
if (chan[i].std.duty.had && i>=4) {
|
||||
chan[i].noise=chan[i].std.duty.val;
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.wave.had) {
|
||||
if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) {
|
||||
chan[i].wave=chan[i].std.wave.val;
|
||||
chan[i].ws.changeWave1(chan[i].wave);
|
||||
if (!chan[i].keyOff) chan[i].keyOn=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.panL.had) {
|
||||
chan[i].pan&=0x0f;
|
||||
chan[i].pan|=(chan[i].std.panL.val&15)<<4;
|
||||
}
|
||||
if (chan[i].std.panR.had) {
|
||||
chan[i].pan&=0xf0;
|
||||
chan[i].pan|=chan[i].std.panR.val&15;
|
||||
}
|
||||
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,-2048,2048);
|
||||
} else {
|
||||
chan[i].pitch2=chan[i].std.pitch.val;
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].active) {
|
||||
if (chan[i].ws.tick() || (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1)) {
|
||||
updateWave(i);
|
||||
}
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_PCE);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE);
|
||||
if (chan[i].freq>1048575) chan[i].freq=1048575;
|
||||
if (chan[i].keyOn) {
|
||||
}
|
||||
if (chan[i].keyOff) {
|
||||
}
|
||||
if (chan[i].keyOn) chan[i].keyOn=false;
|
||||
if (chan[i].keyOff) chan[i].keyOff=false;
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
}
|
||||
|
||||
// update state
|
||||
switch (devType) {
|
||||
case 1:
|
||||
if (chan[0].active && !isMuted[0]) {
|
||||
rWrite(0x15,chan[0].outVol);
|
||||
} else {
|
||||
rWrite(0x15,0);
|
||||
}
|
||||
if (chan[1].active && !isMuted[1]) {
|
||||
rWrite(0x1a,chan[1].outVol);
|
||||
} else {
|
||||
rWrite(0x1a,0);
|
||||
}
|
||||
if (chan[2].active && !isMuted[2]) {
|
||||
rWrite(0x1f,chan[2].outVol);
|
||||
} else {
|
||||
rWrite(0x1f,0);
|
||||
}
|
||||
|
||||
rWrite(0x10,(chan[0].freq)&15);
|
||||
rWrite(0x11,(chan[0].freq>>4)&15);
|
||||
rWrite(0x12,(chan[0].freq>>8)&15);
|
||||
rWrite(0x13,(chan[0].freq>>12)&15);
|
||||
rWrite(0x14,(chan[0].freq>>16)&15);
|
||||
|
||||
rWrite(0x16,(chan[1].freq>>4)&15);
|
||||
rWrite(0x17,(chan[1].freq>>8)&15);
|
||||
rWrite(0x18,(chan[1].freq>>12)&15);
|
||||
rWrite(0x19,(chan[1].freq>>16)&15);
|
||||
|
||||
rWrite(0x1b,(chan[2].freq>>4)&15);
|
||||
rWrite(0x1c,(chan[2].freq>>8)&15);
|
||||
rWrite(0x1d,(chan[2].freq>>12)&15);
|
||||
rWrite(0x1e,(chan[2].freq>>16)&15);
|
||||
|
||||
rWrite(0x05,0);
|
||||
rWrite(0x0a,1);
|
||||
rWrite(0x0f,2);
|
||||
break;
|
||||
case 15:
|
||||
for (int i=0; i<8; i++) {
|
||||
if (chan[i].active && !isMuted[i]) {
|
||||
rWrite((i<<3)+0x03,chan[i].outVol);
|
||||
} else {
|
||||
rWrite((i<<3)+0x03,0);
|
||||
}
|
||||
rWrite((i<<3)+0x04,chan[i].freq&0xff);
|
||||
rWrite((i<<3)+0x05,(chan[i].freq>>8)&0xff);
|
||||
rWrite((i<<3)+0x06,((chan[i].freq>>15)&15)|(i<<4));
|
||||
}
|
||||
break;
|
||||
case 30:
|
||||
for (int i=0; i<8; i++) {
|
||||
if (chan[i].active && !isMuted[i]) {
|
||||
rWrite((i<<3)+0x100,(chan[i].outVol*((chan[i].pan>>4)&15))/15);
|
||||
rWrite((i<<3)+0x104,((chan[i].outVol*(chan[i].pan&15))/15)|(chan[(i+1)&7].noise?0x80:0));
|
||||
} else {
|
||||
rWrite((i<<3)+0x100,0);
|
||||
rWrite((i<<3)+0x104,(chan[(i+1)&7].noise?0x80:0));
|
||||
}
|
||||
rWrite((i<<3)+0x103,chan[i].freq&0xff);
|
||||
rWrite((i<<3)+0x102,(chan[i].freq>>8)&0xff);
|
||||
rWrite((i<<3)+0x101,((chan[i].freq>>15)&15)|(i<<4));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformNamcoWSG::dispatch(DivCommand c) {
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_PCE);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
}
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
chan[c.chan].macroInit(ins);
|
||||
if (chan[c.chan].wave<0) {
|
||||
chan[c.chan].wave=0;
|
||||
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
|
||||
}
|
||||
chan[c.chan].ws.init(ins,32,15,chan[c.chan].insChanged);
|
||||
chan[c.chan].insChanged=false;
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].macroInit(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
case DIV_CMD_ENV_RELEASE:
|
||||
chan[c.chan].std.release();
|
||||
break;
|
||||
case DIV_CMD_INSTRUMENT:
|
||||
if (chan[c.chan].ins!=c.value || c.value2==1) {
|
||||
chan[c.chan].ins=c.value;
|
||||
chan[c.chan].insChanged=true;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_VOLUME:
|
||||
if (chan[c.chan].vol!=c.value) {
|
||||
chan[c.chan].vol=c.value;
|
||||
if (!chan[c.chan].std.vol.has) {
|
||||
chan[c.chan].outVol=c.value;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_GET_VOLUME:
|
||||
if (chan[c.chan].std.vol.has) {
|
||||
return chan[c.chan].vol;
|
||||
}
|
||||
return chan[c.chan].outVol;
|
||||
break;
|
||||
case DIV_CMD_PITCH:
|
||||
chan[c.chan].pitch=c.value;
|
||||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
case DIV_CMD_WAVE:
|
||||
chan[c.chan].wave=c.value;
|
||||
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
|
||||
chan[c.chan].keyOn=true;
|
||||
break;
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
int destFreq=NOTE_FREQUENCY(c.value2);
|
||||
bool return2=false;
|
||||
if (destFreq>chan[c.chan].baseFreq) {
|
||||
chan[c.chan].baseFreq+=c.value*((parent->song.linearPitch==2)?1:8);
|
||||
if (chan[c.chan].baseFreq>=destFreq) {
|
||||
chan[c.chan].baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
} else {
|
||||
chan[c.chan].baseFreq-=c.value*((parent->song.linearPitch==2)?1:8);
|
||||
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_STD_NOISE_MODE:
|
||||
chan[c.chan].noise=c.value;
|
||||
break;
|
||||
case DIV_CMD_PANNING: {
|
||||
chan[c.chan].pan=(c.value&0xf0)|(c.value2>>4);
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_LEGATO:
|
||||
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)));
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_PCE));
|
||||
}
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
return 15;
|
||||
break;
|
||||
case DIV_ALWAYS_SET_VOLUME:
|
||||
return 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void DivPlatformNamcoWSG::muteChannel(int ch, bool mute) {
|
||||
isMuted[ch]=mute;
|
||||
}
|
||||
|
||||
void DivPlatformNamcoWSG::forceIns() {
|
||||
for (int i=0; i<chans; i++) {
|
||||
chan[i].insChanged=true;
|
||||
chan[i].freqChanged=true;
|
||||
updateWave(i);
|
||||
}
|
||||
}
|
||||
|
||||
void* DivPlatformNamcoWSG::getChanState(int ch) {
|
||||
return &chan[ch];
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformNamcoWSG::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformNamcoWSG::getRegisterPool() {
|
||||
return regPool;
|
||||
}
|
||||
|
||||
int DivPlatformNamcoWSG::getRegisterPoolSize() {
|
||||
return (devType==1)?32:64;
|
||||
}
|
||||
|
||||
void DivPlatformNamcoWSG::reset() {
|
||||
while (!writes.empty()) writes.pop();
|
||||
memset(regPool,0,128);
|
||||
for (int i=0; i<chans; i++) {
|
||||
chan[i]=DivPlatformNamcoWSG::Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
chan[i].ws.setEngine(parent);
|
||||
chan[i].ws.init(NULL,32,15,false);
|
||||
}
|
||||
if (dumpWrites) {
|
||||
addWrite(0xffffffff,0);
|
||||
}
|
||||
// TODO: wave memory
|
||||
namco->set_voices(chans);
|
||||
namco->set_stereo((devType==2 || devType==30));
|
||||
namco->device_start(NULL);
|
||||
lastPan=0xff;
|
||||
cycles=0;
|
||||
curChan=-1;
|
||||
}
|
||||
|
||||
bool DivPlatformNamcoWSG::isStereo() {
|
||||
return (devType==30);
|
||||
}
|
||||
|
||||
bool DivPlatformNamcoWSG::keyOffAffectsArp(int ch) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void DivPlatformNamcoWSG::notifyWaveChange(int wave) {
|
||||
for (int i=0; i<chans; i++) {
|
||||
if (chan[i].wave==wave) {
|
||||
chan[i].ws.changeWave1(wave);
|
||||
updateWave(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformNamcoWSG::notifyInsDeletion(void* ins) {
|
||||
for (int i=0; i<chans; i++) {
|
||||
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformNamcoWSG::setDeviceType(int type) {
|
||||
devType=type;
|
||||
switch (type) {
|
||||
case 15:
|
||||
chans=8;
|
||||
break;
|
||||
case 30:
|
||||
chans=8;
|
||||
break;
|
||||
case 1:
|
||||
chans=3;
|
||||
break;
|
||||
case 2:
|
||||
chans=8;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformNamcoWSG::setFlags(unsigned int flags) {
|
||||
chipClock=3072000;
|
||||
rate=chipClock/32;
|
||||
namco->device_clock_changed(rate);
|
||||
for (int i=0; i<chans; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformNamcoWSG::poke(unsigned int addr, unsigned short val) {
|
||||
rWrite(addr,val);
|
||||
}
|
||||
|
||||
void DivPlatformNamcoWSG::poke(std::vector<DivRegWrite>& wlist) {
|
||||
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
|
||||
}
|
||||
|
||||
int DivPlatformNamcoWSG::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||
parent=p;
|
||||
dumpWrites=false;
|
||||
skipRegisterWrites=false;
|
||||
for (int i=0; i<chans; i++) {
|
||||
isMuted[i]=false;
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
}
|
||||
switch (devType) {
|
||||
case 15:
|
||||
namco=new namco_15xx_device(3072000);
|
||||
break;
|
||||
case 30:
|
||||
namco=new namco_cus30_device(3072000);
|
||||
break;
|
||||
default:
|
||||
namco=new namco_device(3072000);
|
||||
break;
|
||||
}
|
||||
setFlags(flags);
|
||||
reset();
|
||||
return 6;
|
||||
}
|
||||
|
||||
void DivPlatformNamcoWSG::quit() {
|
||||
for (int i=0; i<chans; i++) {
|
||||
delete oscBuf[i];
|
||||
}
|
||||
delete namco;
|
||||
}
|
||||
|
||||
DivPlatformNamcoWSG::~DivPlatformNamcoWSG() {
|
||||
}
|
||||
104
src/engine/platform/namcowsg.h
Normal file
104
src/engine/platform/namcowsg.h
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2022 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 _NAMCOWSG_H
|
||||
#define _NAMCOWSG_H
|
||||
|
||||
#include "../dispatch.h"
|
||||
#include <queue>
|
||||
#include "../macroInt.h"
|
||||
#include "../waveSynth.h"
|
||||
#include "sound/namco.h"
|
||||
|
||||
class DivPlatformNamcoWSG: public DivDispatch {
|
||||
struct Channel {
|
||||
int freq, baseFreq, pitch, pitch2, note;
|
||||
int ins;
|
||||
unsigned char pan;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise;
|
||||
signed char vol, outVol, wave;
|
||||
DivMacroInt std;
|
||||
DivWaveSynth ws;
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
pitch2=0;
|
||||
}
|
||||
Channel():
|
||||
freq(0),
|
||||
baseFreq(0),
|
||||
pitch(0),
|
||||
pitch2(0),
|
||||
note(0),
|
||||
ins(-1),
|
||||
pan(255),
|
||||
active(false),
|
||||
insChanged(true),
|
||||
freqChanged(false),
|
||||
keyOn(false),
|
||||
keyOff(false),
|
||||
inPorta(false),
|
||||
noise(false),
|
||||
vol(15),
|
||||
outVol(15),
|
||||
wave(-1) {}
|
||||
};
|
||||
Channel chan[8];
|
||||
DivDispatchOscBuffer* oscBuf[8];
|
||||
bool isMuted[8];
|
||||
struct QueuedWrite {
|
||||
unsigned short addr;
|
||||
unsigned char val;
|
||||
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v) {}
|
||||
};
|
||||
std::queue<QueuedWrite> writes;
|
||||
unsigned char lastPan;
|
||||
|
||||
int cycles, curChan, delay;
|
||||
namco_audio_device* namco;
|
||||
int devType, chans;
|
||||
unsigned char regPool[512];
|
||||
void updateWave(int ch);
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
public:
|
||||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
bool isStereo();
|
||||
bool keyOffAffectsArp(int ch);
|
||||
void setDeviceType(int type);
|
||||
void setFlags(unsigned int flags);
|
||||
void notifyWaveChange(int wave);
|
||||
void notifyInsDeletion(void* ins);
|
||||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformNamcoWSG();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -286,8 +286,8 @@ void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_
|
|||
adpcmB->output<2>(aOut,0);
|
||||
|
||||
if (!isMuted[adpcmChan]) {
|
||||
os[0]+=aOut.data[0];
|
||||
os[1]+=aOut.data[0];
|
||||
os[0]-=aOut.data[0]>>3;
|
||||
os[1]-=aOut.data[0]>>3;
|
||||
oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]+=aOut.data[0];
|
||||
} else {
|
||||
oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]=0;
|
||||
|
|
@ -594,7 +594,7 @@ void DivPlatformOPL::tick(bool sysTick) {
|
|||
bool updateDrums=false;
|
||||
for (int i=0; i<totalChans; i++) {
|
||||
if (chan[i].freqChanged) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2,chipClock,CHIP_FREQBASE);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq)*2,chan[i].pitch2,chipClock,CHIP_FREQBASE);
|
||||
if (chan[i].fixedFreq>0) chan[i].freq=chan[i].fixedFreq;
|
||||
if (chan[i].freq>131071) chan[i].freq=131071;
|
||||
int freqt=toFreq(chan[i].freq)+chan[i].pitch2;
|
||||
|
|
@ -734,12 +734,12 @@ int DivPlatformOPL::dispatch(DivCommand c) {
|
|||
chan[c.chan].sample=ins->amiga.getSample(c.value);
|
||||
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(chan[c.chan].sample);
|
||||
immWrite(9,(s->offB>>2)&0xff);
|
||||
immWrite(10,(s->offB>>10)&0xff);
|
||||
immWrite(9,(s->offB>>5)&0xff);
|
||||
immWrite(10,(s->offB>>13)&0xff);
|
||||
int end=s->offB+s->lengthB-1;
|
||||
immWrite(11,(end>>2)&0xff);
|
||||
immWrite(12,(end>>10)&0xff);
|
||||
immWrite(8,2);
|
||||
immWrite(11,(end>>5)&0xff);
|
||||
immWrite(12,(end>>13)&0xff);
|
||||
immWrite(8,1);
|
||||
immWrite(7,(s->loopStart>=0)?0xb0:0xa0); // start/repeat
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].note=c.value;
|
||||
|
|
@ -774,7 +774,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
|
|||
int end=s->offB+s->lengthB-1;
|
||||
immWrite(11,(end>>2)&0xff);
|
||||
immWrite(12,(end>>10)&0xff);
|
||||
immWrite(8,2);
|
||||
immWrite(8,1);
|
||||
immWrite(7,(s->loopStart>=0)?0xb0:0xa0); // start/repeat
|
||||
int freq=(65536.0*(double)s->rate)/(double)rate;
|
||||
immWrite(16,freq&0xff);
|
||||
|
|
@ -1698,7 +1698,7 @@ int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, unsigned int f
|
|||
adpcmBMemLen=0;
|
||||
iface.adpcmBMem=adpcmBMem;
|
||||
iface.sampleBank=0;
|
||||
adpcmB=new ymfm::adpcm_b_engine(iface,2);
|
||||
adpcmB=new ymfm::adpcm_b_engine(iface,5);
|
||||
}
|
||||
|
||||
reset();
|
||||
|
|
|
|||
|
|
@ -300,7 +300,7 @@ void DivPlatformOPLL::tick(bool sysTick) {
|
|||
|
||||
for (int i=0; i<11; i++) {
|
||||
if (chan[i].freqChanged) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2,chipClock,CHIP_FREQBASE);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq)*2,chan[i].pitch2,chipClock,CHIP_FREQBASE);
|
||||
if (chan[i].fixedFreq>0) chan[i].freq=chan[i].fixedFreq;
|
||||
if (chan[i].freq>262143) chan[i].freq=262143;
|
||||
int freqt=toFreq(chan[i].freq)+chan[i].pitch2;
|
||||
|
|
|
|||
434
src/engine/platform/rf5c68.cpp
Normal file
434
src/engine/platform/rf5c68.cpp
Normal file
|
|
@ -0,0 +1,434 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2022 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 "rf5c68.h"
|
||||
#include "../engine.h"
|
||||
#include "../../ta-log.h"
|
||||
#include <math.h>
|
||||
|
||||
#define rWrite(a,v) {if(!skipRegisterWrites) {rf5c68.rf5c68_w(a,v); regPool[a]=v; if(dumpWrites) addWrite(a,v);}}
|
||||
|
||||
#define CHIP_FREQBASE 786432
|
||||
|
||||
const char* regCheatSheetRF5C68[]={
|
||||
"Volume", "0",
|
||||
"Panning", "1",
|
||||
"FreqL", "2",
|
||||
"FreqH", "3",
|
||||
"LoopStartL", "4",
|
||||
"LoopStartH", "5",
|
||||
"StartH", "6",
|
||||
"Control", "7",
|
||||
"Disable", "8",
|
||||
NULL
|
||||
};
|
||||
|
||||
const char** DivPlatformRF5C68::getRegisterSheet() {
|
||||
return regCheatSheetRF5C68;
|
||||
}
|
||||
|
||||
const char* DivPlatformRF5C68::getEffectName(unsigned char effect) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformRF5C68::chWrite(unsigned char ch, unsigned int addr, unsigned char val) {
|
||||
if (!skipRegisterWrites) {
|
||||
if (curChan!=ch) {
|
||||
curChan=ch;
|
||||
rWrite(7,curChan|0xc0);
|
||||
}
|
||||
regPool[16+((ch)<<4)+((addr)&0x0f)]=val;
|
||||
rWrite(addr,val);
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformRF5C68::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
short buf[16][256];
|
||||
short* chBufPtrs[16]={
|
||||
buf[0],buf[1],buf[2],buf[3],buf[4],buf[5],buf[6],buf[7],
|
||||
buf[8],buf[9],buf[10],buf[11],buf[12],buf[13],buf[14],buf[15]
|
||||
};
|
||||
size_t pos=start;
|
||||
while (len > 0) {
|
||||
size_t blockLen=MIN(len,256);
|
||||
short* bufPtrs[2]={&bufL[pos],&bufR[pos]};
|
||||
rf5c68.sound_stream_update(bufPtrs,chBufPtrs,blockLen);
|
||||
for (int i=0; i<8; i++) {
|
||||
for (size_t j=0; j<blockLen; j++) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=buf[i*2][j]+buf[i*2+1][j];
|
||||
}
|
||||
}
|
||||
pos+=blockLen;
|
||||
len-=blockLen;
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformRF5C68::tick(bool sysTick) {
|
||||
for (int i=0; i<8; i++) {
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.vol.had) {
|
||||
chan[i].outVol=((chan[i].vol&0xff)*chan[i].std.vol.val)>>6;
|
||||
chWrite(i,0,chan[i].outVol);
|
||||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note);
|
||||
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,-2048,2048);
|
||||
} else {
|
||||
chan[i].pitch2=chan[i].std.pitch.val;
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
// panning registers are reversed in this chip
|
||||
if (chan[i].std.panL.had) {
|
||||
chan[i].panning&=0xf0;
|
||||
chan[i].panning|=chan[i].std.panL.val&15;
|
||||
}
|
||||
if (chan[i].std.panR.had) {
|
||||
chan[i].panning&=0x0f;
|
||||
chan[i].panning|=(chan[i].std.panR.val&15)<<4;
|
||||
}
|
||||
if (chan[i].std.panL.had || chan[i].std.panR.had) {
|
||||
chWrite(i,0x05,isMuted[i]?0:chan[i].panning);
|
||||
}
|
||||
if (chan[i].setPos) {
|
||||
// force keyon
|
||||
chan[i].keyOn=true;
|
||||
chan[i].setPos=false;
|
||||
} else {
|
||||
chan[i].audPos=0;
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
unsigned char keyon=regPool[8]&~(1<<i);
|
||||
unsigned char keyoff=keyon|(1<<i);
|
||||
DivSample* s=parent->getSample(chan[i].sample);
|
||||
double off=(s->centerRate>=1)?((double)s->centerRate/8363.0):1.0;
|
||||
chan[i].freq=(int)(off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE));
|
||||
if (chan[i].freq>65535) chan[i].freq=65535;
|
||||
if (chan[i].keyOn) {
|
||||
unsigned int start=s->offRF5C68;
|
||||
unsigned int loop=start+s->length8;
|
||||
if (chan[i].audPos>0) {
|
||||
start=start+MIN(chan[i].audPos,s->length8);
|
||||
}
|
||||
if (s->loopStart>=0) {
|
||||
loop=start+s->loopStart;
|
||||
}
|
||||
start=MIN(start,getSampleMemCapacity()-31);
|
||||
loop=MIN(loop,getSampleMemCapacity()-31);
|
||||
rWrite(8,keyoff); // force keyoff first
|
||||
chWrite(i,6,start>>8);
|
||||
chWrite(i,4,loop&0xff);
|
||||
chWrite(i,5,loop>>8);
|
||||
if (!chan[i].std.vol.had) {
|
||||
chan[i].outVol=chan[i].vol;
|
||||
chWrite(i,0,chan[i].outVol);
|
||||
}
|
||||
rWrite(8,keyon);
|
||||
chan[i].keyOn=false;
|
||||
}
|
||||
if (chan[i].keyOff) {
|
||||
rWrite(8,keyoff);
|
||||
chan[i].keyOff=false;
|
||||
}
|
||||
if (chan[i].freqChanged) {
|
||||
chWrite(i,2,chan[i].freq&0xff);
|
||||
chWrite(i,3,chan[i].freq>>8);
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformRF5C68::dispatch(DivCommand c) {
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
|
||||
chan[c.chan].sample=ins->amiga.getSample(c.value);
|
||||
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.sampleLen) {
|
||||
chan[c.chan].sample=-1;
|
||||
}
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
}
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
chan[c.chan].macroInit(ins);
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
chan[c.chan].sample=-1;
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].macroInit(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
case DIV_CMD_ENV_RELEASE:
|
||||
chan[c.chan].std.release();
|
||||
break;
|
||||
case DIV_CMD_INSTRUMENT:
|
||||
if (chan[c.chan].ins!=c.value || c.value2==1) {
|
||||
chan[c.chan].ins=c.value;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_VOLUME:
|
||||
if (chan[c.chan].vol!=c.value) {
|
||||
chan[c.chan].vol=c.value;
|
||||
if (!chan[c.chan].std.vol.has) {
|
||||
chan[c.chan].outVol=c.value;
|
||||
chWrite(c.chan,0,chan[c.chan].outVol);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_GET_VOLUME:
|
||||
if (chan[c.chan].std.vol.has) {
|
||||
return chan[c.chan].vol;
|
||||
}
|
||||
return chan[c.chan].outVol;
|
||||
break;
|
||||
case DIV_CMD_PANNING:
|
||||
chan[c.chan].panning=(c.value>>4)|(c.value2&0xf0);
|
||||
chWrite(c.chan,1,isMuted[c.chan]?0:chan[c.chan].panning);
|
||||
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);
|
||||
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].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val-12):(0)));
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA));
|
||||
}
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_POS:
|
||||
chan[c.chan].audPos=c.value;
|
||||
chan[c.chan].setPos=true;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
return 255;
|
||||
break;
|
||||
case DIV_ALWAYS_SET_VOLUME:
|
||||
return 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void DivPlatformRF5C68::muteChannel(int ch, bool mute) {
|
||||
isMuted[ch]=mute;
|
||||
chWrite(ch,1,mute?0:chan[ch].panning);
|
||||
}
|
||||
|
||||
void DivPlatformRF5C68::forceIns() {
|
||||
for (int i=0; i<8; i++) {
|
||||
chan[i].insChanged=true;
|
||||
chan[i].freqChanged=true;
|
||||
chan[i].sample=-1;
|
||||
}
|
||||
}
|
||||
|
||||
void* DivPlatformRF5C68::getChanState(int ch) {
|
||||
return &chan[ch];
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformRF5C68::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
||||
void DivPlatformRF5C68::reset() {
|
||||
memset(regPool,0,144);
|
||||
rf5c68.device_reset();
|
||||
rWrite(0x08,0xff); // keyoff all channels
|
||||
for (int i=0; i<8; i++) {
|
||||
chan[i]=DivPlatformRF5C68::Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
chWrite(i,0,255);
|
||||
chWrite(i,1,isMuted[i]?0:255);
|
||||
}
|
||||
}
|
||||
|
||||
bool DivPlatformRF5C68::isStereo() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void DivPlatformRF5C68::notifyInsChange(int ins) {
|
||||
for (int i=0; i<8; i++) {
|
||||
if (chan[i].ins==ins) {
|
||||
chan[i].insChanged=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformRF5C68::notifyWaveChange(int wave) {
|
||||
// TODO when wavetables are added
|
||||
// TODO they probably won't be added unless the samples reside in RAM
|
||||
}
|
||||
|
||||
void DivPlatformRF5C68::notifyInsDeletion(void* ins) {
|
||||
for (int i=0; i<8; i++) {
|
||||
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformRF5C68::setFlags(unsigned int flags) {
|
||||
switch (flags&0x0f) {
|
||||
case 1: chipClock=10000000; break;
|
||||
case 2: chipClock=12500000; break;
|
||||
default: chipClock=8000000; break;
|
||||
}
|
||||
chipType=flags>>4;
|
||||
rate=chipClock/384;
|
||||
for (int i=0; i<8; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
rf5c68=(chipType==1)?rf5c164_device():rf5c68_device();
|
||||
rf5c68.device_start(sampleMem);
|
||||
}
|
||||
|
||||
void DivPlatformRF5C68::poke(unsigned int addr, unsigned short val) {
|
||||
rWrite(addr&0x0f,val);
|
||||
}
|
||||
|
||||
void DivPlatformRF5C68::poke(std::vector<DivRegWrite>& wlist) {
|
||||
for (DivRegWrite& i: wlist) rWrite(i.addr&0x0f,i.val);
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformRF5C68::getRegisterPool() {
|
||||
return regPool;
|
||||
}
|
||||
|
||||
int DivPlatformRF5C68::getRegisterPoolSize() {
|
||||
return 144;
|
||||
}
|
||||
|
||||
const void* DivPlatformRF5C68::getSampleMem(int index) {
|
||||
return index == 0 ? sampleMem : NULL;
|
||||
}
|
||||
|
||||
size_t DivPlatformRF5C68::getSampleMemCapacity(int index) {
|
||||
return index == 0 ? 65536 : 0;
|
||||
}
|
||||
|
||||
size_t DivPlatformRF5C68::getSampleMemUsage(int index) {
|
||||
return index == 0 ? sampleMemLen : 0;
|
||||
}
|
||||
|
||||
void DivPlatformRF5C68::renderSamples() {
|
||||
memset(sampleMem,0,getSampleMemCapacity());
|
||||
|
||||
size_t memPos=0;
|
||||
for (int i=0; i<parent->song.sampleLen; i++) {
|
||||
DivSample* s=parent->song.sample[i];
|
||||
int length=s->length8;
|
||||
int actualLength=MIN((int)(getSampleMemCapacity()-memPos)-31,length);
|
||||
if (actualLength>0) {
|
||||
s->offRF5C68=memPos;
|
||||
for (int j=0; j<actualLength; j++) {
|
||||
// convert to signed magnitude
|
||||
signed char val=s->data8[j];
|
||||
CLAMP_VAR(val,-127,126);
|
||||
sampleMem[memPos++]=(val>0)?(val|0x80):(0-val);
|
||||
}
|
||||
// write end of sample marker
|
||||
memset(&sampleMem[memPos],0xff,31);
|
||||
memPos+=31;
|
||||
}
|
||||
if (actualLength<length) {
|
||||
logW("out of RF5C68 PCM memory for sample %d!",i);
|
||||
break;
|
||||
}
|
||||
// align memPos to 256-byte boundary
|
||||
memPos=(memPos+0xff)&~0xff;
|
||||
}
|
||||
sampleMemLen=memPos;
|
||||
}
|
||||
|
||||
int DivPlatformRF5C68::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||
parent=p;
|
||||
dumpWrites=false;
|
||||
skipRegisterWrites=false;
|
||||
|
||||
for (int i=0; i<8; i++) {
|
||||
isMuted[i]=false;
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
}
|
||||
sampleMem=new unsigned char[getSampleMemCapacity()];
|
||||
sampleMemLen=0;
|
||||
setFlags(flags);
|
||||
reset();
|
||||
|
||||
return 8;
|
||||
}
|
||||
|
||||
void DivPlatformRF5C68::quit() {
|
||||
delete[] sampleMem;
|
||||
for (int i=0; i<8; i++) {
|
||||
delete oscBuf[i];
|
||||
}
|
||||
}
|
||||
105
src/engine/platform/rf5c68.h
Normal file
105
src/engine/platform/rf5c68.h
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2022 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 _RF5C68_H
|
||||
#define _RF5C68_H
|
||||
|
||||
#include "../dispatch.h"
|
||||
#include <queue>
|
||||
#include "../macroInt.h"
|
||||
#include "sound/rf5c68.h"
|
||||
|
||||
class DivPlatformRF5C68: public DivDispatch {
|
||||
struct Channel {
|
||||
int freq, baseFreq, pitch, pitch2;
|
||||
unsigned int audPos;
|
||||
int sample, wave, ins;
|
||||
int note;
|
||||
int panning;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, setPos;
|
||||
int vol, outVol;
|
||||
DivMacroInt std;
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
pitch2=0;
|
||||
}
|
||||
Channel():
|
||||
freq(0),
|
||||
baseFreq(0),
|
||||
pitch(0),
|
||||
pitch2(0),
|
||||
audPos(0),
|
||||
sample(-1),
|
||||
ins(-1),
|
||||
note(0),
|
||||
panning(255),
|
||||
active(false),
|
||||
insChanged(true),
|
||||
freqChanged(false),
|
||||
keyOn(false),
|
||||
keyOff(false),
|
||||
inPorta(false),
|
||||
setPos(false),
|
||||
vol(255),
|
||||
outVol(255) {}
|
||||
};
|
||||
Channel chan[8];
|
||||
DivDispatchOscBuffer* oscBuf[8];
|
||||
bool isMuted[8];
|
||||
int chipType;
|
||||
unsigned char curChan;
|
||||
|
||||
unsigned char* sampleMem;
|
||||
size_t sampleMemLen;
|
||||
rf5c68_device rf5c68;
|
||||
unsigned char regPool[144];
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
|
||||
public:
|
||||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
bool isStereo();
|
||||
void setChipModel(int type);
|
||||
void notifyInsChange(int ins);
|
||||
void notifyWaveChange(int wave);
|
||||
void notifyInsDeletion(void* ins);
|
||||
void setFlags(unsigned int flags);
|
||||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
const void* getSampleMem(int index = 0);
|
||||
size_t getSampleMemCapacity(int index = 0);
|
||||
size_t getSampleMemUsage(int index = 0);
|
||||
void renderSamples();
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
private:
|
||||
void chWrite(unsigned char ch, unsigned int addr, unsigned char val);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -581,7 +581,7 @@ YM2203 English datasheet: http://www.appleii-box.de/APPLE2/JonasCard/YM2203%20da
|
|||
YM2203 Japanese datasheet contents, translated: http://www.larwe.com/technical/chip_ym2203.html
|
||||
*/
|
||||
|
||||
// additional modifications by tildearrow and Eulous for furnace (particularly AY8930 emulation)
|
||||
// additional modifications by tildearrow, Eulous, cam900 and Grauw for furnace (particularly AY8930 emulation)
|
||||
|
||||
#include "ay8910.h"
|
||||
#include <stdio.h>
|
||||
|
|
|
|||
797
src/engine/platform/sound/namco.cpp
Normal file
797
src/engine/platform/sound/namco.cpp
Normal file
|
|
@ -0,0 +1,797 @@
|
|||
// license:BSD-3-Clause
|
||||
// copyright-holders:Nicola Salmoria,Aaron Giles
|
||||
/***************************************************************************
|
||||
|
||||
NAMCO sound driver.
|
||||
|
||||
This driver handles the four known types of NAMCO wavetable sounds:
|
||||
|
||||
- 3-voice mono (PROM-based design: Pac-Man, Pengo, Dig Dug, etc)
|
||||
- 8-voice quadrophonic (Pole Position 1, Pole Position 2)
|
||||
- 8-voice mono (custom 15XX: Mappy, Dig Dug 2, etc)
|
||||
- 8-voice stereo (System 1)
|
||||
|
||||
The 15XX custom does not have a DAC of its own; instead, it streams
|
||||
the 4-bit PROM data directly into the 99XX custom DAC. Most pre-99XX
|
||||
(and pre-15XX) Namco games use a LS273 latch (cleared when sound is
|
||||
disabled), a 4.7K/2.2K/1K/470 resistor-weighted DAC, and a 4066 and
|
||||
second group of resistors (10K/22K/47K/100K) for volume control.
|
||||
Pole Position does more complicated sound mixing: a 4051 multiplexes
|
||||
wavetable sound with four signals derived from the 52XX and 54XX, the
|
||||
selected signal is distributed to four volume control sections, and
|
||||
finally the engine noise is mixed into all four channels. The later
|
||||
CUS30 also uses the 99XX DAC, or two 99XX in the optional 16-channel
|
||||
stereo configuration, but it uses no PROM and delivers its own samples.
|
||||
|
||||
The CUS30 has been decapped and verified to be a ULA.
|
||||
|
||||
***************************************************************************/
|
||||
|
||||
// additional modifications by tildearrow for furnace
|
||||
|
||||
#include "namco.h"
|
||||
#include <string.h>
|
||||
|
||||
/* quality parameter: internal sample rate is 192 KHz, output is 48 KHz */
|
||||
#define INTERNAL_RATE 192000
|
||||
|
||||
/* 16 bits: sample bits of the stream buffer */
|
||||
/* 4 bits: volume */
|
||||
/* 4 bits: prom sample bits */
|
||||
#define MIXLEVEL (1 << (16 - 4 - 4))
|
||||
|
||||
/* stream output level */
|
||||
#define OUTPUT_LEVEL(n) ((n) * MIXLEVEL / m_voices)
|
||||
|
||||
/* a position of waveform sample */
|
||||
#define WAVEFORM_POSITION(n) (((n) >> m_f_fracbits) & 0x1f)
|
||||
|
||||
namco_audio_device::namco_audio_device(uint32_t clock)
|
||||
: m_wave_ptr(NULL)
|
||||
, m_last_channel(nullptr)
|
||||
, m_wavedata(nullptr)
|
||||
, m_wave_size(0)
|
||||
, m_sound_enable(false)
|
||||
, m_namco_clock(0)
|
||||
, m_sample_rate(0)
|
||||
, m_f_fracbits(0)
|
||||
, m_voices(0)
|
||||
, m_stereo(false)
|
||||
{
|
||||
}
|
||||
|
||||
namco_device::namco_device(uint32_t clock)
|
||||
: namco_audio_device(clock)
|
||||
{
|
||||
}
|
||||
|
||||
namco_15xx_device::namco_15xx_device(uint32_t clock)
|
||||
:namco_audio_device(clock)
|
||||
{
|
||||
}
|
||||
|
||||
namco_cus30_device::namco_cus30_device(uint32_t clock)
|
||||
: namco_audio_device(clock)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// device_start - device-specific startup
|
||||
//-------------------------------------------------
|
||||
|
||||
void namco_audio_device::device_start(unsigned char* wavePtr)
|
||||
{
|
||||
/* extract globals from the interface */
|
||||
m_last_channel = m_channel_list + m_voices;
|
||||
|
||||
m_wave_ptr = wavePtr;
|
||||
|
||||
memset(m_waveram_alloc,0,1024);
|
||||
|
||||
/* build the waveform table */
|
||||
build_decoded_waveform(m_wave_ptr);
|
||||
|
||||
/* start with sound enabled, many games don't have a sound enable register */
|
||||
m_sound_enable = true;
|
||||
|
||||
/* reset all the voices */
|
||||
for (sound_channel *voice = m_channel_list; voice < m_last_channel; voice++)
|
||||
{
|
||||
voice->frequency = 0;
|
||||
voice->volume[0] = voice->volume[1] = 0;
|
||||
voice->waveform_select = 0;
|
||||
voice->counter = 0;
|
||||
voice->noise_sw = 0;
|
||||
voice->noise_state = 0;
|
||||
voice->noise_seed = 1;
|
||||
voice->noise_counter = 0;
|
||||
voice->noise_hold = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void namco_audio_device::device_clock_changed(int clk)
|
||||
{
|
||||
int clock_multiple;
|
||||
|
||||
// adjust internal clock
|
||||
m_namco_clock = clk;
|
||||
for (clock_multiple = 0; m_namco_clock < INTERNAL_RATE; clock_multiple++)
|
||||
m_namco_clock *= 2;
|
||||
|
||||
m_f_fracbits = clock_multiple + 15;
|
||||
|
||||
// adjust output clock
|
||||
m_sample_rate = m_namco_clock;
|
||||
|
||||
//logerror("Namco: freq fractional bits = %d: internal freq = %d, output freq = %d\n", m_f_fracbits, m_namco_clock, m_sample_rate);
|
||||
}
|
||||
|
||||
|
||||
/* update the decoded waveform data */
|
||||
void namco_audio_device::update_namco_waveform(int offset, uint8_t data)
|
||||
{
|
||||
if (m_wave_size == 1)
|
||||
{
|
||||
int16_t wdata;
|
||||
int v;
|
||||
|
||||
/* use full byte, first 4 high bits, then low 4 bits */
|
||||
for (v = 0; v < (int)MAX_VOLUME; v++)
|
||||
{
|
||||
wdata = ((data >> 4) & 0x0f) - 8;
|
||||
m_waveform[v][offset * 2] = OUTPUT_LEVEL(wdata * v);
|
||||
wdata = (data & 0x0f) - 8;
|
||||
m_waveform[v][offset * 2 + 1] = OUTPUT_LEVEL(wdata * v);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int v;
|
||||
|
||||
/* use only low 4 bits */
|
||||
for (v = 0; v < (int)MAX_VOLUME; v++)
|
||||
m_waveform[v][offset] = OUTPUT_LEVEL(((data & 0x0f) - 8) * v);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* build the decoded waveform table */
|
||||
void namco_audio_device::build_decoded_waveform(uint8_t *rgnbase)
|
||||
{
|
||||
if (rgnbase != nullptr)
|
||||
m_wavedata = rgnbase;
|
||||
else
|
||||
{
|
||||
m_wavedata = m_waveram_alloc;
|
||||
}
|
||||
|
||||
for (int offset = 0; offset < 256; offset++)
|
||||
update_namco_waveform(offset, m_wavedata[offset]);
|
||||
}
|
||||
|
||||
|
||||
/* generate sound by oversampling */
|
||||
uint32_t namco_audio_device::namco_update_one(short* buffer, int size, const int16_t *wave, uint32_t counter, uint32_t freq)
|
||||
{
|
||||
for (int sampindex = 0; sampindex < size; sampindex++)
|
||||
{
|
||||
buffer[sampindex]+=wave[WAVEFORM_POSITION(counter)];
|
||||
counter += freq;
|
||||
}
|
||||
|
||||
return counter;
|
||||
}
|
||||
|
||||
|
||||
void namco_audio_device::sound_enable_w(int state)
|
||||
{
|
||||
m_sound_enable = state;
|
||||
}
|
||||
|
||||
void namco_device::device_start(unsigned char* wavePtr) {
|
||||
memset(m_soundregs,0,1024);
|
||||
namco_audio_device::device_start(wavePtr);
|
||||
}
|
||||
|
||||
void namco_15xx_device::device_start(unsigned char* wavePtr) {
|
||||
memset(m_soundregs,0,1024);
|
||||
namco_audio_device::device_start(wavePtr);
|
||||
}
|
||||
|
||||
void namco_cus30_device::device_start(unsigned char* wavePtr) {
|
||||
namco_audio_device::device_start(wavePtr);
|
||||
}
|
||||
|
||||
|
||||
/********************************************************************************/
|
||||
|
||||
/* pacman register map
|
||||
0x05: ch 0 waveform select
|
||||
0x0a: ch 1 waveform select
|
||||
0x0f: ch 2 waveform select
|
||||
|
||||
0x10: ch 0 the first voice has extra frequency bits
|
||||
0x11-0x14: ch 0 frequency
|
||||
0x15: ch 0 volume
|
||||
|
||||
0x16-0x19: ch 1 frequency
|
||||
0x1a: ch 1 volume
|
||||
|
||||
0x1b-0x1e: ch 2 frequency
|
||||
0x1f: ch 2 volume
|
||||
*/
|
||||
|
||||
void namco_device::pacman_sound_w(int offset, uint8_t data)
|
||||
{
|
||||
sound_channel *voice;
|
||||
int ch;
|
||||
|
||||
data &= 0x0f;
|
||||
if (m_soundregs[offset] == data)
|
||||
return;
|
||||
|
||||
/* set the register */
|
||||
m_soundregs[offset] = data;
|
||||
|
||||
if (offset < 0x10)
|
||||
ch = (offset - 5) / 5;
|
||||
else if (offset == 0x10)
|
||||
ch = 0;
|
||||
else
|
||||
ch = (offset - 0x11) / 5;
|
||||
|
||||
if (ch >= m_voices)
|
||||
return;
|
||||
|
||||
/* recompute the voice parameters */
|
||||
voice = m_channel_list + ch;
|
||||
switch (offset - ch * 5)
|
||||
{
|
||||
case 0x05:
|
||||
voice->waveform_select = data & 7;
|
||||
break;
|
||||
|
||||
case 0x10:
|
||||
case 0x11:
|
||||
case 0x12:
|
||||
case 0x13:
|
||||
case 0x14:
|
||||
/* the frequency has 20 bits */
|
||||
/* the first voice has extra frequency bits */
|
||||
voice->frequency = (ch == 0) ? m_soundregs[0x10] : 0;
|
||||
voice->frequency += (m_soundregs[ch * 5 + 0x11] << 4);
|
||||
voice->frequency += (m_soundregs[ch * 5 + 0x12] << 8);
|
||||
voice->frequency += (m_soundregs[ch * 5 + 0x13] << 12);
|
||||
voice->frequency += (m_soundregs[ch * 5 + 0x14] << 16); /* always 0 */
|
||||
break;
|
||||
|
||||
case 0x15:
|
||||
voice->volume[0] = data;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void namco_cus30_device::pacman_sound_w(int offset, uint8_t data)
|
||||
{
|
||||
sound_channel *voice;
|
||||
int ch;
|
||||
|
||||
uint8_t *soundregs = &m_wavedata[0x100];
|
||||
|
||||
data &= 0x0f;
|
||||
if (soundregs[offset] == data)
|
||||
return;
|
||||
|
||||
/* set the register */
|
||||
soundregs[offset] = data;
|
||||
|
||||
if (offset < 0x10)
|
||||
ch = (offset - 5) / 5;
|
||||
else if (offset == 0x10)
|
||||
ch = 0;
|
||||
else
|
||||
ch = (offset - 0x11) / 5;
|
||||
|
||||
if (ch >= m_voices)
|
||||
return;
|
||||
|
||||
/* recompute the voice parameters */
|
||||
voice = m_channel_list + ch;
|
||||
switch (offset - ch * 5)
|
||||
{
|
||||
case 0x05:
|
||||
voice->waveform_select = data & 7;
|
||||
break;
|
||||
|
||||
case 0x10:
|
||||
case 0x11:
|
||||
case 0x12:
|
||||
case 0x13:
|
||||
case 0x14:
|
||||
/* the frequency has 20 bits */
|
||||
/* the first voice has extra frequency bits */
|
||||
voice->frequency = (ch == 0) ? soundregs[0x10] : 0;
|
||||
voice->frequency += (soundregs[ch * 5 + 0x11] << 4);
|
||||
voice->frequency += (soundregs[ch * 5 + 0x12] << 8);
|
||||
voice->frequency += (soundregs[ch * 5 + 0x13] << 12);
|
||||
voice->frequency += (soundregs[ch * 5 + 0x14] << 16); /* always 0 */
|
||||
break;
|
||||
|
||||
case 0x15:
|
||||
voice->volume[0] = data;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/********************************************************************************/
|
||||
|
||||
/* polepos register map
|
||||
Note: even if there are 8 voices, the game doesn't use the first 2 because
|
||||
it select the 54XX/52XX outputs on those channels
|
||||
|
||||
0x00-0x01 ch 0 frequency
|
||||
0x02 ch 0 xxxx---- GAIN 2 volume
|
||||
0x03 ch 0 xxxx---- GAIN 3 volume
|
||||
----xxxx GAIN 4 volume
|
||||
|
||||
0x04-0x07 ch 1
|
||||
|
||||
.
|
||||
.
|
||||
.
|
||||
|
||||
0x1c-0x1f ch 7
|
||||
|
||||
0x23 ch 0 xxxx---- GAIN 1 volume
|
||||
-----xxx waveform select
|
||||
----x-xx channel output select
|
||||
0-7 (all the same, shared with waveform select) = wave
|
||||
8 = CHANL1 (54XX pins 17-20)
|
||||
9 = CHANL2 (54XX pins 8-11)
|
||||
A = CHANL3 (54XX pins 4-7)
|
||||
B = CHANL4 (52XX)
|
||||
0x27 ch 1
|
||||
0x2b ch 2
|
||||
0x2f ch 3
|
||||
0x33 ch 4
|
||||
0x37 ch 5
|
||||
0x3b ch 6
|
||||
0x3f ch 7
|
||||
*/
|
||||
|
||||
uint8_t namco_device::polepos_sound_r(int offset)
|
||||
{
|
||||
return m_soundregs[offset];
|
||||
}
|
||||
|
||||
void namco_device::polepos_sound_w(int offset, uint8_t data)
|
||||
{
|
||||
sound_channel *voice;
|
||||
int ch;
|
||||
|
||||
if (m_soundregs[offset] == data)
|
||||
return;
|
||||
|
||||
/* set the register */
|
||||
m_soundregs[offset] = data;
|
||||
|
||||
ch = (offset & 0x1f) / 4;
|
||||
|
||||
/* recompute the voice parameters */
|
||||
voice = m_channel_list + ch;
|
||||
switch (offset & 0x23)
|
||||
{
|
||||
case 0x00:
|
||||
case 0x01:
|
||||
/* the frequency has 16 bits */
|
||||
voice->frequency = m_soundregs[ch * 4 + 0x00];
|
||||
voice->frequency += m_soundregs[ch * 4 + 0x01] << 8;
|
||||
break;
|
||||
|
||||
case 0x23:
|
||||
voice->waveform_select = data & 7;
|
||||
// https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wimplicit-fallthrough
|
||||
// fall through
|
||||
case 0x02:
|
||||
case 0x03:
|
||||
voice->volume[0] = voice->volume[1] = 0;
|
||||
// front speakers ?
|
||||
voice->volume[0] += m_soundregs[ch * 4 + 0x03] >> 4;
|
||||
voice->volume[1] += m_soundregs[ch * 4 + 0x03] & 0x0f;
|
||||
// rear speakers ?
|
||||
voice->volume[0] += m_soundregs[ch * 4 + 0x23] >> 4;
|
||||
voice->volume[1] += m_soundregs[ch * 4 + 0x02] >> 4;
|
||||
|
||||
voice->volume[0] /= 2;
|
||||
voice->volume[1] /= 2;
|
||||
|
||||
/* if 54XX or 52XX selected, silence this voice */
|
||||
if (m_soundregs[ch * 4 + 0x23] & 8)
|
||||
voice->volume[0] = voice->volume[1] = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/********************************************************************************/
|
||||
|
||||
/* 15XX register map
|
||||
0x03 ch 0 volume
|
||||
0x04-0x05 ch 0 frequency
|
||||
0x06 ch 0 waveform select & frequency
|
||||
|
||||
0x0b ch 1 volume
|
||||
0x0c-0x0d ch 1 frequency
|
||||
0x0e ch 1 waveform select & frequency
|
||||
|
||||
.
|
||||
.
|
||||
.
|
||||
|
||||
0x3b ch 7 volume
|
||||
0x3c-0x3d ch 7 frequency
|
||||
0x3e ch 7 waveform select & frequency
|
||||
|
||||
Grobda also stuffs values into register offset 0x02 with a frequency of zero
|
||||
to make 15XX channels act like a 4-bit DAC instead of waveform voices. This
|
||||
has been emulated by allowing writes to set the upper counter bits directly.
|
||||
Possibly offsets 0x00 and 0x01 can be used to set the fractional bits.
|
||||
*/
|
||||
|
||||
template <typename T, typename U> constexpr T make_bitmask(U n)
|
||||
{
|
||||
return T((n < (int)(8 * sizeof(T)) ? (std::make_unsigned_t<T>(1) << n) : std::make_unsigned_t<T>(0)) - 1);
|
||||
}
|
||||
|
||||
void namco_15xx_device::namco_15xx_w(int offset, uint8_t data)
|
||||
{
|
||||
sound_channel *voice;
|
||||
int ch;
|
||||
|
||||
if (m_soundregs[offset] == data)
|
||||
return;
|
||||
|
||||
/* set the register */
|
||||
m_soundregs[offset] = data;
|
||||
|
||||
ch = offset / 8;
|
||||
if (ch >= m_voices)
|
||||
return;
|
||||
|
||||
/* recompute the voice parameters */
|
||||
voice = m_channel_list + ch;
|
||||
switch (offset - ch * 8)
|
||||
{
|
||||
case 0x02:
|
||||
voice->counter &= make_bitmask<uint32_t>(m_f_fracbits);
|
||||
voice->counter |= uint32_t(data & 0x1f) << m_f_fracbits;
|
||||
break;
|
||||
|
||||
case 0x03:
|
||||
voice->volume[0] = data & 0x0f;
|
||||
break;
|
||||
|
||||
case 0x06:
|
||||
voice->waveform_select = (data >> 4) & 7;
|
||||
// https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wimplicit-fallthrough
|
||||
// fall through
|
||||
case 0x04:
|
||||
case 0x05:
|
||||
/* the frequency has 20 bits */
|
||||
voice->frequency = m_soundregs[ch * 8 + 0x04];
|
||||
voice->frequency += m_soundregs[ch * 8 + 0x05] << 8;
|
||||
voice->frequency += (m_soundregs[ch * 8 + 0x06] & 15) << 16; /* high bits are from here */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/********************************************************************************/
|
||||
|
||||
/* namcos1 register map
|
||||
0x00 ch 0 left volume
|
||||
0x01 ch 0 waveform select & frequency
|
||||
0x02-0x03 ch 0 frequency
|
||||
0x04 ch 0 right volume AND
|
||||
0x04 ch 1 noise sw
|
||||
|
||||
0x08 ch 1 left volume
|
||||
0x09 ch 1 waveform select & frequency
|
||||
0x0a-0x0b ch 1 frequency
|
||||
0x0c ch 1 right volume AND
|
||||
0x0c ch 2 noise sw
|
||||
|
||||
.
|
||||
.
|
||||
.
|
||||
|
||||
0x38 ch 7 left volume
|
||||
0x39 ch 7 waveform select & frequency
|
||||
0x3a-0x3b ch 7 frequency
|
||||
0x3c ch 7 right volume AND
|
||||
0x3c ch 0 noise sw
|
||||
*/
|
||||
|
||||
void namco_cus30_device::namcos1_sound_w(int offset, uint8_t data)
|
||||
{
|
||||
sound_channel *voice;
|
||||
int ch;
|
||||
int nssw;
|
||||
|
||||
|
||||
/* verify the offset */
|
||||
if (offset > 63)
|
||||
{
|
||||
//logerror("NAMCOS1 sound: Attempting to write past the 64 registers segment\n");
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t *soundregs = &m_wavedata[0x100];
|
||||
|
||||
if (soundregs[offset] == data)
|
||||
return;
|
||||
|
||||
/* set the register */
|
||||
soundregs[offset] = data;
|
||||
|
||||
ch = offset / 8;
|
||||
if (ch >= m_voices)
|
||||
return;
|
||||
|
||||
/* recompute the voice parameters */
|
||||
voice = m_channel_list + ch;
|
||||
switch (offset - ch * 8)
|
||||
{
|
||||
case 0x00:
|
||||
voice->volume[0] = data & 0x0f;
|
||||
break;
|
||||
|
||||
case 0x01:
|
||||
voice->waveform_select = (data >> 4) & 15;
|
||||
// https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wimplicit-fallthrough
|
||||
// fall through
|
||||
case 0x02:
|
||||
case 0x03:
|
||||
/* the frequency has 20 bits */
|
||||
voice->frequency = (soundregs[ch * 8 + 0x01] & 15) << 16; /* high bits are from here */
|
||||
voice->frequency += soundregs[ch * 8 + 0x02] << 8;
|
||||
voice->frequency += soundregs[ch * 8 + 0x03];
|
||||
break;
|
||||
|
||||
case 0x04:
|
||||
voice->volume[1] = data & 0x0f;
|
||||
|
||||
nssw = ((data & 0x80) >> 7);
|
||||
if (++voice == m_last_channel)
|
||||
voice = m_channel_list;
|
||||
voice->noise_sw = nssw;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void namco_cus30_device::namcos1_cus30_w(int offset, uint8_t data)
|
||||
{
|
||||
if (offset < 0x100)
|
||||
{
|
||||
if (m_wavedata[offset] != data)
|
||||
{
|
||||
|
||||
m_wavedata[offset] = data;
|
||||
|
||||
/* update the decoded waveform table */
|
||||
update_namco_waveform(offset, data);
|
||||
}
|
||||
}
|
||||
else if (offset < 0x140) {
|
||||
namcos1_sound_w(offset - 0x100,data);
|
||||
}
|
||||
else
|
||||
m_wavedata[offset] = data;
|
||||
}
|
||||
|
||||
uint8_t namco_cus30_device::namcos1_cus30_r(int offset)
|
||||
{
|
||||
return m_wavedata[offset];
|
||||
}
|
||||
|
||||
uint8_t namco_15xx_device::sharedram_r(int offset)
|
||||
{
|
||||
return m_soundregs[offset];
|
||||
}
|
||||
|
||||
void namco_15xx_device::sharedram_w(int offset, uint8_t data)
|
||||
{
|
||||
if (offset < 0x40)
|
||||
namco_15xx_w(offset, data);
|
||||
else
|
||||
{
|
||||
m_soundregs[offset] = data;
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------
|
||||
// sound_stream_update - handle a stream update
|
||||
//-------------------------------------------------
|
||||
|
||||
void namco_audio_device::sound_stream_update(short** outputs, int len)
|
||||
{
|
||||
if (m_stereo)
|
||||
{
|
||||
/* zap the contents of the buffers */
|
||||
memset(outputs[0],0,len*sizeof(short));
|
||||
memset(outputs[1],0,len*sizeof(short));
|
||||
|
||||
/* if no sound, we're done */
|
||||
if (!m_sound_enable)
|
||||
return;
|
||||
|
||||
/* loop over each voice and add its contribution */
|
||||
for (sound_channel *voice = m_channel_list; voice < m_last_channel; voice++)
|
||||
{
|
||||
short* lmix = outputs[0];
|
||||
short* rmix = outputs[1];
|
||||
int lv = voice->volume[0];
|
||||
int rv = voice->volume[1];
|
||||
|
||||
if (voice->noise_sw)
|
||||
{
|
||||
int f = voice->frequency & 0xff;
|
||||
|
||||
/* only update if we have non-zero volume */
|
||||
if (lv || rv)
|
||||
{
|
||||
int hold_time = 1 << (m_f_fracbits - 16);
|
||||
int hold = voice->noise_hold;
|
||||
uint32_t delta = f << 4;
|
||||
uint32_t c = voice->noise_counter;
|
||||
int16_t l_noise_data = OUTPUT_LEVEL(0x07 * (lv >> 1));
|
||||
int16_t r_noise_data = OUTPUT_LEVEL(0x07 * (rv >> 1));
|
||||
int i;
|
||||
|
||||
/* add our contribution */
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
int cnt;
|
||||
|
||||
if (voice->noise_state)
|
||||
{
|
||||
lmix[i]+=l_noise_data;
|
||||
rmix[i]+=r_noise_data;
|
||||
}
|
||||
else
|
||||
{
|
||||
lmix[i]+=-l_noise_data;
|
||||
rmix[i]+=-r_noise_data;
|
||||
}
|
||||
|
||||
if (hold)
|
||||
{
|
||||
hold--;
|
||||
continue;
|
||||
}
|
||||
|
||||
hold = hold_time;
|
||||
|
||||
c += delta;
|
||||
cnt = (c >> 12);
|
||||
c &= (1 << 12) - 1;
|
||||
for( ;cnt > 0; cnt--)
|
||||
{
|
||||
if ((voice->noise_seed + 1) & 2) voice->noise_state ^= 1;
|
||||
if (voice->noise_seed & 1) voice->noise_seed ^= 0x28000;
|
||||
voice->noise_seed >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* update the counter and hold time for this voice */
|
||||
voice->noise_counter = c;
|
||||
voice->noise_hold = hold;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* save the counter for this voice */
|
||||
uint32_t c = voice->counter;
|
||||
|
||||
/* only update if we have non-zero left volume */
|
||||
if (lv)
|
||||
{
|
||||
const int16_t *lw = &m_waveform[lv][voice->waveform_select * 32];
|
||||
|
||||
/* generate sound into the buffer */
|
||||
c = namco_update_one(lmix, len, lw, voice->counter, voice->frequency);
|
||||
}
|
||||
|
||||
/* only update if we have non-zero right volume */
|
||||
if (rv)
|
||||
{
|
||||
const int16_t *rw = &m_waveform[rv][voice->waveform_select * 32];
|
||||
|
||||
/* generate sound into the buffer */
|
||||
c = namco_update_one(rmix, len, rw, voice->counter, voice->frequency);
|
||||
}
|
||||
|
||||
/* update the counter for this voice */
|
||||
voice->counter = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sound_channel *voice;
|
||||
|
||||
short* buffer = outputs[0];
|
||||
/* zap the contents of the buffer */
|
||||
memset(buffer,0,len*sizeof(short));
|
||||
|
||||
/* if no sound, we're done */
|
||||
if (!m_sound_enable)
|
||||
return;
|
||||
|
||||
/* loop over each voice and add its contribution */
|
||||
for (voice = m_channel_list; voice < m_last_channel; voice++)
|
||||
{
|
||||
int v = voice->volume[0];
|
||||
if (voice->noise_sw)
|
||||
{
|
||||
int f = voice->frequency & 0xff;
|
||||
|
||||
/* only update if we have non-zero volume */
|
||||
if (v)
|
||||
{
|
||||
int hold_time = 1 << (m_f_fracbits - 16);
|
||||
int hold = voice->noise_hold;
|
||||
uint32_t delta = f << 4;
|
||||
uint32_t c = voice->noise_counter;
|
||||
int16_t noise_data = OUTPUT_LEVEL(0x07 * (v >> 1));
|
||||
int i;
|
||||
|
||||
/* add our contribution */
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
int cnt;
|
||||
|
||||
if (voice->noise_state)
|
||||
buffer[i]+=noise_data;
|
||||
else
|
||||
buffer[i]+=-noise_data;
|
||||
|
||||
if (hold)
|
||||
{
|
||||
hold--;
|
||||
continue;
|
||||
}
|
||||
|
||||
hold = hold_time;
|
||||
|
||||
c += delta;
|
||||
cnt = (c >> 12);
|
||||
c &= (1 << 12) - 1;
|
||||
for( ;cnt > 0; cnt--)
|
||||
{
|
||||
if ((voice->noise_seed + 1) & 2) voice->noise_state ^= 1;
|
||||
if (voice->noise_seed & 1) voice->noise_seed ^= 0x28000;
|
||||
voice->noise_seed >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* update the counter and hold time for this voice */
|
||||
voice->noise_counter = c;
|
||||
voice->noise_hold = hold;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* only update if we have non-zero volume */
|
||||
if (v)
|
||||
{
|
||||
const int16_t *w = &m_waveform[v][voice->waveform_select * 32];
|
||||
|
||||
/* generate sound into buffer and update the counter for this voice */
|
||||
voice->counter = namco_update_one(buffer, len, w, voice->counter, voice->frequency);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
128
src/engine/platform/sound/namco.h
Normal file
128
src/engine/platform/sound/namco.h
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
// license:BSD-3-Clause
|
||||
// copyright-holders:Nicola Salmoria,Aaron Giles
|
||||
#ifndef MAME_SOUND_NAMCO_H
|
||||
#define MAME_SOUND_NAMCO_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <memory>
|
||||
|
||||
class namco_audio_device
|
||||
{
|
||||
public:
|
||||
// configuration
|
||||
void set_voices(int voices) { m_voices = voices; }
|
||||
void set_stereo(bool stereo) { m_stereo = stereo; }
|
||||
|
||||
void sound_enable_w(int state);
|
||||
|
||||
static constexpr unsigned MAX_VOICES = 8;
|
||||
static constexpr unsigned MAX_VOLUME = 16;
|
||||
|
||||
/* this structure defines the parameters for a channel */
|
||||
struct sound_channel
|
||||
{
|
||||
uint32_t frequency;
|
||||
uint32_t counter;
|
||||
int32_t volume[2];
|
||||
int32_t noise_sw;
|
||||
int32_t noise_state;
|
||||
int32_t noise_seed;
|
||||
uint32_t noise_counter;
|
||||
int32_t noise_hold;
|
||||
int32_t waveform_select;
|
||||
};
|
||||
|
||||
namco_audio_device(uint32_t clock);
|
||||
|
||||
// device-level overrides
|
||||
virtual void device_start(unsigned char* wavePtr);
|
||||
void device_clock_changed(int clk);
|
||||
|
||||
// internal state
|
||||
|
||||
void build_decoded_waveform( uint8_t *rgnbase );
|
||||
void update_namco_waveform(int offset, uint8_t data);
|
||||
uint32_t namco_update_one(short* buffer, int size, const int16_t *wave, uint32_t counter, uint32_t freq);
|
||||
|
||||
/* waveform region */
|
||||
uint8_t* m_wave_ptr;
|
||||
|
||||
/* data about the sound system */
|
||||
sound_channel m_channel_list[MAX_VOICES];
|
||||
sound_channel *m_last_channel;
|
||||
uint8_t *m_wavedata;
|
||||
|
||||
/* global sound parameters */
|
||||
int m_wave_size;
|
||||
bool m_sound_enable;
|
||||
int m_namco_clock;
|
||||
int m_sample_rate;
|
||||
int m_f_fracbits;
|
||||
|
||||
int m_voices; /* number of voices */
|
||||
bool m_stereo; /* set to indicate stereo (e.g., System 1) */
|
||||
|
||||
uint8_t m_waveram_alloc[0x400];
|
||||
|
||||
/* decoded waveform table */
|
||||
int16_t m_waveform[MAX_VOLUME][512];
|
||||
|
||||
virtual void sound_stream_update(short** outputs, int len);
|
||||
virtual ~namco_audio_device() {}
|
||||
};
|
||||
|
||||
class namco_device : public namco_audio_device
|
||||
{
|
||||
public:
|
||||
namco_device(uint32_t clock);
|
||||
|
||||
void pacman_sound_w(int offset, uint8_t data);
|
||||
|
||||
uint8_t polepos_sound_r(int offset);
|
||||
void polepos_sound_w(int offset, uint8_t data);
|
||||
|
||||
void device_start(unsigned char* wavePtr);
|
||||
|
||||
~namco_device() {}
|
||||
|
||||
private:
|
||||
uint8_t m_soundregs[0x400];
|
||||
};
|
||||
|
||||
|
||||
class namco_15xx_device : public namco_audio_device
|
||||
{
|
||||
public:
|
||||
namco_15xx_device(uint32_t clock);
|
||||
|
||||
void namco_15xx_w(int offset, uint8_t data);
|
||||
uint8_t sharedram_r(int offset);
|
||||
void sharedram_w(int offset, uint8_t data);
|
||||
|
||||
void device_start(unsigned char* wavePtr);
|
||||
|
||||
~namco_15xx_device() {}
|
||||
|
||||
private:
|
||||
uint8_t m_soundregs[0x400];
|
||||
};
|
||||
|
||||
|
||||
class namco_cus30_device : public namco_audio_device
|
||||
{
|
||||
public:
|
||||
namco_cus30_device(uint32_t clock);
|
||||
|
||||
void namcos1_cus30_w(int offset, uint8_t data); /* wavedata + sound registers + RAM */
|
||||
uint8_t namcos1_cus30_r(int offset);
|
||||
void namcos1_sound_w(int offset, uint8_t data);
|
||||
|
||||
void pacman_sound_w(int offset, uint8_t data);
|
||||
|
||||
void device_start(unsigned char* wavePtr);
|
||||
|
||||
~namco_cus30_device() {}
|
||||
};
|
||||
|
||||
#endif // MAME_SOUND_NAMCO_H
|
||||
326
src/engine/platform/sound/oki/okim6258.cpp
Normal file
326
src/engine/platform/sound/oki/okim6258.cpp
Normal file
|
|
@ -0,0 +1,326 @@
|
|||
// license:BSD-3-Clause
|
||||
// copyright-holders:Barry Rodewald
|
||||
/**********************************************************************************************
|
||||
*
|
||||
* OKI MSM6258 ADPCM
|
||||
*
|
||||
* TODO:
|
||||
* 3-bit ADPCM support
|
||||
* Recording?
|
||||
*
|
||||
**********************************************************************************************/
|
||||
|
||||
|
||||
#include "emu.h"
|
||||
#include "okim6258.h"
|
||||
|
||||
#define COMMAND_STOP (1 << 0)
|
||||
#define COMMAND_PLAY (1 << 1)
|
||||
#define COMMAND_RECORD (1 << 2)
|
||||
|
||||
#define STATUS_PLAYING (1 << 1)
|
||||
#define STATUS_RECORDING (1 << 2)
|
||||
|
||||
static const int dividers[4] = { 1024, 768, 512, 512 };
|
||||
|
||||
/* step size index shift table */
|
||||
static const int index_shift[8] = { -1, -1, -1, -1, 2, 4, 6, 8 };
|
||||
|
||||
/* lookup table for the precomputed difference */
|
||||
static int diff_lookup[49*16];
|
||||
|
||||
/* tables computed? */
|
||||
static int tables_computed = 0;
|
||||
|
||||
|
||||
|
||||
// device type definition
|
||||
DEFINE_DEVICE_TYPE(OKIM6258, okim6258_device, "okim6258", "OKI MSM6258 ADPCM")
|
||||
|
||||
|
||||
//**************************************************************************
|
||||
// LIVE DEVICE
|
||||
//**************************************************************************
|
||||
|
||||
//-------------------------------------------------
|
||||
// okim6258_device - constructor
|
||||
//-------------------------------------------------
|
||||
|
||||
okim6258_device::okim6258_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
|
||||
: device_t(mconfig, OKIM6258, tag, owner, clock),
|
||||
device_sound_interface(mconfig, *this),
|
||||
m_status(0),
|
||||
m_start_divider(0),
|
||||
m_divider(512),
|
||||
m_adpcm_type(0),
|
||||
m_data_in(0),
|
||||
m_nibble_shift(0),
|
||||
m_stream(nullptr),
|
||||
m_output_bits(0),
|
||||
m_signal(0),
|
||||
m_step(0)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**********************************************************************************************
|
||||
|
||||
compute_tables -- compute the difference tables
|
||||
|
||||
***********************************************************************************************/
|
||||
|
||||
static void compute_tables()
|
||||
{
|
||||
/* nibble to bit map */
|
||||
static const int nbl2bit[16][4] =
|
||||
{
|
||||
{ 1, 0, 0, 0}, { 1, 0, 0, 1}, { 1, 0, 1, 0}, { 1, 0, 1, 1},
|
||||
{ 1, 1, 0, 0}, { 1, 1, 0, 1}, { 1, 1, 1, 0}, { 1, 1, 1, 1},
|
||||
{-1, 0, 0, 0}, {-1, 0, 0, 1}, {-1, 0, 1, 0}, {-1, 0, 1, 1},
|
||||
{-1, 1, 0, 0}, {-1, 1, 0, 1}, {-1, 1, 1, 0}, {-1, 1, 1, 1}
|
||||
};
|
||||
|
||||
int step, nib;
|
||||
|
||||
/* loop over all possible steps */
|
||||
for (step = 0; step <= 48; step++)
|
||||
{
|
||||
/* compute the step value */
|
||||
int stepval = floor(16.0 * pow(11.0 / 10.0, (double)step));
|
||||
|
||||
/* loop over all nibbles and compute the difference */
|
||||
for (nib = 0; nib < 16; nib++)
|
||||
{
|
||||
diff_lookup[step*16 + nib] = nbl2bit[nib][0] *
|
||||
(stepval * nbl2bit[nib][1] +
|
||||
stepval/2 * nbl2bit[nib][2] +
|
||||
stepval/4 * nbl2bit[nib][3] +
|
||||
stepval/8);
|
||||
}
|
||||
}
|
||||
|
||||
tables_computed = 1;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// device_start - device-specific startup
|
||||
//-------------------------------------------------
|
||||
|
||||
void okim6258_device::device_start()
|
||||
{
|
||||
compute_tables();
|
||||
|
||||
m_divider = dividers[m_start_divider];
|
||||
|
||||
m_stream = stream_alloc(0, 1, clock()/m_divider);
|
||||
|
||||
m_signal = -2;
|
||||
m_step = 0;
|
||||
|
||||
state_save_register();
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// device_reset - device-specific reset
|
||||
//-------------------------------------------------
|
||||
|
||||
void okim6258_device::device_reset()
|
||||
{
|
||||
m_stream->update();
|
||||
|
||||
m_signal = -2;
|
||||
m_step = 0;
|
||||
m_status = 0;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// sound_stream_update - handle a stream update
|
||||
//-------------------------------------------------
|
||||
|
||||
void okim6258_device::sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs)
|
||||
{
|
||||
auto &buffer = outputs[0];
|
||||
|
||||
if (m_status & STATUS_PLAYING)
|
||||
{
|
||||
int nibble_shift = m_nibble_shift;
|
||||
|
||||
for (int sampindex = 0; sampindex < buffer.samples(); sampindex++)
|
||||
{
|
||||
/* Compute the new amplitude and update the current step */
|
||||
int nibble = (m_data_in >> nibble_shift) & 0xf;
|
||||
|
||||
/* Output to the buffer */
|
||||
int16_t sample = clock_adpcm(nibble);
|
||||
|
||||
nibble_shift ^= 4;
|
||||
|
||||
buffer.put_int(sampindex, sample, 32768);
|
||||
}
|
||||
|
||||
/* Update the parameters */
|
||||
m_nibble_shift = nibble_shift;
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer.fill(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**********************************************************************************************
|
||||
|
||||
state save support for MAME
|
||||
|
||||
***********************************************************************************************/
|
||||
|
||||
void okim6258_device::state_save_register()
|
||||
{
|
||||
save_item(NAME(m_status));
|
||||
save_item(NAME(m_divider));
|
||||
save_item(NAME(m_data_in));
|
||||
save_item(NAME(m_nibble_shift));
|
||||
save_item(NAME(m_signal));
|
||||
save_item(NAME(m_step));
|
||||
}
|
||||
|
||||
|
||||
int16_t okim6258_device::clock_adpcm(uint8_t nibble)
|
||||
{
|
||||
int32_t max = (1 << (m_output_bits - 1)) - 1;
|
||||
int32_t min = -(1 << (m_output_bits - 1));
|
||||
|
||||
m_signal += diff_lookup[m_step * 16 + (nibble & 15)];
|
||||
|
||||
/* clamp to the maximum */
|
||||
if (m_signal > max)
|
||||
m_signal = max;
|
||||
else if (m_signal < min)
|
||||
m_signal = min;
|
||||
|
||||
/* adjust the step size and clamp */
|
||||
m_step += index_shift[nibble & 7];
|
||||
if (m_step > 48)
|
||||
m_step = 48;
|
||||
else if (m_step < 0)
|
||||
m_step = 0;
|
||||
|
||||
/* return the signal scaled up to 32767 */
|
||||
return m_signal << 4;
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************************************
|
||||
|
||||
okim6258::set_divider -- set the master clock divider
|
||||
|
||||
***********************************************************************************************/
|
||||
|
||||
void okim6258_device::set_divider(int val)
|
||||
{
|
||||
m_divider = dividers[val];
|
||||
notify_clock_changed();
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************************************
|
||||
|
||||
okim6258::set_clock -- set the master clock
|
||||
|
||||
***********************************************************************************************/
|
||||
|
||||
void okim6258_device::device_clock_changed()
|
||||
{
|
||||
m_stream->set_sample_rate(clock() / m_divider);
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************************************
|
||||
|
||||
okim6258::get_vclk -- get the VCLK/sampling frequency
|
||||
|
||||
***********************************************************************************************/
|
||||
|
||||
int okim6258_device::get_vclk()
|
||||
{
|
||||
return (clock() / m_divider);
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************************************
|
||||
|
||||
okim6258_status_r -- read the status port of an OKIM6258-compatible chip
|
||||
|
||||
***********************************************************************************************/
|
||||
|
||||
uint8_t okim6258_device::status_r()
|
||||
{
|
||||
m_stream->update();
|
||||
|
||||
return (m_status & STATUS_PLAYING) ? 0x00 : 0x80;
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************************************
|
||||
|
||||
okim6258_data_w -- write to the control port of an OKIM6258-compatible chip
|
||||
|
||||
***********************************************************************************************/
|
||||
void okim6258_device::data_w(uint8_t data)
|
||||
{
|
||||
/* update the stream */
|
||||
m_stream->update();
|
||||
|
||||
m_data_in = data;
|
||||
m_nibble_shift = 0;
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************************************
|
||||
|
||||
okim6258_ctrl_w -- write to the control port of an OKIM6258-compatible chip
|
||||
|
||||
***********************************************************************************************/
|
||||
|
||||
void okim6258_device::ctrl_w(uint8_t data)
|
||||
{
|
||||
m_stream->update();
|
||||
|
||||
if (data & COMMAND_STOP)
|
||||
{
|
||||
m_status &= ~(STATUS_PLAYING | STATUS_RECORDING);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data & COMMAND_PLAY)
|
||||
{
|
||||
if (!(m_status & STATUS_PLAYING))
|
||||
{
|
||||
m_status |= STATUS_PLAYING;
|
||||
|
||||
/* Also reset the ADPCM parameters */
|
||||
m_signal = -2;
|
||||
m_step = 0;
|
||||
m_nibble_shift = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_status &= ~STATUS_PLAYING;
|
||||
}
|
||||
|
||||
if (data & COMMAND_RECORD)
|
||||
{
|
||||
logerror("M6258: Record enabled\n");
|
||||
m_status |= STATUS_RECORDING;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_status &= ~STATUS_RECORDING;
|
||||
}
|
||||
}
|
||||
74
src/engine/platform/sound/oki/okim6258.h
Normal file
74
src/engine/platform/sound/oki/okim6258.h
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
// license:BSD-3-Clause
|
||||
// copyright-holders:Barry Rodewald
|
||||
#ifndef MAME_SOUND_OKIM6258_H
|
||||
#define MAME_SOUND_OKIM6258_H
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
//**************************************************************************
|
||||
// TYPE DEFINITIONS
|
||||
//**************************************************************************
|
||||
|
||||
// ======================> okim6258_device
|
||||
|
||||
class okim6258_device : public device_t,
|
||||
public device_sound_interface
|
||||
{
|
||||
public:
|
||||
static constexpr int FOSC_DIV_BY_1024 = 0;
|
||||
static constexpr int FOSC_DIV_BY_768 = 1;
|
||||
static constexpr int FOSC_DIV_BY_512 = 2;
|
||||
|
||||
static constexpr int TYPE_3BITS = 0;
|
||||
static constexpr int TYPE_4BITS = 1;
|
||||
|
||||
static constexpr int OUTPUT_10BITS = 10;
|
||||
static constexpr int OUTPUT_12BITS = 12;
|
||||
|
||||
okim6258_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
|
||||
|
||||
// configuration
|
||||
void set_start_div(int div) { m_start_divider = div; }
|
||||
void set_type(int type) { m_adpcm_type = type; }
|
||||
void set_outbits(int outbit) { m_output_bits = outbit; }
|
||||
|
||||
uint8_t status_r();
|
||||
void data_w(uint8_t data);
|
||||
void ctrl_w(uint8_t data);
|
||||
|
||||
void set_divider(int val);
|
||||
int get_vclk();
|
||||
|
||||
protected:
|
||||
// device-level overrides
|
||||
virtual void device_start() override;
|
||||
virtual void device_reset() override;
|
||||
virtual void device_clock_changed() override;
|
||||
|
||||
// sound stream update overrides
|
||||
virtual void sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs) override;
|
||||
|
||||
private:
|
||||
void state_save_register();
|
||||
int16_t clock_adpcm(uint8_t nibble);
|
||||
|
||||
uint8_t m_status;
|
||||
|
||||
uint32_t m_start_divider;
|
||||
uint32_t m_divider; /* master clock divider */
|
||||
uint8_t m_adpcm_type; /* 3/4 bit ADPCM select */
|
||||
uint8_t m_data_in; /* ADPCM data-in register */
|
||||
uint8_t m_nibble_shift; /* nibble select */
|
||||
sound_stream *m_stream; /* which stream are we playing on? */
|
||||
|
||||
uint8_t m_output_bits; /* D/A precision is 10-bits but 12-bit data can be
|
||||
output serially to an external DAC */
|
||||
|
||||
int32_t m_signal;
|
||||
int32_t m_step;
|
||||
};
|
||||
|
||||
DECLARE_DEVICE_TYPE(OKIM6258, okim6258_device)
|
||||
|
||||
#endif // MAME_SOUND_OKIM6258_H
|
||||
255
src/engine/platform/sound/rf5c68.cpp
Normal file
255
src/engine/platform/sound/rf5c68.cpp
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
// license:BSD-3-Clause
|
||||
// copyright-holders:Olivier Galibert,Aaron Giles
|
||||
/*********************************************************/
|
||||
/* ricoh RF5C68(or clone) PCM controller */
|
||||
/* */
|
||||
/* TODO: Verify RF5C105,164 (Sega CD/Mega CD) */
|
||||
/* differences */
|
||||
/*********************************************************/
|
||||
|
||||
#include "rf5c68.h"
|
||||
#include <algorithm>
|
||||
|
||||
|
||||
//**************************************************************************
|
||||
// LIVE DEVICE
|
||||
//**************************************************************************
|
||||
|
||||
//-------------------------------------------------
|
||||
// rf5c68_device - constructor
|
||||
//-------------------------------------------------
|
||||
|
||||
rf5c68_device::rf5c68_device(int output_bits)
|
||||
: m_cbank(0)
|
||||
, m_wbank(0)
|
||||
, m_enable(0)
|
||||
, m_output_bits(output_bits)
|
||||
, m_ext_mem(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
rf5c68_device::rf5c68_device()
|
||||
: rf5c68_device(10)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// rf5c164_device - constructor
|
||||
//-------------------------------------------------
|
||||
|
||||
rf5c164_device::rf5c164_device()
|
||||
: rf5c68_device(16)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// device_start - device-specific startup
|
||||
//-------------------------------------------------
|
||||
|
||||
void rf5c68_device::device_start(u8 *ext_mem)
|
||||
{
|
||||
m_ext_mem = ext_mem;
|
||||
}
|
||||
|
||||
void rf5c68_device::device_reset()
|
||||
{
|
||||
m_cbank = 0;
|
||||
m_wbank = 0;
|
||||
m_enable = 0;
|
||||
|
||||
for (pcm_channel &chan : m_chan) {
|
||||
chan.enable = 0;
|
||||
chan.env = 0;
|
||||
chan.pan = 0;
|
||||
chan.start = 0;
|
||||
chan.addr = 0;
|
||||
chan.step = 0;
|
||||
chan.loopst = 0;
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------
|
||||
// sound_stream_update - handle a stream update
|
||||
//-------------------------------------------------
|
||||
|
||||
void rf5c68_device::sound_stream_update(s16 **outputs, s16 **channel_outputs, u32 samples)
|
||||
{
|
||||
/* bail if not enabled */
|
||||
if (!m_enable)
|
||||
{
|
||||
std::fill_n(outputs[0], samples, 0);
|
||||
std::fill_n(outputs[1], samples, 0);
|
||||
for (unsigned int i = 0; i < NUM_CHANNELS; i++) {
|
||||
std::fill_n(channel_outputs[i*2], samples, 0);
|
||||
std::fill_n(channel_outputs[i*2+1], samples, 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_mixleft.size() < samples)
|
||||
m_mixleft.resize(samples);
|
||||
if (m_mixright.size() < samples)
|
||||
m_mixright.resize(samples);
|
||||
|
||||
std::fill_n(&m_mixleft[0], samples, 0);
|
||||
std::fill_n(&m_mixright[0], samples, 0);
|
||||
|
||||
/* loop over channels */
|
||||
for (unsigned int i = 0; i < NUM_CHANNELS; i++)
|
||||
{
|
||||
pcm_channel &chan = m_chan[i];
|
||||
/* if this channel is active, accumulate samples */
|
||||
if (chan.enable)
|
||||
{
|
||||
int lv = (chan.pan & 0x0f) * chan.env;
|
||||
int rv = ((chan.pan >> 4) & 0x0f) * chan.env;
|
||||
|
||||
/* loop over the sample buffer */
|
||||
for (u32 j = 0; j < samples; j++)
|
||||
{
|
||||
int sample;
|
||||
|
||||
/* fetch the sample and handle looping */
|
||||
sample = m_ext_mem[(chan.addr >> 11) & 0xffff];
|
||||
if (sample == 0xff)
|
||||
{
|
||||
chan.addr = chan.loopst << 11;
|
||||
sample = m_ext_mem[(chan.addr >> 11) & 0xffff];
|
||||
|
||||
/* if we loop to a loop point, we're effectively dead */
|
||||
if (sample == 0xff)
|
||||
break;
|
||||
}
|
||||
chan.addr += chan.step;
|
||||
|
||||
/* add to the buffer */
|
||||
s32 left_out = ((sample & 0x7f) * lv) >> 5;
|
||||
s32 right_out = ((sample & 0x7f) * rv) >> 5;
|
||||
if ((sample & 0x80) == 0)
|
||||
{
|
||||
left_out = -left_out;
|
||||
right_out = -right_out;
|
||||
}
|
||||
channel_outputs[i*2][j] = (s16)left_out;
|
||||
channel_outputs[i*2+1][j] = (s16)right_out;
|
||||
m_mixleft[j] += left_out;
|
||||
m_mixright[j] += right_out;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::fill_n(channel_outputs[i*2], samples, 0);
|
||||
std::fill_n(channel_outputs[i*2+1], samples, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
now clamp and shift the result (output is only 10 bits for RF5C68, 16 bits for RF5C164)
|
||||
reference: Mega CD hardware manual, RF5C68 datasheet
|
||||
*/
|
||||
const u8 output_shift = (m_output_bits > 16) ? 0 : (16 - m_output_bits);
|
||||
const s32 output_nandmask = (1 << output_shift) - 1;
|
||||
for (u32 j = 0; j < samples; j++)
|
||||
{
|
||||
s32 outleft = std::min(std::max(m_mixleft[j], -32768), 32767);
|
||||
s32 outright = std::min(std::max(m_mixright[j], -32768), 32767);
|
||||
outputs[0][j] = outleft & ~output_nandmask;
|
||||
outputs[1][j] = outright & ~output_nandmask;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// RF5C68 write register
|
||||
//-------------------------------------------------
|
||||
|
||||
// TODO: RF5C164 only?
|
||||
u8 rf5c68_device::rf5c68_r(offs_t offset)
|
||||
{
|
||||
u8 shift;
|
||||
|
||||
shift = (offset & 1) ? 11 + 8 : 11;
|
||||
|
||||
// printf("%08x\n",(m_chan[(offset & 0x0e) >> 1].addr));
|
||||
|
||||
return (m_chan[(offset & 0x0e) >> 1].addr) >> (shift);
|
||||
}
|
||||
|
||||
void rf5c68_device::rf5c68_w(offs_t offset, u8 data)
|
||||
{
|
||||
pcm_channel &chan = m_chan[m_cbank];
|
||||
int i;
|
||||
|
||||
/* switch off the address */
|
||||
switch (offset)
|
||||
{
|
||||
case 0x00: /* envelope */
|
||||
chan.env = data;
|
||||
break;
|
||||
|
||||
case 0x01: /* pan */
|
||||
chan.pan = data;
|
||||
break;
|
||||
|
||||
case 0x02: /* FDL */
|
||||
chan.step = (chan.step & 0xff00) | (data & 0x00ff);
|
||||
break;
|
||||
|
||||
case 0x03: /* FDH */
|
||||
chan.step = (chan.step & 0x00ff) | ((data << 8) & 0xff00);
|
||||
break;
|
||||
|
||||
case 0x04: /* LSL */
|
||||
chan.loopst = (chan.loopst & 0xff00) | (data & 0x00ff);
|
||||
break;
|
||||
|
||||
case 0x05: /* LSH */
|
||||
chan.loopst = (chan.loopst & 0x00ff) | ((data << 8) & 0xff00);
|
||||
break;
|
||||
|
||||
case 0x06: /* ST */
|
||||
chan.start = data;
|
||||
if (!chan.enable)
|
||||
chan.addr = chan.start << (8 + 11);
|
||||
break;
|
||||
|
||||
case 0x07: /* control reg */
|
||||
m_enable = (data >> 7) & 1;
|
||||
if (data & 0x40)
|
||||
m_cbank = data & 7;
|
||||
else
|
||||
m_wbank = (data & 0xf) << 12;
|
||||
break;
|
||||
|
||||
case 0x08: /* channel on/off reg */
|
||||
for (i = 0; i < 8; i++)
|
||||
{
|
||||
m_chan[i].enable = (~data >> i) & 1;
|
||||
if (!m_chan[i].enable)
|
||||
m_chan[i].addr = m_chan[i].start << (8 + 11);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// RF5C68 read memory
|
||||
//-------------------------------------------------
|
||||
|
||||
u8 rf5c68_device::rf5c68_mem_r(offs_t offset)
|
||||
{
|
||||
return m_ext_mem[m_wbank | offset];
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// RF5C68 write memory
|
||||
//-------------------------------------------------
|
||||
|
||||
void rf5c68_device::rf5c68_mem_w(offs_t offset, u8 data)
|
||||
{
|
||||
m_ext_mem[m_wbank | offset] = data;
|
||||
}
|
||||
87
src/engine/platform/sound/rf5c68.h
Normal file
87
src/engine/platform/sound/rf5c68.h
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
// license:BSD-3-Clause
|
||||
// copyright-holders:Olivier Galibert,Aaron Giles
|
||||
/*********************************************************/
|
||||
/* ricoh RF5C68(or clone) PCM controller */
|
||||
/*********************************************************/
|
||||
|
||||
#include <vector>
|
||||
|
||||
#ifndef MAME_SOUND_RF5C68_H
|
||||
#define MAME_SOUND_RF5C68_H
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace rf5c68
|
||||
{
|
||||
typedef unsigned char u8;
|
||||
typedef signed char s8;
|
||||
typedef unsigned short u16;
|
||||
typedef signed short s16;
|
||||
typedef unsigned int u32;
|
||||
typedef signed int s32;
|
||||
typedef signed int offs_t;
|
||||
}
|
||||
|
||||
|
||||
//**************************************************************************
|
||||
// TYPE DEFINITIONS
|
||||
//**************************************************************************
|
||||
|
||||
#define RF5C68_SAMPLE_END_CB_MEMBER(_name) void _name(int channel)
|
||||
|
||||
|
||||
// ======================> rf5c68_device
|
||||
|
||||
using namespace rf5c68;
|
||||
class rf5c68_device
|
||||
{
|
||||
public:
|
||||
rf5c68_device();
|
||||
|
||||
u8 rf5c68_r(offs_t offset);
|
||||
void rf5c68_w(offs_t offset, u8 data);
|
||||
|
||||
u8 rf5c68_mem_r(offs_t offset);
|
||||
void rf5c68_mem_w(offs_t offset, u8 data);
|
||||
|
||||
void device_start(u8 *ext_mem);
|
||||
void device_reset();
|
||||
|
||||
void sound_stream_update(s16 **outputs, s16 **channel_outputs, u32 samples);
|
||||
|
||||
protected:
|
||||
rf5c68_device(int output_bits);
|
||||
|
||||
private:
|
||||
static constexpr unsigned NUM_CHANNELS = 8;
|
||||
|
||||
struct pcm_channel
|
||||
{
|
||||
pcm_channel() { }
|
||||
|
||||
u8 enable;
|
||||
u8 env;
|
||||
u8 pan;
|
||||
u8 start;
|
||||
u32 addr;
|
||||
u16 step;
|
||||
u16 loopst;
|
||||
};
|
||||
|
||||
pcm_channel m_chan[NUM_CHANNELS];
|
||||
u8 m_cbank;
|
||||
u16 m_wbank;
|
||||
u8 m_enable;
|
||||
int m_output_bits;
|
||||
std::vector<s32> m_mixleft;
|
||||
std::vector<s32> m_mixright;
|
||||
u8 *m_ext_mem;
|
||||
};
|
||||
|
||||
class rf5c164_device : public rf5c68_device
|
||||
{
|
||||
public:
|
||||
rf5c164_device();
|
||||
};
|
||||
|
||||
#endif // MAME_SOUND_RF5C68_H
|
||||
|
|
@ -110,7 +110,7 @@ void SoundUnit::NextSample(short* l, short* r) {
|
|||
}
|
||||
}
|
||||
}
|
||||
fns[i]=ns[i]*chan[i].vol*2;
|
||||
fns[i]=ns[i]*chan[i].vol*(chan[i].flags.pcm?4:2);
|
||||
if (chan[i].flags.fmode!=0) {
|
||||
int ff=chan[i].cutoff;
|
||||
nslow[i]=nslow[i]+(((ff)*nsband[i])>>16);
|
||||
|
|
|
|||
785
src/engine/platform/sound/ymz280b.cpp
Normal file
785
src/engine/platform/sound/ymz280b.cpp
Normal file
|
|
@ -0,0 +1,785 @@
|
|||
// license:BSD-3-Clause
|
||||
// copyright-holders:Aaron Giles
|
||||
/*
|
||||
|
||||
Yamaha YMZ280B driver
|
||||
by Aaron Giles
|
||||
|
||||
YMZ280B 8-Channel PCMD8 PCM/ADPCM Decoder
|
||||
|
||||
Features as listed in LSI-4MZ280B3 data sheet:
|
||||
Voice data stored in external memory can be played back simultaneously for up to eight voices
|
||||
Voice data format can be selected from 4-bit ADPCM, 8-bit PCM and 16-bit PCM
|
||||
Control of voice data external memory
|
||||
Up to 16M bytes of ROM or SRAM (x 8 bits, access time 150ms max) can be connected
|
||||
Continuous access is possible
|
||||
Loop playback between selective addresses is possible
|
||||
Voice data playback frequency control
|
||||
4-bit ADPCM ................ 0.172 to 44.1kHz in 256 steps
|
||||
8-bit PCM, 16-bit PCM ...... 0.172 to 88.2kHz in 512 steps
|
||||
256 steps total level and 16 steps panpot can be set
|
||||
Voice signal is output in stereo 16-bit 2's complement MSB-first format
|
||||
|
||||
TODO:
|
||||
- Is memory handling 100% correct? At the moment, Konami firebeat.c is the only
|
||||
hardware currently emulated that uses external handlers.
|
||||
It also happens to be the only one using 16-bit PCM.
|
||||
|
||||
Some other drivers (eg. bishi.cpp, bfm_sc4/5.cpp) also use ROM readback.
|
||||
|
||||
*/
|
||||
|
||||
#include "ymz280b.h"
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define MAX_SAMPLE_CHUNK 10000
|
||||
|
||||
static constexpr unsigned FRAC_BITS = 8;
|
||||
static constexpr s32 FRAC_ONE = 1 << FRAC_BITS;
|
||||
|
||||
/* step size index shift table */
|
||||
static constexpr int index_scale[8] = { 0x0e6, 0x0e6, 0x0e6, 0x0e6, 0x133, 0x199, 0x200, 0x266 };
|
||||
|
||||
/* lookup table for the precomputed difference */
|
||||
static int diff_lookup[16];
|
||||
|
||||
|
||||
void ymz280b_device::update_step(struct YMZ280BVoice *voice)
|
||||
{
|
||||
int frequency;
|
||||
|
||||
/* compute the frequency */
|
||||
if (voice->mode == 1)
|
||||
frequency = voice->fnum & 0x0ff;
|
||||
else
|
||||
frequency = voice->fnum & 0x1ff;
|
||||
voice->output_step = frequency + 1; // ((fnum + 1) * (input clock / 384)) / 256
|
||||
}
|
||||
|
||||
|
||||
void ymz280b_device::update_volumes(struct YMZ280BVoice *voice)
|
||||
{
|
||||
if (voice->pan == 8)
|
||||
{
|
||||
voice->output_left = voice->level;
|
||||
voice->output_right = voice->level;
|
||||
}
|
||||
else if (voice->pan < 8)
|
||||
{
|
||||
voice->output_left = voice->level;
|
||||
|
||||
/* pan 1 is hard-left, what's pan 0? for now assume same as pan 1 */
|
||||
voice->output_right = (voice->pan == 0) ? 0 : voice->level * (voice->pan - 1) / 7;
|
||||
}
|
||||
else
|
||||
{
|
||||
voice->output_left = voice->level * (15 - voice->pan) / 7;
|
||||
voice->output_right = voice->level;
|
||||
}
|
||||
}
|
||||
|
||||
/**********************************************************************************************
|
||||
|
||||
compute_tables -- compute the difference tables
|
||||
|
||||
***********************************************************************************************/
|
||||
|
||||
static void compute_tables()
|
||||
{
|
||||
/* loop over all nibbles and compute the difference */
|
||||
for (int nib = 0; nib < 16; nib++)
|
||||
{
|
||||
int value = (nib & 0x07) * 2 + 1;
|
||||
diff_lookup[nib] = (nib & 0x08) ? -value : value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**********************************************************************************************
|
||||
|
||||
generate_adpcm -- general ADPCM decoding routine
|
||||
|
||||
***********************************************************************************************/
|
||||
|
||||
int ymz280b_device::generate_adpcm(struct YMZ280BVoice *voice, s16 *buffer, int samples)
|
||||
{
|
||||
u32 position = voice->position;
|
||||
int signal = voice->signal;
|
||||
int step = voice->step;
|
||||
int val;
|
||||
|
||||
/* two cases: first cases is non-looping */
|
||||
if (!voice->looping)
|
||||
{
|
||||
/* loop while we still have samples to generate */
|
||||
while (samples)
|
||||
{
|
||||
/* compute the new amplitude and update the current step */
|
||||
val = m_ext_mem[position / 2] >> ((~position & 1) << 2);
|
||||
signal += (step * diff_lookup[val & 15]) / 8;
|
||||
|
||||
/* clamp to the maximum */
|
||||
if (signal > 32767)
|
||||
signal = 32767;
|
||||
else if (signal < -32768)
|
||||
signal = -32768;
|
||||
|
||||
/* adjust the step size and clamp */
|
||||
step = (step * index_scale[val & 7]) >> 8;
|
||||
if (step > 0x6000)
|
||||
step = 0x6000;
|
||||
else if (step < 0x7f)
|
||||
step = 0x7f;
|
||||
|
||||
/* output to the buffer, scaling by the volume */
|
||||
*buffer++ = signal;
|
||||
samples--;
|
||||
|
||||
/* next! */
|
||||
position++;
|
||||
if (position >= voice->stop)
|
||||
{
|
||||
voice->ended = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* second case: looping */
|
||||
else
|
||||
{
|
||||
/* loop while we still have samples to generate */
|
||||
while (samples)
|
||||
{
|
||||
/* compute the new amplitude and update the current step */
|
||||
val = m_ext_mem[position / 2] >> ((~position & 1) << 2);
|
||||
signal += (step * diff_lookup[val & 15]) / 8;
|
||||
|
||||
/* clamp to the maximum */
|
||||
if (signal > 32767)
|
||||
signal = 32767;
|
||||
else if (signal < -32768)
|
||||
signal = -32768;
|
||||
|
||||
/* adjust the step size and clamp */
|
||||
step = (step * index_scale[val & 7]) >> 8;
|
||||
if (step > 0x6000)
|
||||
step = 0x6000;
|
||||
else if (step < 0x7f)
|
||||
step = 0x7f;
|
||||
|
||||
/* output to the buffer, scaling by the volume */
|
||||
*buffer++ = signal;
|
||||
samples--;
|
||||
|
||||
/* next! */
|
||||
position++;
|
||||
if (position == voice->loop_start && voice->loop_count == 0)
|
||||
{
|
||||
voice->loop_signal = signal;
|
||||
voice->loop_step = step;
|
||||
}
|
||||
if (position >= voice->loop_end)
|
||||
{
|
||||
if (voice->keyon)
|
||||
{
|
||||
position = voice->loop_start;
|
||||
signal = voice->loop_signal;
|
||||
step = voice->loop_step;
|
||||
voice->loop_count++;
|
||||
}
|
||||
}
|
||||
if (position >= voice->stop)
|
||||
{
|
||||
voice->ended = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* update the parameters */
|
||||
voice->position = position;
|
||||
voice->signal = signal;
|
||||
voice->step = step;
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**********************************************************************************************
|
||||
|
||||
generate_pcm8 -- general 8-bit PCM decoding routine
|
||||
|
||||
***********************************************************************************************/
|
||||
|
||||
int ymz280b_device::generate_pcm8(struct YMZ280BVoice *voice, s16 *buffer, int samples)
|
||||
{
|
||||
u32 position = voice->position;
|
||||
int val;
|
||||
|
||||
/* two cases: first cases is non-looping */
|
||||
if (!voice->looping)
|
||||
{
|
||||
/* loop while we still have samples to generate */
|
||||
while (samples)
|
||||
{
|
||||
/* fetch the current value */
|
||||
val = m_ext_mem[position / 2];
|
||||
|
||||
/* output to the buffer, scaling by the volume */
|
||||
*buffer++ = (s8)val * 256;
|
||||
samples--;
|
||||
|
||||
/* next! */
|
||||
position += 2;
|
||||
if (position >= voice->stop)
|
||||
{
|
||||
voice->ended = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* second case: looping */
|
||||
else
|
||||
{
|
||||
/* loop while we still have samples to generate */
|
||||
while (samples)
|
||||
{
|
||||
/* fetch the current value */
|
||||
val = m_ext_mem[position / 2];
|
||||
|
||||
/* output to the buffer, scaling by the volume */
|
||||
*buffer++ = (s8)val * 256;
|
||||
samples--;
|
||||
|
||||
/* next! */
|
||||
position += 2;
|
||||
if (position >= voice->loop_end)
|
||||
{
|
||||
if (voice->keyon)
|
||||
position = voice->loop_start;
|
||||
}
|
||||
if (position >= voice->stop)
|
||||
{
|
||||
voice->ended = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* update the parameters */
|
||||
voice->position = position;
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**********************************************************************************************
|
||||
|
||||
generate_pcm16 -- general 16-bit PCM decoding routine
|
||||
|
||||
***********************************************************************************************/
|
||||
|
||||
int ymz280b_device::generate_pcm16(struct YMZ280BVoice *voice, s16 *buffer, int samples)
|
||||
{
|
||||
u32 position = voice->position;
|
||||
int val;
|
||||
|
||||
/* two cases: first cases is non-looping */
|
||||
if (!voice->looping)
|
||||
{
|
||||
/* loop while we still have samples to generate */
|
||||
while (samples)
|
||||
{
|
||||
/* fetch the current value */
|
||||
val = (s16)((m_ext_mem[position / 2 + 1] << 8) + m_ext_mem[position / 2 + 0]);
|
||||
|
||||
/* output to the buffer, scaling by the volume */
|
||||
*buffer++ = val;
|
||||
samples--;
|
||||
|
||||
/* next! */
|
||||
position += 4;
|
||||
if (position >= voice->stop)
|
||||
{
|
||||
voice->ended = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* second case: looping */
|
||||
else
|
||||
{
|
||||
/* loop while we still have samples to generate */
|
||||
while (samples)
|
||||
{
|
||||
/* fetch the current value */
|
||||
val = (s16)((m_ext_mem[position / 2 + 1] << 8) + m_ext_mem[position / 2 + 0]);
|
||||
|
||||
/* output to the buffer, scaling by the volume */
|
||||
*buffer++ = val;
|
||||
samples--;
|
||||
|
||||
/* next! */
|
||||
position += 4;
|
||||
if (position >= voice->loop_end)
|
||||
{
|
||||
if (voice->keyon)
|
||||
position = voice->loop_start;
|
||||
}
|
||||
if (position >= voice->stop)
|
||||
{
|
||||
voice->ended = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* update the parameters */
|
||||
voice->position = position;
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// sound_stream_update - handle a stream update
|
||||
//-------------------------------------------------
|
||||
|
||||
void ymz280b_device::sound_stream_update(s16 **outputs, int samples)
|
||||
{
|
||||
int v;
|
||||
|
||||
/* loop over voices */
|
||||
for (v = 0; v < 8; v++)
|
||||
{
|
||||
struct YMZ280BVoice *voice = &m_voice[v];
|
||||
s16 prev = voice->last_sample;
|
||||
s16 curr = voice->curr_sample;
|
||||
s16 *curr_data = m_scratch.get();
|
||||
s16 *ldest = outputs[v*2];
|
||||
s16 *rdest = outputs[v*2+1];
|
||||
s32 sampindex = 0;
|
||||
u32 new_samples, samples_left;
|
||||
u32 final_pos;
|
||||
int remaining = samples;
|
||||
int lvol = voice->output_left;
|
||||
int rvol = voice->output_right;
|
||||
|
||||
/* quick out if we're not playing and we're at 0 */
|
||||
if (!voice->playing && curr == 0 && prev == 0)
|
||||
{
|
||||
memset(ldest, 0, samples * sizeof(s16));
|
||||
memset(rdest, 0, samples * sizeof(s16));
|
||||
/* make sure next sound plays immediately */
|
||||
voice->output_pos = FRAC_ONE;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* finish off the current sample */
|
||||
/* interpolate */
|
||||
while (remaining > 0 && voice->output_pos < FRAC_ONE)
|
||||
{
|
||||
s32 interp_sample = ((s32(prev) * (FRAC_ONE - voice->output_pos)) + (s32(curr) * voice->output_pos)) >> FRAC_BITS;
|
||||
ldest[sampindex] = (s16)(interp_sample * lvol / 256);
|
||||
rdest[sampindex] = (s16)(interp_sample * rvol / 256);
|
||||
sampindex++;
|
||||
voice->output_pos += voice->output_step;
|
||||
remaining--;
|
||||
}
|
||||
|
||||
/* if we're over, continue; otherwise, we're done */
|
||||
if (voice->output_pos >= FRAC_ONE)
|
||||
voice->output_pos -= FRAC_ONE;
|
||||
else
|
||||
continue;
|
||||
|
||||
/* compute how many new samples we need */
|
||||
final_pos = voice->output_pos + remaining * voice->output_step;
|
||||
new_samples = (final_pos + FRAC_ONE) >> FRAC_BITS;
|
||||
if (new_samples > MAX_SAMPLE_CHUNK)
|
||||
new_samples = MAX_SAMPLE_CHUNK;
|
||||
samples_left = new_samples;
|
||||
|
||||
/* generate them into our buffer */
|
||||
switch (voice->playing << 7 | voice->mode)
|
||||
{
|
||||
case 0x81: samples_left = generate_adpcm(voice, m_scratch.get(), new_samples); break;
|
||||
case 0x82: samples_left = generate_pcm8(voice, m_scratch.get(), new_samples); break;
|
||||
case 0x83: samples_left = generate_pcm16(voice, m_scratch.get(), new_samples); break;
|
||||
default: samples_left = 0; memset(m_scratch.get(), 0, new_samples * sizeof(m_scratch[0])); break;
|
||||
}
|
||||
|
||||
if (samples_left || voice->ended)
|
||||
{
|
||||
voice->ended = false;
|
||||
|
||||
/* if there are leftovers, ramp back to 0 */
|
||||
int base = new_samples - samples_left;
|
||||
int t = (base == 0) ? curr : m_scratch[base - 1];
|
||||
for (u32 i = 0; i < samples_left; i++)
|
||||
{
|
||||
if (t < 0) t = -((-t * 15) >> 4);
|
||||
else if (t > 0) t = (t * 15) >> 4;
|
||||
m_scratch[base + i] = t;
|
||||
}
|
||||
|
||||
/* if we hit the end and IRQs are enabled, signal it */
|
||||
if (base != 0)
|
||||
{
|
||||
voice->playing = 0;
|
||||
|
||||
/* set update_irq_state_timer. IRQ is signaled on next CPU execution. */
|
||||
voice->irq_schedule = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* advance forward one sample */
|
||||
prev = curr;
|
||||
curr = *curr_data++;
|
||||
|
||||
/* then sample-rate convert with linear interpolation */
|
||||
while (remaining > 0)
|
||||
{
|
||||
/* interpolate */
|
||||
while (remaining > 0 && voice->output_pos < FRAC_ONE)
|
||||
{
|
||||
int interp_sample = ((s32(prev) * (FRAC_ONE - voice->output_pos)) + (s32(curr) * voice->output_pos)) >> FRAC_BITS;
|
||||
ldest[sampindex] = (s16)(interp_sample * lvol / 256);
|
||||
rdest[sampindex] = (s16)(interp_sample * rvol / 256);
|
||||
sampindex++;
|
||||
voice->output_pos += voice->output_step;
|
||||
remaining--;
|
||||
}
|
||||
|
||||
/* if we're over, grab the next samples */
|
||||
if (voice->output_pos >= FRAC_ONE)
|
||||
{
|
||||
voice->output_pos -= FRAC_ONE;
|
||||
prev = curr;
|
||||
curr = *curr_data++;
|
||||
}
|
||||
}
|
||||
|
||||
/* remember the last samples */
|
||||
voice->last_sample = prev;
|
||||
voice->curr_sample = curr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// device_start - device-specific startup
|
||||
//-------------------------------------------------
|
||||
|
||||
void ymz280b_device::device_start(u8 *ext_mem)
|
||||
{
|
||||
m_ext_mem = ext_mem;
|
||||
|
||||
/* compute ADPCM tables */
|
||||
compute_tables();
|
||||
|
||||
/* allocate memory */
|
||||
assert(MAX_SAMPLE_CHUNK < 0x10000);
|
||||
m_scratch = std::make_unique<s16[]>(MAX_SAMPLE_CHUNK);
|
||||
|
||||
for (auto & elem : m_voice)
|
||||
{
|
||||
update_step(&elem);
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------
|
||||
// device_reset - device-specific reset
|
||||
//-------------------------------------------------
|
||||
|
||||
void ymz280b_device::device_reset()
|
||||
{
|
||||
/* initial clear registers */
|
||||
for (int i = 0xff; i >= 0; i--)
|
||||
{
|
||||
m_current_register = i;
|
||||
write_to_register(0);
|
||||
}
|
||||
|
||||
m_current_register = 0;
|
||||
m_status_register = 0;
|
||||
m_ext_mem_address = 0;
|
||||
|
||||
/* clear other voice parameters */
|
||||
for (auto &elem : m_voice)
|
||||
{
|
||||
struct YMZ280BVoice *voice = &elem;
|
||||
|
||||
voice->curr_sample = 0;
|
||||
voice->last_sample = 0;
|
||||
voice->output_pos = FRAC_ONE;
|
||||
voice->playing = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**********************************************************************************************
|
||||
|
||||
write_to_register -- handle a write to the current register
|
||||
|
||||
***********************************************************************************************/
|
||||
|
||||
void ymz280b_device::write_to_register(int data)
|
||||
{
|
||||
struct YMZ280BVoice *voice;
|
||||
int i;
|
||||
|
||||
/* lower registers follow a pattern */
|
||||
if (m_current_register < 0x80)
|
||||
{
|
||||
voice = &m_voice[(m_current_register >> 2) & 7];
|
||||
|
||||
switch (m_current_register & 0xe3)
|
||||
{
|
||||
case 0x00: /* pitch low 8 bits */
|
||||
voice->fnum = (voice->fnum & 0x100) | (data & 0xff);
|
||||
update_step(voice);
|
||||
break;
|
||||
|
||||
case 0x01: /* pitch upper 1 bit, loop, key on, mode */
|
||||
voice->fnum = (voice->fnum & 0xff) | ((data & 0x01) << 8);
|
||||
voice->looping = (data & 0x10) >> 4;
|
||||
if ((data & 0x60) == 0) data &= 0x7f; /* ignore mode setting and set to same state as KON=0 */
|
||||
else voice->mode = (data & 0x60) >> 5;
|
||||
if (!voice->keyon && (data & 0x80) && m_keyon_enable)
|
||||
{
|
||||
voice->playing = 1;
|
||||
voice->position = voice->start;
|
||||
voice->signal = voice->loop_signal = 0;
|
||||
voice->step = voice->loop_step = 0x7f;
|
||||
voice->loop_count = 0;
|
||||
|
||||
/* if update_irq_state_timer is set, cancel it. */
|
||||
voice->irq_schedule = 0;
|
||||
}
|
||||
else if (voice->keyon && !(data & 0x80))
|
||||
{
|
||||
voice->playing = 0;
|
||||
|
||||
/* if update_irq_state_timer is set, cancel it. */
|
||||
voice->irq_schedule = 0;
|
||||
}
|
||||
voice->keyon = (data & 0x80) >> 7;
|
||||
update_step(voice);
|
||||
break;
|
||||
|
||||
case 0x02: /* total level */
|
||||
voice->level = data;
|
||||
update_volumes(voice);
|
||||
break;
|
||||
|
||||
case 0x03: /* pan */
|
||||
voice->pan = data & 0x0f;
|
||||
update_volumes(voice);
|
||||
break;
|
||||
|
||||
case 0x20: /* start address high */
|
||||
voice->start = (voice->start & (0x00ffff << 1)) | (data << 17);
|
||||
break;
|
||||
|
||||
case 0x21: /* loop start address high */
|
||||
voice->loop_start = (voice->loop_start & (0x00ffff << 1)) | (data << 17);
|
||||
break;
|
||||
|
||||
case 0x22: /* loop end address high */
|
||||
voice->loop_end = (voice->loop_end & (0x00ffff << 1)) | (data << 17);
|
||||
break;
|
||||
|
||||
case 0x23: /* stop address high */
|
||||
voice->stop = (voice->stop & (0x00ffff << 1)) | (data << 17);
|
||||
break;
|
||||
|
||||
case 0x40: /* start address middle */
|
||||
voice->start = (voice->start & (0xff00ff << 1)) | (data << 9);
|
||||
break;
|
||||
|
||||
case 0x41: /* loop start address middle */
|
||||
voice->loop_start = (voice->loop_start & (0xff00ff << 1)) | (data << 9);
|
||||
break;
|
||||
|
||||
case 0x42: /* loop end address middle */
|
||||
voice->loop_end = (voice->loop_end & (0xff00ff << 1)) | (data << 9);
|
||||
break;
|
||||
|
||||
case 0x43: /* stop address middle */
|
||||
voice->stop = (voice->stop & (0xff00ff << 1)) | (data << 9);
|
||||
break;
|
||||
|
||||
case 0x60: /* start address low */
|
||||
voice->start = (voice->start & (0xffff00 << 1)) | (data << 1);
|
||||
break;
|
||||
|
||||
case 0x61: /* loop start address low */
|
||||
voice->loop_start = (voice->loop_start & (0xffff00 << 1)) | (data << 1);
|
||||
break;
|
||||
|
||||
case 0x62: /* loop end address low */
|
||||
voice->loop_end = (voice->loop_end & (0xffff00 << 1)) | (data << 1);
|
||||
break;
|
||||
|
||||
case 0x63: /* stop address low */
|
||||
voice->stop = (voice->stop & (0xffff00 << 1)) | (data << 1);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (data != 0)
|
||||
printf("YMZ280B: unknown register write %02X = %02X\n", m_current_register, data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* upper registers are special */
|
||||
else
|
||||
{
|
||||
switch (m_current_register)
|
||||
{
|
||||
/* DSP related (not implemented yet) */
|
||||
case 0x80: // d0-2: DSP Rch, d3: enable Rch (0: yes, 1: no), d4-6: DSP Lch, d7: enable Lch (0: yes, 1: no)
|
||||
case 0x81: // d0: enable control of $82 (0: yes, 1: no)
|
||||
case 0x82: // DSP data
|
||||
//printf("YMZ280B: DSP register write %02X = %02X\n", m_current_register, data);
|
||||
break;
|
||||
|
||||
case 0x84: /* ROM readback / RAM write (high) */
|
||||
m_ext_mem_address_hi = data << 16;
|
||||
break;
|
||||
|
||||
case 0x85: /* ROM readback / RAM write (middle) */
|
||||
m_ext_mem_address_mid = data << 8;
|
||||
break;
|
||||
|
||||
case 0x86: /* ROM readback / RAM write (low) -> update latch */
|
||||
m_ext_mem_address = m_ext_mem_address_hi | m_ext_mem_address_mid | data;
|
||||
if (m_ext_mem_enable)
|
||||
m_ext_readlatch = m_ext_mem[m_ext_mem_address];
|
||||
break;
|
||||
|
||||
case 0x87: /* RAM write */
|
||||
if (m_ext_mem_enable)
|
||||
{
|
||||
m_ext_mem[m_ext_mem_address] = data;
|
||||
m_ext_mem_address = (m_ext_mem_address + 1) & 0xffffff;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xfe: /* IRQ mask */
|
||||
m_irq_mask = data;
|
||||
break;
|
||||
|
||||
case 0xff: /* IRQ enable, test, etc */
|
||||
m_ext_mem_enable = (data & 0x40) >> 6;
|
||||
m_irq_enable = (data & 0x10) >> 4;
|
||||
|
||||
if (m_keyon_enable && !(data & 0x80))
|
||||
{
|
||||
for (i = 0; i < 8; i++)
|
||||
{
|
||||
m_voice[i].playing = 0;
|
||||
|
||||
/* if update_irq_state_timer is set, cancel it. */
|
||||
m_voice[i].irq_schedule = 0;
|
||||
}
|
||||
}
|
||||
else if (!m_keyon_enable && (data & 0x80))
|
||||
{
|
||||
for (i = 0; i < 8; i++)
|
||||
{
|
||||
if (m_voice[i].keyon && m_voice[i].looping)
|
||||
m_voice[i].playing = 1;
|
||||
}
|
||||
}
|
||||
m_keyon_enable = (data & 0x80) >> 7;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (data != 0)
|
||||
printf("YMZ280B: unknown register write %02X = %02X\n", m_current_register, data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**********************************************************************************************
|
||||
|
||||
compute_status -- determine the status bits
|
||||
|
||||
***********************************************************************************************/
|
||||
|
||||
int ymz280b_device::compute_status()
|
||||
{
|
||||
u8 result;
|
||||
|
||||
result = m_status_register;
|
||||
|
||||
/* clear the IRQ state */
|
||||
m_status_register = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**********************************************************************************************
|
||||
|
||||
read/write -- handle external accesses
|
||||
|
||||
***********************************************************************************************/
|
||||
|
||||
u8 ymz280b_device::read(offs_t offset)
|
||||
{
|
||||
if ((offset & 1) == 0)
|
||||
{
|
||||
if (!m_ext_mem_enable)
|
||||
return 0xff;
|
||||
|
||||
/* read from external memory */
|
||||
u8 ret = m_ext_readlatch;
|
||||
m_ext_readlatch = m_ext_mem[m_ext_mem_address];
|
||||
m_ext_mem_address = (m_ext_mem_address + 1) & 0xffffff;
|
||||
return ret;
|
||||
}
|
||||
else
|
||||
return compute_status();
|
||||
}
|
||||
|
||||
|
||||
void ymz280b_device::write(offs_t offset, u8 data)
|
||||
{
|
||||
if ((offset & 1) == 0)
|
||||
m_current_register = data;
|
||||
else
|
||||
{
|
||||
write_to_register(data);
|
||||
}
|
||||
}
|
||||
|
||||
ymz280b_device::ymz280b_device()
|
||||
: m_current_register(0)
|
||||
, m_status_register(0)
|
||||
, m_irq_mask(0)
|
||||
, m_irq_enable(0)
|
||||
, m_keyon_enable(0)
|
||||
, m_ext_mem_enable(0)
|
||||
, m_ext_readlatch(0)
|
||||
, m_ext_mem_address_hi(0)
|
||||
, m_ext_mem_address_mid(0)
|
||||
, m_ext_mem_address(0)
|
||||
{
|
||||
memset(m_voice, 0, sizeof(m_voice));
|
||||
}
|
||||
104
src/engine/platform/sound/ymz280b.h
Normal file
104
src/engine/platform/sound/ymz280b.h
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
// license:BSD-3-Clause
|
||||
// copyright-holders:Aaron Giles
|
||||
/**********************************************************************************************
|
||||
*
|
||||
* Yamaha YMZ280B driver
|
||||
* by Aaron Giles
|
||||
*
|
||||
**********************************************************************************************/
|
||||
|
||||
#include <memory>
|
||||
|
||||
#ifndef MAME_SOUND_YMZ280B_H
|
||||
#define MAME_SOUND_YMZ280B_H
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace ymz280b
|
||||
{
|
||||
typedef unsigned char u8;
|
||||
typedef signed char s8;
|
||||
typedef unsigned short u16;
|
||||
typedef signed short s16;
|
||||
typedef unsigned int u32;
|
||||
typedef signed int s32;
|
||||
typedef signed int offs_t;
|
||||
}
|
||||
|
||||
using namespace ymz280b;
|
||||
class ymz280b_device
|
||||
{
|
||||
public:
|
||||
ymz280b_device();
|
||||
|
||||
u8 read(offs_t offset);
|
||||
void write(offs_t offset, u8 data);
|
||||
|
||||
void device_start(u8 *ext_mem);
|
||||
void device_reset();
|
||||
|
||||
void sound_stream_update(s16 **outputs, int samples);
|
||||
|
||||
private:
|
||||
/* struct describing a single playing ADPCM voice */
|
||||
struct YMZ280BVoice
|
||||
{
|
||||
u8 playing; /* 1 if we are actively playing */
|
||||
bool ended; /* indicate voice has ended in case samples_left is 0 */
|
||||
|
||||
u8 keyon; /* 1 if the key is on */
|
||||
u8 looping; /* 1 if looping is enabled */
|
||||
u8 mode; /* current playback mode */
|
||||
u16 fnum; /* frequency */
|
||||
u8 level; /* output level */
|
||||
u8 pan; /* panning */
|
||||
|
||||
u32 start; /* start address, in nibbles */
|
||||
u32 stop; /* stop address, in nibbles */
|
||||
u32 loop_start; /* loop start address, in nibbles */
|
||||
u32 loop_end; /* loop end address, in nibbles */
|
||||
u32 position; /* current position, in nibbles */
|
||||
|
||||
s32 signal; /* current ADPCM signal */
|
||||
s32 step; /* current ADPCM step */
|
||||
|
||||
s32 loop_signal; /* signal at loop start */
|
||||
s32 loop_step; /* step at loop start */
|
||||
u32 loop_count; /* number of loops so far */
|
||||
|
||||
s32 output_left; /* output volume (left) */
|
||||
s32 output_right; /* output volume (right) */
|
||||
s32 output_step; /* step value for frequency conversion */
|
||||
s32 output_pos; /* current fractional position */
|
||||
s16 last_sample; /* last sample output */
|
||||
s16 curr_sample; /* current sample target */
|
||||
u8 irq_schedule; /* 1 if the IRQ state is updated by timer */
|
||||
};
|
||||
|
||||
void update_step(struct YMZ280BVoice *voice);
|
||||
void update_volumes(struct YMZ280BVoice *voice);
|
||||
int generate_adpcm(struct YMZ280BVoice *voice, s16 *buffer, int samples);
|
||||
int generate_pcm8(struct YMZ280BVoice *voice, s16 *buffer, int samples);
|
||||
int generate_pcm16(struct YMZ280BVoice *voice, s16 *buffer, int samples);
|
||||
void write_to_register(int data);
|
||||
int compute_status();
|
||||
|
||||
// internal state
|
||||
struct YMZ280BVoice m_voice[8]; /* the 8 voices */
|
||||
u8 m_current_register; /* currently accessible register */
|
||||
u8 m_status_register; /* current status register */
|
||||
u8 m_irq_mask; /* current IRQ mask */
|
||||
u8 m_irq_enable; /* current IRQ enable */
|
||||
u8 m_keyon_enable; /* key on enable */
|
||||
u8 m_ext_mem_enable; /* external memory enable */
|
||||
u8 m_ext_readlatch; /* external memory prefetched data */
|
||||
u32 m_ext_mem_address_hi;
|
||||
u32 m_ext_mem_address_mid;
|
||||
u32 m_ext_mem_address; /* where the CPU can read the ROM */
|
||||
|
||||
u8 *m_ext_mem;
|
||||
|
||||
std::unique_ptr<s16[]> m_scratch;
|
||||
};
|
||||
|
||||
#endif // MAME_SOUND_YMZ280B_H
|
||||
|
|
@ -44,7 +44,7 @@ const char* DivPlatformSoundUnit::getEffectName(unsigned char effect) {
|
|||
return "13xx: Set resonance (0 to F)";
|
||||
break;
|
||||
case 0x14:
|
||||
return "14xx: Set filter mode (bit 0: ring mod; bit 1: low pass; bit 2: band pass; bit 3: high pass)";
|
||||
return "14xx: Set filter mode (bit 0: ring mod; bit 1: low pass; bit 2: high pass; bit 3: band pass)";
|
||||
break;
|
||||
case 0x15:
|
||||
return "15xx: Set frequency sweep period low byte";
|
||||
|
|
@ -73,6 +73,12 @@ const char* DivPlatformSoundUnit::getEffectName(unsigned char effect) {
|
|||
case 0x1d:
|
||||
return "1Dxx: Set cutoff sweep boundary";
|
||||
break;
|
||||
case 0x1e:
|
||||
return "17xx: Set phase reset period low byte";
|
||||
break;
|
||||
case 0x1f:
|
||||
return "18xx: Set phase reset period high byte";
|
||||
break;
|
||||
case 0x20:
|
||||
return "20xx: Toggle frequency sweep (bit 0-6: speed; bit 7: direction is up)";
|
||||
break;
|
||||
|
|
@ -169,7 +175,7 @@ void DivPlatformSoundUnit::tick(bool sysTick) {
|
|||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].std.ex1.had) {
|
||||
chan[i].cutoff=chan[i].std.ex1.val&16383;
|
||||
chan[i].cutoff=((chan[i].std.ex1.val&16383)*chan[i].baseCutoff)/16380;
|
||||
chWrite(i,0x06,chan[i].cutoff&0xff);
|
||||
chWrite(i,0x07,chan[i].cutoff>>8);
|
||||
}
|
||||
|
|
@ -208,9 +214,11 @@ void DivPlatformSoundUnit::tick(bool sysTick) {
|
|||
DivSample* sample=parent->getSample(ins->amiga.getSample(chan[i].note));
|
||||
if (sample!=NULL) {
|
||||
unsigned int sampleEnd=sample->offSU+(s->isLoopable()?sample->loopEnd:sample->samples);
|
||||
unsigned int off=sample->offSU+chan[i].hasOffset;
|
||||
chan[i].hasOffset=0;
|
||||
if (sampleEnd>=getSampleMemCapacity(0)) sampleEnd=getSampleMemCapacity(0)-1;
|
||||
chWrite(i,0x0a,sample->offSU&0xff);
|
||||
chWrite(i,0x0b,sample->offSU>>8);
|
||||
chWrite(i,0x0a,off&0xff);
|
||||
chWrite(i,0x0b,off>>8);
|
||||
chWrite(i,0x0c,sampleEnd&0xff);
|
||||
chWrite(i,0x0d,sampleEnd>>8);
|
||||
if (s->isLoopable()) {
|
||||
|
|
@ -242,6 +250,7 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) {
|
|||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_SU);
|
||||
if (chan[c.chan].pcm && ins->type!=DIV_INS_AMIGA) {
|
||||
chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA);
|
||||
writeControl(c.chan);
|
||||
writeControlUpper(c.chan);
|
||||
}
|
||||
|
|
@ -293,7 +302,113 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) {
|
|||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
case DIV_CMD_WAVE:
|
||||
chan[c.chan].wave=c.value;
|
||||
chan[c.chan].wave=c.value&7;
|
||||
writeControl(c.chan);
|
||||
break;
|
||||
case DIV_CMD_STD_NOISE_MODE:
|
||||
chan[c.chan].duty=c.value&127;
|
||||
chWrite(c.chan,0x08,chan[c.chan].duty);
|
||||
break;
|
||||
case DIV_CMD_C64_RESONANCE:
|
||||
chan[c.chan].res=c.value;
|
||||
chWrite(c.chan,0x09,chan[c.chan].res);
|
||||
break;
|
||||
case DIV_CMD_C64_FILTER_MODE:
|
||||
chan[c.chan].control=c.value&15;
|
||||
break;
|
||||
case DIV_CMD_SU_SWEEP_PERIOD_LOW: {
|
||||
switch (c.value) {
|
||||
case 0:
|
||||
chan[c.chan].freqSweepP=(chan[c.chan].freqSweepP&0xff00)|c.value2;
|
||||
chWrite(c.chan,0x10,chan[c.chan].freqSweepP&0xff);
|
||||
break;
|
||||
case 1:
|
||||
chan[c.chan].volSweepP=(chan[c.chan].volSweepP&0xff00)|c.value2;
|
||||
chWrite(c.chan,0x14,chan[c.chan].volSweepP&0xff);
|
||||
break;
|
||||
case 2:
|
||||
chan[c.chan].cutSweepP=(chan[c.chan].cutSweepP&0xff00)|c.value2;
|
||||
chWrite(c.chan,0x18,chan[c.chan].cutSweepP&0xff);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_SU_SWEEP_PERIOD_HIGH: {
|
||||
switch (c.value) {
|
||||
case 0:
|
||||
chan[c.chan].freqSweepP=(chan[c.chan].freqSweepP&0xff)|(c.value2<<8);
|
||||
chWrite(c.chan,0x11,chan[c.chan].freqSweepP>>8);
|
||||
break;
|
||||
case 1:
|
||||
chan[c.chan].volSweepP=(chan[c.chan].volSweepP&0xff)|(c.value2<<8);
|
||||
chWrite(c.chan,0x15,chan[c.chan].volSweepP>>8);
|
||||
break;
|
||||
case 2:
|
||||
chan[c.chan].cutSweepP=(chan[c.chan].cutSweepP&0xff)|(c.value2<<8);
|
||||
chWrite(c.chan,0x19,chan[c.chan].cutSweepP>>8);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_SU_SWEEP_BOUND: {
|
||||
switch (c.value) {
|
||||
case 0:
|
||||
chan[c.chan].freqSweepB=c.value2;
|
||||
chWrite(c.chan,0x13,chan[c.chan].freqSweepB);
|
||||
break;
|
||||
case 1:
|
||||
chan[c.chan].volSweepB=c.value2;
|
||||
chWrite(c.chan,0x17,chan[c.chan].volSweepB);
|
||||
break;
|
||||
case 2:
|
||||
chan[c.chan].cutSweepB=c.value2;
|
||||
chWrite(c.chan,0x1b,chan[c.chan].cutSweepB);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_SU_SWEEP_ENABLE: {
|
||||
switch (c.value) {
|
||||
case 0:
|
||||
chan[c.chan].freqSweepV=c.value2;
|
||||
chan[c.chan].freqSweep=(c.value2>0);
|
||||
chWrite(c.chan,0x12,chan[c.chan].freqSweepV);
|
||||
break;
|
||||
case 1:
|
||||
chan[c.chan].volSweepV=c.value2;
|
||||
chan[c.chan].volSweep=(c.value2>0);
|
||||
chWrite(c.chan,0x16,chan[c.chan].volSweepV);
|
||||
break;
|
||||
case 2:
|
||||
chan[c.chan].cutSweepV=c.value2;
|
||||
chan[c.chan].cutSweep=(c.value2>0);
|
||||
chWrite(c.chan,0x1a,chan[c.chan].cutSweepV);
|
||||
break;
|
||||
}
|
||||
writeControlUpper(c.chan);
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_SU_SYNC_PERIOD_LOW:
|
||||
chan[c.chan].syncTimer=(chan[c.chan].syncTimer&0xff00)|c.value;
|
||||
chan[c.chan].timerSync=(chan[c.chan].syncTimer>0);
|
||||
chWrite(c.chan,0x1e,chan[c.chan].syncTimer&0xff);
|
||||
chWrite(c.chan,0x1f,chan[c.chan].syncTimer>>8);
|
||||
writeControlUpper(c.chan);
|
||||
break;
|
||||
case DIV_CMD_SU_SYNC_PERIOD_HIGH:
|
||||
chan[c.chan].syncTimer=(chan[c.chan].syncTimer&0xff)|(c.value<<8);
|
||||
chan[c.chan].timerSync=(chan[c.chan].syncTimer>0);
|
||||
chWrite(c.chan,0x1e,chan[c.chan].syncTimer&0xff);
|
||||
chWrite(c.chan,0x1f,chan[c.chan].syncTimer>>8);
|
||||
writeControlUpper(c.chan);
|
||||
break;
|
||||
case DIV_CMD_C64_FINE_CUTOFF:
|
||||
chan[c.chan].baseCutoff=c.value;
|
||||
if (!chan[c.chan].std.ex1.has) {
|
||||
chan[c.chan].cutoff=chan[c.chan].baseCutoff;
|
||||
chWrite(c.chan,0x06,chan[c.chan].cutoff&0xff);
|
||||
chWrite(c.chan,0x07,chan[c.chan].cutoff>>8);
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
int destFreq=NOTE_FREQUENCY(c.value2);
|
||||
|
|
@ -323,6 +438,10 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) {
|
|||
chWrite(c.chan,0x03,chan[c.chan].pan);
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_SAMPLE_POS:
|
||||
chan[c.chan].hasOffset=c.value;
|
||||
chan[c.chan].keyOn=true;
|
||||
break;
|
||||
case DIV_CMD_LEGATO:
|
||||
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)));
|
||||
chan[c.chan].freqChanged=true;
|
||||
|
|
|
|||
|
|
@ -28,11 +28,15 @@
|
|||
class DivPlatformSoundUnit: public DivDispatch {
|
||||
struct Channel {
|
||||
int freq, baseFreq, pitch, pitch2, note;
|
||||
int ins, cutoff, res, control;
|
||||
int ins, cutoff, baseCutoff, res, control, hasOffset;
|
||||
signed char pan;
|
||||
unsigned char duty;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, phaseReset, filterPhaseReset;
|
||||
bool pcmLoop, timerSync, freqSweep, volSweep, cutSweep;
|
||||
unsigned short freqSweepP, volSweepP, cutSweepP;
|
||||
unsigned char freqSweepB, volSweepB, cutSweepB;
|
||||
unsigned char freqSweepV, volSweepV, cutSweepV;
|
||||
unsigned short syncTimer;
|
||||
signed char vol, outVol, wave;
|
||||
DivMacroInt std;
|
||||
void macroInit(DivInstrument* which) {
|
||||
|
|
@ -46,9 +50,11 @@ class DivPlatformSoundUnit: public DivDispatch {
|
|||
pitch2(0),
|
||||
note(0),
|
||||
ins(-1),
|
||||
cutoff(65535),
|
||||
cutoff(16383),
|
||||
baseCutoff(16380),
|
||||
res(0),
|
||||
control(0),
|
||||
hasOffset(0),
|
||||
pan(0),
|
||||
duty(63),
|
||||
active(false),
|
||||
|
|
@ -66,6 +72,16 @@ class DivPlatformSoundUnit: public DivDispatch {
|
|||
freqSweep(false),
|
||||
volSweep(false),
|
||||
cutSweep(false),
|
||||
freqSweepP(0),
|
||||
volSweepP(0),
|
||||
cutSweepP(0),
|
||||
freqSweepB(0),
|
||||
volSweepB(0),
|
||||
cutSweepB(0),
|
||||
freqSweepV(0),
|
||||
volSweepV(0),
|
||||
cutSweepV(0),
|
||||
syncTimer(0),
|
||||
vol(127),
|
||||
outVol(127),
|
||||
wave(0) {}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ int DivPlatformYM2203Ext::dispatch(DivCommand c) {
|
|||
opChan[ch].insChanged=false;
|
||||
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
opChan[ch].baseFreq=NOTE_FREQUENCY(c.value);
|
||||
opChan[ch].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
|
||||
opChan[ch].portaPause=false;
|
||||
opChan[ch].freqChanged=true;
|
||||
}
|
||||
|
|
@ -192,7 +192,7 @@ int DivPlatformYM2203Ext::dispatch(DivCommand c) {
|
|||
break;
|
||||
}
|
||||
case DIV_CMD_LEGATO: {
|
||||
opChan[ch].baseFreq=NOTE_FREQUENCY(c.value);
|
||||
opChan[ch].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
|
||||
opChan[ch].freqChanged=true;
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
574
src/engine/platform/ym2608ext.cpp
Normal file
574
src/engine/platform/ym2608ext.cpp
Normal file
|
|
@ -0,0 +1,574 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2022 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 "ym2608ext.h"
|
||||
#include "../engine.h"
|
||||
#include <math.h>
|
||||
|
||||
#include "ym2610shared.h"
|
||||
#include "fmshared_OPN.h"
|
||||
|
||||
int DivPlatformYM2608Ext::dispatch(DivCommand c) {
|
||||
if (c.chan<2) {
|
||||
return DivPlatformYM2608::dispatch(c);
|
||||
}
|
||||
if (c.chan>5) {
|
||||
c.chan-=3;
|
||||
return DivPlatformYM2608::dispatch(c);
|
||||
}
|
||||
int ch=c.chan-2;
|
||||
int ordch=orderedOps[ch];
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
|
||||
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[ordch];
|
||||
DivInstrumentFM::Operator op=ins->fm.op[ordch];
|
||||
// TODO: how does this work?!
|
||||
if (isOpMuted[ch]) {
|
||||
rWrite(baseAddr+0x40,127);
|
||||
} else {
|
||||
if (opChan[ch].insChanged) {
|
||||
rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[ch].vol&0x7f))/127));
|
||||
}
|
||||
}
|
||||
if (opChan[ch].insChanged) {
|
||||
rWrite(baseAddr+0x30,(op.mult&15)|(dtTable[op.dt&7]<<4));
|
||||
rWrite(baseAddr+0x50,(op.ar&31)|(op.rs<<6));
|
||||
rWrite(baseAddr+0x60,(op.dr&31)|(op.am<<7));
|
||||
rWrite(baseAddr+0x70,op.d2r&31);
|
||||
rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4));
|
||||
rWrite(baseAddr+0x90,op.ssgEnv&15);
|
||||
}
|
||||
if (opChan[ch].insChanged) { // TODO how does this work?
|
||||
rWrite(chanOffs[2]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3));
|
||||
rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4));
|
||||
}
|
||||
opChan[ch].insChanged=false;
|
||||
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
opChan[ch].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
|
||||
opChan[ch].portaPause=false;
|
||||
opChan[ch].freqChanged=true;
|
||||
}
|
||||
opChan[ch].keyOn=true;
|
||||
opChan[ch].active=true;
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
opChan[ch].keyOff=true;
|
||||
opChan[ch].keyOn=false;
|
||||
opChan[ch].active=false;
|
||||
break;
|
||||
case DIV_CMD_VOLUME: {
|
||||
opChan[ch].vol=c.value;
|
||||
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[ordch];
|
||||
DivInstrumentFM::Operator op=ins->fm.op[ordch];
|
||||
if (isOpMuted[ch]) {
|
||||
rWrite(baseAddr+0x40,127);
|
||||
} else {
|
||||
rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[ch].vol&0x7f))/127));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_GET_VOLUME: {
|
||||
return opChan[ch].vol;
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_INSTRUMENT:
|
||||
if (opChan[ch].ins!=c.value || c.value2==1) {
|
||||
opChan[ch].insChanged=true;
|
||||
}
|
||||
opChan[ch].ins=c.value;
|
||||
break;
|
||||
case DIV_CMD_PANNING: {
|
||||
if (c.value==0 && c.value2==0) {
|
||||
opChan[ch].pan=3;
|
||||
} else {
|
||||
opChan[ch].pan=(c.value2>0)|((c.value>0)<<1);
|
||||
}
|
||||
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
|
||||
if (parent->song.sharedExtStat) {
|
||||
for (int i=0; i<4; i++) {
|
||||
if (ch==i) continue;
|
||||
opChan[i].pan=opChan[ch].pan;
|
||||
}
|
||||
}
|
||||
rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4));
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_PITCH: {
|
||||
opChan[ch].pitch=c.value;
|
||||
opChan[ch].freqChanged=true;
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
if (parent->song.linearPitch==2) {
|
||||
int destFreq=NOTE_FREQUENCY(c.value2);
|
||||
bool return2=false;
|
||||
if (destFreq>opChan[ch].baseFreq) {
|
||||
opChan[ch].baseFreq+=c.value;
|
||||
if (opChan[ch].baseFreq>=destFreq) {
|
||||
opChan[ch].baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
} else {
|
||||
opChan[ch].baseFreq-=c.value;
|
||||
if (opChan[ch].baseFreq<=destFreq) {
|
||||
opChan[ch].baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
}
|
||||
opChan[ch].freqChanged=true;
|
||||
if (return2) {
|
||||
//opChan[ch].inPorta=false;
|
||||
return 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false);
|
||||
int boundaryTop=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,12,false);
|
||||
int destFreq=NOTE_FNUM_BLOCK(c.value2,11);
|
||||
int newFreq;
|
||||
bool return2=false;
|
||||
if (opChan[ch].portaPause) {
|
||||
opChan[ch].baseFreq=opChan[ch].portaPauseFreq;
|
||||
}
|
||||
if (destFreq>opChan[ch].baseFreq) {
|
||||
newFreq=opChan[ch].baseFreq+c.value;
|
||||
if (newFreq>=destFreq) {
|
||||
newFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
} else {
|
||||
newFreq=opChan[ch].baseFreq-c.value;
|
||||
if (newFreq<=destFreq) {
|
||||
newFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
}
|
||||
// what the heck!
|
||||
if (!opChan[ch].portaPause) {
|
||||
if ((newFreq&0x7ff)>boundaryTop && (newFreq&0xf800)<0x3800) {
|
||||
if (parent->song.fbPortaPause) {
|
||||
opChan[ch].portaPauseFreq=(boundaryBottom)|((newFreq+0x800)&0xf800);
|
||||
opChan[ch].portaPause=true;
|
||||
break;
|
||||
} else {
|
||||
newFreq=(newFreq>>1)|((newFreq+0x800)&0xf800);
|
||||
}
|
||||
}
|
||||
if ((newFreq&0x7ff)<boundaryBottom && (newFreq&0xf800)>0) {
|
||||
if (parent->song.fbPortaPause) {
|
||||
opChan[ch].portaPauseFreq=newFreq=(boundaryTop-1)|((newFreq-0x800)&0xf800);
|
||||
opChan[ch].portaPause=true;
|
||||
break;
|
||||
} else {
|
||||
newFreq=(newFreq<<1)|((newFreq-0x800)&0xf800);
|
||||
}
|
||||
}
|
||||
}
|
||||
opChan[ch].portaPause=false;
|
||||
opChan[ch].freqChanged=true;
|
||||
opChan[ch].baseFreq=newFreq;
|
||||
if (return2) return 2;
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_LEGATO: {
|
||||
opChan[ch].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
|
||||
opChan[ch].freqChanged=true;
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_LFO: {
|
||||
rWrite(0x22,(c.value&7)|((c.value>>4)<<3));
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_MULT: { // TODO
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
|
||||
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
|
||||
DivInstrumentFM::Operator op=ins->fm.op[orderedOps[c.value]];
|
||||
rWrite(baseAddr+0x30,(c.value2&15)|(dtTable[op.dt&7]<<4));
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_TL: { // TODO
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
|
||||
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
|
||||
if (isOutput[ins->fm.alg][c.value]) {
|
||||
rWrite(baseAddr+0x40,127-(((127-c.value2)*(opChan[ch].vol&0x7f))/127));
|
||||
} else {
|
||||
rWrite(baseAddr+0x40,c.value2);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_AR: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[i];
|
||||
op.ar=c.value2&31;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[i];
|
||||
rWrite(baseAddr+0x50,(op.ar&31)|(op.rs<<6));
|
||||
}
|
||||
} else {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
|
||||
op.ar=c.value2&31;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+0x50,(op.ar&31)|(op.rs<<6));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_RS: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[i];
|
||||
op.rs=c.value2&3;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
|
||||
op.rs=c.value2&3;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_AM: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[i];
|
||||
op.am=c.value2&1;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
|
||||
op.am=c.value2&1;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_DR: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[i];
|
||||
op.dr=c.value2&31;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
|
||||
op.dr=c.value2&31;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_SL: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[i];
|
||||
op.sl=c.value2&15;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
|
||||
op.sl=c.value2&15;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_RR: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[i];
|
||||
op.rr=c.value2&15;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
|
||||
op.rr=c.value2&15;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_D2R: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[i];
|
||||
op.d2r=c.value2&31;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31);
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
|
||||
op.d2r=c.value2&31;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_DT: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[i];
|
||||
op.dt=c.value&7;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
|
||||
op.dt=c.value2&7;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_SSG: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[i];
|
||||
op.ssgEnv=8^(c.value2&15);
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
|
||||
op.ssgEnv=8^(c.value2&15);
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
return 127;
|
||||
break;
|
||||
case DIV_ALWAYS_SET_VOLUME:
|
||||
return 0;
|
||||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
break;
|
||||
default:
|
||||
//printf("WARNING: unimplemented command %d\n",c.cmd);
|
||||
break;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int opChanOffsL[4]={
|
||||
0xa9, 0xaa, 0xa8, 0xa2
|
||||
};
|
||||
|
||||
static int opChanOffsH[4]={
|
||||
0xad, 0xae, 0xac, 0xa6
|
||||
};
|
||||
|
||||
void DivPlatformYM2608Ext::tick(bool sysTick) {
|
||||
if (extMode) {
|
||||
bool writeSomething=false;
|
||||
unsigned char writeMask=2;
|
||||
for (int i=0; i<4; i++) {
|
||||
writeMask|=opChan[i].active<<(4+i);
|
||||
if (opChan[i].keyOn || opChan[i].keyOff) {
|
||||
writeSomething=true;
|
||||
writeMask&=~(1<<(4+i));
|
||||
opChan[i].keyOff=false;
|
||||
}
|
||||
}
|
||||
if (writeSomething) {
|
||||
immWrite(0x28,writeMask);
|
||||
}
|
||||
}
|
||||
|
||||
DivPlatformYM2608::tick(sysTick);
|
||||
|
||||
bool writeNoteOn=false;
|
||||
unsigned char writeMask=2;
|
||||
if (extMode) for (int i=0; i<4; i++) {
|
||||
if (opChan[i].freqChanged) {
|
||||
if (parent->song.linearPitch==2) {
|
||||
opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,false,4,opChan[i].pitch2,chipClock,CHIP_FREQBASE,11);
|
||||
} else {
|
||||
int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,false,4,opChan[i].pitch2);
|
||||
int block=(opChan[i].baseFreq&0xf800)>>11;
|
||||
if (fNum<0) fNum=0;
|
||||
if (fNum>2047) {
|
||||
while (block<7) {
|
||||
fNum>>=1;
|
||||
block++;
|
||||
}
|
||||
if (fNum>2047) fNum=2047;
|
||||
}
|
||||
opChan[i].freq=(block<<11)|fNum;
|
||||
}
|
||||
if (opChan[i].freq>0x3fff) opChan[i].freq=0x3fff;
|
||||
immWrite(opChanOffsH[i],opChan[i].freq>>8);
|
||||
immWrite(opChanOffsL[i],opChan[i].freq&0xff);
|
||||
}
|
||||
writeMask|=opChan[i].active<<(4+i);
|
||||
if (opChan[i].keyOn) {
|
||||
writeNoteOn=true;
|
||||
writeMask|=1<<(4+i);
|
||||
opChan[i].keyOn=false;
|
||||
}
|
||||
}
|
||||
if (writeNoteOn) {
|
||||
immWrite(0x28,writeMask);
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformYM2608Ext::muteChannel(int ch, bool mute) {
|
||||
if (ch<2) {
|
||||
DivPlatformYM2608::muteChannel(ch,mute);
|
||||
return;
|
||||
}
|
||||
if (ch>5) {
|
||||
DivPlatformYM2608::muteChannel(ch-3,mute);
|
||||
return;
|
||||
}
|
||||
isOpMuted[ch-2]=mute;
|
||||
|
||||
int ordch=orderedOps[ch-2];
|
||||
DivInstrument* ins=parent->getIns(opChan[ch-2].ins,DIV_INS_FM);
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[ordch];
|
||||
DivInstrumentFM::Operator op=ins->fm.op[ordch];
|
||||
if (isOpMuted[ch-2]) {
|
||||
rWrite(baseAddr+0x40,127);
|
||||
} else if (isOutput[ins->fm.alg][ordch]) {
|
||||
rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[ch-2].vol&0x7f))/127));
|
||||
} else {
|
||||
rWrite(baseAddr+0x40,op.tl);
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformYM2608Ext::forceIns() {
|
||||
for (int i=0; i<6; i++) {
|
||||
for (int j=0; j<4; j++) {
|
||||
unsigned short baseAddr=chanOffs[i]|opOffs[j];
|
||||
DivInstrumentFM::Operator& op=chan[i].state.op[j];
|
||||
if (i==2) { // extended channel
|
||||
if (isOpMuted[j]) {
|
||||
rWrite(baseAddr+0x40,127);
|
||||
} else if (isOutput[chan[i].state.alg][j]) {
|
||||
rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[j].vol&0x7f))/127));
|
||||
} else {
|
||||
rWrite(baseAddr+0x40,op.tl);
|
||||
}
|
||||
} else {
|
||||
if (isMuted[i]) {
|
||||
rWrite(baseAddr+ADDR_TL,127);
|
||||
} else {
|
||||
if (isOutput[chan[i].state.alg][j]) {
|
||||
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127));
|
||||
} else {
|
||||
rWrite(baseAddr+ADDR_TL,op.tl);
|
||||
}
|
||||
}
|
||||
}
|
||||
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
|
||||
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
|
||||
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
|
||||
rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31);
|
||||
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
|
||||
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
|
||||
}
|
||||
rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3));
|
||||
rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
|
||||
if (chan[i].active) {
|
||||
chan[i].keyOn=true;
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
for (int i=6; i<16; i++) {
|
||||
chan[i].insChanged=true;
|
||||
}
|
||||
ay->forceIns();
|
||||
ay->flushWrites();
|
||||
for (DivRegWrite& i: ay->getRegisterWrites()) {
|
||||
immWrite(i.addr&15,i.val);
|
||||
}
|
||||
ay->getRegisterWrites().clear();
|
||||
for (int i=0; i<4; i++) {
|
||||
opChan[i].insChanged=true;
|
||||
if (opChan[i].active) {
|
||||
opChan[i].keyOn=true;
|
||||
opChan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void* DivPlatformYM2608Ext::getChanState(int ch) {
|
||||
if (ch>=6) return &chan[ch-3];
|
||||
if (ch>=2) return &opChan[ch-2];
|
||||
return &chan[ch];
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformYM2608Ext::getOscBuffer(int ch) {
|
||||
if (ch>=6) return oscBuf[ch-3];
|
||||
if (ch<3) return oscBuf[ch];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformYM2608Ext::reset() {
|
||||
DivPlatformYM2608::reset();
|
||||
|
||||
for (int i=0; i<4; i++) {
|
||||
opChan[i]=DivPlatformYM2608Ext::OpChannel();
|
||||
opChan[i].vol=127;
|
||||
}
|
||||
|
||||
// channel 2 mode
|
||||
immWrite(0x27,0x40);
|
||||
extMode=true;
|
||||
}
|
||||
|
||||
bool DivPlatformYM2608Ext::keyOffAffectsArp(int ch) {
|
||||
return (ch>8);
|
||||
}
|
||||
|
||||
void DivPlatformYM2608Ext::notifyInsChange(int ins) {
|
||||
DivPlatformYM2608::notifyInsChange(ins);
|
||||
for (int i=0; i<4; i++) {
|
||||
if (opChan[i].ins==ins) {
|
||||
opChan[i].insChanged=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformYM2608Ext::init(DivEngine* parent, int channels, int sugRate, unsigned int flags) {
|
||||
DivPlatformYM2608::init(parent,channels,sugRate,flags);
|
||||
for (int i=0; i<4; i++) {
|
||||
isOpMuted[i]=false;
|
||||
}
|
||||
|
||||
reset();
|
||||
return 19;
|
||||
}
|
||||
|
||||
void DivPlatformYM2608Ext::quit() {
|
||||
DivPlatformYM2608::quit();
|
||||
}
|
||||
|
||||
DivPlatformYM2608Ext::~DivPlatformYM2608Ext() {
|
||||
}
|
||||
51
src/engine/platform/ym2608ext.h
Normal file
51
src/engine/platform/ym2608ext.h
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2022 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 "../dispatch.h"
|
||||
|
||||
#include "ym2608.h"
|
||||
|
||||
class DivPlatformYM2608Ext: public DivPlatformYM2608 {
|
||||
struct OpChannel {
|
||||
DivMacroInt std;
|
||||
unsigned char freqH, freqL;
|
||||
int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
|
||||
signed char konCycles;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause;
|
||||
int vol;
|
||||
unsigned char pan;
|
||||
OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), portaPauseFreq(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {}
|
||||
};
|
||||
OpChannel opChan[4];
|
||||
bool isOpMuted[4];
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
public:
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
bool keyOffAffectsArp(int ch);
|
||||
void notifyInsChange(int ins);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformYM2608Ext();
|
||||
};
|
||||
|
|
@ -63,7 +63,7 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) {
|
|||
opChan[ch].insChanged=false;
|
||||
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
opChan[ch].baseFreq=NOTE_FREQUENCY(c.value);
|
||||
opChan[ch].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
|
||||
opChan[ch].portaPause=false;
|
||||
opChan[ch].freqChanged=true;
|
||||
}
|
||||
|
|
@ -192,7 +192,7 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) {
|
|||
break;
|
||||
}
|
||||
case DIV_CMD_LEGATO: {
|
||||
opChan[ch].baseFreq=NOTE_FREQUENCY(c.value);
|
||||
opChan[ch].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
|
||||
opChan[ch].freqChanged=true;
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
460
src/engine/platform/ymz280b.cpp
Normal file
460
src/engine/platform/ymz280b.cpp
Normal file
|
|
@ -0,0 +1,460 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2022 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 "ymz280b.h"
|
||||
#include "../engine.h"
|
||||
#include "../../ta-log.h"
|
||||
#include <math.h>
|
||||
#include <map>
|
||||
|
||||
#define CHIP_FREQBASE 98304
|
||||
|
||||
#define rWrite(a,v) {if(!skipRegisterWrites) {ymz280b.write(0,a); ymz280b.write(1,v); regPool[a]=v; if(dumpWrites) addWrite(a,v); }}
|
||||
|
||||
const char* regCheatSheetYMZ280B[]={
|
||||
"CHx_Freq", "00+x*4",
|
||||
"CHx_Control", "01+x*4",
|
||||
"CHx_Volume", "02+x*4",
|
||||
"CHx_Panning", "03+x*4",
|
||||
"CHx_StartH", "20+x*4",
|
||||
"CHx_LoopStartH", "21+x*4",
|
||||
"CHx_LoopEndH", "22+x*4",
|
||||
"CHx_EndH", "23+x*4",
|
||||
"CHx_StartM", "40+x*4",
|
||||
"CHx_LoopStartM", "41+x*4",
|
||||
"CHx_LoopEndM", "42+x*4",
|
||||
"CHx_EndM", "43+x*4",
|
||||
"CHx_StartL", "60+x*4",
|
||||
"CHx_LoopStartL", "61+x*4",
|
||||
"CHx_LoopEndL", "62+x*4",
|
||||
"CHx_EndL", "63+x*4",
|
||||
"DSP_Channel", "80",
|
||||
"DSP_Enable", "81",
|
||||
"DSP_Data", "82",
|
||||
"RAM_AddrH", "84",
|
||||
"RAM_AddrM", "85",
|
||||
"RAM_AddrL", "86",
|
||||
"RAM_Data", "87",
|
||||
"IRQ_Enable", "E0",
|
||||
"Enable", "FF",
|
||||
NULL
|
||||
};
|
||||
|
||||
const char** DivPlatformYMZ280B::getRegisterSheet() {
|
||||
return regCheatSheetYMZ280B;
|
||||
}
|
||||
|
||||
const char* DivPlatformYMZ280B::getEffectName(unsigned char effect) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformYMZ280B::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
short buf[16][256];
|
||||
short *bufPtrs[16]={
|
||||
buf[0],buf[1],buf[2],buf[3],buf[4],buf[5],buf[6],buf[7],
|
||||
buf[8],buf[9],buf[10],buf[11],buf[12],buf[13],buf[14],buf[15]
|
||||
};
|
||||
size_t pos=start;
|
||||
while (len > 0) {
|
||||
size_t blockLen = MIN(len, 256);
|
||||
ymz280b.sound_stream_update(bufPtrs, blockLen);
|
||||
for (size_t i=0; i<blockLen; i++) {
|
||||
int dataL=0;
|
||||
int dataR=0;
|
||||
for (int j=0; j<8; j++) {
|
||||
dataL+=buf[j*2][i];
|
||||
dataR+=buf[j*2+1][i];
|
||||
oscBuf[j]->data[oscBuf[j]->needle++]=(short)(((int)buf[j*2][i]+buf[j*2+1][i])/2);
|
||||
}
|
||||
bufL[pos]=(short)(dataL/8);
|
||||
bufR[pos]=(short)(dataR/8);
|
||||
pos++;
|
||||
}
|
||||
len-=blockLen;
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformYMZ280B::tick(bool sysTick) {
|
||||
for (int i=0; i<8; i++) {
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.vol.had) {
|
||||
chan[i].outVol=((chan[i].vol&0xff)*chan[i].std.vol.val)>>6;
|
||||
writeOutVol(i);
|
||||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note);
|
||||
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,-2048,2048);
|
||||
} else {
|
||||
chan[i].pitch2=chan[i].std.pitch.val;
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].std.panL.had) { // panning
|
||||
chan[i].panning=MIN((chan[i].std.panL.val*15/16+15)/2+1,15);
|
||||
rWrite(0x03+i*4,chan[i].panning);
|
||||
}
|
||||
if (chan[i].setPos) {
|
||||
// force keyon
|
||||
chan[i].keyOn=true;
|
||||
chan[i].setPos=false;
|
||||
} else {
|
||||
chan[i].audPos=0;
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
DivSample* s=parent->getSample(chan[i].sample);
|
||||
unsigned char ctrl;
|
||||
switch (s->depth) {
|
||||
case 3: ctrl=0x20; break;
|
||||
case 8: ctrl=0x40; break;
|
||||
case 16: ctrl=0x60; break;
|
||||
default: ctrl=0;
|
||||
}
|
||||
double off=(s->centerRate>=1)?((double)s->centerRate/8363.0):1.0;
|
||||
chan[i].freq=(int)(off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE))-1;
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
if (chan[i].freq>511) chan[i].freq=511;
|
||||
// ADPCM has half the range
|
||||
if (s->depth==3 && chan[i].freq>255) chan[i].freq=255;
|
||||
ctrl|=(chan[i].active?0x80:0)|((s->loopStart>=0)?0x10:0)|(chan[i].freq>>8);
|
||||
if (chan[i].keyOn) {
|
||||
unsigned int start=s->offYMZ280B;
|
||||
unsigned int loop=0;
|
||||
unsigned int end=MIN(start+s->getCurBufLen(),getSampleMemCapacity()-1);
|
||||
if (chan[i].audPos>0) {
|
||||
switch (s->depth) {
|
||||
case 3: start+=chan[i].audPos/2; break;
|
||||
case 8: start+=chan[i].audPos; break;
|
||||
case 16: start+=chan[i].audPos*2; break;
|
||||
}
|
||||
start=MIN(start,end);
|
||||
}
|
||||
if (s->loopStart>=0) {
|
||||
switch (s->depth) {
|
||||
case 3: loop=start+s->loopStart/2; break;
|
||||
case 8: loop=start+s->loopStart; break;
|
||||
case 16: loop=start+s->loopStart*2; break;
|
||||
}
|
||||
loop=MIN(loop,end);
|
||||
}
|
||||
rWrite(0x01+i*4,ctrl&~0x80); // force keyoff first
|
||||
rWrite(0x20+i*4,(start>>16)&0xff);
|
||||
rWrite(0x21+i*4,(loop>>16)&0xff);
|
||||
rWrite(0x22+i*4,(end>>16)&0xff);
|
||||
rWrite(0x23+i*4,(end>>16)&0xff);
|
||||
rWrite(0x40+i*4,(start>>8)&0xff);
|
||||
rWrite(0x41+i*4,(loop>>8)&0xff);
|
||||
rWrite(0x42+i*4,(end>>8)&0xff);
|
||||
rWrite(0x43+i*4,(end>>8)&0xff);
|
||||
rWrite(0x60+i*4,start&0xff);
|
||||
rWrite(0x61+i*4,loop&0xff);
|
||||
rWrite(0x62+i*4,end&0xff);
|
||||
rWrite(0x63+i*4,end&0xff);
|
||||
if (!chan[i].std.vol.had) {
|
||||
chan[i].outVol=chan[i].vol;
|
||||
writeOutVol(i);
|
||||
}
|
||||
chan[i].keyOn=false;
|
||||
}
|
||||
if (chan[i].keyOff) {
|
||||
chan[i].keyOff=false;
|
||||
}
|
||||
if (chan[i].freqChanged) {
|
||||
rWrite(0x00+i*4,chan[i].freq&0xff);
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
rWrite(0x01+i*4,ctrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformYMZ280B::dispatch(DivCommand c) {
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
|
||||
chan[c.chan].sample=ins->amiga.getSample(c.value);
|
||||
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.sampleLen) {
|
||||
chan[c.chan].sample=-1;
|
||||
}
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
}
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
chan[c.chan].macroInit(ins);
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
chan[c.chan].sample=-1;
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].macroInit(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
case DIV_CMD_ENV_RELEASE:
|
||||
chan[c.chan].std.release();
|
||||
break;
|
||||
case DIV_CMD_INSTRUMENT:
|
||||
if (chan[c.chan].ins!=c.value || c.value2==1) {
|
||||
chan[c.chan].ins=c.value;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_VOLUME:
|
||||
if (chan[c.chan].vol!=c.value) {
|
||||
chan[c.chan].vol=c.value;
|
||||
if (!chan[c.chan].std.vol.has) {
|
||||
chan[c.chan].outVol=c.value;
|
||||
writeOutVol(c.chan);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_GET_VOLUME:
|
||||
if (chan[c.chan].std.vol.has) {
|
||||
return chan[c.chan].vol;
|
||||
}
|
||||
return chan[c.chan].outVol;
|
||||
break;
|
||||
case DIV_CMD_PANNING:
|
||||
chan[c.chan].panning=MIN(parent->convertPanSplitToLinearLR(c.value,c.value2,15)+1,15);
|
||||
rWrite(0x03+c.chan*4,chan[c.chan].panning);
|
||||
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);
|
||||
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].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val-12):(0)));
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA));
|
||||
}
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_POS:
|
||||
chan[c.chan].audPos=c.value;
|
||||
chan[c.chan].setPos=true;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
return 255;
|
||||
break;
|
||||
case DIV_ALWAYS_SET_VOLUME:
|
||||
return 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void DivPlatformYMZ280B::writeOutVol(int ch) {
|
||||
unsigned char val=isMuted[ch]?0:chan[ch].outVol;
|
||||
rWrite(0x02+ch*4,val);
|
||||
}
|
||||
|
||||
void DivPlatformYMZ280B::muteChannel(int ch, bool mute) {
|
||||
isMuted[ch]=mute;
|
||||
writeOutVol(ch);
|
||||
}
|
||||
|
||||
void DivPlatformYMZ280B::forceIns() {
|
||||
for (int i=0; i<8; i++) {
|
||||
chan[i].insChanged=true;
|
||||
chan[i].freqChanged=true;
|
||||
chan[i].sample=-1;
|
||||
}
|
||||
}
|
||||
|
||||
void* DivPlatformYMZ280B::getChanState(int ch) {
|
||||
return &chan[ch];
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformYMZ280B::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
||||
void DivPlatformYMZ280B::reset() {
|
||||
memset(regPool,0,256);
|
||||
ymz280b.device_reset();
|
||||
rWrite(0xff,0x80); // enable keyon
|
||||
for (int i=0; i<8; i++) {
|
||||
chan[i]=DivPlatformYMZ280B::Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
rWrite(0x02+i*4,255);
|
||||
rWrite(0x03+i*4,8);
|
||||
}
|
||||
}
|
||||
|
||||
bool DivPlatformYMZ280B::isStereo() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void DivPlatformYMZ280B::notifyInsChange(int ins) {
|
||||
for (int i=0; i<8; i++) {
|
||||
if (chan[i].ins==ins) {
|
||||
chan[i].insChanged=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformYMZ280B::notifyWaveChange(int wave) {
|
||||
// TODO when wavetables are added
|
||||
// TODO they probably won't be added unless the samples reside in RAM
|
||||
}
|
||||
|
||||
void DivPlatformYMZ280B::notifyInsDeletion(void* ins) {
|
||||
for (int i=0; i<8; i++) {
|
||||
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformYMZ280B::poke(unsigned int addr, unsigned short val) {
|
||||
rWrite(addr,val);
|
||||
}
|
||||
|
||||
void DivPlatformYMZ280B::poke(std::vector<DivRegWrite>& wlist) {
|
||||
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformYMZ280B::getRegisterPool() {
|
||||
return regPool;
|
||||
}
|
||||
|
||||
int DivPlatformYMZ280B::getRegisterPoolSize() {
|
||||
return 256;
|
||||
}
|
||||
|
||||
float DivPlatformYMZ280B::getPostAmp() {
|
||||
// according to MAME core's mixing
|
||||
return 4.0f;
|
||||
}
|
||||
|
||||
const void* DivPlatformYMZ280B::getSampleMem(int index) {
|
||||
return index == 0 ? sampleMem : NULL;
|
||||
}
|
||||
|
||||
size_t DivPlatformYMZ280B::getSampleMemCapacity(int index) {
|
||||
return index == 0 ? 16777216 : 0;
|
||||
}
|
||||
|
||||
size_t DivPlatformYMZ280B::getSampleMemUsage(int index) {
|
||||
return index == 0 ? sampleMemLen : 0;
|
||||
}
|
||||
|
||||
void DivPlatformYMZ280B::renderSamples() {
|
||||
memset(sampleMem,0,getSampleMemCapacity());
|
||||
|
||||
size_t memPos=0;
|
||||
for (int i=0; i<parent->song.sampleLen; i++) {
|
||||
DivSample* s=parent->song.sample[i];
|
||||
int length=s->getCurBufLen();
|
||||
unsigned char* src=(unsigned char*)s->getCurBuf();
|
||||
int actualLength=MIN((int)(getSampleMemCapacity()-memPos),length);
|
||||
if (actualLength>0) {
|
||||
memcpy(&sampleMem[memPos],src,actualLength);
|
||||
s->offYMZ280B=memPos;
|
||||
memPos+=length;
|
||||
}
|
||||
if (actualLength<length) {
|
||||
logW("out of YMZ280B PCM memory for sample %d!",i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
sampleMemLen=memPos;
|
||||
}
|
||||
|
||||
void DivPlatformYMZ280B::setChipModel(int type) {
|
||||
chipType=type;
|
||||
}
|
||||
|
||||
int DivPlatformYMZ280B::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||
parent=p;
|
||||
dumpWrites=false;
|
||||
skipRegisterWrites=false;
|
||||
|
||||
for (int i=0; i<8; i++) {
|
||||
isMuted[i]=false;
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
}
|
||||
setFlags(flags);
|
||||
|
||||
rate=(chipType==759)?32000:44100;
|
||||
chipClock=rate*384;
|
||||
sampleMem=new unsigned char[getSampleMemCapacity()];
|
||||
sampleMemLen=0;
|
||||
ymz280b.device_start(sampleMem);
|
||||
reset();
|
||||
|
||||
for (int i=0; i<8; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
return 8;
|
||||
}
|
||||
|
||||
void DivPlatformYMZ280B::quit() {
|
||||
delete[] sampleMem;
|
||||
for (int i=0; i<8; i++) {
|
||||
delete oscBuf[i];
|
||||
}
|
||||
}
|
||||
104
src/engine/platform/ymz280b.h
Normal file
104
src/engine/platform/ymz280b.h
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2022 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 _YMZ280B_H
|
||||
#define _YMZ280B_H
|
||||
|
||||
#include "../dispatch.h"
|
||||
#include <queue>
|
||||
#include "../macroInt.h"
|
||||
#include "sound/ymz280b.h"
|
||||
|
||||
class DivPlatformYMZ280B: public DivDispatch {
|
||||
struct Channel {
|
||||
int freq, baseFreq, pitch, pitch2;
|
||||
unsigned int audPos;
|
||||
int sample, wave, ins;
|
||||
int note;
|
||||
int panning;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, setPos;
|
||||
int vol, outVol;
|
||||
DivMacroInt std;
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
pitch2=0;
|
||||
}
|
||||
Channel():
|
||||
freq(0),
|
||||
baseFreq(0),
|
||||
pitch(0),
|
||||
pitch2(0),
|
||||
audPos(0),
|
||||
sample(-1),
|
||||
ins(-1),
|
||||
note(0),
|
||||
panning(8),
|
||||
active(false),
|
||||
insChanged(true),
|
||||
freqChanged(false),
|
||||
keyOn(false),
|
||||
keyOff(false),
|
||||
inPorta(false),
|
||||
setPos(false),
|
||||
vol(255),
|
||||
outVol(255) {}
|
||||
};
|
||||
Channel chan[8];
|
||||
DivDispatchOscBuffer* oscBuf[8];
|
||||
bool isMuted[8];
|
||||
int chipType;
|
||||
|
||||
unsigned char* sampleMem;
|
||||
size_t sampleMemLen;
|
||||
ymz280b_device ymz280b;
|
||||
unsigned char regPool[256];
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
|
||||
public:
|
||||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
float getPostAmp();
|
||||
bool isStereo();
|
||||
void setChipModel(int type);
|
||||
void notifyInsChange(int ins);
|
||||
void notifyWaveChange(int wave);
|
||||
void notifyInsDeletion(void* ins);
|
||||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
const void* getSampleMem(int index = 0);
|
||||
size_t getSampleMemCapacity(int index = 0);
|
||||
size_t getSampleMemUsage(int index = 0);
|
||||
void renderSamples();
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
private:
|
||||
void writeOutVol(int ch);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -40,10 +40,30 @@ const char* DivPlatformZXBeeper::getEffectName(unsigned char effect) {
|
|||
}
|
||||
|
||||
void DivPlatformZXBeeper::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
bool o=false;
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
// clock here
|
||||
bool o=false;
|
||||
if (curSample>=0 && curSample<parent->song.sampleLen) {
|
||||
if (--curSamplePeriod<0) {
|
||||
DivSample* s=parent->getSample(curSample);
|
||||
if (s->samples>0) {
|
||||
sampleOut=(s->data8[curSamplePos++]>0);
|
||||
if (curSamplePos>=s->samples) curSample=-1;
|
||||
// 256 bits
|
||||
if (curSamplePos>2047) curSample=-1;
|
||||
|
||||
curSamplePeriod=15;
|
||||
} else {
|
||||
curSample=-1;
|
||||
}
|
||||
}
|
||||
o=sampleOut;
|
||||
bufL[h]=o?16384:0;
|
||||
continue;
|
||||
}
|
||||
|
||||
unsigned short oldPos=chan[curChan].sPosition;
|
||||
o=false;
|
||||
|
||||
if (sOffTimer) {
|
||||
sOffTimer--;
|
||||
|
|
@ -95,7 +115,7 @@ void DivPlatformZXBeeper::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
if (chan[i].active) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,0,chan[i].pitch2,chipClock,CHIP_FREQBASE);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE);
|
||||
if (chan[i].freq>65535) chan[i].freq=65535;
|
||||
}
|
||||
if (chan[i].keyOn) {
|
||||
|
|
@ -128,9 +148,7 @@ int DivPlatformZXBeeper::dispatch(DivCommand c) {
|
|||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
chan[c.chan].dacSample=-1;
|
||||
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
|
||||
chan[c.chan].pcm=false;
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].macroInit(NULL);
|
||||
|
|
@ -190,13 +208,9 @@ int DivPlatformZXBeeper::dispatch(DivCommand c) {
|
|||
chan[c.chan].duty=c.value;
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_MODE:
|
||||
chan[c.chan].pcm=c.value;
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_BANK:
|
||||
sampleBank=c.value;
|
||||
if (sampleBank>(parent->song.sample.size()/12)) {
|
||||
sampleBank=parent->song.sample.size()/12;
|
||||
}
|
||||
curSample=c.value;
|
||||
curSamplePos=0;
|
||||
curSamplePeriod=0;
|
||||
break;
|
||||
case DIV_CMD_LEGATO:
|
||||
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)));
|
||||
|
|
@ -265,8 +279,11 @@ void DivPlatformZXBeeper::reset() {
|
|||
cycles=0;
|
||||
curChan=0;
|
||||
sOffTimer=0;
|
||||
sampleBank=0;
|
||||
ulaOut=0;
|
||||
curSample=-1;
|
||||
curSamplePos=0;
|
||||
curSamplePeriod=0;
|
||||
sampleOut=false;
|
||||
}
|
||||
|
||||
bool DivPlatformZXBeeper::keyOffAffectsArp(int ch) {
|
||||
|
|
|
|||
|
|
@ -27,10 +27,8 @@
|
|||
class DivPlatformZXBeeper: public DivDispatch {
|
||||
struct Channel {
|
||||
int freq, baseFreq, pitch, pitch2, note;
|
||||
int dacPeriod, dacRate;
|
||||
unsigned int dacPos;
|
||||
int dacSample, ins;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, furnaceDac;
|
||||
int ins;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta;
|
||||
signed char vol, outVol;
|
||||
unsigned short sPosition;
|
||||
unsigned char duty;
|
||||
|
|
@ -45,10 +43,6 @@ class DivPlatformZXBeeper: public DivDispatch {
|
|||
pitch(0),
|
||||
pitch2(0),
|
||||
note(0),
|
||||
dacPeriod(0),
|
||||
dacRate(0),
|
||||
dacPos(0),
|
||||
dacSample(-1),
|
||||
ins(-1),
|
||||
active(false),
|
||||
insChanged(true),
|
||||
|
|
@ -56,9 +50,6 @@ class DivPlatformZXBeeper: public DivDispatch {
|
|||
keyOn(false),
|
||||
keyOff(false),
|
||||
inPorta(false),
|
||||
noise(false),
|
||||
pcm(false),
|
||||
furnaceDac(false),
|
||||
vol(1),
|
||||
outVol(1),
|
||||
sPosition(0),
|
||||
|
|
@ -75,11 +66,12 @@ class DivPlatformZXBeeper: public DivDispatch {
|
|||
std::queue<QueuedWrite> writes;
|
||||
unsigned char lastPan, ulaOut;
|
||||
|
||||
int cycles, curChan, sOffTimer, delay;
|
||||
int cycles, curChan, sOffTimer, delay, curSample, curSamplePeriod;
|
||||
unsigned int curSamplePos;
|
||||
int tempL[32];
|
||||
int tempR[32];
|
||||
unsigned char sampleBank, lfoMode, lfoSpeed;
|
||||
unsigned char regPool[128];
|
||||
bool sampleOut;
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
public:
|
||||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
|
|
|
|||
|
|
@ -61,6 +61,8 @@ const char* cmdName[]={
|
|||
"SAMPLE_FREQ",
|
||||
"SAMPLE_BANK",
|
||||
"SAMPLE_POS",
|
||||
"SAMPLE_TRANSWAVE_SLICE_MODE", // (enabled)
|
||||
"SAMPLE_TRANSWAVE_SLICE_POS", // (slice)
|
||||
|
||||
"FM_HARD_RESET",
|
||||
"FM_LFO",
|
||||
|
|
@ -183,6 +185,13 @@ const char* cmdName[]={
|
|||
"ES5506_ENVELOPE_K2RAMP",
|
||||
"ES5506_PAUSE",
|
||||
|
||||
"DIV_CMD_SU_SWEEP_PERIOD_LOW",
|
||||
"DIV_CMD_SU_SWEEP_PERIOD_HIGH",
|
||||
"DIV_CMD_SU_SWEEP_BOUND",
|
||||
"DIV_CMD_SU_SWEEP_ENABLE",
|
||||
"DIV_CMD_SU_SYNC_PERIOD_LOW",
|
||||
"DIV_CMD_SU_SYNC_PERIOD_HIGH",
|
||||
|
||||
"ALWAYS_SET_VOLUME"
|
||||
};
|
||||
|
||||
|
|
@ -913,16 +922,23 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
|
|||
if (!freelance) {
|
||||
if (--subticks<=0) {
|
||||
subticks=tickMult;
|
||||
if (stepPlay!=1) if (--ticks<=0) {
|
||||
ret=endOfSong;
|
||||
if (endOfSong) {
|
||||
if (song.loopModality!=2) {
|
||||
playSub(true);
|
||||
if (stepPlay!=1) {
|
||||
tempoAccum+=curSubSong->virtualTempoN;
|
||||
while (tempoAccum>=curSubSong->virtualTempoD) {
|
||||
tempoAccum-=curSubSong->virtualTempoD;
|
||||
if (--ticks<=0) {
|
||||
ret=endOfSong;
|
||||
if (endOfSong) {
|
||||
if (song.loopModality!=2) {
|
||||
playSub(true);
|
||||
}
|
||||
}
|
||||
endOfSong=false;
|
||||
if (stepPlay==2) stepPlay=1;
|
||||
nextRow();
|
||||
break;
|
||||
}
|
||||
}
|
||||
endOfSong=false;
|
||||
if (stepPlay==2) stepPlay=1;
|
||||
nextRow();
|
||||
}
|
||||
// process stuff
|
||||
for (int i=0; i<chans; i++) {
|
||||
|
|
|
|||
|
|
@ -97,17 +97,12 @@ bool DivSample::initInternal(DivSampleDepth d, int count) {
|
|||
dataDPCM=new unsigned char[lengthDPCM];
|
||||
memset(dataDPCM,0,lengthDPCM);
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_AICA_ADPCM: // AICA ADPCM
|
||||
if (dataAICA!=NULL) delete[] dataAICA;
|
||||
lengthAICA=(count+1)/2;
|
||||
dataAICA=new unsigned char[(lengthAICA+255)&(~0xff)];
|
||||
memset(dataAICA,0,(lengthAICA+255)&(~0xff));
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_YMZ_ADPCM: // YMZ ADPCM
|
||||
if (dataZ!=NULL) delete[] dataZ;
|
||||
lengthZ=(count+1)/2;
|
||||
dataZ=new unsigned char[(lengthZ+255)&(~0xff)];
|
||||
memset(dataZ,0,(lengthZ+255)&(~0xff));
|
||||
// for padding AICA sample
|
||||
dataZ=new unsigned char[(lengthZ+3)&(~0x03)];
|
||||
memset(dataZ,0,(lengthZ+3)&(~0x03));
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_QSOUND_ADPCM: // QSound ADPCM
|
||||
if (dataQSoundA!=NULL) delete[] dataQSoundA;
|
||||
|
|
@ -127,12 +122,6 @@ bool DivSample::initInternal(DivSampleDepth d, int count) {
|
|||
dataB=new unsigned char[(lengthB+255)&(~0xff)];
|
||||
memset(dataB,0,(lengthB+255)&(~0xff));
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_X68K_ADPCM: // X68000 ADPCM
|
||||
if (dataX68!=NULL) delete[] dataX68;
|
||||
lengthX68=(count+1)/2;
|
||||
dataX68=new unsigned char[lengthX68];
|
||||
memset(dataX68,0,lengthX68);
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_8BIT: // 8-bit
|
||||
if (data8!=NULL) delete[] data8;
|
||||
length8=count;
|
||||
|
|
@ -678,9 +667,6 @@ void DivSample::render() {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case DIV_SAMPLE_DEPTH_AICA_ADPCM: // AICA ADPCM
|
||||
aica_decode(dataAICA,data16,samples);
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_YMZ_ADPCM: // YMZ ADPCM
|
||||
ymz_decode(dataZ,data16,samples);
|
||||
break;
|
||||
|
|
@ -693,9 +679,6 @@ void DivSample::render() {
|
|||
case DIV_SAMPLE_DEPTH_ADPCM_B: // ADPCM-B
|
||||
ymb_decode(dataB,data16,samples);
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_X68K_ADPCM: // X6800 ADPCM
|
||||
oki6258_decode(dataX68,data16,samples);
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_8BIT: // 8-bit PCM
|
||||
for (unsigned int i=0; i<samples; i++) {
|
||||
data16[i]=data8[i]<<8;
|
||||
|
|
@ -736,13 +719,9 @@ void DivSample::render() {
|
|||
if (accum>127) accum=127;
|
||||
}
|
||||
}
|
||||
if (depth!=DIV_SAMPLE_DEPTH_AICA_ADPCM) { // AICA ADPCM
|
||||
if (!initInternal(DIV_SAMPLE_DEPTH_AICA_ADPCM,samples)) return;
|
||||
aica_encode(data16,dataAICA,(samples+511)&(~0x1ff));
|
||||
}
|
||||
if (depth!=DIV_SAMPLE_DEPTH_YMZ_ADPCM) { // YMZ ADPCM
|
||||
if (!initInternal(DIV_SAMPLE_DEPTH_YMZ_ADPCM,samples)) return;
|
||||
ymz_encode(data16,dataZ,(samples+511)&(~0x1ff));
|
||||
if (!initInternal(3,samples)) return;
|
||||
ymz_encode(data16,dataZ,(samples+7)&(~0x7));
|
||||
}
|
||||
if (depth!=DIV_SAMPLE_DEPTH_QSOUND_ADPCM) { // QSound ADPCM
|
||||
if (!initInternal(DIV_SAMPLE_DEPTH_QSOUND_ADPCM,samples)) return;
|
||||
|
|
@ -757,12 +736,8 @@ void DivSample::render() {
|
|||
if (!initInternal(DIV_SAMPLE_DEPTH_ADPCM_B,samples)) return;
|
||||
ymb_encode(data16,dataB,(samples+511)&(~0x1ff));
|
||||
}
|
||||
if (depth!=DIV_SAMPLE_DEPTH_X68K_ADPCM) { // X68000 ADPCM
|
||||
if (!initInternal(DIV_SAMPLE_DEPTH_X68K_ADPCM,samples)) return;
|
||||
oki6258_encode(data16,dataX68,samples);
|
||||
}
|
||||
if (depth!=DIV_SAMPLE_DEPTH_8BIT) { // 8-bit PCM
|
||||
if (!initInternal(DIV_SAMPLE_DEPTH_8BIT,samples)) return;
|
||||
if (!initInternal(8,samples)) return;
|
||||
for (unsigned int i=0; i<samples; i++) {
|
||||
data8[i]=data16[i]>>8;
|
||||
}
|
||||
|
|
@ -780,8 +755,6 @@ void* DivSample::getCurBuf() {
|
|||
return data1;
|
||||
case DIV_SAMPLE_DEPTH_1BIT_DPCM:
|
||||
return dataDPCM;
|
||||
case DIV_SAMPLE_DEPTH_AICA_ADPCM:
|
||||
return dataAICA;
|
||||
case DIV_SAMPLE_DEPTH_YMZ_ADPCM:
|
||||
return dataZ;
|
||||
case DIV_SAMPLE_DEPTH_QSOUND_ADPCM:
|
||||
|
|
@ -790,8 +763,6 @@ void* DivSample::getCurBuf() {
|
|||
return dataA;
|
||||
case DIV_SAMPLE_DEPTH_ADPCM_B:
|
||||
return dataB;
|
||||
case DIV_SAMPLE_DEPTH_X68K_ADPCM:
|
||||
return dataX68;
|
||||
case DIV_SAMPLE_DEPTH_8BIT:
|
||||
return data8;
|
||||
case DIV_SAMPLE_DEPTH_BRR:
|
||||
|
|
@ -812,8 +783,6 @@ unsigned int DivSample::getCurBufLen() {
|
|||
return length1;
|
||||
case DIV_SAMPLE_DEPTH_1BIT_DPCM:
|
||||
return lengthDPCM;
|
||||
case DIV_SAMPLE_DEPTH_AICA_ADPCM:
|
||||
return lengthAICA;
|
||||
case DIV_SAMPLE_DEPTH_YMZ_ADPCM:
|
||||
return lengthZ;
|
||||
case DIV_SAMPLE_DEPTH_QSOUND_ADPCM:
|
||||
|
|
@ -822,8 +791,6 @@ unsigned int DivSample::getCurBufLen() {
|
|||
return lengthA;
|
||||
case DIV_SAMPLE_DEPTH_ADPCM_B:
|
||||
return lengthB;
|
||||
case DIV_SAMPLE_DEPTH_X68K_ADPCM:
|
||||
return lengthX68;
|
||||
case DIV_SAMPLE_DEPTH_8BIT:
|
||||
return length8;
|
||||
case DIV_SAMPLE_DEPTH_BRR:
|
||||
|
|
@ -930,12 +897,10 @@ DivSample::~DivSample() {
|
|||
if (data16) delete[] data16;
|
||||
if (data1) delete[] data1;
|
||||
if (dataDPCM) delete[] dataDPCM;
|
||||
if (dataAICA) delete[] dataAICA;
|
||||
if (dataZ) delete[] dataZ;
|
||||
if (dataQSoundA) delete[] dataQSoundA;
|
||||
if (dataA) delete[] dataA;
|
||||
if (dataB) delete[] dataB;
|
||||
if (dataX68) delete[] dataX68;
|
||||
if (dataBRR) delete[] dataBRR;
|
||||
if (dataVOX) delete[] dataVOX;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,12 +38,10 @@ enum DivResampleFilters {
|
|||
enum DivSampleDepth: unsigned char {
|
||||
DIV_SAMPLE_DEPTH_1BIT=0,
|
||||
DIV_SAMPLE_DEPTH_1BIT_DPCM=1,
|
||||
DIV_SAMPLE_DEPTH_AICA_ADPCM=2,
|
||||
DIV_SAMPLE_DEPTH_YMZ_ADPCM=3,
|
||||
DIV_SAMPLE_DEPTH_QSOUND_ADPCM=4,
|
||||
DIV_SAMPLE_DEPTH_ADPCM_A=5,
|
||||
DIV_SAMPLE_DEPTH_ADPCM_B=6,
|
||||
DIV_SAMPLE_DEPTH_X68K_ADPCM=7,
|
||||
DIV_SAMPLE_DEPTH_8BIT=8,
|
||||
DIV_SAMPLE_DEPTH_BRR=9,
|
||||
DIV_SAMPLE_DEPTH_VOX=10,
|
||||
|
|
@ -105,15 +103,13 @@ struct DivSample {
|
|||
// valid values are:
|
||||
// - 0: ZX Spectrum overlay drum (1-bit)
|
||||
// - 1: 1-bit NES DPCM (1-bit)
|
||||
// - 2: AICA ADPCM
|
||||
// - 3: YMZ ADPCM
|
||||
// - 4: QSound ADPCM
|
||||
// - 5: ADPCM-A
|
||||
// - 6: ADPCM-B
|
||||
// - 7: X68000 ADPCM
|
||||
// - 8: 8-bit PCM
|
||||
// - 9: BRR (SNES)
|
||||
// - 10: VOX
|
||||
// - 10: VOX ADPCM
|
||||
// - 16: 16-bit PCM
|
||||
DivSampleDepth depth;
|
||||
|
||||
|
|
@ -122,18 +118,16 @@ struct DivSample {
|
|||
short* data16; // 16
|
||||
unsigned char* data1; // 0
|
||||
unsigned char* dataDPCM; // 1
|
||||
unsigned char* dataAICA; // 2
|
||||
unsigned char* dataZ; // 3
|
||||
unsigned char* dataQSoundA; // 4
|
||||
unsigned char* dataA; // 5
|
||||
unsigned char* dataB; // 6
|
||||
unsigned char* dataX68; // 7
|
||||
unsigned char* dataBRR; // 9
|
||||
unsigned char* dataVOX; // 10
|
||||
|
||||
unsigned int length8, length16, length1, lengthDPCM, lengthAICA, lengthZ, lengthQSoundA, lengthA, lengthB, lengthX68, lengthBRR, lengthVOX;
|
||||
unsigned int off8, off16, off1, offDPCM, offAICA, offZ, offQSoundA, offA, offB, offX68, offBRR, offVOX;
|
||||
unsigned int offSegaPCM, offQSound, offX1_010, offES5506, offSU;
|
||||
unsigned int length8, length16, length1, lengthDPCM, lengthZ, lengthQSoundA, lengthA, lengthB, lengthBRR, lengthVOX;
|
||||
unsigned int off8, off16, off1, offDPCM, offZ, offQSoundA, offA, offB, offBRR, offVOX;
|
||||
unsigned int offSegaPCM, offQSound, offX1_010, offES5506, offSU, offYMZ280B, offRF5C68;
|
||||
|
||||
unsigned int samples;
|
||||
|
||||
|
|
@ -273,36 +267,30 @@ struct DivSample {
|
|||
data16(NULL),
|
||||
data1(NULL),
|
||||
dataDPCM(NULL),
|
||||
dataAICA(NULL),
|
||||
dataZ(NULL),
|
||||
dataQSoundA(NULL),
|
||||
dataA(NULL),
|
||||
dataB(NULL),
|
||||
dataX68(NULL),
|
||||
dataBRR(NULL),
|
||||
dataVOX(NULL),
|
||||
length8(0),
|
||||
length16(0),
|
||||
length1(0),
|
||||
lengthDPCM(0),
|
||||
lengthAICA(0),
|
||||
lengthZ(0),
|
||||
lengthQSoundA(0),
|
||||
lengthA(0),
|
||||
lengthB(0),
|
||||
lengthX68(0),
|
||||
lengthBRR(0),
|
||||
lengthVOX(0),
|
||||
off8(0),
|
||||
off16(0),
|
||||
off1(0),
|
||||
offDPCM(0),
|
||||
offAICA(0),
|
||||
offZ(0),
|
||||
offQSoundA(0),
|
||||
offA(0),
|
||||
offB(0),
|
||||
offX68(0),
|
||||
offBRR(0),
|
||||
offVOX(0),
|
||||
offSegaPCM(0),
|
||||
|
|
@ -310,6 +298,7 @@ struct DivSample {
|
|||
offX1_010(0),
|
||||
offES5506(0),
|
||||
offSU(0),
|
||||
offRF5C68(0),
|
||||
samples(0) {}
|
||||
~DivSample();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -107,6 +107,10 @@ enum DivSystem {
|
|||
DIV_SYSTEM_SOUND_UNIT,
|
||||
DIV_SYSTEM_MSM6295,
|
||||
DIV_SYSTEM_MSM6258,
|
||||
DIV_SYSTEM_YMZ280B,
|
||||
DIV_SYSTEM_NAMCO,
|
||||
DIV_SYSTEM_NAMCO_15XX,
|
||||
DIV_SYSTEM_NAMCO_CUS30,
|
||||
DIV_SYSTEM_DUMMY,
|
||||
DIV_SYSTEM_MAX // boundary for max system number
|
||||
};
|
||||
|
|
@ -115,6 +119,7 @@ struct DivSubSong {
|
|||
String name, notes;
|
||||
unsigned char hilightA, hilightB;
|
||||
unsigned char timeBase, speed1, speed2, arpLen;
|
||||
short virtualTempoN, virtualTempoD;
|
||||
bool pal;
|
||||
bool customTempo;
|
||||
float hz;
|
||||
|
|
@ -137,6 +142,8 @@ struct DivSubSong {
|
|||
speed1(6),
|
||||
speed2(6),
|
||||
arpLen(1),
|
||||
virtualTempoN(150),
|
||||
virtualTempoD(150),
|
||||
pal(true),
|
||||
customTempo(false),
|
||||
hz(60.0),
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "dispatch.h"
|
||||
#include "engine.h"
|
||||
#include "song.h"
|
||||
#include "../ta-log.h"
|
||||
|
|
@ -1313,7 +1314,7 @@ void DivEngine::registerSystems() {
|
|||
);
|
||||
|
||||
sysDefs[DIV_SYSTEM_PC98]=new DivSysDef(
|
||||
"Yamaha YM2608 (OPNA)", NULL, 0x8e, 0, 16, true, true, 0, false,
|
||||
"Yamaha YM2608 (OPNA)", NULL, 0x8e, 0, 16, true, true, 0x151, false,
|
||||
"OPN but twice the FM channels, stereo makes a come-back and has rhythm and ADPCM channels.",
|
||||
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Kick", "Snare", "Top", "HiHat", "Tom", "Rim", "ADPCM"},
|
||||
{"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "BD", "SD", "TP", "HH", "TM", "RM", "P"},
|
||||
|
|
@ -1325,7 +1326,7 @@ void DivEngine::registerSystems() {
|
|||
);
|
||||
|
||||
sysDefs[DIV_SYSTEM_PC98_EXT]=new DivSysDef(
|
||||
"Yamaha YM2608 (OPNA) Extended Channel 3", NULL, 0xb7, 0, 19, true, true, 0, false,
|
||||
"Yamaha YM2608 (OPNA) Extended Channel 3", NULL, 0xb7, 0, 19, true, true, 0x151, false,
|
||||
"OPN but twice the FM channels, stereo makes a come-back and has rhythm and ADPCM channels.\nthis one is in Extended Channel mode, which turns the second FM channel into four operators with independent notes/frequencies",
|
||||
{"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Kick", "Snare", "Top", "HiHat", "Tom", "Rim", "ADPCM"},
|
||||
{"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "BD", "SD", "TP", "HH", "TM", "RM", "P"},
|
||||
|
|
@ -1400,7 +1401,7 @@ void DivEngine::registerSystems() {
|
|||
);
|
||||
|
||||
sysDefs[DIV_SYSTEM_RF5C68]=new DivSysDef(
|
||||
"Ricoh RF5C68", NULL, 0x95, 0, 8, false, true, 0, false,
|
||||
"Ricoh RF5C68", NULL, 0x95, 0, 8, false, true, 0x151, false,
|
||||
"this is like SNES' sound chip but without interpolation and the rest of nice bits.",
|
||||
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"},
|
||||
{"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"},
|
||||
|
|
@ -1521,7 +1522,21 @@ void DivEngine::registerSystems() {
|
|||
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6"},
|
||||
{"CH1", "CH2", "CH3", "CH4", "CH5", "CH6"},
|
||||
{DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE},
|
||||
{DIV_INS_BEEPER, DIV_INS_BEEPER, DIV_INS_BEEPER, DIV_INS_BEEPER, DIV_INS_BEEPER, DIV_INS_BEEPER}
|
||||
{DIV_INS_BEEPER, DIV_INS_BEEPER, DIV_INS_BEEPER, DIV_INS_BEEPER, DIV_INS_BEEPER, DIV_INS_BEEPER},
|
||||
{},
|
||||
[this](int ch, unsigned char effect, unsigned char effectVal) -> bool {
|
||||
switch (effect) {
|
||||
case 0x12: // pulse width
|
||||
dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal));
|
||||
break;
|
||||
case 0x17: // overlay sample
|
||||
dispatchCmd(DivCommand(DIV_CMD_SAMPLE_MODE,ch,effectVal));
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
sysDefs[DIV_SYSTEM_YM2612_EXT]=new DivSysDef(
|
||||
|
|
@ -1827,6 +1842,12 @@ void DivEngine::registerSystems() {
|
|||
case 0x11: // filter mode
|
||||
dispatchCmd(DivCommand(DIV_CMD_ES5506_FILTER_MODE,ch,effectVal&3));
|
||||
break;
|
||||
case 0x12: // pause
|
||||
dispatchCmd(DivCommand(DIV_CMD_ES5506_PAUSE,ch,effectVal&1));
|
||||
break;
|
||||
case 0x13: // slice enable
|
||||
dispatchCmd(DivCommand(DIV_CMD_SAMPLE_TRANSWAVE_SLICE_MODE,ch,effectVal&1));
|
||||
break;
|
||||
case 0x14: // filter coefficient K1, 8 bit LSB
|
||||
dispatchCmd(DivCommand(DIV_CMD_ES5506_FILTER_K1,ch,effectVal&0xff,0x00ff));
|
||||
break;
|
||||
|
|
@ -1862,6 +1883,8 @@ void DivEngine::registerSystems() {
|
|||
dispatchCmd(DivCommand(DIV_CMD_ES5506_FILTER_K1,ch,((effect&0x0f)<<12)|((effectVal&0xff)<<4),0xfff0));
|
||||
} else if ((effect&0xf0)==0x40) { // filter coefficient K2, 12 bit MSB
|
||||
dispatchCmd(DivCommand(DIV_CMD_ES5506_FILTER_K2,ch,((effect&0x0f)<<12)|((effectVal&0xff)<<4),0xfff0));
|
||||
} else if ((effect&0xf0)==0x50) { // slice position
|
||||
dispatchCmd(DivCommand(DIV_CMD_SAMPLE_TRANSWAVE_SLICE_POS,ch,((effect&0x0f)<<8)|(effectVal&0xff)));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1891,7 +1914,7 @@ void DivEngine::registerSystems() {
|
|||
);
|
||||
|
||||
sysDefs[DIV_SYSTEM_Y8950]=new DivSysDef(
|
||||
"Yamaha Y8950", NULL, 0xb2, 0, 10, true, false, 0, false,
|
||||
"Yamaha Y8950", NULL, 0xb2, 0, 10, true, false, 0x151, false,
|
||||
"like OPL but with an ADPCM channel.",
|
||||
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9", "PCM"},
|
||||
{"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "PCM"},
|
||||
|
|
@ -1903,7 +1926,7 @@ void DivEngine::registerSystems() {
|
|||
);
|
||||
|
||||
sysDefs[DIV_SYSTEM_Y8950_DRUMS]=new DivSysDef(
|
||||
"Yamaha Y8950 with drums", NULL, 0xb3, 0, 12, true, false, 0, false,
|
||||
"Yamaha Y8950 with drums", NULL, 0xb3, 0, 12, true, false, 0x151, false,
|
||||
"the Y8950 chip, in drums mode.",
|
||||
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Kick", "Snare", "Tom", "Top", "HiHat", "PCM"},
|
||||
{"F1", "F2", "F3", "F4", "F5", "F6", "BD", "SD", "TM", "TP", "HH", "PCM"},
|
||||
|
|
@ -1932,7 +1955,75 @@ void DivEngine::registerSystems() {
|
|||
{"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"},
|
||||
{DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE},
|
||||
{DIV_INS_SU, DIV_INS_SU, DIV_INS_SU, DIV_INS_SU, DIV_INS_SU, DIV_INS_SU, DIV_INS_SU, DIV_INS_SU},
|
||||
{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},
|
||||
[](int,unsigned char,unsigned char) -> bool {return false;},
|
||||
[this](int ch, unsigned char effect, unsigned char effectVal) -> bool {
|
||||
switch (effect) {
|
||||
case 0x10: // waveform
|
||||
dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal));
|
||||
break;
|
||||
case 0x12: // duty cycle
|
||||
dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal));
|
||||
break;
|
||||
case 0x13: // resonance
|
||||
dispatchCmd(DivCommand(DIV_CMD_C64_RESONANCE,ch,effectVal));
|
||||
break;
|
||||
case 0x14: // filter mode
|
||||
dispatchCmd(DivCommand(DIV_CMD_C64_FILTER_MODE,ch,effectVal));
|
||||
break;
|
||||
case 0x15: // freq sweep
|
||||
dispatchCmd(DivCommand(DIV_CMD_SU_SWEEP_PERIOD_LOW,ch,0,effectVal));
|
||||
break;
|
||||
case 0x16: // freq sweep
|
||||
dispatchCmd(DivCommand(DIV_CMD_SU_SWEEP_PERIOD_HIGH,ch,0,effectVal));
|
||||
break;
|
||||
case 0x17: // vol sweep
|
||||
dispatchCmd(DivCommand(DIV_CMD_SU_SWEEP_PERIOD_LOW,ch,1,effectVal));
|
||||
break;
|
||||
case 0x18: // vol sweep
|
||||
dispatchCmd(DivCommand(DIV_CMD_SU_SWEEP_PERIOD_HIGH,ch,1,effectVal));
|
||||
break;
|
||||
case 0x19: // cut sweep
|
||||
dispatchCmd(DivCommand(DIV_CMD_SU_SWEEP_PERIOD_LOW,ch,2,effectVal));
|
||||
break;
|
||||
case 0x1a: // cut sweep
|
||||
dispatchCmd(DivCommand(DIV_CMD_SU_SWEEP_PERIOD_HIGH,ch,2,effectVal));
|
||||
break;
|
||||
case 0x1b: // freq sweep bound
|
||||
dispatchCmd(DivCommand(DIV_CMD_SU_SWEEP_BOUND,ch,0,effectVal));
|
||||
break;
|
||||
case 0x1c: // vol sweep bound
|
||||
dispatchCmd(DivCommand(DIV_CMD_SU_SWEEP_BOUND,ch,1,effectVal));
|
||||
break;
|
||||
case 0x1d: // cut sweep bound
|
||||
dispatchCmd(DivCommand(DIV_CMD_SU_SWEEP_BOUND,ch,2,effectVal));
|
||||
break;
|
||||
case 0x1e: // sync low
|
||||
dispatchCmd(DivCommand(DIV_CMD_SU_SYNC_PERIOD_LOW,ch,effectVal));
|
||||
break;
|
||||
case 0x1f: // sync high
|
||||
dispatchCmd(DivCommand(DIV_CMD_SU_SYNC_PERIOD_HIGH,ch,effectVal));
|
||||
break;
|
||||
case 0x20: // freq sweep enable
|
||||
dispatchCmd(DivCommand(DIV_CMD_SU_SWEEP_ENABLE,ch,0,effectVal));
|
||||
break;
|
||||
case 0x21: // vol sweep enable
|
||||
dispatchCmd(DivCommand(DIV_CMD_SU_SWEEP_ENABLE,ch,1,effectVal));
|
||||
break;
|
||||
case 0x22: // cut sweep enable
|
||||
dispatchCmd(DivCommand(DIV_CMD_SU_SWEEP_ENABLE,ch,2,effectVal));
|
||||
break;
|
||||
case 0x40: case 0x41: case 0x42: case 0x43:
|
||||
case 0x44: case 0x45: case 0x46: case 0x47:
|
||||
case 0x48: case 0x49: case 0x4a: case 0x4b:
|
||||
case 0x4c: case 0x4d: case 0x4e: case 0x4f: // cutoff
|
||||
dispatchCmd(DivCommand(DIV_CMD_C64_FINE_CUTOFF,ch,(((effect&0x0f)<<8)|effectVal)*4));
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
sysDefs[DIV_SYSTEM_MSM6295]=new DivSysDef(
|
||||
|
|
@ -1953,9 +2044,65 @@ void DivEngine::registerSystems() {
|
|||
{DIV_INS_AMIGA}
|
||||
);
|
||||
|
||||
sysDefs[DIV_SYSTEM_YMZ280B]=new DivSysDef(
|
||||
"Yamaha YMZ280B", NULL, 0xb8, 0, 8, false, true, 0x151, false,
|
||||
"used in some arcade boards. Can play back either 4-bit ADPCM, 8-bit PCM or 16-bit PCM.",
|
||||
{"PCM 1", "PCM 2", "PCM 3", "PCM 4", "PCM 5", "PCM 6", "PCM 7", "PCM 8"},
|
||||
{"1", "2", "3", "4", "5", "6", "7", "8"},
|
||||
{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_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}
|
||||
);
|
||||
|
||||
auto namcoEffectHandler=[this](int ch, unsigned char effect, unsigned char effectVal) -> bool {
|
||||
switch (effect) {
|
||||
case 0x10: // select waveform
|
||||
dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal));
|
||||
break;
|
||||
case 0x11: // noise mode
|
||||
dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal));
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
sysDefs[DIV_SYSTEM_NAMCO]=new DivSysDef(
|
||||
"Namco WSG", NULL, 0xb9, 0, 3, false, true, 0, false,
|
||||
"a wavetable sound chip used in Pac-Man, among other early Namco arcade games.",
|
||||
{"Channel 1", "Channel 2", "Channel 3"},
|
||||
{"CH1", "CH2", "CH3"},
|
||||
{DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE},
|
||||
{DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO},
|
||||
{},
|
||||
namcoEffectHandler
|
||||
);
|
||||
|
||||
sysDefs[DIV_SYSTEM_NAMCO_15XX]=new DivSysDef(
|
||||
"Namco 15XX WSG", NULL, 0xba, 0, 8, false, true, 0, false,
|
||||
"successor of the original Namco WSG chip, used in later Namco arcade games.",
|
||||
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"},
|
||||
{"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"},
|
||||
{DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE},
|
||||
{DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO},
|
||||
{},
|
||||
namcoEffectHandler
|
||||
);
|
||||
|
||||
sysDefs[DIV_SYSTEM_NAMCO_CUS30]=new DivSysDef(
|
||||
"Namco CUS30 WSG", NULL, 0xbb, 0, 8, false, true, 0, false,
|
||||
"like Namco 15XX but with stereo sound.",
|
||||
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"},
|
||||
{"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"},
|
||||
{DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE},
|
||||
{DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO, DIV_INS_NAMCO},
|
||||
{},
|
||||
namcoEffectHandler
|
||||
);
|
||||
|
||||
sysDefs[DIV_SYSTEM_DUMMY]=new DivSysDef(
|
||||
"Dummy System", NULL, 0xfd, 0, 8, false, true, 0, false,
|
||||
"this is a system designed for testing purposes..",
|
||||
"this is a system designed for testing purposes.",
|
||||
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"},
|
||||
{"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"},
|
||||
{DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE},
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
|
|||
unsigned char baseAddr2=isSecond?0x80:0;
|
||||
unsigned short baseAddr2S=isSecond?0x8000:0;
|
||||
unsigned char smsAddr=isSecond?0x30:0x50;
|
||||
unsigned char rf5c68Addr=isSecond?0xb1:0xb0;
|
||||
if (write.addr==0xffffffff) { // Furnace fake reset
|
||||
switch (sys) {
|
||||
case DIV_SYSTEM_YM2612:
|
||||
|
|
@ -167,6 +168,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
|
|||
case DIV_SYSTEM_YM2610_EXT:
|
||||
case DIV_SYSTEM_YM2610_FULL_EXT:
|
||||
case DIV_SYSTEM_YM2610B_EXT:
|
||||
// TODO: YM2610B channels 1 and 4 and ADPCM-B
|
||||
for (int i=0; i<2; i++) { // set SL and RR to highest
|
||||
w->writeC(8|baseAddr1);
|
||||
w->writeC(0x81+i);
|
||||
|
|
@ -240,6 +242,45 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
|
|||
w->writeC(0);
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_OPN:
|
||||
case DIV_SYSTEM_OPN_EXT:
|
||||
for (int i=0; i<3; i++) { // set SL and RR to highest
|
||||
w->writeC(5|baseAddr1);
|
||||
w->writeC(0x80+i);
|
||||
w->writeC(0xff);
|
||||
w->writeC(5|baseAddr1);
|
||||
w->writeC(0x84+i);
|
||||
w->writeC(0xff);
|
||||
w->writeC(5|baseAddr1);
|
||||
w->writeC(0x88+i);
|
||||
w->writeC(0xff);
|
||||
w->writeC(5|baseAddr1);
|
||||
w->writeC(0x8c+i);
|
||||
w->writeC(0xff);
|
||||
}
|
||||
for (int i=0; i<3; i++) { // note off
|
||||
w->writeC(5|baseAddr1);
|
||||
w->writeC(0x28);
|
||||
w->writeC(i);
|
||||
}
|
||||
|
||||
// SSG
|
||||
w->writeC(5|baseAddr1);
|
||||
w->writeC(7);
|
||||
w->writeC(0x3f);
|
||||
|
||||
w->writeC(5|baseAddr1);
|
||||
w->writeC(8);
|
||||
w->writeC(0);
|
||||
|
||||
w->writeC(5|baseAddr1);
|
||||
w->writeC(9);
|
||||
w->writeC(0);
|
||||
|
||||
w->writeC(5|baseAddr1);
|
||||
w->writeC(10);
|
||||
w->writeC(0);
|
||||
break;
|
||||
case DIV_SYSTEM_AY8910:
|
||||
w->writeC(0xa0);
|
||||
w->writeC(7|baseAddr2);
|
||||
|
|
@ -351,7 +392,6 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
|
|||
}
|
||||
}
|
||||
break;
|
||||
// TODO: it's 3:35am
|
||||
case DIV_SYSTEM_OPL:
|
||||
case DIV_SYSTEM_OPL_DRUMS:
|
||||
// disable envelope
|
||||
|
|
@ -376,6 +416,31 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
|
|||
w->writeC(0);
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_Y8950:
|
||||
case DIV_SYSTEM_Y8950_DRUMS:
|
||||
// disable envelope
|
||||
for (int i=0; i<6; i++) {
|
||||
w->writeC(0x0b|baseAddr1);
|
||||
w->writeC(0x80+i);
|
||||
w->writeC(0x0f);
|
||||
w->writeC(0x0b|baseAddr1);
|
||||
w->writeC(0x88+i);
|
||||
w->writeC(0x0f);
|
||||
w->writeC(0x0b|baseAddr1);
|
||||
w->writeC(0x90+i);
|
||||
w->writeC(0x0f);
|
||||
}
|
||||
// key off + freq reset
|
||||
for (int i=0; i<9; i++) {
|
||||
w->writeC(0x0b|baseAddr1);
|
||||
w->writeC(0xa0+i);
|
||||
w->writeC(0);
|
||||
w->writeC(0x0b|baseAddr1);
|
||||
w->writeC(0xb0+i);
|
||||
w->writeC(0);
|
||||
}
|
||||
// TODO: ADPCM
|
||||
break;
|
||||
case DIV_SYSTEM_OPL2:
|
||||
case DIV_SYSTEM_OPL2_DRUMS:
|
||||
// disable envelope
|
||||
|
|
@ -450,6 +515,13 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
|
|||
w->writeC(0);
|
||||
w->writeC(0);
|
||||
break;
|
||||
case DIV_SYSTEM_RF5C68:
|
||||
w->writeC(rf5c68Addr);
|
||||
w->writeC(7);
|
||||
w->writeC(0);
|
||||
w->writeC(rf5c68Addr);
|
||||
w->writeC(8);
|
||||
w->writeC(0xff);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
@ -572,10 +644,26 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
|
|||
}
|
||||
break;
|
||||
case DIV_SYSTEM_OPN:
|
||||
case DIV_SYSTEM_OPN_EXT:
|
||||
w->writeC(5|baseAddr1);
|
||||
w->writeC(write.addr&0xff);
|
||||
w->writeC(write.val);
|
||||
break;
|
||||
case DIV_SYSTEM_PC98:
|
||||
case DIV_SYSTEM_PC98_EXT:
|
||||
switch (write.addr>>8) {
|
||||
case 0: // port 0
|
||||
w->writeC(6|baseAddr1);
|
||||
w->writeC(write.addr&0xff);
|
||||
w->writeC(write.val);
|
||||
break;
|
||||
case 1: // port 1
|
||||
w->writeC(7|baseAddr1);
|
||||
w->writeC(write.addr&0xff);
|
||||
w->writeC(write.val);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_OPLL:
|
||||
case DIV_SYSTEM_OPLL_DRUMS:
|
||||
case DIV_SYSTEM_VRC7:
|
||||
|
|
@ -628,6 +716,12 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
|
|||
w->writeC(write.addr&0xff);
|
||||
w->writeC(write.val);
|
||||
break;
|
||||
case DIV_SYSTEM_Y8950:
|
||||
case DIV_SYSTEM_Y8950_DRUMS:
|
||||
w->writeC(0x0c|baseAddr1);
|
||||
w->writeC(write.addr&0xff);
|
||||
w->writeC(write.val);
|
||||
break;
|
||||
case DIV_SYSTEM_OPL2:
|
||||
case DIV_SYSTEM_OPL2_DRUMS:
|
||||
w->writeC(0x0a|baseAddr1);
|
||||
|
|
@ -714,6 +808,16 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
|
|||
logW("SCC+: writing to unmapped address %.2x!",write.addr);
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_YMZ280B:
|
||||
w->writeC(0x0d|baseAddr1);
|
||||
w->writeC(write.addr&0xff);
|
||||
w->writeC(write.val&0xff);
|
||||
break;
|
||||
case DIV_SYSTEM_RF5C68:
|
||||
w->writeC(rf5c68Addr);
|
||||
w->writeC(write.addr&0xff);
|
||||
w->writeC(write.val);
|
||||
break;
|
||||
default:
|
||||
logW("write not handled!");
|
||||
break;
|
||||
|
|
@ -829,11 +933,15 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
|||
bool writeDACSamples=false;
|
||||
bool writeNESSamples=false;
|
||||
bool writePCESamples=false;
|
||||
DivDispatch* writeADPCM[2]={NULL,NULL};
|
||||
DivDispatch* writeADPCM_OPNA[2]={NULL,NULL};
|
||||
DivDispatch* writeADPCM_OPNB[2]={NULL,NULL};
|
||||
DivDispatch* writeADPCM_Y8950[2]={NULL,NULL};
|
||||
int writeSegaPCM=0;
|
||||
DivDispatch* writeX1010[2]={NULL,NULL};
|
||||
DivDispatch* writeQSound[2]={NULL,NULL};
|
||||
DivDispatch* writeES5506[2]={NULL,NULL};
|
||||
DivDispatch* writeZ280[2]={NULL,NULL};
|
||||
DivDispatch* writeRF5C68[2]={NULL,NULL};
|
||||
|
||||
for (int i=0; i<song.systemLen; i++) {
|
||||
willExport[i]=false;
|
||||
|
|
@ -940,11 +1048,11 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
|||
if (!hasOPNB) {
|
||||
hasOPNB=disCont[i].dispatch->chipClock;
|
||||
willExport[i]=true;
|
||||
writeADPCM[0]=disCont[i].dispatch;
|
||||
writeADPCM_OPNB[0]=disCont[i].dispatch;
|
||||
} else if (!(hasOPNB&0x40000000)) {
|
||||
isSecond[i]=true;
|
||||
willExport[i]=true;
|
||||
writeADPCM[1]=disCont[i].dispatch;
|
||||
writeADPCM_OPNB[1]=disCont[i].dispatch;
|
||||
hasOPNB|=0x40000000;
|
||||
howManyChips++;
|
||||
}
|
||||
|
|
@ -1033,6 +1141,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
|||
}
|
||||
break;
|
||||
case DIV_SYSTEM_OPN:
|
||||
case DIV_SYSTEM_OPN_EXT:
|
||||
if (!hasOPN) {
|
||||
hasOPN=disCont[i].dispatch->chipClock;
|
||||
willExport[i]=true;
|
||||
|
|
@ -1044,6 +1153,20 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
|||
howManyChips++;
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_PC98:
|
||||
case DIV_SYSTEM_PC98_EXT:
|
||||
if (!hasOPNA) {
|
||||
hasOPNA=disCont[i].dispatch->chipClock;
|
||||
willExport[i]=true;
|
||||
writeADPCM_OPNA[0]=disCont[i].dispatch;
|
||||
} else if (!(hasOPNA&0x40000000)) {
|
||||
isSecond[i]=true;
|
||||
willExport[i]=true;
|
||||
writeADPCM_OPNA[1]=disCont[i].dispatch;
|
||||
hasOPNA|=0x40000000;
|
||||
howManyChips++;
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_OPLL:
|
||||
case DIV_SYSTEM_OPLL_DRUMS:
|
||||
case DIV_SYSTEM_VRC7:
|
||||
|
|
@ -1138,6 +1261,20 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
|||
howManyChips++;
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_Y8950:
|
||||
case DIV_SYSTEM_Y8950_DRUMS:
|
||||
if (!hasY8950) {
|
||||
hasY8950=disCont[i].dispatch->chipClock;
|
||||
willExport[i]=true;
|
||||
writeADPCM_Y8950[0]=disCont[i].dispatch;
|
||||
} else if (!(hasY8950&0x40000000)) {
|
||||
isSecond[i]=true;
|
||||
willExport[i]=true;
|
||||
writeADPCM_Y8950[1]=disCont[i].dispatch;
|
||||
hasY8950|=0x40000000;
|
||||
howManyChips++;
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_OPL2:
|
||||
case DIV_SYSTEM_OPL2_DRUMS:
|
||||
if (!hasOPL2) {
|
||||
|
|
@ -1180,6 +1317,37 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
|||
howManyChips++;
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_YMZ280B:
|
||||
if (!hasZ280) {
|
||||
hasZ280=disCont[i].dispatch->chipClock;
|
||||
willExport[i]=true;
|
||||
writeZ280[0]=disCont[i].dispatch;
|
||||
} else if (!(hasZ280&0x40000000)) {
|
||||
isSecond[i]=true;
|
||||
willExport[i]=true;
|
||||
writeZ280[1]=disCont[i].dispatch;
|
||||
hasZ280|=0x40000000;
|
||||
howManyChips++;
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_RF5C68:
|
||||
// here's the dumb part: VGM thinks RF5C68 and RF5C164 are different
|
||||
// chips even though the only difference is the output resolution
|
||||
// these system types are currently handled by reusing isSecond flag
|
||||
// also this system is not dual-able
|
||||
if ((song.systemFlags[i]>>4)==1) {
|
||||
if (!hasRFC1) {
|
||||
hasRFC1=disCont[i].dispatch->chipClock;
|
||||
isSecond[i]=true;
|
||||
willExport[i]=true;
|
||||
writeRF5C68[1]=disCont[i].dispatch;
|
||||
}
|
||||
} else if (!hasRFC) {
|
||||
hasRFC=disCont[i].dispatch->chipClock;
|
||||
willExport[i]=true;
|
||||
writeRF5C68[0]=disCont[i].dispatch;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
@ -1443,30 +1611,46 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
|||
}
|
||||
|
||||
for (int i=0; i<2; i++) {
|
||||
if (writeADPCM[i]!=NULL && writeADPCM[i]->getSampleMemUsage(0)>0) {
|
||||
// ADPCM (OPNA)
|
||||
if (writeADPCM_OPNA[i]!=NULL && writeADPCM_OPNA[i]->getSampleMemUsage(0)>0) {
|
||||
w->writeC(0x67);
|
||||
w->writeC(0x66);
|
||||
w->writeC(0x81);
|
||||
w->writeI((writeADPCM_OPNA[i]->getSampleMemUsage(0)+8)|(i*0x80000000));
|
||||
w->writeI(writeADPCM_OPNA[i]->getSampleMemCapacity(0));
|
||||
w->writeI(0);
|
||||
w->write(writeADPCM_OPNA[i]->getSampleMem(0),writeADPCM_OPNA[i]->getSampleMemUsage(0));
|
||||
}
|
||||
// ADPCM-A (OPNB)
|
||||
if (writeADPCM_OPNB[i]!=NULL && writeADPCM_OPNB[i]->getSampleMemUsage(0)>0) {
|
||||
w->writeC(0x67);
|
||||
w->writeC(0x66);
|
||||
w->writeC(0x82);
|
||||
w->writeI((writeADPCM[i]->getSampleMemUsage(0)+8)|(i*0x80000000));
|
||||
w->writeI(writeADPCM[i]->getSampleMemCapacity(0));
|
||||
w->writeI((writeADPCM_OPNB[i]->getSampleMemUsage(0)+8)|(i*0x80000000));
|
||||
w->writeI(writeADPCM_OPNB[i]->getSampleMemCapacity(0));
|
||||
w->writeI(0);
|
||||
w->write(writeADPCM[i]->getSampleMem(0),writeADPCM[i]->getSampleMemUsage(0));
|
||||
w->write(writeADPCM_OPNB[i]->getSampleMem(0),writeADPCM_OPNB[i]->getSampleMemUsage(0));
|
||||
}
|
||||
}
|
||||
|
||||
for (int i=0; i<2; i++) {
|
||||
if (writeADPCM[i]!=NULL && writeADPCM[i]->getSampleMemUsage(1)>0) {
|
||||
// ADPCM-B (OPNB)
|
||||
if (writeADPCM_OPNB[i]!=NULL && writeADPCM_OPNB[i]->getSampleMemUsage(1)>0) {
|
||||
w->writeC(0x67);
|
||||
w->writeC(0x66);
|
||||
w->writeC(0x83);
|
||||
w->writeI((writeADPCM[i]->getSampleMemUsage(1)+8)|(i*0x80000000));
|
||||
w->writeI(writeADPCM[i]->getSampleMemCapacity(1));
|
||||
w->writeI((writeADPCM_OPNB[i]->getSampleMemUsage(1)+8)|(i*0x80000000));
|
||||
w->writeI(writeADPCM_OPNB[i]->getSampleMemCapacity(1));
|
||||
w->writeI(0);
|
||||
w->write(writeADPCM[i]->getSampleMem(1),writeADPCM[i]->getSampleMemUsage(1));
|
||||
w->write(writeADPCM_OPNB[i]->getSampleMem(1),writeADPCM_OPNB[i]->getSampleMemUsage(1));
|
||||
}
|
||||
// ADPCM (Y8950)
|
||||
if (writeADPCM_Y8950[i]!=NULL && writeADPCM_Y8950[i]->getSampleMemUsage(0)>0) {
|
||||
w->writeC(0x67);
|
||||
w->writeC(0x66);
|
||||
w->writeC(0x88);
|
||||
w->writeI((writeADPCM_Y8950[i]->getSampleMemUsage(0)+8)|(i*0x80000000));
|
||||
w->writeI(writeADPCM_Y8950[i]->getSampleMemCapacity(0));
|
||||
w->writeI(0);
|
||||
w->write(writeADPCM_Y8950[i]->getSampleMem(0),writeADPCM_Y8950[i]->getSampleMemUsage(0));
|
||||
}
|
||||
}
|
||||
|
||||
for (int i=0; i<2; i++) {
|
||||
if (writeQSound[i]!=NULL && writeQSound[i]->getSampleMemUsage()>0) {
|
||||
unsigned int blockSize=(writeQSound[i]->getSampleMemUsage()+0xffff)&(~0xffff);
|
||||
if (blockSize > 0x1000000) {
|
||||
|
|
@ -1480,9 +1664,6 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
|||
w->writeI(0);
|
||||
w->write(writeQSound[i]->getSampleMem(),blockSize);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i=0; i<2; i++) {
|
||||
if (writeX1010[i]!=NULL && writeX1010[i]->getSampleMemUsage()>0) {
|
||||
w->writeC(0x67);
|
||||
w->writeC(0x66);
|
||||
|
|
@ -1492,6 +1673,27 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
|||
w->writeI(0);
|
||||
w->write(writeX1010[i]->getSampleMem(),writeX1010[i]->getSampleMemUsage());
|
||||
}
|
||||
if (writeZ280[i]!=NULL && writeZ280[i]->getSampleMemUsage()>0) {
|
||||
w->writeC(0x67);
|
||||
w->writeC(0x66);
|
||||
w->writeC(0x86);
|
||||
w->writeI((writeZ280[i]->getSampleMemUsage()+8)|(i*0x80000000));
|
||||
w->writeI(writeZ280[i]->getSampleMemCapacity());
|
||||
w->writeI(0);
|
||||
w->write(writeZ280[i]->getSampleMem(),writeZ280[i]->getSampleMemUsage());
|
||||
}
|
||||
}
|
||||
|
||||
for (int i=0; i<2; i++) {
|
||||
if (writeRF5C68[i]!=NULL && writeRF5C68[i]->getSampleMemUsage()>0) {
|
||||
w->writeC(0x67);
|
||||
w->writeC(0x66);
|
||||
w->writeC(0xc0+i);
|
||||
w->writeI(writeRF5C68[i]->getSampleMemUsage()+8);
|
||||
w->writeI(writeRF5C68[i]->getSampleMemCapacity());
|
||||
w->writeI(0);
|
||||
w->write(writeRF5C68[i]->getSampleMem(),writeRF5C68[i]->getSampleMemUsage());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO
|
||||
|
|
|
|||
|
|
@ -29,11 +29,13 @@ bool DivWaveSynth::activeChanged() {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool DivWaveSynth::tick() {
|
||||
if (--subDivCounter>0) return false;
|
||||
|
||||
bool DivWaveSynth::tick(bool skipSubDiv) {
|
||||
bool updated=first;
|
||||
first=false;
|
||||
if (--subDivCounter>0 && !skipSubDiv) {
|
||||
return updated;
|
||||
}
|
||||
|
||||
subDivCounter=e->tickMult;
|
||||
if (!state.enabled) return updated;
|
||||
if (width<1) return false;
|
||||
|
|
@ -67,7 +69,7 @@ bool DivWaveSynth::tick() {
|
|||
case DIV_WS_AVERAGE:
|
||||
for (int i=0; i<=state.speed; i++) {
|
||||
int pos1=(pos+1>=width)?0:(pos+1);
|
||||
output[pos]=(output[pos]*state.param1+output[pos1]*(256-state.param1))>>8;
|
||||
output[pos]=(128+output[pos]*(256-state.param1)+output[pos1]*state.param1)>>8;
|
||||
if (output[pos]<0) output[pos]=0;
|
||||
if (output[pos]>height) output[pos]=height;
|
||||
if (++pos>=width) pos=0;
|
||||
|
|
@ -84,17 +86,115 @@ bool DivWaveSynth::tick() {
|
|||
}
|
||||
updated=true;
|
||||
break;
|
||||
case DIV_WS_CHORUS:
|
||||
for (int i=0; i<=state.speed; i++) {
|
||||
output[pos]=(wave1[pos]+wave1[(pos+stage)%width])>>1;
|
||||
if (++pos>=width) {
|
||||
pos=0;
|
||||
stage+=state.param1;
|
||||
while (stage>=width) stage-=width;
|
||||
}
|
||||
}
|
||||
updated=true;
|
||||
break;
|
||||
case DIV_WS_WIPE:
|
||||
for (int i=0; i<=state.speed; i++) {
|
||||
output[pos]=(stage&1)?wave1[pos]:wave2[pos];
|
||||
if (output[pos]<0) output[pos]=0;
|
||||
if (output[pos]>height) output[pos]=height;
|
||||
if (++pos>=width) {
|
||||
pos=0;
|
||||
stage=!stage;
|
||||
}
|
||||
}
|
||||
updated=true;
|
||||
break;
|
||||
case DIV_WS_FADE:
|
||||
for (int i=0; i<=state.speed; i++) {
|
||||
output[pos]=wave1[pos]+(((wave2[pos]-wave1[pos])*stage)>>9);
|
||||
if (++pos>=width) {
|
||||
pos=0;
|
||||
stage+=1+state.param1;
|
||||
if (stage>512) stage=512;
|
||||
}
|
||||
}
|
||||
updated=true;
|
||||
break;
|
||||
case DIV_WS_PING_PONG:
|
||||
for (int i=0; i<=state.speed; i++) {
|
||||
output[pos]=wave1[pos]+(((wave2[pos]-wave1[pos])*stage)>>8);
|
||||
if (++pos>=width) {
|
||||
pos=0;
|
||||
if (stageDir) {
|
||||
stage-=1+state.param1;
|
||||
if (stage<=0) {
|
||||
stageDir=false;
|
||||
stage=0;
|
||||
}
|
||||
} else {
|
||||
stage+=1+state.param1;
|
||||
if (stage>=256) {
|
||||
stageDir=true;
|
||||
stage=256;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
updated=true;
|
||||
break;
|
||||
case DIV_WS_OVERLAY:
|
||||
for (int i=0; i<=state.speed; i++) {
|
||||
output[pos]+=wave2[pos];
|
||||
if (output[pos]>=height) output[pos]-=height;
|
||||
if (++pos>=width) pos=0;
|
||||
}
|
||||
updated=true;
|
||||
break;
|
||||
case DIV_WS_NEGATIVE_OVERLAY:
|
||||
for (int i=0; i<=state.speed; i++) {
|
||||
output[pos]-=wave2[pos];
|
||||
if (output[pos]<0) output[pos]+=height;
|
||||
if (++pos>=width) pos=0;
|
||||
}
|
||||
updated=true;
|
||||
break;
|
||||
case DIV_WS_PHASE_DUAL:
|
||||
case DIV_WS_SLIDE:
|
||||
for (int i=0; i<=state.speed; i++) {
|
||||
int newPos=(pos+stage)%(width*2);
|
||||
if (newPos>=width) {
|
||||
output[pos]=wave2[newPos-width];
|
||||
} else {
|
||||
output[pos]=wave1[newPos];
|
||||
}
|
||||
if (++pos>=width) {
|
||||
pos=0;
|
||||
if (++stage>=width*2) stage=0;
|
||||
}
|
||||
}
|
||||
updated=true;
|
||||
break;
|
||||
case DIV_WS_MIX:
|
||||
for (int i=0; i<=state.speed; i++) {
|
||||
output[pos]=(wave1[pos]+wave2[(pos+stage)%width])>>1;
|
||||
if (++pos>=width) {
|
||||
pos=0;
|
||||
stage+=state.param1;
|
||||
while (stage>=width) stage-=width;
|
||||
}
|
||||
}
|
||||
updated=true;
|
||||
break;
|
||||
case DIV_WS_PHASE_MOD:
|
||||
for (int i=0; i<=state.speed; i++) {
|
||||
int mod=(wave2[pos]*(state.param2-stage))>>8;
|
||||
output[pos]=wave1[(pos+mod)%width];
|
||||
if (++pos>=width) {
|
||||
pos=0;
|
||||
stage+=state.param1;
|
||||
if (stage>state.param2) stage=state.param2;
|
||||
}
|
||||
}
|
||||
updated=true;
|
||||
break;
|
||||
}
|
||||
divCounter=state.rateDivider;
|
||||
|
|
@ -169,11 +269,13 @@ void DivWaveSynth::init(DivInstrument* which, int w, int h, bool insChanged) {
|
|||
if (insChanged || !state.global) {
|
||||
pos=0;
|
||||
stage=0;
|
||||
divCounter=1+state.rateDivider;
|
||||
stageDir=false;
|
||||
divCounter=0;
|
||||
subDivCounter=0;
|
||||
first=true;
|
||||
|
||||
changeWave1(state.wave1);
|
||||
changeWave2(state.wave2);
|
||||
tick(true);
|
||||
first=true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ class DivWaveSynth {
|
|||
DivEngine* e;
|
||||
DivInstrumentWaveSynth state;
|
||||
int pos, stage, divCounter, width, height, subDivCounter;
|
||||
bool first, activeChangedB;
|
||||
bool first, activeChangedB, stageDir;
|
||||
unsigned char wave1[256];
|
||||
unsigned char wave2[256];
|
||||
public:
|
||||
|
|
@ -46,7 +46,7 @@ class DivWaveSynth {
|
|||
* tick this DivWaveSynth.
|
||||
* @return whether the wave has changed.
|
||||
*/
|
||||
bool tick();
|
||||
bool tick(bool skipSubDiv=false);
|
||||
/**
|
||||
* set the wave width.
|
||||
* @param value the width.
|
||||
|
|
@ -80,7 +80,8 @@ class DivWaveSynth {
|
|||
height(31),
|
||||
subDivCounter(0),
|
||||
first(false),
|
||||
activeChangedB(false) {
|
||||
activeChangedB(false),
|
||||
stageDir(false) {
|
||||
memset(wave1,0,256);
|
||||
memset(wave2,0,256);
|
||||
memset(output,0,sizeof(int)*256);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue