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:
cam900 2022-05-25 00:52:00 +09:00
commit 17881837ab
156 changed files with 86578 additions and 714 deletions

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

@ -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),

View file

@ -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");

View file

@ -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
};

View file

@ -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);

View file

@ -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;
};

View file

@ -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);

View file

@ -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) {

View file

@ -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;

View 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() {
}

View 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

View file

@ -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();

View file

@ -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;

View 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];
}
}

View 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

View file

@ -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>

View 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);
}
}
}
}
}

View 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

View 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;
}
}

View 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

View 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;
}

View 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

View file

@ -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);

View 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));
}

View 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

View file

@ -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;

View file

@ -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) {}

View file

@ -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;
}

View 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() {
}

View 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();
};

View file

@ -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;
}

View 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];
}
}

View 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

View file

@ -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) {

View file

@ -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);

View file

@ -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++) {

View file

@ -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;
}

View file

@ -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();
};

View file

@ -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),

View file

@ -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},

View file

@ -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

View file

@ -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;
}
}

View file

@ -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);