Merge branch 'master' of https://github.com/tildearrow/furnace into ymz280b
This commit is contained in:
commit
581f6d5d05
136 changed files with 82785 additions and 502 deletions
|
|
@ -169,6 +169,13 @@ enum DivDispatchCmds {
|
|||
DIV_CMD_N163_GLOBAL_WAVE_LOADLEN,
|
||||
DIV_CMD_N163_GLOBAL_WAVE_LOADMODE,
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -32,6 +32,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"
|
||||
|
|
@ -248,6 +249,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:
|
||||
|
|
|
|||
|
|
@ -58,6 +58,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
|
||||
};
|
||||
|
|
|
|||
|
|
@ -472,9 +472,9 @@ void DivPlatformGenesis::tick(bool sysTick) {
|
|||
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) {
|
||||
|
|
@ -499,7 +499,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);
|
||||
|
|
|
|||
|
|
@ -461,9 +461,9 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
|
|||
if (extMode) for (int i=0; i<4; i++) {
|
||||
if (opChan[i].freqChanged) {
|
||||
if (parent->song.linearPitch==2) {
|
||||
opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,false,4,opChan[i].pitch2,chipClock,CHIP_FREQBASE,11);
|
||||
opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,false,2,opChan[i].pitch2,chipClock,CHIP_FREQBASE,11);
|
||||
} else {
|
||||
int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,false,4,opChan[i].pitch2);
|
||||
int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,false,2,opChan[i].pitch2);
|
||||
int block=(opChan[i].baseFreq&0xf800)>>11;
|
||||
if (fNum<0) fNum=0;
|
||||
if (fNum>2047) {
|
||||
|
|
|
|||
|
|
@ -358,7 +358,7 @@ void DivPlatformN163::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
// TODO: what is this mess?
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,0,chan[i].pitch2,chipClock,CHIP_FREQBASE);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE);
|
||||
chan[i].freq=(((chan[i].freq*chan[i].waveLen)*(chanMax+1))/16);
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
if (chan[i].freq>0x3ffff) chan[i].freq=0x3ffff;
|
||||
|
|
|
|||
432
src/engine/platform/namcowsg.cpp
Normal file
432
src/engine/platform/namcowsg.cpp
Normal file
|
|
@ -0,0 +1,432 @@
|
|||
/**
|
||||
* 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 chWrite(c,a,v) \
|
||||
if (!skipRegisterWrites) { \
|
||||
if (curChan!=c) { \
|
||||
curChan=c; \
|
||||
rWrite(0,curChan); \
|
||||
} \
|
||||
regPool[16+((c)<<4)+((a)&0x0f)]=v; \
|
||||
rWrite(a,v); \
|
||||
}
|
||||
|
||||
#define CHIP_DIVIDER 32
|
||||
|
||||
const char* regCheatSheetNamcoWSG[]={
|
||||
"Select", "0",
|
||||
"MasterVol", "1",
|
||||
"FreqL", "2",
|
||||
"FreqH", "3",
|
||||
"DataCtl", "4",
|
||||
"ChanVol", "5",
|
||||
"WaveCtl", "6",
|
||||
"NoiseCtl", "7",
|
||||
"LFOFreq", "8",
|
||||
"LFOCtl", "9",
|
||||
NULL
|
||||
};
|
||||
|
||||
const char** DivPlatformNamcoWSG::getRegisterSheet() {
|
||||
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]=w.val;
|
||||
writes.pop();
|
||||
}
|
||||
namco->sound_stream_update(buf,len);
|
||||
}
|
||||
|
||||
void DivPlatformNamcoWSG::updateWave(int ch) {
|
||||
chWrite(ch,0x04,0x5f);
|
||||
chWrite(ch,0x04,0x1f);
|
||||
for (int i=0; i<32; i++) {
|
||||
chWrite(ch,0x06,chan[ch].ws.output[i]);
|
||||
}
|
||||
if (chan[ch].active) {
|
||||
chWrite(ch,0x04,0x80|chan[ch].outVol);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
chWrite(i,0x04,0x80|chan[i].outVol);
|
||||
}
|
||||
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_PERIODIC(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(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_PERIODIC(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.panL.had || chan[i].std.panR.had) {
|
||||
chWrite(i,0x05,isMuted[i]?0:chan[i].pan);
|
||||
}
|
||||
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,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
|
||||
if (chan[i].freq>4095) chan[i].freq=4095;
|
||||
chWrite(i,0x02,chan[i].freq&0xff);
|
||||
chWrite(i,0x03,chan[i].freq>>8);
|
||||
if (chan[i].keyOn) {
|
||||
//rWrite(16+i*5,0x80);
|
||||
//chWrite(i,0x04,0x80|chan[i].vol);
|
||||
}
|
||||
if (chan[i].keyOff) {
|
||||
chWrite(i,0x04,0);
|
||||
}
|
||||
if (chan[i].keyOn) chan[i].keyOn=false;
|
||||
if (chan[i].keyOff) chan[i].keyOff=false;
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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_PERIODIC(c.value);
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
}
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
chWrite(c.chan,0x04,0x80|chan[c.chan].vol);
|
||||
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;
|
||||
if (chan[c.chan].active) chWrite(c.chan,0x04,0x80|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_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_PERIODIC(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_STD_NOISE_MODE:
|
||||
chan[c.chan].noise=c.value;
|
||||
chWrite(c.chan,0x07,chan[c.chan].noise?(0x80|chan[c.chan].note):0);
|
||||
break;
|
||||
case DIV_CMD_PANNING: {
|
||||
chan[c.chan].pan=(c.value&0xf0)|(c.value2>>4);
|
||||
chWrite(c.chan,0x05,isMuted[c.chan]?0:chan[c.chan].pan);
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_LEGATO:
|
||||
chan[c.chan].baseFreq=NOTE_PERIODIC(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;
|
||||
chWrite(ch,0x05,isMuted[ch]?0:chan[ch].pan);
|
||||
}
|
||||
|
||||
void DivPlatformNamcoWSG::forceIns() {
|
||||
for (int i=0; i<chans; i++) {
|
||||
chan[i].insChanged=true;
|
||||
chan[i].freqChanged=true;
|
||||
updateWave(i);
|
||||
chWrite(i,0x05,isMuted[i]?0:chan[i].pan);
|
||||
}
|
||||
}
|
||||
|
||||
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 112;
|
||||
}
|
||||
|
||||
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->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:
|
||||
case 30:
|
||||
chans=8;
|
||||
break;
|
||||
default:
|
||||
chans=3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformNamcoWSG::setFlags(unsigned int flags) {
|
||||
chipClock=3072000;
|
||||
rate=chipClock/16;
|
||||
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() {
|
||||
}
|
||||
106
src/engine/platform/namcowsg.h
Normal file
106
src/engine/platform/namcowsg.h
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
/**
|
||||
* 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(31),
|
||||
outVol(31),
|
||||
wave(-1) {}
|
||||
};
|
||||
Channel chan[8];
|
||||
DivDispatchOscBuffer* oscBuf[8];
|
||||
bool isMuted[8];
|
||||
struct QueuedWrite {
|
||||
unsigned char addr;
|
||||
unsigned char val;
|
||||
QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {}
|
||||
};
|
||||
std::queue<QueuedWrite> writes;
|
||||
unsigned char lastPan;
|
||||
|
||||
int cycles, curChan, delay;
|
||||
int tempL[32];
|
||||
int tempR[32];
|
||||
namco_audio_device* namco;
|
||||
int devType, chans;
|
||||
unsigned char regPool[512];
|
||||
void updateWave(int ch);
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
public:
|
||||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
bool isStereo();
|
||||
bool keyOffAffectsArp(int ch);
|
||||
void setDeviceType(int type);
|
||||
void setFlags(unsigned int flags);
|
||||
void notifyWaveChange(int wave);
|
||||
void notifyInsDeletion(void* ins);
|
||||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformNamcoWSG();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -286,8 +286,8 @@ void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_
|
|||
adpcmB->output<2>(aOut,0);
|
||||
|
||||
if (!isMuted[adpcmChan]) {
|
||||
os[0]+=aOut.data[0];
|
||||
os[1]+=aOut.data[0];
|
||||
os[0]-=aOut.data[0]>>3;
|
||||
os[1]-=aOut.data[0]>>3;
|
||||
oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]+=aOut.data[0];
|
||||
} else {
|
||||
oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]=0;
|
||||
|
|
@ -594,7 +594,7 @@ void DivPlatformOPL::tick(bool sysTick) {
|
|||
bool updateDrums=false;
|
||||
for (int i=0; i<totalChans; i++) {
|
||||
if (chan[i].freqChanged) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2,chipClock,CHIP_FREQBASE);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq)*2,chan[i].pitch2,chipClock,CHIP_FREQBASE);
|
||||
if (chan[i].fixedFreq>0) chan[i].freq=chan[i].fixedFreq;
|
||||
if (chan[i].freq>131071) chan[i].freq=131071;
|
||||
int freqt=toFreq(chan[i].freq)+chan[i].pitch2;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
780
src/engine/platform/sound/namco.cpp
Normal file
780
src/engine/platform/sound/namco.cpp
Normal file
|
|
@ -0,0 +1,780 @@
|
|||
// 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;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
|
||||
/********************************************************************************/
|
||||
|
||||
/* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
121
src/engine/platform/sound/namco.h
Normal file
121
src/engine/platform/sound/namco.h
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
// license:BSD-3-Clause
|
||||
// copyright-holders:Nicola Salmoria,Aaron Giles
|
||||
#ifndef MAME_SOUND_NAMCO_H
|
||||
#define MAME_SOUND_NAMCO_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
|
||||
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);
|
||||
|
||||
~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);
|
||||
|
||||
~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);
|
||||
|
||||
~namco_cus30_device() {}
|
||||
};
|
||||
|
||||
#endif // MAME_SOUND_NAMCO_H
|
||||
326
src/engine/platform/sound/oki/okim6258.cpp
Normal file
326
src/engine/platform/sound/oki/okim6258.cpp
Normal file
|
|
@ -0,0 +1,326 @@
|
|||
// license:BSD-3-Clause
|
||||
// copyright-holders:Barry Rodewald
|
||||
/**********************************************************************************************
|
||||
*
|
||||
* OKI MSM6258 ADPCM
|
||||
*
|
||||
* TODO:
|
||||
* 3-bit ADPCM support
|
||||
* Recording?
|
||||
*
|
||||
**********************************************************************************************/
|
||||
|
||||
|
||||
#include "emu.h"
|
||||
#include "okim6258.h"
|
||||
|
||||
#define COMMAND_STOP (1 << 0)
|
||||
#define COMMAND_PLAY (1 << 1)
|
||||
#define COMMAND_RECORD (1 << 2)
|
||||
|
||||
#define STATUS_PLAYING (1 << 1)
|
||||
#define STATUS_RECORDING (1 << 2)
|
||||
|
||||
static const int dividers[4] = { 1024, 768, 512, 512 };
|
||||
|
||||
/* step size index shift table */
|
||||
static const int index_shift[8] = { -1, -1, -1, -1, 2, 4, 6, 8 };
|
||||
|
||||
/* lookup table for the precomputed difference */
|
||||
static int diff_lookup[49*16];
|
||||
|
||||
/* tables computed? */
|
||||
static int tables_computed = 0;
|
||||
|
||||
|
||||
|
||||
// device type definition
|
||||
DEFINE_DEVICE_TYPE(OKIM6258, okim6258_device, "okim6258", "OKI MSM6258 ADPCM")
|
||||
|
||||
|
||||
//**************************************************************************
|
||||
// LIVE DEVICE
|
||||
//**************************************************************************
|
||||
|
||||
//-------------------------------------------------
|
||||
// okim6258_device - constructor
|
||||
//-------------------------------------------------
|
||||
|
||||
okim6258_device::okim6258_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
|
||||
: device_t(mconfig, OKIM6258, tag, owner, clock),
|
||||
device_sound_interface(mconfig, *this),
|
||||
m_status(0),
|
||||
m_start_divider(0),
|
||||
m_divider(512),
|
||||
m_adpcm_type(0),
|
||||
m_data_in(0),
|
||||
m_nibble_shift(0),
|
||||
m_stream(nullptr),
|
||||
m_output_bits(0),
|
||||
m_signal(0),
|
||||
m_step(0)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**********************************************************************************************
|
||||
|
||||
compute_tables -- compute the difference tables
|
||||
|
||||
***********************************************************************************************/
|
||||
|
||||
static void compute_tables()
|
||||
{
|
||||
/* nibble to bit map */
|
||||
static const int nbl2bit[16][4] =
|
||||
{
|
||||
{ 1, 0, 0, 0}, { 1, 0, 0, 1}, { 1, 0, 1, 0}, { 1, 0, 1, 1},
|
||||
{ 1, 1, 0, 0}, { 1, 1, 0, 1}, { 1, 1, 1, 0}, { 1, 1, 1, 1},
|
||||
{-1, 0, 0, 0}, {-1, 0, 0, 1}, {-1, 0, 1, 0}, {-1, 0, 1, 1},
|
||||
{-1, 1, 0, 0}, {-1, 1, 0, 1}, {-1, 1, 1, 0}, {-1, 1, 1, 1}
|
||||
};
|
||||
|
||||
int step, nib;
|
||||
|
||||
/* loop over all possible steps */
|
||||
for (step = 0; step <= 48; step++)
|
||||
{
|
||||
/* compute the step value */
|
||||
int stepval = floor(16.0 * pow(11.0 / 10.0, (double)step));
|
||||
|
||||
/* loop over all nibbles and compute the difference */
|
||||
for (nib = 0; nib < 16; nib++)
|
||||
{
|
||||
diff_lookup[step*16 + nib] = nbl2bit[nib][0] *
|
||||
(stepval * nbl2bit[nib][1] +
|
||||
stepval/2 * nbl2bit[nib][2] +
|
||||
stepval/4 * nbl2bit[nib][3] +
|
||||
stepval/8);
|
||||
}
|
||||
}
|
||||
|
||||
tables_computed = 1;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// device_start - device-specific startup
|
||||
//-------------------------------------------------
|
||||
|
||||
void okim6258_device::device_start()
|
||||
{
|
||||
compute_tables();
|
||||
|
||||
m_divider = dividers[m_start_divider];
|
||||
|
||||
m_stream = stream_alloc(0, 1, clock()/m_divider);
|
||||
|
||||
m_signal = -2;
|
||||
m_step = 0;
|
||||
|
||||
state_save_register();
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// device_reset - device-specific reset
|
||||
//-------------------------------------------------
|
||||
|
||||
void okim6258_device::device_reset()
|
||||
{
|
||||
m_stream->update();
|
||||
|
||||
m_signal = -2;
|
||||
m_step = 0;
|
||||
m_status = 0;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// sound_stream_update - handle a stream update
|
||||
//-------------------------------------------------
|
||||
|
||||
void okim6258_device::sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs)
|
||||
{
|
||||
auto &buffer = outputs[0];
|
||||
|
||||
if (m_status & STATUS_PLAYING)
|
||||
{
|
||||
int nibble_shift = m_nibble_shift;
|
||||
|
||||
for (int sampindex = 0; sampindex < buffer.samples(); sampindex++)
|
||||
{
|
||||
/* Compute the new amplitude and update the current step */
|
||||
int nibble = (m_data_in >> nibble_shift) & 0xf;
|
||||
|
||||
/* Output to the buffer */
|
||||
int16_t sample = clock_adpcm(nibble);
|
||||
|
||||
nibble_shift ^= 4;
|
||||
|
||||
buffer.put_int(sampindex, sample, 32768);
|
||||
}
|
||||
|
||||
/* Update the parameters */
|
||||
m_nibble_shift = nibble_shift;
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer.fill(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**********************************************************************************************
|
||||
|
||||
state save support for MAME
|
||||
|
||||
***********************************************************************************************/
|
||||
|
||||
void okim6258_device::state_save_register()
|
||||
{
|
||||
save_item(NAME(m_status));
|
||||
save_item(NAME(m_divider));
|
||||
save_item(NAME(m_data_in));
|
||||
save_item(NAME(m_nibble_shift));
|
||||
save_item(NAME(m_signal));
|
||||
save_item(NAME(m_step));
|
||||
}
|
||||
|
||||
|
||||
int16_t okim6258_device::clock_adpcm(uint8_t nibble)
|
||||
{
|
||||
int32_t max = (1 << (m_output_bits - 1)) - 1;
|
||||
int32_t min = -(1 << (m_output_bits - 1));
|
||||
|
||||
m_signal += diff_lookup[m_step * 16 + (nibble & 15)];
|
||||
|
||||
/* clamp to the maximum */
|
||||
if (m_signal > max)
|
||||
m_signal = max;
|
||||
else if (m_signal < min)
|
||||
m_signal = min;
|
||||
|
||||
/* adjust the step size and clamp */
|
||||
m_step += index_shift[nibble & 7];
|
||||
if (m_step > 48)
|
||||
m_step = 48;
|
||||
else if (m_step < 0)
|
||||
m_step = 0;
|
||||
|
||||
/* return the signal scaled up to 32767 */
|
||||
return m_signal << 4;
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************************************
|
||||
|
||||
okim6258::set_divider -- set the master clock divider
|
||||
|
||||
***********************************************************************************************/
|
||||
|
||||
void okim6258_device::set_divider(int val)
|
||||
{
|
||||
m_divider = dividers[val];
|
||||
notify_clock_changed();
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************************************
|
||||
|
||||
okim6258::set_clock -- set the master clock
|
||||
|
||||
***********************************************************************************************/
|
||||
|
||||
void okim6258_device::device_clock_changed()
|
||||
{
|
||||
m_stream->set_sample_rate(clock() / m_divider);
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************************************
|
||||
|
||||
okim6258::get_vclk -- get the VCLK/sampling frequency
|
||||
|
||||
***********************************************************************************************/
|
||||
|
||||
int okim6258_device::get_vclk()
|
||||
{
|
||||
return (clock() / m_divider);
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************************************
|
||||
|
||||
okim6258_status_r -- read the status port of an OKIM6258-compatible chip
|
||||
|
||||
***********************************************************************************************/
|
||||
|
||||
uint8_t okim6258_device::status_r()
|
||||
{
|
||||
m_stream->update();
|
||||
|
||||
return (m_status & STATUS_PLAYING) ? 0x00 : 0x80;
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************************************
|
||||
|
||||
okim6258_data_w -- write to the control port of an OKIM6258-compatible chip
|
||||
|
||||
***********************************************************************************************/
|
||||
void okim6258_device::data_w(uint8_t data)
|
||||
{
|
||||
/* update the stream */
|
||||
m_stream->update();
|
||||
|
||||
m_data_in = data;
|
||||
m_nibble_shift = 0;
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************************************
|
||||
|
||||
okim6258_ctrl_w -- write to the control port of an OKIM6258-compatible chip
|
||||
|
||||
***********************************************************************************************/
|
||||
|
||||
void okim6258_device::ctrl_w(uint8_t data)
|
||||
{
|
||||
m_stream->update();
|
||||
|
||||
if (data & COMMAND_STOP)
|
||||
{
|
||||
m_status &= ~(STATUS_PLAYING | STATUS_RECORDING);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data & COMMAND_PLAY)
|
||||
{
|
||||
if (!(m_status & STATUS_PLAYING))
|
||||
{
|
||||
m_status |= STATUS_PLAYING;
|
||||
|
||||
/* Also reset the ADPCM parameters */
|
||||
m_signal = -2;
|
||||
m_step = 0;
|
||||
m_nibble_shift = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_status &= ~STATUS_PLAYING;
|
||||
}
|
||||
|
||||
if (data & COMMAND_RECORD)
|
||||
{
|
||||
logerror("M6258: Record enabled\n");
|
||||
m_status |= STATUS_RECORDING;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_status &= ~STATUS_RECORDING;
|
||||
}
|
||||
}
|
||||
74
src/engine/platform/sound/oki/okim6258.h
Normal file
74
src/engine/platform/sound/oki/okim6258.h
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
// license:BSD-3-Clause
|
||||
// copyright-holders:Barry Rodewald
|
||||
#ifndef MAME_SOUND_OKIM6258_H
|
||||
#define MAME_SOUND_OKIM6258_H
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
//**************************************************************************
|
||||
// TYPE DEFINITIONS
|
||||
//**************************************************************************
|
||||
|
||||
// ======================> okim6258_device
|
||||
|
||||
class okim6258_device : public device_t,
|
||||
public device_sound_interface
|
||||
{
|
||||
public:
|
||||
static constexpr int FOSC_DIV_BY_1024 = 0;
|
||||
static constexpr int FOSC_DIV_BY_768 = 1;
|
||||
static constexpr int FOSC_DIV_BY_512 = 2;
|
||||
|
||||
static constexpr int TYPE_3BITS = 0;
|
||||
static constexpr int TYPE_4BITS = 1;
|
||||
|
||||
static constexpr int OUTPUT_10BITS = 10;
|
||||
static constexpr int OUTPUT_12BITS = 12;
|
||||
|
||||
okim6258_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
|
||||
|
||||
// configuration
|
||||
void set_start_div(int div) { m_start_divider = div; }
|
||||
void set_type(int type) { m_adpcm_type = type; }
|
||||
void set_outbits(int outbit) { m_output_bits = outbit; }
|
||||
|
||||
uint8_t status_r();
|
||||
void data_w(uint8_t data);
|
||||
void ctrl_w(uint8_t data);
|
||||
|
||||
void set_divider(int val);
|
||||
int get_vclk();
|
||||
|
||||
protected:
|
||||
// device-level overrides
|
||||
virtual void device_start() override;
|
||||
virtual void device_reset() override;
|
||||
virtual void device_clock_changed() override;
|
||||
|
||||
// sound stream update overrides
|
||||
virtual void sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs) override;
|
||||
|
||||
private:
|
||||
void state_save_register();
|
||||
int16_t clock_adpcm(uint8_t nibble);
|
||||
|
||||
uint8_t m_status;
|
||||
|
||||
uint32_t m_start_divider;
|
||||
uint32_t m_divider; /* master clock divider */
|
||||
uint8_t m_adpcm_type; /* 3/4 bit ADPCM select */
|
||||
uint8_t m_data_in; /* ADPCM data-in register */
|
||||
uint8_t m_nibble_shift; /* nibble select */
|
||||
sound_stream *m_stream; /* which stream are we playing on? */
|
||||
|
||||
uint8_t m_output_bits; /* D/A precision is 10-bits but 12-bit data can be
|
||||
output serially to an external DAC */
|
||||
|
||||
int32_t m_signal;
|
||||
int32_t m_step;
|
||||
};
|
||||
|
||||
DECLARE_DEVICE_TYPE(OKIM6258, okim6258_device)
|
||||
|
||||
#endif // MAME_SOUND_OKIM6258_H
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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+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 (sample->loopStart>=0 && sample->loopStart<(int)sample->samples) {
|
||||
|
|
@ -242,6 +250,7 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) {
|
|||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_SU);
|
||||
if (chan[c.chan].pcm && ins->type!=DIV_INS_AMIGA) {
|
||||
chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA);
|
||||
writeControl(c.chan);
|
||||
writeControlUpper(c.chan);
|
||||
}
|
||||
|
|
@ -293,7 +302,113 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) {
|
|||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
case DIV_CMD_WAVE:
|
||||
chan[c.chan].wave=c.value;
|
||||
chan[c.chan].wave=c.value&7;
|
||||
writeControl(c.chan);
|
||||
break;
|
||||
case DIV_CMD_STD_NOISE_MODE:
|
||||
chan[c.chan].duty=c.value&127;
|
||||
chWrite(c.chan,0x08,chan[c.chan].duty);
|
||||
break;
|
||||
case DIV_CMD_C64_RESONANCE:
|
||||
chan[c.chan].res=c.value;
|
||||
chWrite(c.chan,0x09,chan[c.chan].res);
|
||||
break;
|
||||
case DIV_CMD_C64_FILTER_MODE:
|
||||
chan[c.chan].control=c.value&15;
|
||||
break;
|
||||
case DIV_CMD_SU_SWEEP_PERIOD_LOW: {
|
||||
switch (c.value) {
|
||||
case 0:
|
||||
chan[c.chan].freqSweepP=(chan[c.chan].freqSweepP&0xff00)|c.value2;
|
||||
chWrite(c.chan,0x10,chan[c.chan].freqSweepP&0xff);
|
||||
break;
|
||||
case 1:
|
||||
chan[c.chan].volSweepP=(chan[c.chan].volSweepP&0xff00)|c.value2;
|
||||
chWrite(c.chan,0x14,chan[c.chan].volSweepP&0xff);
|
||||
break;
|
||||
case 2:
|
||||
chan[c.chan].cutSweepP=(chan[c.chan].cutSweepP&0xff00)|c.value2;
|
||||
chWrite(c.chan,0x18,chan[c.chan].cutSweepP&0xff);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_SU_SWEEP_PERIOD_HIGH: {
|
||||
switch (c.value) {
|
||||
case 0:
|
||||
chan[c.chan].freqSweepP=(chan[c.chan].freqSweepP&0xff)|(c.value2<<8);
|
||||
chWrite(c.chan,0x11,chan[c.chan].freqSweepP>>8);
|
||||
break;
|
||||
case 1:
|
||||
chan[c.chan].volSweepP=(chan[c.chan].volSweepP&0xff)|(c.value2<<8);
|
||||
chWrite(c.chan,0x15,chan[c.chan].volSweepP>>8);
|
||||
break;
|
||||
case 2:
|
||||
chan[c.chan].cutSweepP=(chan[c.chan].cutSweepP&0xff)|(c.value2<<8);
|
||||
chWrite(c.chan,0x19,chan[c.chan].cutSweepP>>8);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_SU_SWEEP_BOUND: {
|
||||
switch (c.value) {
|
||||
case 0:
|
||||
chan[c.chan].freqSweepB=c.value2;
|
||||
chWrite(c.chan,0x13,chan[c.chan].freqSweepB);
|
||||
break;
|
||||
case 1:
|
||||
chan[c.chan].volSweepB=c.value2;
|
||||
chWrite(c.chan,0x17,chan[c.chan].volSweepB);
|
||||
break;
|
||||
case 2:
|
||||
chan[c.chan].cutSweepB=c.value2;
|
||||
chWrite(c.chan,0x1b,chan[c.chan].cutSweepB);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_SU_SWEEP_ENABLE: {
|
||||
switch (c.value) {
|
||||
case 0:
|
||||
chan[c.chan].freqSweepV=c.value2;
|
||||
chan[c.chan].freqSweep=(c.value2>0);
|
||||
chWrite(c.chan,0x12,chan[c.chan].freqSweepV);
|
||||
break;
|
||||
case 1:
|
||||
chan[c.chan].volSweepV=c.value2;
|
||||
chan[c.chan].volSweep=(c.value2>0);
|
||||
chWrite(c.chan,0x16,chan[c.chan].volSweepV);
|
||||
break;
|
||||
case 2:
|
||||
chan[c.chan].cutSweepV=c.value2;
|
||||
chan[c.chan].cutSweep=(c.value2>0);
|
||||
chWrite(c.chan,0x1a,chan[c.chan].cutSweepV);
|
||||
break;
|
||||
}
|
||||
writeControlUpper(c.chan);
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_SU_SYNC_PERIOD_LOW:
|
||||
chan[c.chan].syncTimer=(chan[c.chan].syncTimer&0xff00)|c.value;
|
||||
chan[c.chan].timerSync=(chan[c.chan].syncTimer>0);
|
||||
chWrite(c.chan,0x1e,chan[c.chan].syncTimer&0xff);
|
||||
chWrite(c.chan,0x1f,chan[c.chan].syncTimer>>8);
|
||||
writeControlUpper(c.chan);
|
||||
break;
|
||||
case DIV_CMD_SU_SYNC_PERIOD_HIGH:
|
||||
chan[c.chan].syncTimer=(chan[c.chan].syncTimer&0xff)|(c.value<<8);
|
||||
chan[c.chan].timerSync=(chan[c.chan].syncTimer>0);
|
||||
chWrite(c.chan,0x1e,chan[c.chan].syncTimer&0xff);
|
||||
chWrite(c.chan,0x1f,chan[c.chan].syncTimer>>8);
|
||||
writeControlUpper(c.chan);
|
||||
break;
|
||||
case DIV_CMD_C64_FINE_CUTOFF:
|
||||
chan[c.chan].baseCutoff=c.value;
|
||||
if (!chan[c.chan].std.ex1.has) {
|
||||
chan[c.chan].cutoff=chan[c.chan].baseCutoff;
|
||||
chWrite(c.chan,0x06,chan[c.chan].cutoff&0xff);
|
||||
chWrite(c.chan,0x07,chan[c.chan].cutoff>>8);
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
int destFreq=NOTE_FREQUENCY(c.value2);
|
||||
|
|
@ -323,6 +438,10 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) {
|
|||
chWrite(c.chan,0x03,chan[c.chan].pan);
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_SAMPLE_POS:
|
||||
chan[c.chan].hasOffset=c.value;
|
||||
chan[c.chan].keyOn=true;
|
||||
break;
|
||||
case DIV_CMD_LEGATO:
|
||||
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)));
|
||||
chan[c.chan].freqChanged=true;
|
||||
|
|
|
|||
|
|
@ -28,11 +28,15 @@
|
|||
class DivPlatformSoundUnit: public DivDispatch {
|
||||
struct Channel {
|
||||
int freq, baseFreq, pitch, pitch2, note;
|
||||
int ins, cutoff, res, control;
|
||||
int ins, cutoff, baseCutoff, res, control, hasOffset;
|
||||
signed char pan;
|
||||
unsigned char duty;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, phaseReset, filterPhaseReset;
|
||||
bool pcmLoop, timerSync, freqSweep, volSweep, cutSweep;
|
||||
unsigned short freqSweepP, volSweepP, cutSweepP;
|
||||
unsigned char freqSweepB, volSweepB, cutSweepB;
|
||||
unsigned char freqSweepV, volSweepV, cutSweepV;
|
||||
unsigned short syncTimer;
|
||||
signed char vol, outVol, wave;
|
||||
DivMacroInt std;
|
||||
void macroInit(DivInstrument* which) {
|
||||
|
|
@ -46,9 +50,11 @@ class DivPlatformSoundUnit: public DivDispatch {
|
|||
pitch2(0),
|
||||
note(0),
|
||||
ins(-1),
|
||||
cutoff(65535),
|
||||
cutoff(16383),
|
||||
baseCutoff(16380),
|
||||
res(0),
|
||||
control(0),
|
||||
hasOffset(0),
|
||||
pan(0),
|
||||
duty(63),
|
||||
active(false),
|
||||
|
|
@ -66,6 +72,16 @@ class DivPlatformSoundUnit: public DivDispatch {
|
|||
freqSweep(false),
|
||||
volSweep(false),
|
||||
cutSweep(false),
|
||||
freqSweepP(0),
|
||||
volSweepP(0),
|
||||
cutSweepP(0),
|
||||
freqSweepB(0),
|
||||
volSweepB(0),
|
||||
cutSweepB(0),
|
||||
freqSweepV(0),
|
||||
volSweepV(0),
|
||||
cutSweepV(0),
|
||||
syncTimer(0),
|
||||
vol(127),
|
||||
outVol(127),
|
||||
wave(0) {}
|
||||
|
|
|
|||
574
src/engine/platform/ym2608ext.cpp
Normal file
574
src/engine/platform/ym2608ext.cpp
Normal file
|
|
@ -0,0 +1,574 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2022 tildearrow and contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "ym2608ext.h"
|
||||
#include "../engine.h"
|
||||
#include <math.h>
|
||||
|
||||
#include "ym2610shared.h"
|
||||
#include "fmshared_OPN.h"
|
||||
|
||||
int DivPlatformYM2608Ext::dispatch(DivCommand c) {
|
||||
if (c.chan<2) {
|
||||
return DivPlatformYM2608::dispatch(c);
|
||||
}
|
||||
if (c.chan>5) {
|
||||
c.chan-=3;
|
||||
return DivPlatformYM2608::dispatch(c);
|
||||
}
|
||||
int ch=c.chan-2;
|
||||
int ordch=orderedOps[ch];
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
|
||||
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[ordch];
|
||||
DivInstrumentFM::Operator op=ins->fm.op[ordch];
|
||||
// TODO: how does this work?!
|
||||
if (isOpMuted[ch]) {
|
||||
rWrite(baseAddr+0x40,127);
|
||||
} else {
|
||||
if (opChan[ch].insChanged) {
|
||||
rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[ch].vol&0x7f))/127));
|
||||
}
|
||||
}
|
||||
if (opChan[ch].insChanged) {
|
||||
rWrite(baseAddr+0x30,(op.mult&15)|(dtTable[op.dt&7]<<4));
|
||||
rWrite(baseAddr+0x50,(op.ar&31)|(op.rs<<6));
|
||||
rWrite(baseAddr+0x60,(op.dr&31)|(op.am<<7));
|
||||
rWrite(baseAddr+0x70,op.d2r&31);
|
||||
rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4));
|
||||
rWrite(baseAddr+0x90,op.ssgEnv&15);
|
||||
}
|
||||
if (opChan[ch].insChanged) { // TODO how does this work?
|
||||
rWrite(chanOffs[2]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3));
|
||||
rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4));
|
||||
}
|
||||
opChan[ch].insChanged=false;
|
||||
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
opChan[ch].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
|
||||
opChan[ch].portaPause=false;
|
||||
opChan[ch].freqChanged=true;
|
||||
}
|
||||
opChan[ch].keyOn=true;
|
||||
opChan[ch].active=true;
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
opChan[ch].keyOff=true;
|
||||
opChan[ch].keyOn=false;
|
||||
opChan[ch].active=false;
|
||||
break;
|
||||
case DIV_CMD_VOLUME: {
|
||||
opChan[ch].vol=c.value;
|
||||
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[ordch];
|
||||
DivInstrumentFM::Operator op=ins->fm.op[ordch];
|
||||
if (isOpMuted[ch]) {
|
||||
rWrite(baseAddr+0x40,127);
|
||||
} else {
|
||||
rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[ch].vol&0x7f))/127));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_GET_VOLUME: {
|
||||
return opChan[ch].vol;
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_INSTRUMENT:
|
||||
if (opChan[ch].ins!=c.value || c.value2==1) {
|
||||
opChan[ch].insChanged=true;
|
||||
}
|
||||
opChan[ch].ins=c.value;
|
||||
break;
|
||||
case DIV_CMD_PANNING: {
|
||||
if (c.value==0 && c.value2==0) {
|
||||
opChan[ch].pan=3;
|
||||
} else {
|
||||
opChan[ch].pan=(c.value2>0)|((c.value>0)<<1);
|
||||
}
|
||||
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
|
||||
if (parent->song.sharedExtStat) {
|
||||
for (int i=0; i<4; i++) {
|
||||
if (ch==i) continue;
|
||||
opChan[i].pan=opChan[ch].pan;
|
||||
}
|
||||
}
|
||||
rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4));
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_PITCH: {
|
||||
opChan[ch].pitch=c.value;
|
||||
opChan[ch].freqChanged=true;
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
if (parent->song.linearPitch==2) {
|
||||
int destFreq=NOTE_FREQUENCY(c.value2);
|
||||
bool return2=false;
|
||||
if (destFreq>opChan[ch].baseFreq) {
|
||||
opChan[ch].baseFreq+=c.value;
|
||||
if (opChan[ch].baseFreq>=destFreq) {
|
||||
opChan[ch].baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
} else {
|
||||
opChan[ch].baseFreq-=c.value;
|
||||
if (opChan[ch].baseFreq<=destFreq) {
|
||||
opChan[ch].baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
}
|
||||
opChan[ch].freqChanged=true;
|
||||
if (return2) {
|
||||
//opChan[ch].inPorta=false;
|
||||
return 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false);
|
||||
int boundaryTop=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,12,false);
|
||||
int destFreq=NOTE_FNUM_BLOCK(c.value2,11);
|
||||
int newFreq;
|
||||
bool return2=false;
|
||||
if (opChan[ch].portaPause) {
|
||||
opChan[ch].baseFreq=opChan[ch].portaPauseFreq;
|
||||
}
|
||||
if (destFreq>opChan[ch].baseFreq) {
|
||||
newFreq=opChan[ch].baseFreq+c.value;
|
||||
if (newFreq>=destFreq) {
|
||||
newFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
} else {
|
||||
newFreq=opChan[ch].baseFreq-c.value;
|
||||
if (newFreq<=destFreq) {
|
||||
newFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
}
|
||||
// what the heck!
|
||||
if (!opChan[ch].portaPause) {
|
||||
if ((newFreq&0x7ff)>boundaryTop && (newFreq&0xf800)<0x3800) {
|
||||
if (parent->song.fbPortaPause) {
|
||||
opChan[ch].portaPauseFreq=(boundaryBottom)|((newFreq+0x800)&0xf800);
|
||||
opChan[ch].portaPause=true;
|
||||
break;
|
||||
} else {
|
||||
newFreq=(newFreq>>1)|((newFreq+0x800)&0xf800);
|
||||
}
|
||||
}
|
||||
if ((newFreq&0x7ff)<boundaryBottom && (newFreq&0xf800)>0) {
|
||||
if (parent->song.fbPortaPause) {
|
||||
opChan[ch].portaPauseFreq=newFreq=(boundaryTop-1)|((newFreq-0x800)&0xf800);
|
||||
opChan[ch].portaPause=true;
|
||||
break;
|
||||
} else {
|
||||
newFreq=(newFreq<<1)|((newFreq-0x800)&0xf800);
|
||||
}
|
||||
}
|
||||
}
|
||||
opChan[ch].portaPause=false;
|
||||
opChan[ch].freqChanged=true;
|
||||
opChan[ch].baseFreq=newFreq;
|
||||
if (return2) return 2;
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_LEGATO: {
|
||||
opChan[ch].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
|
||||
opChan[ch].freqChanged=true;
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_LFO: {
|
||||
rWrite(0x22,(c.value&7)|((c.value>>4)<<3));
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_MULT: { // TODO
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
|
||||
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
|
||||
DivInstrumentFM::Operator op=ins->fm.op[orderedOps[c.value]];
|
||||
rWrite(baseAddr+0x30,(c.value2&15)|(dtTable[op.dt&7]<<4));
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_TL: { // TODO
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
|
||||
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
|
||||
if (isOutput[ins->fm.alg][c.value]) {
|
||||
rWrite(baseAddr+0x40,127-(((127-c.value2)*(opChan[ch].vol&0x7f))/127));
|
||||
} else {
|
||||
rWrite(baseAddr+0x40,c.value2);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_AR: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[i];
|
||||
op.ar=c.value2&31;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[i];
|
||||
rWrite(baseAddr+0x50,(op.ar&31)|(op.rs<<6));
|
||||
}
|
||||
} else {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
|
||||
op.ar=c.value2&31;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+0x50,(op.ar&31)|(op.rs<<6));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_RS: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[i];
|
||||
op.rs=c.value2&3;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
|
||||
op.rs=c.value2&3;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_AM: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[i];
|
||||
op.am=c.value2&1;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
|
||||
op.am=c.value2&1;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_DR: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[i];
|
||||
op.dr=c.value2&31;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
|
||||
op.dr=c.value2&31;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_SL: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[i];
|
||||
op.sl=c.value2&15;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
|
||||
op.sl=c.value2&15;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_RR: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[i];
|
||||
op.rr=c.value2&15;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
|
||||
op.rr=c.value2&15;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_D2R: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[i];
|
||||
op.d2r=c.value2&31;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31);
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
|
||||
op.d2r=c.value2&31;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_DT: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[i];
|
||||
op.dt=c.value&7;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
|
||||
op.dt=c.value2&7;
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_SSG: {
|
||||
if (c.value<0) {
|
||||
for (int i=0; i<4; i++) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[i];
|
||||
op.ssgEnv=8^(c.value2&15);
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[i];
|
||||
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
|
||||
}
|
||||
} else if (c.value<4) {
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
|
||||
op.ssgEnv=8^(c.value2&15);
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
|
||||
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
return 127;
|
||||
break;
|
||||
case DIV_ALWAYS_SET_VOLUME:
|
||||
return 0;
|
||||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
break;
|
||||
default:
|
||||
//printf("WARNING: unimplemented command %d\n",c.cmd);
|
||||
break;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int opChanOffsL[4]={
|
||||
0xa9, 0xaa, 0xa8, 0xa2
|
||||
};
|
||||
|
||||
static int opChanOffsH[4]={
|
||||
0xad, 0xae, 0xac, 0xa6
|
||||
};
|
||||
|
||||
void DivPlatformYM2608Ext::tick(bool sysTick) {
|
||||
if (extMode) {
|
||||
bool writeSomething=false;
|
||||
unsigned char writeMask=2;
|
||||
for (int i=0; i<4; i++) {
|
||||
writeMask|=opChan[i].active<<(4+i);
|
||||
if (opChan[i].keyOn || opChan[i].keyOff) {
|
||||
writeSomething=true;
|
||||
writeMask&=~(1<<(4+i));
|
||||
opChan[i].keyOff=false;
|
||||
}
|
||||
}
|
||||
if (writeSomething) {
|
||||
immWrite(0x28,writeMask);
|
||||
}
|
||||
}
|
||||
|
||||
DivPlatformYM2608::tick(sysTick);
|
||||
|
||||
bool writeNoteOn=false;
|
||||
unsigned char writeMask=2;
|
||||
if (extMode) for (int i=0; i<4; i++) {
|
||||
if (opChan[i].freqChanged) {
|
||||
if (parent->song.linearPitch==2) {
|
||||
opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,false,4,opChan[i].pitch2,chipClock,CHIP_FREQBASE,11);
|
||||
} else {
|
||||
int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,false,4,opChan[i].pitch2);
|
||||
int block=(opChan[i].baseFreq&0xf800)>>11;
|
||||
if (fNum<0) fNum=0;
|
||||
if (fNum>2047) {
|
||||
while (block<7) {
|
||||
fNum>>=1;
|
||||
block++;
|
||||
}
|
||||
if (fNum>2047) fNum=2047;
|
||||
}
|
||||
opChan[i].freq=(block<<11)|fNum;
|
||||
}
|
||||
if (opChan[i].freq>0x3fff) opChan[i].freq=0x3fff;
|
||||
immWrite(opChanOffsH[i],opChan[i].freq>>8);
|
||||
immWrite(opChanOffsL[i],opChan[i].freq&0xff);
|
||||
}
|
||||
writeMask|=opChan[i].active<<(4+i);
|
||||
if (opChan[i].keyOn) {
|
||||
writeNoteOn=true;
|
||||
writeMask|=1<<(4+i);
|
||||
opChan[i].keyOn=false;
|
||||
}
|
||||
}
|
||||
if (writeNoteOn) {
|
||||
immWrite(0x28,writeMask);
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformYM2608Ext::muteChannel(int ch, bool mute) {
|
||||
if (ch<2) {
|
||||
DivPlatformYM2608::muteChannel(ch,mute);
|
||||
return;
|
||||
}
|
||||
if (ch>5) {
|
||||
DivPlatformYM2608::muteChannel(ch-3,mute);
|
||||
return;
|
||||
}
|
||||
isOpMuted[ch-2]=mute;
|
||||
|
||||
int ordch=orderedOps[ch-2];
|
||||
DivInstrument* ins=parent->getIns(opChan[ch-2].ins,DIV_INS_FM);
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[ordch];
|
||||
DivInstrumentFM::Operator op=ins->fm.op[ordch];
|
||||
if (isOpMuted[ch-2]) {
|
||||
rWrite(baseAddr+0x40,127);
|
||||
} else if (isOutput[ins->fm.alg][ordch]) {
|
||||
rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[ch-2].vol&0x7f))/127));
|
||||
} else {
|
||||
rWrite(baseAddr+0x40,op.tl);
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformYM2608Ext::forceIns() {
|
||||
for (int i=0; i<6; i++) {
|
||||
for (int j=0; j<4; j++) {
|
||||
unsigned short baseAddr=chanOffs[i]|opOffs[j];
|
||||
DivInstrumentFM::Operator& op=chan[i].state.op[j];
|
||||
if (i==2) { // extended channel
|
||||
if (isOpMuted[j]) {
|
||||
rWrite(baseAddr+0x40,127);
|
||||
} else if (isOutput[chan[i].state.alg][j]) {
|
||||
rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[j].vol&0x7f))/127));
|
||||
} else {
|
||||
rWrite(baseAddr+0x40,op.tl);
|
||||
}
|
||||
} else {
|
||||
if (isMuted[i]) {
|
||||
rWrite(baseAddr+ADDR_TL,127);
|
||||
} else {
|
||||
if (isOutput[chan[i].state.alg][j]) {
|
||||
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127));
|
||||
} else {
|
||||
rWrite(baseAddr+ADDR_TL,op.tl);
|
||||
}
|
||||
}
|
||||
}
|
||||
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
|
||||
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
|
||||
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
|
||||
rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31);
|
||||
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
|
||||
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
|
||||
}
|
||||
rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3));
|
||||
rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
|
||||
if (chan[i].active) {
|
||||
chan[i].keyOn=true;
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
for (int i=6; i<16; i++) {
|
||||
chan[i].insChanged=true;
|
||||
}
|
||||
ay->forceIns();
|
||||
ay->flushWrites();
|
||||
for (DivRegWrite& i: ay->getRegisterWrites()) {
|
||||
immWrite(i.addr&15,i.val);
|
||||
}
|
||||
ay->getRegisterWrites().clear();
|
||||
for (int i=0; i<4; i++) {
|
||||
opChan[i].insChanged=true;
|
||||
if (opChan[i].active) {
|
||||
opChan[i].keyOn=true;
|
||||
opChan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void* DivPlatformYM2608Ext::getChanState(int ch) {
|
||||
if (ch>=6) return &chan[ch-3];
|
||||
if (ch>=2) return &opChan[ch-2];
|
||||
return &chan[ch];
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformYM2608Ext::getOscBuffer(int ch) {
|
||||
if (ch>=6) return oscBuf[ch-3];
|
||||
if (ch<3) return oscBuf[ch];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformYM2608Ext::reset() {
|
||||
DivPlatformYM2608::reset();
|
||||
|
||||
for (int i=0; i<4; i++) {
|
||||
opChan[i]=DivPlatformYM2608Ext::OpChannel();
|
||||
opChan[i].vol=127;
|
||||
}
|
||||
|
||||
// channel 2 mode
|
||||
immWrite(0x27,0x40);
|
||||
extMode=true;
|
||||
}
|
||||
|
||||
bool DivPlatformYM2608Ext::keyOffAffectsArp(int ch) {
|
||||
return (ch>8);
|
||||
}
|
||||
|
||||
void DivPlatformYM2608Ext::notifyInsChange(int ins) {
|
||||
DivPlatformYM2608::notifyInsChange(ins);
|
||||
for (int i=0; i<4; i++) {
|
||||
if (opChan[i].ins==ins) {
|
||||
opChan[i].insChanged=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformYM2608Ext::init(DivEngine* parent, int channels, int sugRate, unsigned int flags) {
|
||||
DivPlatformYM2608::init(parent,channels,sugRate,flags);
|
||||
for (int i=0; i<4; i++) {
|
||||
isOpMuted[i]=false;
|
||||
}
|
||||
|
||||
reset();
|
||||
return 19;
|
||||
}
|
||||
|
||||
void DivPlatformYM2608Ext::quit() {
|
||||
DivPlatformYM2608::quit();
|
||||
}
|
||||
|
||||
DivPlatformYM2608Ext::~DivPlatformYM2608Ext() {
|
||||
}
|
||||
51
src/engine/platform/ym2608ext.h
Normal file
51
src/engine/platform/ym2608ext.h
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2022 tildearrow and contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "../dispatch.h"
|
||||
|
||||
#include "ym2608.h"
|
||||
|
||||
class DivPlatformYM2608Ext: public DivPlatformYM2608 {
|
||||
struct OpChannel {
|
||||
DivMacroInt std;
|
||||
unsigned char freqH, freqL;
|
||||
int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
|
||||
signed char konCycles;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause;
|
||||
int vol;
|
||||
unsigned char pan;
|
||||
OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), portaPauseFreq(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {}
|
||||
};
|
||||
OpChannel opChan[4];
|
||||
bool isOpMuted[4];
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
public:
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
bool keyOffAffectsArp(int ch);
|
||||
void notifyInsChange(int ins);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformYM2608Ext();
|
||||
};
|
||||
|
|
@ -40,10 +40,30 @@ const char* DivPlatformZXBeeper::getEffectName(unsigned char effect) {
|
|||
}
|
||||
|
||||
void DivPlatformZXBeeper::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
bool o=false;
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
// clock here
|
||||
bool o=false;
|
||||
if (curSample>=0 && curSample<parent->song.sampleLen) {
|
||||
if (--curSamplePeriod<0) {
|
||||
DivSample* s=parent->getSample(curSample);
|
||||
if (s->samples>0) {
|
||||
sampleOut=(s->data8[curSamplePos++]>0);
|
||||
if (curSamplePos>=s->samples) curSample=-1;
|
||||
// 256 bits
|
||||
if (curSamplePos>2047) curSample=-1;
|
||||
|
||||
curSamplePeriod=15;
|
||||
} else {
|
||||
curSample=-1;
|
||||
}
|
||||
}
|
||||
o=sampleOut;
|
||||
bufL[h]=o?16384:0;
|
||||
continue;
|
||||
}
|
||||
|
||||
unsigned short oldPos=chan[curChan].sPosition;
|
||||
o=false;
|
||||
|
||||
if (sOffTimer) {
|
||||
sOffTimer--;
|
||||
|
|
@ -95,7 +115,7 @@ void DivPlatformZXBeeper::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
if (chan[i].active) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,0,chan[i].pitch2,chipClock,CHIP_FREQBASE);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE);
|
||||
if (chan[i].freq>65535) chan[i].freq=65535;
|
||||
}
|
||||
if (chan[i].keyOn) {
|
||||
|
|
@ -128,9 +148,7 @@ int DivPlatformZXBeeper::dispatch(DivCommand c) {
|
|||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
chan[c.chan].dacSample=-1;
|
||||
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
|
||||
chan[c.chan].pcm=false;
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].macroInit(NULL);
|
||||
|
|
@ -190,13 +208,9 @@ int DivPlatformZXBeeper::dispatch(DivCommand c) {
|
|||
chan[c.chan].duty=c.value;
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_MODE:
|
||||
chan[c.chan].pcm=c.value;
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_BANK:
|
||||
sampleBank=c.value;
|
||||
if (sampleBank>(parent->song.sample.size()/12)) {
|
||||
sampleBank=parent->song.sample.size()/12;
|
||||
}
|
||||
curSample=c.value;
|
||||
curSamplePos=0;
|
||||
curSamplePeriod=0;
|
||||
break;
|
||||
case DIV_CMD_LEGATO:
|
||||
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)));
|
||||
|
|
@ -265,8 +279,11 @@ void DivPlatformZXBeeper::reset() {
|
|||
cycles=0;
|
||||
curChan=0;
|
||||
sOffTimer=0;
|
||||
sampleBank=0;
|
||||
ulaOut=0;
|
||||
curSample=-1;
|
||||
curSamplePos=0;
|
||||
curSamplePeriod=0;
|
||||
sampleOut=false;
|
||||
}
|
||||
|
||||
bool DivPlatformZXBeeper::keyOffAffectsArp(int ch) {
|
||||
|
|
|
|||
|
|
@ -27,10 +27,8 @@
|
|||
class DivPlatformZXBeeper: public DivDispatch {
|
||||
struct Channel {
|
||||
int freq, baseFreq, pitch, pitch2, note;
|
||||
int dacPeriod, dacRate;
|
||||
unsigned int dacPos;
|
||||
int dacSample, ins;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, furnaceDac;
|
||||
int ins;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta;
|
||||
signed char vol, outVol;
|
||||
unsigned short sPosition;
|
||||
unsigned char duty;
|
||||
|
|
@ -45,10 +43,6 @@ class DivPlatformZXBeeper: public DivDispatch {
|
|||
pitch(0),
|
||||
pitch2(0),
|
||||
note(0),
|
||||
dacPeriod(0),
|
||||
dacRate(0),
|
||||
dacPos(0),
|
||||
dacSample(-1),
|
||||
ins(-1),
|
||||
active(false),
|
||||
insChanged(true),
|
||||
|
|
@ -56,9 +50,6 @@ class DivPlatformZXBeeper: public DivDispatch {
|
|||
keyOn(false),
|
||||
keyOff(false),
|
||||
inPorta(false),
|
||||
noise(false),
|
||||
pcm(false),
|
||||
furnaceDac(false),
|
||||
vol(1),
|
||||
outVol(1),
|
||||
sPosition(0),
|
||||
|
|
@ -75,11 +66,12 @@ class DivPlatformZXBeeper: public DivDispatch {
|
|||
std::queue<QueuedWrite> writes;
|
||||
unsigned char lastPan, ulaOut;
|
||||
|
||||
int cycles, curChan, sOffTimer, delay;
|
||||
int cycles, curChan, sOffTimer, delay, curSample, curSamplePeriod;
|
||||
unsigned int curSamplePos;
|
||||
int tempL[32];
|
||||
int tempR[32];
|
||||
unsigned char sampleBank, lfoMode, lfoSpeed;
|
||||
unsigned char regPool[128];
|
||||
bool sampleOut;
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
public:
|
||||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
|
|
|
|||
|
|
@ -171,6 +171,13 @@ const char* cmdName[]={
|
|||
"N163_GLOBAL_WAVE_LOADLEN",
|
||||
"N163_GLOBAL_WAVE_LOADMODE",
|
||||
|
||||
"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"
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -118,12 +118,6 @@ bool DivSample::initInternal(unsigned char d, int count) {
|
|||
dataB=new unsigned char[(lengthB+255)&(~0xff)];
|
||||
memset(dataB,0,(lengthB+255)&(~0xff));
|
||||
break;
|
||||
case 7: // X68000 ADPCM
|
||||
if (dataX68!=NULL) delete[] dataX68;
|
||||
lengthX68=(count+1)/2;
|
||||
dataX68=new unsigned char[lengthX68];
|
||||
memset(dataX68,0,lengthX68);
|
||||
break;
|
||||
case 8: // 8-bit
|
||||
if (data8!=NULL) delete[] data8;
|
||||
length8=count;
|
||||
|
|
@ -676,9 +670,6 @@ void DivSample::render() {
|
|||
case 6: // ADPCM-B
|
||||
ymb_decode(dataB,data16,samples);
|
||||
break;
|
||||
case 7: // X6800 ADPCM
|
||||
oki6258_decode(dataX68,data16,samples);
|
||||
break;
|
||||
case 8: // 8-bit PCM
|
||||
for (unsigned int i=0; i<samples; i++) {
|
||||
data16[i]=data8[i]<<8;
|
||||
|
|
@ -736,10 +727,6 @@ void DivSample::render() {
|
|||
if (!initInternal(6,samples)) return;
|
||||
ymb_encode(data16,dataB,(samples+511)&(~0x1ff));
|
||||
}
|
||||
if (depth!=7) { // X68000 ADPCM
|
||||
if (!initInternal(7,samples)) return;
|
||||
oki6258_encode(data16,dataX68,samples);
|
||||
}
|
||||
if (depth!=8) { // 8-bit PCM
|
||||
if (!initInternal(8,samples)) return;
|
||||
for (unsigned int i=0; i<samples; i++) {
|
||||
|
|
@ -767,8 +754,6 @@ void* DivSample::getCurBuf() {
|
|||
return dataA;
|
||||
case 6:
|
||||
return dataB;
|
||||
case 7:
|
||||
return dataX68;
|
||||
case 8:
|
||||
return data8;
|
||||
case 9:
|
||||
|
|
@ -795,8 +780,6 @@ unsigned int DivSample::getCurBufLen() {
|
|||
return lengthA;
|
||||
case 6:
|
||||
return lengthB;
|
||||
case 7:
|
||||
return lengthX68;
|
||||
case 8:
|
||||
return length8;
|
||||
case 9:
|
||||
|
|
@ -903,7 +886,6 @@ DivSample::~DivSample() {
|
|||
if (dataQSoundA) delete[] dataQSoundA;
|
||||
if (dataA) delete[] dataA;
|
||||
if (dataB) delete[] dataB;
|
||||
if (dataX68) delete[] dataX68;
|
||||
if (dataBRR) delete[] dataBRR;
|
||||
if (dataVOX) delete[] dataVOX;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,10 +66,9 @@ struct DivSample {
|
|||
// - 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
|
||||
unsigned char depth;
|
||||
|
||||
|
|
@ -82,12 +81,11 @@ struct DivSample {
|
|||
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, lengthZ, lengthQSoundA, lengthA, lengthB, lengthX68, lengthBRR, lengthVOX;
|
||||
unsigned int off8, off16, off1, offDPCM, offZ, offQSoundA, offA, offB, offX68, offBRR, offVOX;
|
||||
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, offSU, offYMZ280B;
|
||||
|
||||
unsigned int samples;
|
||||
|
|
@ -224,7 +222,6 @@ struct DivSample {
|
|||
dataQSoundA(NULL),
|
||||
dataA(NULL),
|
||||
dataB(NULL),
|
||||
dataX68(NULL),
|
||||
dataBRR(NULL),
|
||||
dataVOX(NULL),
|
||||
length8(0),
|
||||
|
|
@ -235,7 +232,6 @@ struct DivSample {
|
|||
lengthQSoundA(0),
|
||||
lengthA(0),
|
||||
lengthB(0),
|
||||
lengthX68(0),
|
||||
lengthBRR(0),
|
||||
lengthVOX(0),
|
||||
off8(0),
|
||||
|
|
@ -246,7 +242,6 @@ struct DivSample {
|
|||
offQSoundA(0),
|
||||
offA(0),
|
||||
offB(0),
|
||||
offX68(0),
|
||||
offBRR(0),
|
||||
offVOX(0),
|
||||
offSegaPCM(0),
|
||||
|
|
|
|||
|
|
@ -108,6 +108,9 @@ enum DivSystem {
|
|||
DIV_SYSTEM_MSM6295,
|
||||
DIV_SYSTEM_MSM6258,
|
||||
DIV_SYSTEM_YMZ280B,
|
||||
DIV_SYSTEM_NAMCO,
|
||||
DIV_SYSTEM_NAMCO_15XX,
|
||||
DIV_SYSTEM_NAMCO_CUS30,
|
||||
DIV_SYSTEM_DUMMY
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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"},
|
||||
|
|
@ -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(
|
||||
|
|
@ -1821,7 +1836,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"},
|
||||
|
|
@ -1833,7 +1848,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"},
|
||||
|
|
@ -1862,7 +1877,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(
|
||||
|
|
@ -1892,9 +1975,42 @@ void DivEngine::registerSystems() {
|
|||
{DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}
|
||||
);
|
||||
|
||||
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},
|
||||
{},
|
||||
waveOnlyEffectHandler
|
||||
);
|
||||
|
||||
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},
|
||||
{},
|
||||
waveOnlyEffectHandler
|
||||
);
|
||||
|
||||
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},
|
||||
{},
|
||||
waveOnlyEffectHandler
|
||||
);
|
||||
|
||||
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},
|
||||
|
|
|
|||
|
|
@ -167,6 +167,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 +241,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);
|
||||
|
|
@ -317,7 +357,6 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
|
|||
w->writeC(0xd6+i);
|
||||
}
|
||||
break;
|
||||
// TODO: it's 3:35am
|
||||
case DIV_SYSTEM_OPL:
|
||||
case DIV_SYSTEM_OPL_DRUMS:
|
||||
// disable envelope
|
||||
|
|
@ -342,6 +381,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
|
||||
|
|
@ -538,10 +602,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:
|
||||
|
|
@ -589,6 +669,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);
|
||||
|
|
@ -795,7 +881,9 @@ 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};
|
||||
|
|
@ -906,11 +994,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++;
|
||||
}
|
||||
|
|
@ -999,6 +1087,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;
|
||||
|
|
@ -1010,6 +1099,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:
|
||||
|
|
@ -1090,6 +1193,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) {
|
||||
|
|
@ -1408,23 +1525,45 @@ 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));
|
||||
}
|
||||
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));
|
||||
}
|
||||
if (writeQSound[i]!=NULL && writeQSound[i]->getSampleMemUsage()>0) {
|
||||
unsigned int blockSize=(writeQSound[i]->getSampleMemUsage()+0xffff)&(~0xffff);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue