mirror of
https://github.com/tildearrow/furnace.git
synced 2024-11-04 20:05:05 +00:00
Merge branch 'master' of https://github.com/tildearrow/furnace into rf5c68
This commit is contained in:
commit
27a412c134
20 changed files with 2468 additions and 74 deletions
|
@ -319,6 +319,8 @@ src/engine/platform/sound/vrcvi/vrcvi.cpp
|
||||||
|
|
||||||
src/engine/platform/sound/scc/scc.cpp
|
src/engine/platform/sound/scc/scc.cpp
|
||||||
|
|
||||||
|
src/engine/platform/sound/ymz280b.cpp
|
||||||
|
|
||||||
src/engine/platform/sound/rf5c68.cpp
|
src/engine/platform/sound/rf5c68.cpp
|
||||||
|
|
||||||
src/engine/platform/oplAInterface.cpp
|
src/engine/platform/oplAInterface.cpp
|
||||||
|
@ -386,6 +388,8 @@ src/engine/platform/pet.cpp
|
||||||
src/engine/platform/vic20.cpp
|
src/engine/platform/vic20.cpp
|
||||||
src/engine/platform/vrc6.cpp
|
src/engine/platform/vrc6.cpp
|
||||||
src/engine/platform/scc.cpp
|
src/engine/platform/scc.cpp
|
||||||
|
src/engine/platform/ymz280b.cpp
|
||||||
|
src/engine/platform/namcowsg.cpp
|
||||||
src/engine/platform/rf5c68.cpp
|
src/engine/platform/rf5c68.cpp
|
||||||
src/engine/platform/dummy.cpp
|
src/engine/platform/dummy.cpp
|
||||||
)
|
)
|
||||||
|
|
|
@ -59,6 +59,7 @@
|
||||||
#include "platform/fds.h"
|
#include "platform/fds.h"
|
||||||
#include "platform/mmc5.h"
|
#include "platform/mmc5.h"
|
||||||
#include "platform/scc.h"
|
#include "platform/scc.h"
|
||||||
|
#include "platform/ymz280b.h"
|
||||||
#include "platform/rf5c68.h"
|
#include "platform/rf5c68.h"
|
||||||
#include "platform/dummy.h"
|
#include "platform/dummy.h"
|
||||||
#include "../ta-log.h"
|
#include "../ta-log.h"
|
||||||
|
@ -352,6 +353,10 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
||||||
dispatch=new DivPlatformSCC;
|
dispatch=new DivPlatformSCC;
|
||||||
((DivPlatformSCC*)dispatch)->setChipModel(true);
|
((DivPlatformSCC*)dispatch)->setChipModel(true);
|
||||||
break;
|
break;
|
||||||
|
case DIV_SYSTEM_YMZ280B:
|
||||||
|
dispatch=new DivPlatformYMZ280B;
|
||||||
|
((DivPlatformYMZ280B*)dispatch)->setChipModel(280);
|
||||||
|
break;
|
||||||
case DIV_SYSTEM_RF5C68:
|
case DIV_SYSTEM_RF5C68:
|
||||||
dispatch=new DivPlatformRF5C68;
|
dispatch=new DivPlatformRF5C68;
|
||||||
break;
|
break;
|
||||||
|
|
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
|
|
@ -80,11 +80,13 @@ namco_cus30_device::namco_cus30_device(uint32_t clock)
|
||||||
// device_start - device-specific startup
|
// device_start - device-specific startup
|
||||||
//-------------------------------------------------
|
//-------------------------------------------------
|
||||||
|
|
||||||
void namco_audio_device::device_start()
|
void namco_audio_device::device_start(unsigned char* wavePtr)
|
||||||
{
|
{
|
||||||
/* extract globals from the interface */
|
/* extract globals from the interface */
|
||||||
m_last_channel = m_channel_list + m_voices;
|
m_last_channel = m_channel_list + m_voices;
|
||||||
|
|
||||||
|
m_wave_ptr = wavePtr;
|
||||||
|
|
||||||
/* build the waveform table */
|
/* build the waveform table */
|
||||||
build_decoded_waveform(m_wave_ptr);
|
build_decoded_waveform(m_wave_ptr);
|
||||||
|
|
||||||
|
@ -106,34 +108,19 @@ void namco_audio_device::device_start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void namco_audio_device::device_clock_changed(int clk)
|
||||||
void namco_device::device_start()
|
|
||||||
{
|
{
|
||||||
namco_audio_device::device_start();
|
int clock_multiple;
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void namco_15xx_device::device_start()
|
|
||||||
{
|
|
||||||
namco_audio_device::device_start();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void namco_audio_device::device_clock_changed()
|
|
||||||
{
|
|
||||||
//int clock_multiple;
|
|
||||||
|
|
||||||
/*
|
|
||||||
// adjust internal clock
|
// adjust internal clock
|
||||||
m_namco_clock = clock();
|
m_namco_clock = clk;
|
||||||
for (clock_multiple = 0; m_namco_clock < INTERNAL_RATE; clock_multiple++)
|
for (clock_multiple = 0; m_namco_clock < INTERNAL_RATE; clock_multiple++)
|
||||||
m_namco_clock *= 2;
|
m_namco_clock *= 2;
|
||||||
|
|
||||||
m_f_fracbits = clock_multiple + 15;
|
m_f_fracbits = clock_multiple + 15;
|
||||||
|
|
||||||
// adjust output clock
|
// adjust output clock
|
||||||
m_sample_rate = m_namco_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);
|
//logerror("Namco: freq fractional bits = %d: internal freq = %d, output freq = %d\n", m_f_fracbits, m_namco_clock, m_sample_rate);
|
||||||
}
|
}
|
||||||
|
@ -791,18 +778,3 @@ void namco_audio_device::sound_stream_update(short** outputs, int len)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void namco_device::sound_stream_update(short** outputs, int len)
|
|
||||||
{
|
|
||||||
namco_audio_device::sound_stream_update(outputs,len);
|
|
||||||
}
|
|
||||||
|
|
||||||
void namco_15xx_device::sound_stream_update(short** outputs, int len)
|
|
||||||
{
|
|
||||||
namco_audio_device::sound_stream_update(outputs,len);
|
|
||||||
}
|
|
||||||
|
|
||||||
void namco_cus30_device::sound_stream_update(short** outputs, int len)
|
|
||||||
{
|
|
||||||
namco_audio_device::sound_stream_update(outputs,len);
|
|
||||||
}
|
|
||||||
|
|
|
@ -15,7 +15,6 @@ public:
|
||||||
|
|
||||||
void sound_enable_w(int state);
|
void sound_enable_w(int state);
|
||||||
|
|
||||||
protected:
|
|
||||||
static constexpr unsigned MAX_VOICES = 8;
|
static constexpr unsigned MAX_VOICES = 8;
|
||||||
static constexpr unsigned MAX_VOLUME = 16;
|
static constexpr unsigned MAX_VOLUME = 16;
|
||||||
|
|
||||||
|
@ -36,8 +35,8 @@ protected:
|
||||||
namco_audio_device(uint32_t clock);
|
namco_audio_device(uint32_t clock);
|
||||||
|
|
||||||
// device-level overrides
|
// device-level overrides
|
||||||
void device_start();
|
void device_start(unsigned char* wavePtr);
|
||||||
void device_clock_changed();
|
void device_clock_changed(int clk);
|
||||||
|
|
||||||
// internal state
|
// internal state
|
||||||
|
|
||||||
|
@ -69,6 +68,7 @@ protected:
|
||||||
int16_t m_waveform[MAX_VOLUME][512];
|
int16_t m_waveform[MAX_VOLUME][512];
|
||||||
|
|
||||||
virtual void sound_stream_update(short** outputs, int len);
|
virtual void sound_stream_update(short** outputs, int len);
|
||||||
|
virtual ~namco_audio_device() {}
|
||||||
};
|
};
|
||||||
|
|
||||||
class namco_device : public namco_audio_device
|
class namco_device : public namco_audio_device
|
||||||
|
@ -81,11 +81,7 @@ public:
|
||||||
uint8_t polepos_sound_r(int offset);
|
uint8_t polepos_sound_r(int offset);
|
||||||
void polepos_sound_w(int offset, uint8_t data);
|
void polepos_sound_w(int offset, uint8_t data);
|
||||||
|
|
||||||
protected:
|
~namco_device() {}
|
||||||
// device-level overrides
|
|
||||||
virtual void device_start();
|
|
||||||
|
|
||||||
virtual void sound_stream_update(short** outputs, int len);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint8_t m_soundregs[0x400];
|
uint8_t m_soundregs[0x400];
|
||||||
|
@ -101,11 +97,7 @@ public:
|
||||||
uint8_t sharedram_r(int offset);
|
uint8_t sharedram_r(int offset);
|
||||||
void sharedram_w(int offset, uint8_t data);
|
void sharedram_w(int offset, uint8_t data);
|
||||||
|
|
||||||
protected:
|
~namco_15xx_device() {}
|
||||||
// device-level overrides
|
|
||||||
virtual void device_start();
|
|
||||||
|
|
||||||
virtual void sound_stream_update(short** outputs, int len);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint8_t m_soundregs[0x400];
|
uint8_t m_soundregs[0x400];
|
||||||
|
@ -123,8 +115,7 @@ public:
|
||||||
|
|
||||||
void pacman_sound_w(int offset, uint8_t data);
|
void pacman_sound_w(int offset, uint8_t data);
|
||||||
|
|
||||||
protected:
|
~namco_cus30_device() {}
|
||||||
virtual void sound_stream_update(short** outputs, int len);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // MAME_SOUND_NAMCO_H
|
#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
|
785
src/engine/platform/sound/ymz280b.cpp
Normal file
785
src/engine/platform/sound/ymz280b.cpp
Normal file
|
@ -0,0 +1,785 @@
|
||||||
|
// license:BSD-3-Clause
|
||||||
|
// copyright-holders:Aaron Giles
|
||||||
|
/*
|
||||||
|
|
||||||
|
Yamaha YMZ280B driver
|
||||||
|
by Aaron Giles
|
||||||
|
|
||||||
|
YMZ280B 8-Channel PCMD8 PCM/ADPCM Decoder
|
||||||
|
|
||||||
|
Features as listed in LSI-4MZ280B3 data sheet:
|
||||||
|
Voice data stored in external memory can be played back simultaneously for up to eight voices
|
||||||
|
Voice data format can be selected from 4-bit ADPCM, 8-bit PCM and 16-bit PCM
|
||||||
|
Control of voice data external memory
|
||||||
|
Up to 16M bytes of ROM or SRAM (x 8 bits, access time 150ms max) can be connected
|
||||||
|
Continuous access is possible
|
||||||
|
Loop playback between selective addresses is possible
|
||||||
|
Voice data playback frequency control
|
||||||
|
4-bit ADPCM ................ 0.172 to 44.1kHz in 256 steps
|
||||||
|
8-bit PCM, 16-bit PCM ...... 0.172 to 88.2kHz in 512 steps
|
||||||
|
256 steps total level and 16 steps panpot can be set
|
||||||
|
Voice signal is output in stereo 16-bit 2's complement MSB-first format
|
||||||
|
|
||||||
|
TODO:
|
||||||
|
- Is memory handling 100% correct? At the moment, Konami firebeat.c is the only
|
||||||
|
hardware currently emulated that uses external handlers.
|
||||||
|
It also happens to be the only one using 16-bit PCM.
|
||||||
|
|
||||||
|
Some other drivers (eg. bishi.cpp, bfm_sc4/5.cpp) also use ROM readback.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ymz280b.h"
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define MAX_SAMPLE_CHUNK 10000
|
||||||
|
|
||||||
|
static constexpr unsigned FRAC_BITS = 8;
|
||||||
|
static constexpr s32 FRAC_ONE = 1 << FRAC_BITS;
|
||||||
|
|
||||||
|
/* step size index shift table */
|
||||||
|
static constexpr int index_scale[8] = { 0x0e6, 0x0e6, 0x0e6, 0x0e6, 0x133, 0x199, 0x200, 0x266 };
|
||||||
|
|
||||||
|
/* lookup table for the precomputed difference */
|
||||||
|
static int diff_lookup[16];
|
||||||
|
|
||||||
|
|
||||||
|
void ymz280b_device::update_step(struct YMZ280BVoice *voice)
|
||||||
|
{
|
||||||
|
int frequency;
|
||||||
|
|
||||||
|
/* compute the frequency */
|
||||||
|
if (voice->mode == 1)
|
||||||
|
frequency = voice->fnum & 0x0ff;
|
||||||
|
else
|
||||||
|
frequency = voice->fnum & 0x1ff;
|
||||||
|
voice->output_step = frequency + 1; // ((fnum + 1) * (input clock / 384)) / 256
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ymz280b_device::update_volumes(struct YMZ280BVoice *voice)
|
||||||
|
{
|
||||||
|
if (voice->pan == 8)
|
||||||
|
{
|
||||||
|
voice->output_left = voice->level;
|
||||||
|
voice->output_right = voice->level;
|
||||||
|
}
|
||||||
|
else if (voice->pan < 8)
|
||||||
|
{
|
||||||
|
voice->output_left = voice->level;
|
||||||
|
|
||||||
|
/* pan 1 is hard-left, what's pan 0? for now assume same as pan 1 */
|
||||||
|
voice->output_right = (voice->pan == 0) ? 0 : voice->level * (voice->pan - 1) / 7;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
voice->output_left = voice->level * (15 - voice->pan) / 7;
|
||||||
|
voice->output_right = voice->level;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**********************************************************************************************
|
||||||
|
|
||||||
|
compute_tables -- compute the difference tables
|
||||||
|
|
||||||
|
***********************************************************************************************/
|
||||||
|
|
||||||
|
static void compute_tables()
|
||||||
|
{
|
||||||
|
/* loop over all nibbles and compute the difference */
|
||||||
|
for (int nib = 0; nib < 16; nib++)
|
||||||
|
{
|
||||||
|
int value = (nib & 0x07) * 2 + 1;
|
||||||
|
diff_lookup[nib] = (nib & 0x08) ? -value : value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**********************************************************************************************
|
||||||
|
|
||||||
|
generate_adpcm -- general ADPCM decoding routine
|
||||||
|
|
||||||
|
***********************************************************************************************/
|
||||||
|
|
||||||
|
int ymz280b_device::generate_adpcm(struct YMZ280BVoice *voice, s16 *buffer, int samples)
|
||||||
|
{
|
||||||
|
u32 position = voice->position;
|
||||||
|
int signal = voice->signal;
|
||||||
|
int step = voice->step;
|
||||||
|
int val;
|
||||||
|
|
||||||
|
/* two cases: first cases is non-looping */
|
||||||
|
if (!voice->looping)
|
||||||
|
{
|
||||||
|
/* loop while we still have samples to generate */
|
||||||
|
while (samples)
|
||||||
|
{
|
||||||
|
/* compute the new amplitude and update the current step */
|
||||||
|
val = m_ext_mem[position / 2] >> ((~position & 1) << 2);
|
||||||
|
signal += (step * diff_lookup[val & 15]) / 8;
|
||||||
|
|
||||||
|
/* clamp to the maximum */
|
||||||
|
if (signal > 32767)
|
||||||
|
signal = 32767;
|
||||||
|
else if (signal < -32768)
|
||||||
|
signal = -32768;
|
||||||
|
|
||||||
|
/* adjust the step size and clamp */
|
||||||
|
step = (step * index_scale[val & 7]) >> 8;
|
||||||
|
if (step > 0x6000)
|
||||||
|
step = 0x6000;
|
||||||
|
else if (step < 0x7f)
|
||||||
|
step = 0x7f;
|
||||||
|
|
||||||
|
/* output to the buffer, scaling by the volume */
|
||||||
|
*buffer++ = signal;
|
||||||
|
samples--;
|
||||||
|
|
||||||
|
/* next! */
|
||||||
|
position++;
|
||||||
|
if (position >= voice->stop)
|
||||||
|
{
|
||||||
|
voice->ended = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* second case: looping */
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* loop while we still have samples to generate */
|
||||||
|
while (samples)
|
||||||
|
{
|
||||||
|
/* compute the new amplitude and update the current step */
|
||||||
|
val = m_ext_mem[position / 2] >> ((~position & 1) << 2);
|
||||||
|
signal += (step * diff_lookup[val & 15]) / 8;
|
||||||
|
|
||||||
|
/* clamp to the maximum */
|
||||||
|
if (signal > 32767)
|
||||||
|
signal = 32767;
|
||||||
|
else if (signal < -32768)
|
||||||
|
signal = -32768;
|
||||||
|
|
||||||
|
/* adjust the step size and clamp */
|
||||||
|
step = (step * index_scale[val & 7]) >> 8;
|
||||||
|
if (step > 0x6000)
|
||||||
|
step = 0x6000;
|
||||||
|
else if (step < 0x7f)
|
||||||
|
step = 0x7f;
|
||||||
|
|
||||||
|
/* output to the buffer, scaling by the volume */
|
||||||
|
*buffer++ = signal;
|
||||||
|
samples--;
|
||||||
|
|
||||||
|
/* next! */
|
||||||
|
position++;
|
||||||
|
if (position == voice->loop_start && voice->loop_count == 0)
|
||||||
|
{
|
||||||
|
voice->loop_signal = signal;
|
||||||
|
voice->loop_step = step;
|
||||||
|
}
|
||||||
|
if (position >= voice->loop_end)
|
||||||
|
{
|
||||||
|
if (voice->keyon)
|
||||||
|
{
|
||||||
|
position = voice->loop_start;
|
||||||
|
signal = voice->loop_signal;
|
||||||
|
step = voice->loop_step;
|
||||||
|
voice->loop_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (position >= voice->stop)
|
||||||
|
{
|
||||||
|
voice->ended = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* update the parameters */
|
||||||
|
voice->position = position;
|
||||||
|
voice->signal = signal;
|
||||||
|
voice->step = step;
|
||||||
|
|
||||||
|
return samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**********************************************************************************************
|
||||||
|
|
||||||
|
generate_pcm8 -- general 8-bit PCM decoding routine
|
||||||
|
|
||||||
|
***********************************************************************************************/
|
||||||
|
|
||||||
|
int ymz280b_device::generate_pcm8(struct YMZ280BVoice *voice, s16 *buffer, int samples)
|
||||||
|
{
|
||||||
|
u32 position = voice->position;
|
||||||
|
int val;
|
||||||
|
|
||||||
|
/* two cases: first cases is non-looping */
|
||||||
|
if (!voice->looping)
|
||||||
|
{
|
||||||
|
/* loop while we still have samples to generate */
|
||||||
|
while (samples)
|
||||||
|
{
|
||||||
|
/* fetch the current value */
|
||||||
|
val = m_ext_mem[position / 2];
|
||||||
|
|
||||||
|
/* output to the buffer, scaling by the volume */
|
||||||
|
*buffer++ = (s8)val * 256;
|
||||||
|
samples--;
|
||||||
|
|
||||||
|
/* next! */
|
||||||
|
position += 2;
|
||||||
|
if (position >= voice->stop)
|
||||||
|
{
|
||||||
|
voice->ended = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* second case: looping */
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* loop while we still have samples to generate */
|
||||||
|
while (samples)
|
||||||
|
{
|
||||||
|
/* fetch the current value */
|
||||||
|
val = m_ext_mem[position / 2];
|
||||||
|
|
||||||
|
/* output to the buffer, scaling by the volume */
|
||||||
|
*buffer++ = (s8)val * 256;
|
||||||
|
samples--;
|
||||||
|
|
||||||
|
/* next! */
|
||||||
|
position += 2;
|
||||||
|
if (position >= voice->loop_end)
|
||||||
|
{
|
||||||
|
if (voice->keyon)
|
||||||
|
position = voice->loop_start;
|
||||||
|
}
|
||||||
|
if (position >= voice->stop)
|
||||||
|
{
|
||||||
|
voice->ended = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* update the parameters */
|
||||||
|
voice->position = position;
|
||||||
|
|
||||||
|
return samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**********************************************************************************************
|
||||||
|
|
||||||
|
generate_pcm16 -- general 16-bit PCM decoding routine
|
||||||
|
|
||||||
|
***********************************************************************************************/
|
||||||
|
|
||||||
|
int ymz280b_device::generate_pcm16(struct YMZ280BVoice *voice, s16 *buffer, int samples)
|
||||||
|
{
|
||||||
|
u32 position = voice->position;
|
||||||
|
int val;
|
||||||
|
|
||||||
|
/* two cases: first cases is non-looping */
|
||||||
|
if (!voice->looping)
|
||||||
|
{
|
||||||
|
/* loop while we still have samples to generate */
|
||||||
|
while (samples)
|
||||||
|
{
|
||||||
|
/* fetch the current value */
|
||||||
|
val = (s16)((m_ext_mem[position / 2 + 1] << 8) + m_ext_mem[position / 2 + 0]);
|
||||||
|
|
||||||
|
/* output to the buffer, scaling by the volume */
|
||||||
|
*buffer++ = val;
|
||||||
|
samples--;
|
||||||
|
|
||||||
|
/* next! */
|
||||||
|
position += 4;
|
||||||
|
if (position >= voice->stop)
|
||||||
|
{
|
||||||
|
voice->ended = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* second case: looping */
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* loop while we still have samples to generate */
|
||||||
|
while (samples)
|
||||||
|
{
|
||||||
|
/* fetch the current value */
|
||||||
|
val = (s16)((m_ext_mem[position / 2 + 1] << 8) + m_ext_mem[position / 2 + 0]);
|
||||||
|
|
||||||
|
/* output to the buffer, scaling by the volume */
|
||||||
|
*buffer++ = val;
|
||||||
|
samples--;
|
||||||
|
|
||||||
|
/* next! */
|
||||||
|
position += 4;
|
||||||
|
if (position >= voice->loop_end)
|
||||||
|
{
|
||||||
|
if (voice->keyon)
|
||||||
|
position = voice->loop_start;
|
||||||
|
}
|
||||||
|
if (position >= voice->stop)
|
||||||
|
{
|
||||||
|
voice->ended = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* update the parameters */
|
||||||
|
voice->position = position;
|
||||||
|
|
||||||
|
return samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------
|
||||||
|
// sound_stream_update - handle a stream update
|
||||||
|
//-------------------------------------------------
|
||||||
|
|
||||||
|
void ymz280b_device::sound_stream_update(s16 **outputs, int samples)
|
||||||
|
{
|
||||||
|
int v;
|
||||||
|
|
||||||
|
/* loop over voices */
|
||||||
|
for (v = 0; v < 8; v++)
|
||||||
|
{
|
||||||
|
struct YMZ280BVoice *voice = &m_voice[v];
|
||||||
|
s16 prev = voice->last_sample;
|
||||||
|
s16 curr = voice->curr_sample;
|
||||||
|
s16 *curr_data = m_scratch.get();
|
||||||
|
s16 *ldest = outputs[v*2];
|
||||||
|
s16 *rdest = outputs[v*2+1];
|
||||||
|
s32 sampindex = 0;
|
||||||
|
u32 new_samples, samples_left;
|
||||||
|
u32 final_pos;
|
||||||
|
int remaining = samples;
|
||||||
|
int lvol = voice->output_left;
|
||||||
|
int rvol = voice->output_right;
|
||||||
|
|
||||||
|
/* quick out if we're not playing and we're at 0 */
|
||||||
|
if (!voice->playing && curr == 0 && prev == 0)
|
||||||
|
{
|
||||||
|
memset(ldest, 0, samples * sizeof(s16));
|
||||||
|
memset(rdest, 0, samples * sizeof(s16));
|
||||||
|
/* make sure next sound plays immediately */
|
||||||
|
voice->output_pos = FRAC_ONE;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* finish off the current sample */
|
||||||
|
/* interpolate */
|
||||||
|
while (remaining > 0 && voice->output_pos < FRAC_ONE)
|
||||||
|
{
|
||||||
|
s32 interp_sample = ((s32(prev) * (FRAC_ONE - voice->output_pos)) + (s32(curr) * voice->output_pos)) >> FRAC_BITS;
|
||||||
|
ldest[sampindex] = (s16)(interp_sample * lvol / 256);
|
||||||
|
rdest[sampindex] = (s16)(interp_sample * rvol / 256);
|
||||||
|
sampindex++;
|
||||||
|
voice->output_pos += voice->output_step;
|
||||||
|
remaining--;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* if we're over, continue; otherwise, we're done */
|
||||||
|
if (voice->output_pos >= FRAC_ONE)
|
||||||
|
voice->output_pos -= FRAC_ONE;
|
||||||
|
else
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* compute how many new samples we need */
|
||||||
|
final_pos = voice->output_pos + remaining * voice->output_step;
|
||||||
|
new_samples = (final_pos + FRAC_ONE) >> FRAC_BITS;
|
||||||
|
if (new_samples > MAX_SAMPLE_CHUNK)
|
||||||
|
new_samples = MAX_SAMPLE_CHUNK;
|
||||||
|
samples_left = new_samples;
|
||||||
|
|
||||||
|
/* generate them into our buffer */
|
||||||
|
switch (voice->playing << 7 | voice->mode)
|
||||||
|
{
|
||||||
|
case 0x81: samples_left = generate_adpcm(voice, m_scratch.get(), new_samples); break;
|
||||||
|
case 0x82: samples_left = generate_pcm8(voice, m_scratch.get(), new_samples); break;
|
||||||
|
case 0x83: samples_left = generate_pcm16(voice, m_scratch.get(), new_samples); break;
|
||||||
|
default: samples_left = 0; memset(m_scratch.get(), 0, new_samples * sizeof(m_scratch[0])); break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (samples_left || voice->ended)
|
||||||
|
{
|
||||||
|
voice->ended = false;
|
||||||
|
|
||||||
|
/* if there are leftovers, ramp back to 0 */
|
||||||
|
int base = new_samples - samples_left;
|
||||||
|
int t = (base == 0) ? curr : m_scratch[base - 1];
|
||||||
|
for (u32 i = 0; i < samples_left; i++)
|
||||||
|
{
|
||||||
|
if (t < 0) t = -((-t * 15) >> 4);
|
||||||
|
else if (t > 0) t = (t * 15) >> 4;
|
||||||
|
m_scratch[base + i] = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* if we hit the end and IRQs are enabled, signal it */
|
||||||
|
if (base != 0)
|
||||||
|
{
|
||||||
|
voice->playing = 0;
|
||||||
|
|
||||||
|
/* set update_irq_state_timer. IRQ is signaled on next CPU execution. */
|
||||||
|
voice->irq_schedule = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* advance forward one sample */
|
||||||
|
prev = curr;
|
||||||
|
curr = *curr_data++;
|
||||||
|
|
||||||
|
/* then sample-rate convert with linear interpolation */
|
||||||
|
while (remaining > 0)
|
||||||
|
{
|
||||||
|
/* interpolate */
|
||||||
|
while (remaining > 0 && voice->output_pos < FRAC_ONE)
|
||||||
|
{
|
||||||
|
int interp_sample = ((s32(prev) * (FRAC_ONE - voice->output_pos)) + (s32(curr) * voice->output_pos)) >> FRAC_BITS;
|
||||||
|
ldest[sampindex] = (s16)(interp_sample * lvol / 256);
|
||||||
|
rdest[sampindex] = (s16)(interp_sample * rvol / 256);
|
||||||
|
sampindex++;
|
||||||
|
voice->output_pos += voice->output_step;
|
||||||
|
remaining--;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* if we're over, grab the next samples */
|
||||||
|
if (voice->output_pos >= FRAC_ONE)
|
||||||
|
{
|
||||||
|
voice->output_pos -= FRAC_ONE;
|
||||||
|
prev = curr;
|
||||||
|
curr = *curr_data++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* remember the last samples */
|
||||||
|
voice->last_sample = prev;
|
||||||
|
voice->curr_sample = curr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//-------------------------------------------------
|
||||||
|
// device_start - device-specific startup
|
||||||
|
//-------------------------------------------------
|
||||||
|
|
||||||
|
void ymz280b_device::device_start(u8 *ext_mem)
|
||||||
|
{
|
||||||
|
m_ext_mem = ext_mem;
|
||||||
|
|
||||||
|
/* compute ADPCM tables */
|
||||||
|
compute_tables();
|
||||||
|
|
||||||
|
/* allocate memory */
|
||||||
|
assert(MAX_SAMPLE_CHUNK < 0x10000);
|
||||||
|
m_scratch = std::make_unique<s16[]>(MAX_SAMPLE_CHUNK);
|
||||||
|
|
||||||
|
for (auto & elem : m_voice)
|
||||||
|
{
|
||||||
|
update_step(&elem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------------------------------------------------
|
||||||
|
// device_reset - device-specific reset
|
||||||
|
//-------------------------------------------------
|
||||||
|
|
||||||
|
void ymz280b_device::device_reset()
|
||||||
|
{
|
||||||
|
/* initial clear registers */
|
||||||
|
for (int i = 0xff; i >= 0; i--)
|
||||||
|
{
|
||||||
|
m_current_register = i;
|
||||||
|
write_to_register(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_current_register = 0;
|
||||||
|
m_status_register = 0;
|
||||||
|
m_ext_mem_address = 0;
|
||||||
|
|
||||||
|
/* clear other voice parameters */
|
||||||
|
for (auto &elem : m_voice)
|
||||||
|
{
|
||||||
|
struct YMZ280BVoice *voice = &elem;
|
||||||
|
|
||||||
|
voice->curr_sample = 0;
|
||||||
|
voice->last_sample = 0;
|
||||||
|
voice->output_pos = FRAC_ONE;
|
||||||
|
voice->playing = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**********************************************************************************************
|
||||||
|
|
||||||
|
write_to_register -- handle a write to the current register
|
||||||
|
|
||||||
|
***********************************************************************************************/
|
||||||
|
|
||||||
|
void ymz280b_device::write_to_register(int data)
|
||||||
|
{
|
||||||
|
struct YMZ280BVoice *voice;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
/* lower registers follow a pattern */
|
||||||
|
if (m_current_register < 0x80)
|
||||||
|
{
|
||||||
|
voice = &m_voice[(m_current_register >> 2) & 7];
|
||||||
|
|
||||||
|
switch (m_current_register & 0xe3)
|
||||||
|
{
|
||||||
|
case 0x00: /* pitch low 8 bits */
|
||||||
|
voice->fnum = (voice->fnum & 0x100) | (data & 0xff);
|
||||||
|
update_step(voice);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x01: /* pitch upper 1 bit, loop, key on, mode */
|
||||||
|
voice->fnum = (voice->fnum & 0xff) | ((data & 0x01) << 8);
|
||||||
|
voice->looping = (data & 0x10) >> 4;
|
||||||
|
if ((data & 0x60) == 0) data &= 0x7f; /* ignore mode setting and set to same state as KON=0 */
|
||||||
|
else voice->mode = (data & 0x60) >> 5;
|
||||||
|
if (!voice->keyon && (data & 0x80) && m_keyon_enable)
|
||||||
|
{
|
||||||
|
voice->playing = 1;
|
||||||
|
voice->position = voice->start;
|
||||||
|
voice->signal = voice->loop_signal = 0;
|
||||||
|
voice->step = voice->loop_step = 0x7f;
|
||||||
|
voice->loop_count = 0;
|
||||||
|
|
||||||
|
/* if update_irq_state_timer is set, cancel it. */
|
||||||
|
voice->irq_schedule = 0;
|
||||||
|
}
|
||||||
|
else if (voice->keyon && !(data & 0x80))
|
||||||
|
{
|
||||||
|
voice->playing = 0;
|
||||||
|
|
||||||
|
/* if update_irq_state_timer is set, cancel it. */
|
||||||
|
voice->irq_schedule = 0;
|
||||||
|
}
|
||||||
|
voice->keyon = (data & 0x80) >> 7;
|
||||||
|
update_step(voice);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x02: /* total level */
|
||||||
|
voice->level = data;
|
||||||
|
update_volumes(voice);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x03: /* pan */
|
||||||
|
voice->pan = data & 0x0f;
|
||||||
|
update_volumes(voice);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x20: /* start address high */
|
||||||
|
voice->start = (voice->start & (0x00ffff << 1)) | (data << 17);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x21: /* loop start address high */
|
||||||
|
voice->loop_start = (voice->loop_start & (0x00ffff << 1)) | (data << 17);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x22: /* loop end address high */
|
||||||
|
voice->loop_end = (voice->loop_end & (0x00ffff << 1)) | (data << 17);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x23: /* stop address high */
|
||||||
|
voice->stop = (voice->stop & (0x00ffff << 1)) | (data << 17);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x40: /* start address middle */
|
||||||
|
voice->start = (voice->start & (0xff00ff << 1)) | (data << 9);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x41: /* loop start address middle */
|
||||||
|
voice->loop_start = (voice->loop_start & (0xff00ff << 1)) | (data << 9);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x42: /* loop end address middle */
|
||||||
|
voice->loop_end = (voice->loop_end & (0xff00ff << 1)) | (data << 9);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x43: /* stop address middle */
|
||||||
|
voice->stop = (voice->stop & (0xff00ff << 1)) | (data << 9);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x60: /* start address low */
|
||||||
|
voice->start = (voice->start & (0xffff00 << 1)) | (data << 1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x61: /* loop start address low */
|
||||||
|
voice->loop_start = (voice->loop_start & (0xffff00 << 1)) | (data << 1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x62: /* loop end address low */
|
||||||
|
voice->loop_end = (voice->loop_end & (0xffff00 << 1)) | (data << 1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x63: /* stop address low */
|
||||||
|
voice->stop = (voice->stop & (0xffff00 << 1)) | (data << 1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (data != 0)
|
||||||
|
printf("YMZ280B: unknown register write %02X = %02X\n", m_current_register, data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* upper registers are special */
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (m_current_register)
|
||||||
|
{
|
||||||
|
/* DSP related (not implemented yet) */
|
||||||
|
case 0x80: // d0-2: DSP Rch, d3: enable Rch (0: yes, 1: no), d4-6: DSP Lch, d7: enable Lch (0: yes, 1: no)
|
||||||
|
case 0x81: // d0: enable control of $82 (0: yes, 1: no)
|
||||||
|
case 0x82: // DSP data
|
||||||
|
//printf("YMZ280B: DSP register write %02X = %02X\n", m_current_register, data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x84: /* ROM readback / RAM write (high) */
|
||||||
|
m_ext_mem_address_hi = data << 16;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x85: /* ROM readback / RAM write (middle) */
|
||||||
|
m_ext_mem_address_mid = data << 8;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x86: /* ROM readback / RAM write (low) -> update latch */
|
||||||
|
m_ext_mem_address = m_ext_mem_address_hi | m_ext_mem_address_mid | data;
|
||||||
|
if (m_ext_mem_enable)
|
||||||
|
m_ext_readlatch = m_ext_mem[m_ext_mem_address];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x87: /* RAM write */
|
||||||
|
if (m_ext_mem_enable)
|
||||||
|
{
|
||||||
|
m_ext_mem[m_ext_mem_address] = data;
|
||||||
|
m_ext_mem_address = (m_ext_mem_address + 1) & 0xffffff;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0xfe: /* IRQ mask */
|
||||||
|
m_irq_mask = data;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0xff: /* IRQ enable, test, etc */
|
||||||
|
m_ext_mem_enable = (data & 0x40) >> 6;
|
||||||
|
m_irq_enable = (data & 0x10) >> 4;
|
||||||
|
|
||||||
|
if (m_keyon_enable && !(data & 0x80))
|
||||||
|
{
|
||||||
|
for (i = 0; i < 8; i++)
|
||||||
|
{
|
||||||
|
m_voice[i].playing = 0;
|
||||||
|
|
||||||
|
/* if update_irq_state_timer is set, cancel it. */
|
||||||
|
m_voice[i].irq_schedule = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!m_keyon_enable && (data & 0x80))
|
||||||
|
{
|
||||||
|
for (i = 0; i < 8; i++)
|
||||||
|
{
|
||||||
|
if (m_voice[i].keyon && m_voice[i].looping)
|
||||||
|
m_voice[i].playing = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_keyon_enable = (data & 0x80) >> 7;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (data != 0)
|
||||||
|
printf("YMZ280B: unknown register write %02X = %02X\n", m_current_register, data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**********************************************************************************************
|
||||||
|
|
||||||
|
compute_status -- determine the status bits
|
||||||
|
|
||||||
|
***********************************************************************************************/
|
||||||
|
|
||||||
|
int ymz280b_device::compute_status()
|
||||||
|
{
|
||||||
|
u8 result;
|
||||||
|
|
||||||
|
result = m_status_register;
|
||||||
|
|
||||||
|
/* clear the IRQ state */
|
||||||
|
m_status_register = 0;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**********************************************************************************************
|
||||||
|
|
||||||
|
read/write -- handle external accesses
|
||||||
|
|
||||||
|
***********************************************************************************************/
|
||||||
|
|
||||||
|
u8 ymz280b_device::read(offs_t offset)
|
||||||
|
{
|
||||||
|
if ((offset & 1) == 0)
|
||||||
|
{
|
||||||
|
if (!m_ext_mem_enable)
|
||||||
|
return 0xff;
|
||||||
|
|
||||||
|
/* read from external memory */
|
||||||
|
u8 ret = m_ext_readlatch;
|
||||||
|
m_ext_readlatch = m_ext_mem[m_ext_mem_address];
|
||||||
|
m_ext_mem_address = (m_ext_mem_address + 1) & 0xffffff;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return compute_status();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ymz280b_device::write(offs_t offset, u8 data)
|
||||||
|
{
|
||||||
|
if ((offset & 1) == 0)
|
||||||
|
m_current_register = data;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
write_to_register(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ymz280b_device::ymz280b_device()
|
||||||
|
: m_current_register(0)
|
||||||
|
, m_status_register(0)
|
||||||
|
, m_irq_mask(0)
|
||||||
|
, m_irq_enable(0)
|
||||||
|
, m_keyon_enable(0)
|
||||||
|
, m_ext_mem_enable(0)
|
||||||
|
, m_ext_readlatch(0)
|
||||||
|
, m_ext_mem_address_hi(0)
|
||||||
|
, m_ext_mem_address_mid(0)
|
||||||
|
, m_ext_mem_address(0)
|
||||||
|
{
|
||||||
|
memset(m_voice, 0, sizeof(m_voice));
|
||||||
|
}
|
104
src/engine/platform/sound/ymz280b.h
Normal file
104
src/engine/platform/sound/ymz280b.h
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
// license:BSD-3-Clause
|
||||||
|
// copyright-holders:Aaron Giles
|
||||||
|
/**********************************************************************************************
|
||||||
|
*
|
||||||
|
* Yamaha YMZ280B driver
|
||||||
|
* by Aaron Giles
|
||||||
|
*
|
||||||
|
**********************************************************************************************/
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#ifndef MAME_SOUND_YMZ280B_H
|
||||||
|
#define MAME_SOUND_YMZ280B_H
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace ymz280b
|
||||||
|
{
|
||||||
|
typedef unsigned char u8;
|
||||||
|
typedef signed char s8;
|
||||||
|
typedef unsigned short u16;
|
||||||
|
typedef signed short s16;
|
||||||
|
typedef unsigned int u32;
|
||||||
|
typedef signed int s32;
|
||||||
|
typedef signed int offs_t;
|
||||||
|
}
|
||||||
|
|
||||||
|
using namespace ymz280b;
|
||||||
|
class ymz280b_device
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ymz280b_device();
|
||||||
|
|
||||||
|
u8 read(offs_t offset);
|
||||||
|
void write(offs_t offset, u8 data);
|
||||||
|
|
||||||
|
void device_start(u8 *ext_mem);
|
||||||
|
void device_reset();
|
||||||
|
|
||||||
|
void sound_stream_update(s16 **outputs, int samples);
|
||||||
|
|
||||||
|
private:
|
||||||
|
/* struct describing a single playing ADPCM voice */
|
||||||
|
struct YMZ280BVoice
|
||||||
|
{
|
||||||
|
u8 playing; /* 1 if we are actively playing */
|
||||||
|
bool ended; /* indicate voice has ended in case samples_left is 0 */
|
||||||
|
|
||||||
|
u8 keyon; /* 1 if the key is on */
|
||||||
|
u8 looping; /* 1 if looping is enabled */
|
||||||
|
u8 mode; /* current playback mode */
|
||||||
|
u16 fnum; /* frequency */
|
||||||
|
u8 level; /* output level */
|
||||||
|
u8 pan; /* panning */
|
||||||
|
|
||||||
|
u32 start; /* start address, in nibbles */
|
||||||
|
u32 stop; /* stop address, in nibbles */
|
||||||
|
u32 loop_start; /* loop start address, in nibbles */
|
||||||
|
u32 loop_end; /* loop end address, in nibbles */
|
||||||
|
u32 position; /* current position, in nibbles */
|
||||||
|
|
||||||
|
s32 signal; /* current ADPCM signal */
|
||||||
|
s32 step; /* current ADPCM step */
|
||||||
|
|
||||||
|
s32 loop_signal; /* signal at loop start */
|
||||||
|
s32 loop_step; /* step at loop start */
|
||||||
|
u32 loop_count; /* number of loops so far */
|
||||||
|
|
||||||
|
s32 output_left; /* output volume (left) */
|
||||||
|
s32 output_right; /* output volume (right) */
|
||||||
|
s32 output_step; /* step value for frequency conversion */
|
||||||
|
s32 output_pos; /* current fractional position */
|
||||||
|
s16 last_sample; /* last sample output */
|
||||||
|
s16 curr_sample; /* current sample target */
|
||||||
|
u8 irq_schedule; /* 1 if the IRQ state is updated by timer */
|
||||||
|
};
|
||||||
|
|
||||||
|
void update_step(struct YMZ280BVoice *voice);
|
||||||
|
void update_volumes(struct YMZ280BVoice *voice);
|
||||||
|
int generate_adpcm(struct YMZ280BVoice *voice, s16 *buffer, int samples);
|
||||||
|
int generate_pcm8(struct YMZ280BVoice *voice, s16 *buffer, int samples);
|
||||||
|
int generate_pcm16(struct YMZ280BVoice *voice, s16 *buffer, int samples);
|
||||||
|
void write_to_register(int data);
|
||||||
|
int compute_status();
|
||||||
|
|
||||||
|
// internal state
|
||||||
|
struct YMZ280BVoice m_voice[8]; /* the 8 voices */
|
||||||
|
u8 m_current_register; /* currently accessible register */
|
||||||
|
u8 m_status_register; /* current status register */
|
||||||
|
u8 m_irq_mask; /* current IRQ mask */
|
||||||
|
u8 m_irq_enable; /* current IRQ enable */
|
||||||
|
u8 m_keyon_enable; /* key on enable */
|
||||||
|
u8 m_ext_mem_enable; /* external memory enable */
|
||||||
|
u8 m_ext_readlatch; /* external memory prefetched data */
|
||||||
|
u32 m_ext_mem_address_hi;
|
||||||
|
u32 m_ext_mem_address_mid;
|
||||||
|
u32 m_ext_mem_address; /* where the CPU can read the ROM */
|
||||||
|
|
||||||
|
u8 *m_ext_mem;
|
||||||
|
|
||||||
|
std::unique_ptr<s16[]> m_scratch;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // MAME_SOUND_YMZ280B_H
|
460
src/engine/platform/ymz280b.cpp
Normal file
460
src/engine/platform/ymz280b.cpp
Normal file
|
@ -0,0 +1,460 @@
|
||||||
|
/**
|
||||||
|
* Furnace Tracker - multi-system chiptune tracker
|
||||||
|
* Copyright (C) 2021-2022 tildearrow and contributors
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ymz280b.h"
|
||||||
|
#include "../engine.h"
|
||||||
|
#include "../../ta-log.h"
|
||||||
|
#include <math.h>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#define CHIP_FREQBASE 98304
|
||||||
|
|
||||||
|
#define rWrite(a,v) {if(!skipRegisterWrites) {ymz280b.write(0,a); ymz280b.write(1,v); regPool[a]=v; if(dumpWrites) addWrite(a,v); }}
|
||||||
|
|
||||||
|
const char* regCheatSheetYMZ280B[]={
|
||||||
|
"CHx_Freq", "00+x*4",
|
||||||
|
"CHx_Control", "01+x*4",
|
||||||
|
"CHx_Volume", "02+x*4",
|
||||||
|
"CHx_Panning", "03+x*4",
|
||||||
|
"CHx_StartH", "20+x*4",
|
||||||
|
"CHx_LoopStartH", "21+x*4",
|
||||||
|
"CHx_LoopEndH", "22+x*4",
|
||||||
|
"CHx_EndH", "23+x*4",
|
||||||
|
"CHx_StartM", "40+x*4",
|
||||||
|
"CHx_LoopStartM", "41+x*4",
|
||||||
|
"CHx_LoopEndM", "42+x*4",
|
||||||
|
"CHx_EndM", "43+x*4",
|
||||||
|
"CHx_StartL", "60+x*4",
|
||||||
|
"CHx_LoopStartL", "61+x*4",
|
||||||
|
"CHx_LoopEndL", "62+x*4",
|
||||||
|
"CHx_EndL", "63+x*4",
|
||||||
|
"DSP_Channel", "80",
|
||||||
|
"DSP_Enable", "81",
|
||||||
|
"DSP_Data", "82",
|
||||||
|
"RAM_AddrH", "84",
|
||||||
|
"RAM_AddrM", "85",
|
||||||
|
"RAM_AddrL", "86",
|
||||||
|
"RAM_Data", "87",
|
||||||
|
"IRQ_Enable", "E0",
|
||||||
|
"Enable", "FF",
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
const char** DivPlatformYMZ280B::getRegisterSheet() {
|
||||||
|
return regCheatSheetYMZ280B;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* DivPlatformYMZ280B::getEffectName(unsigned char effect) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformYMZ280B::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||||
|
short buf[16][256];
|
||||||
|
short *bufPtrs[16]={
|
||||||
|
buf[0],buf[1],buf[2],buf[3],buf[4],buf[5],buf[6],buf[7],
|
||||||
|
buf[8],buf[9],buf[10],buf[11],buf[12],buf[13],buf[14],buf[15]
|
||||||
|
};
|
||||||
|
size_t pos=start;
|
||||||
|
while (len > 0) {
|
||||||
|
size_t blockLen = MIN(len, 256);
|
||||||
|
ymz280b.sound_stream_update(bufPtrs, blockLen);
|
||||||
|
for (size_t i=0; i<blockLen; i++) {
|
||||||
|
int dataL=0;
|
||||||
|
int dataR=0;
|
||||||
|
for (int j=0; j<8; j++) {
|
||||||
|
dataL+=buf[j*2][i];
|
||||||
|
dataR+=buf[j*2+1][i];
|
||||||
|
oscBuf[j]->data[oscBuf[j]->needle++]=(short)(((int)buf[j*2][i]+buf[j*2+1][i])/2);
|
||||||
|
}
|
||||||
|
bufL[pos]=(short)(dataL/8);
|
||||||
|
bufR[pos]=(short)(dataR/8);
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
len-=blockLen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformYMZ280B::tick(bool sysTick) {
|
||||||
|
for (int i=0; i<8; i++) {
|
||||||
|
chan[i].std.next();
|
||||||
|
if (chan[i].std.vol.had) {
|
||||||
|
chan[i].outVol=((chan[i].vol&0xff)*chan[i].std.vol.val)>>6;
|
||||||
|
writeOutVol(i);
|
||||||
|
}
|
||||||
|
if (chan[i].std.arp.had) {
|
||||||
|
if (!chan[i].inPorta) {
|
||||||
|
if (chan[i].std.arp.mode) {
|
||||||
|
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
|
||||||
|
} else {
|
||||||
|
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chan[i].freqChanged=true;
|
||||||
|
} else {
|
||||||
|
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||||
|
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note);
|
||||||
|
chan[i].freqChanged=true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (chan[i].std.pitch.had) {
|
||||||
|
if (chan[i].std.pitch.mode) {
|
||||||
|
chan[i].pitch2+=chan[i].std.pitch.val;
|
||||||
|
CLAMP_VAR(chan[i].pitch2,-2048,2048);
|
||||||
|
} else {
|
||||||
|
chan[i].pitch2=chan[i].std.pitch.val;
|
||||||
|
}
|
||||||
|
chan[i].freqChanged=true;
|
||||||
|
}
|
||||||
|
if (chan[i].std.panL.had) { // panning
|
||||||
|
chan[i].panning=MIN((chan[i].std.panL.val*15/16+15)/2+1,15);
|
||||||
|
rWrite(0x03+i*4,chan[i].panning);
|
||||||
|
}
|
||||||
|
if (chan[i].setPos) {
|
||||||
|
// force keyon
|
||||||
|
chan[i].keyOn=true;
|
||||||
|
chan[i].setPos=false;
|
||||||
|
} else {
|
||||||
|
chan[i].audPos=0;
|
||||||
|
}
|
||||||
|
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||||
|
DivSample* s=parent->getSample(chan[i].sample);
|
||||||
|
unsigned char ctrl;
|
||||||
|
switch (s->depth) {
|
||||||
|
case 3: ctrl=0x20; break;
|
||||||
|
case 8: ctrl=0x40; break;
|
||||||
|
case 16: ctrl=0x60; break;
|
||||||
|
default: ctrl=0;
|
||||||
|
}
|
||||||
|
double off=(s->centerRate>=1)?((double)s->centerRate/8363.0):1.0;
|
||||||
|
chan[i].freq=(int)(off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE))-1;
|
||||||
|
if (chan[i].freq<0) chan[i].freq=0;
|
||||||
|
if (chan[i].freq>511) chan[i].freq=511;
|
||||||
|
// ADPCM has half the range
|
||||||
|
if (s->depth==3 && chan[i].freq>255) chan[i].freq=255;
|
||||||
|
ctrl|=(chan[i].active?0x80:0)|((s->loopStart>=0)?0x10:0)|(chan[i].freq>>8);
|
||||||
|
if (chan[i].keyOn) {
|
||||||
|
unsigned int start=s->offYMZ280B;
|
||||||
|
unsigned int loop=0;
|
||||||
|
unsigned int end=MIN(start+s->getCurBufLen(),getSampleMemCapacity()-1);
|
||||||
|
if (chan[i].audPos>0) {
|
||||||
|
switch (s->depth) {
|
||||||
|
case 3: start+=chan[i].audPos/2; break;
|
||||||
|
case 8: start+=chan[i].audPos; break;
|
||||||
|
case 16: start+=chan[i].audPos*2; break;
|
||||||
|
}
|
||||||
|
start=MIN(start,end);
|
||||||
|
}
|
||||||
|
if (s->loopStart>=0) {
|
||||||
|
switch (s->depth) {
|
||||||
|
case 3: loop=start+s->loopStart/2; break;
|
||||||
|
case 8: loop=start+s->loopStart; break;
|
||||||
|
case 16: loop=start+s->loopStart*2; break;
|
||||||
|
}
|
||||||
|
loop=MIN(loop,end);
|
||||||
|
}
|
||||||
|
rWrite(0x01+i*4,ctrl&~0x80); // force keyoff first
|
||||||
|
rWrite(0x20+i*4,(start>>16)&0xff);
|
||||||
|
rWrite(0x21+i*4,(loop>>16)&0xff);
|
||||||
|
rWrite(0x22+i*4,(end>>16)&0xff);
|
||||||
|
rWrite(0x23+i*4,(end>>16)&0xff);
|
||||||
|
rWrite(0x40+i*4,(start>>8)&0xff);
|
||||||
|
rWrite(0x41+i*4,(loop>>8)&0xff);
|
||||||
|
rWrite(0x42+i*4,(end>>8)&0xff);
|
||||||
|
rWrite(0x43+i*4,(end>>8)&0xff);
|
||||||
|
rWrite(0x60+i*4,start&0xff);
|
||||||
|
rWrite(0x61+i*4,loop&0xff);
|
||||||
|
rWrite(0x62+i*4,end&0xff);
|
||||||
|
rWrite(0x63+i*4,end&0xff);
|
||||||
|
if (!chan[i].std.vol.had) {
|
||||||
|
chan[i].outVol=chan[i].vol;
|
||||||
|
writeOutVol(i);
|
||||||
|
}
|
||||||
|
chan[i].keyOn=false;
|
||||||
|
}
|
||||||
|
if (chan[i].keyOff) {
|
||||||
|
chan[i].keyOff=false;
|
||||||
|
}
|
||||||
|
if (chan[i].freqChanged) {
|
||||||
|
rWrite(0x00+i*4,chan[i].freq&0xff);
|
||||||
|
chan[i].freqChanged=false;
|
||||||
|
}
|
||||||
|
rWrite(0x01+i*4,ctrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int DivPlatformYMZ280B::dispatch(DivCommand c) {
|
||||||
|
switch (c.cmd) {
|
||||||
|
case DIV_CMD_NOTE_ON: {
|
||||||
|
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
|
||||||
|
chan[c.chan].sample=ins->amiga.getSample(c.value);
|
||||||
|
if (c.value!=DIV_NOTE_NULL) {
|
||||||
|
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
|
||||||
|
}
|
||||||
|
if (chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) {
|
||||||
|
chan[c.chan].sample=-1;
|
||||||
|
}
|
||||||
|
if (c.value!=DIV_NOTE_NULL) {
|
||||||
|
chan[c.chan].freqChanged=true;
|
||||||
|
chan[c.chan].note=c.value;
|
||||||
|
}
|
||||||
|
chan[c.chan].active=true;
|
||||||
|
chan[c.chan].keyOn=true;
|
||||||
|
chan[c.chan].macroInit(ins);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DIV_CMD_NOTE_OFF:
|
||||||
|
chan[c.chan].sample=-1;
|
||||||
|
chan[c.chan].active=false;
|
||||||
|
chan[c.chan].keyOff=true;
|
||||||
|
chan[c.chan].macroInit(NULL);
|
||||||
|
break;
|
||||||
|
case DIV_CMD_NOTE_OFF_ENV:
|
||||||
|
case DIV_CMD_ENV_RELEASE:
|
||||||
|
chan[c.chan].std.release();
|
||||||
|
break;
|
||||||
|
case DIV_CMD_INSTRUMENT:
|
||||||
|
if (chan[c.chan].ins!=c.value || c.value2==1) {
|
||||||
|
chan[c.chan].ins=c.value;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DIV_CMD_VOLUME:
|
||||||
|
if (chan[c.chan].vol!=c.value) {
|
||||||
|
chan[c.chan].vol=c.value;
|
||||||
|
if (!chan[c.chan].std.vol.has) {
|
||||||
|
chan[c.chan].outVol=c.value;
|
||||||
|
writeOutVol(c.chan);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DIV_CMD_GET_VOLUME:
|
||||||
|
if (chan[c.chan].std.vol.has) {
|
||||||
|
return chan[c.chan].vol;
|
||||||
|
}
|
||||||
|
return chan[c.chan].outVol;
|
||||||
|
break;
|
||||||
|
case DIV_CMD_PANNING:
|
||||||
|
chan[c.chan].panning=MIN(parent->convertPanSplitToLinearLR(c.value,c.value2,15)+1,15);
|
||||||
|
rWrite(0x03+c.chan*4,chan[c.chan].panning);
|
||||||
|
break;
|
||||||
|
case DIV_CMD_PITCH:
|
||||||
|
chan[c.chan].pitch=c.value;
|
||||||
|
chan[c.chan].freqChanged=true;
|
||||||
|
break;
|
||||||
|
case DIV_CMD_NOTE_PORTA: {
|
||||||
|
int destFreq=NOTE_FREQUENCY(c.value2);
|
||||||
|
bool return2=false;
|
||||||
|
if (destFreq>chan[c.chan].baseFreq) {
|
||||||
|
chan[c.chan].baseFreq+=c.value;
|
||||||
|
if (chan[c.chan].baseFreq>=destFreq) {
|
||||||
|
chan[c.chan].baseFreq=destFreq;
|
||||||
|
return2=true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
chan[c.chan].baseFreq-=c.value;
|
||||||
|
if (chan[c.chan].baseFreq<=destFreq) {
|
||||||
|
chan[c.chan].baseFreq=destFreq;
|
||||||
|
return2=true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chan[c.chan].freqChanged=true;
|
||||||
|
if (return2) {
|
||||||
|
chan[c.chan].inPorta=false;
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DIV_CMD_LEGATO: {
|
||||||
|
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val-12):(0)));
|
||||||
|
chan[c.chan].freqChanged=true;
|
||||||
|
chan[c.chan].note=c.value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DIV_CMD_PRE_PORTA:
|
||||||
|
if (chan[c.chan].active && c.value2) {
|
||||||
|
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA));
|
||||||
|
}
|
||||||
|
chan[c.chan].inPorta=c.value;
|
||||||
|
break;
|
||||||
|
case DIV_CMD_SAMPLE_POS:
|
||||||
|
chan[c.chan].audPos=c.value;
|
||||||
|
chan[c.chan].setPos=true;
|
||||||
|
break;
|
||||||
|
case DIV_CMD_GET_VOLMAX:
|
||||||
|
return 255;
|
||||||
|
break;
|
||||||
|
case DIV_ALWAYS_SET_VOLUME:
|
||||||
|
return 1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformYMZ280B::writeOutVol(int ch) {
|
||||||
|
unsigned char val=isMuted[ch]?0:chan[ch].outVol;
|
||||||
|
rWrite(0x02+ch*4,val);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformYMZ280B::muteChannel(int ch, bool mute) {
|
||||||
|
isMuted[ch]=mute;
|
||||||
|
writeOutVol(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformYMZ280B::forceIns() {
|
||||||
|
for (int i=0; i<8; i++) {
|
||||||
|
chan[i].insChanged=true;
|
||||||
|
chan[i].freqChanged=true;
|
||||||
|
chan[i].sample=-1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void* DivPlatformYMZ280B::getChanState(int ch) {
|
||||||
|
return &chan[ch];
|
||||||
|
}
|
||||||
|
|
||||||
|
DivDispatchOscBuffer* DivPlatformYMZ280B::getOscBuffer(int ch) {
|
||||||
|
return oscBuf[ch];
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformYMZ280B::reset() {
|
||||||
|
memset(regPool,0,256);
|
||||||
|
ymz280b.device_reset();
|
||||||
|
rWrite(0xff,0x80); // enable keyon
|
||||||
|
for (int i=0; i<8; i++) {
|
||||||
|
chan[i]=DivPlatformYMZ280B::Channel();
|
||||||
|
chan[i].std.setEngine(parent);
|
||||||
|
rWrite(0x02+i*4,255);
|
||||||
|
rWrite(0x03+i*4,8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DivPlatformYMZ280B::isStereo() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformYMZ280B::notifyInsChange(int ins) {
|
||||||
|
for (int i=0; i<8; i++) {
|
||||||
|
if (chan[i].ins==ins) {
|
||||||
|
chan[i].insChanged=true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformYMZ280B::notifyWaveChange(int wave) {
|
||||||
|
// TODO when wavetables are added
|
||||||
|
// TODO they probably won't be added unless the samples reside in RAM
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformYMZ280B::notifyInsDeletion(void* ins) {
|
||||||
|
for (int i=0; i<8; i++) {
|
||||||
|
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformYMZ280B::poke(unsigned int addr, unsigned short val) {
|
||||||
|
rWrite(addr,val);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformYMZ280B::poke(std::vector<DivRegWrite>& wlist) {
|
||||||
|
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned char* DivPlatformYMZ280B::getRegisterPool() {
|
||||||
|
return regPool;
|
||||||
|
}
|
||||||
|
|
||||||
|
int DivPlatformYMZ280B::getRegisterPoolSize() {
|
||||||
|
return 256;
|
||||||
|
}
|
||||||
|
|
||||||
|
float DivPlatformYMZ280B::getPostAmp() {
|
||||||
|
// according to MAME core's mixing
|
||||||
|
return 4.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
const void* DivPlatformYMZ280B::getSampleMem(int index) {
|
||||||
|
return index == 0 ? sampleMem : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t DivPlatformYMZ280B::getSampleMemCapacity(int index) {
|
||||||
|
return index == 0 ? 16777216 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t DivPlatformYMZ280B::getSampleMemUsage(int index) {
|
||||||
|
return index == 0 ? sampleMemLen : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformYMZ280B::renderSamples() {
|
||||||
|
memset(sampleMem,0,getSampleMemCapacity());
|
||||||
|
|
||||||
|
size_t memPos=0;
|
||||||
|
for (int i=0; i<parent->song.sampleLen; i++) {
|
||||||
|
DivSample* s=parent->song.sample[i];
|
||||||
|
int length=s->getCurBufLen();
|
||||||
|
unsigned char* src=(unsigned char*)s->getCurBuf();
|
||||||
|
int actualLength=MIN((int)(getSampleMemCapacity()-memPos),length);
|
||||||
|
if (actualLength>0) {
|
||||||
|
memcpy(&sampleMem[memPos],src,actualLength);
|
||||||
|
s->offYMZ280B=memPos;
|
||||||
|
memPos+=length;
|
||||||
|
}
|
||||||
|
if (actualLength<length) {
|
||||||
|
logW("out of YMZ280B PCM memory for sample %d!",i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sampleMemLen=memPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformYMZ280B::setChipModel(int type) {
|
||||||
|
chipType=type;
|
||||||
|
}
|
||||||
|
|
||||||
|
int DivPlatformYMZ280B::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||||
|
parent=p;
|
||||||
|
dumpWrites=false;
|
||||||
|
skipRegisterWrites=false;
|
||||||
|
|
||||||
|
for (int i=0; i<8; i++) {
|
||||||
|
isMuted[i]=false;
|
||||||
|
oscBuf[i]=new DivDispatchOscBuffer;
|
||||||
|
}
|
||||||
|
setFlags(flags);
|
||||||
|
|
||||||
|
rate=(chipType==759)?32000:44100;
|
||||||
|
chipClock=rate*384;
|
||||||
|
sampleMem=new unsigned char[getSampleMemCapacity()];
|
||||||
|
sampleMemLen=0;
|
||||||
|
ymz280b.device_start(sampleMem);
|
||||||
|
reset();
|
||||||
|
|
||||||
|
for (int i=0; i<8; i++) {
|
||||||
|
oscBuf[i]->rate=rate;
|
||||||
|
}
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivPlatformYMZ280B::quit() {
|
||||||
|
delete[] sampleMem;
|
||||||
|
for (int i=0; i<8; i++) {
|
||||||
|
delete oscBuf[i];
|
||||||
|
}
|
||||||
|
}
|
104
src/engine/platform/ymz280b.h
Normal file
104
src/engine/platform/ymz280b.h
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
/**
|
||||||
|
* Furnace Tracker - multi-system chiptune tracker
|
||||||
|
* Copyright (C) 2021-2022 tildearrow and contributors
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _YMZ280B_H
|
||||||
|
#define _YMZ280B_H
|
||||||
|
|
||||||
|
#include "../dispatch.h"
|
||||||
|
#include <queue>
|
||||||
|
#include "../macroInt.h"
|
||||||
|
#include "sound/ymz280b.h"
|
||||||
|
|
||||||
|
class DivPlatformYMZ280B: public DivDispatch {
|
||||||
|
struct Channel {
|
||||||
|
int freq, baseFreq, pitch, pitch2;
|
||||||
|
unsigned int audPos;
|
||||||
|
int sample, wave, ins;
|
||||||
|
int note;
|
||||||
|
int panning;
|
||||||
|
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, setPos;
|
||||||
|
int vol, outVol;
|
||||||
|
DivMacroInt std;
|
||||||
|
void macroInit(DivInstrument* which) {
|
||||||
|
std.init(which);
|
||||||
|
pitch2=0;
|
||||||
|
}
|
||||||
|
Channel():
|
||||||
|
freq(0),
|
||||||
|
baseFreq(0),
|
||||||
|
pitch(0),
|
||||||
|
pitch2(0),
|
||||||
|
audPos(0),
|
||||||
|
sample(-1),
|
||||||
|
ins(-1),
|
||||||
|
note(0),
|
||||||
|
panning(8),
|
||||||
|
active(false),
|
||||||
|
insChanged(true),
|
||||||
|
freqChanged(false),
|
||||||
|
keyOn(false),
|
||||||
|
keyOff(false),
|
||||||
|
inPorta(false),
|
||||||
|
setPos(false),
|
||||||
|
vol(255),
|
||||||
|
outVol(255) {}
|
||||||
|
};
|
||||||
|
Channel chan[8];
|
||||||
|
DivDispatchOscBuffer* oscBuf[8];
|
||||||
|
bool isMuted[8];
|
||||||
|
int chipType;
|
||||||
|
|
||||||
|
unsigned char* sampleMem;
|
||||||
|
size_t sampleMemLen;
|
||||||
|
ymz280b_device ymz280b;
|
||||||
|
unsigned char regPool[256];
|
||||||
|
friend void putDispatchChan(void*,int,int);
|
||||||
|
|
||||||
|
public:
|
||||||
|
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||||
|
int dispatch(DivCommand c);
|
||||||
|
void* getChanState(int chan);
|
||||||
|
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||||
|
unsigned char* getRegisterPool();
|
||||||
|
int getRegisterPoolSize();
|
||||||
|
void reset();
|
||||||
|
void forceIns();
|
||||||
|
void tick(bool sysTick=true);
|
||||||
|
void muteChannel(int ch, bool mute);
|
||||||
|
float getPostAmp();
|
||||||
|
bool isStereo();
|
||||||
|
void setChipModel(int type);
|
||||||
|
void notifyInsChange(int ins);
|
||||||
|
void notifyWaveChange(int wave);
|
||||||
|
void notifyInsDeletion(void* ins);
|
||||||
|
void poke(unsigned int addr, unsigned short val);
|
||||||
|
void poke(std::vector<DivRegWrite>& wlist);
|
||||||
|
const char** getRegisterSheet();
|
||||||
|
const char* getEffectName(unsigned char effect);
|
||||||
|
const void* getSampleMem(int index = 0);
|
||||||
|
size_t getSampleMemCapacity(int index = 0);
|
||||||
|
size_t getSampleMemUsage(int index = 0);
|
||||||
|
void renderSamples();
|
||||||
|
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||||
|
void quit();
|
||||||
|
private:
|
||||||
|
void writeOutVol(int ch);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -96,8 +96,9 @@ bool DivSample::initInternal(unsigned char d, int count) {
|
||||||
case 3: // YMZ ADPCM
|
case 3: // YMZ ADPCM
|
||||||
if (dataZ!=NULL) delete[] dataZ;
|
if (dataZ!=NULL) delete[] dataZ;
|
||||||
lengthZ=(count+1)/2;
|
lengthZ=(count+1)/2;
|
||||||
dataZ=new unsigned char[(lengthZ+255)&(~0xff)];
|
// for padding AICA sample
|
||||||
memset(dataZ,0,(lengthZ+255)&(~0xff));
|
dataZ=new unsigned char[(lengthZ+3)&(~0x03)];
|
||||||
|
memset(dataZ,0,(lengthZ+3)&(~0x03));
|
||||||
break;
|
break;
|
||||||
case 4: // QSound ADPCM
|
case 4: // QSound ADPCM
|
||||||
if (dataQSoundA!=NULL) delete[] dataQSoundA;
|
if (dataQSoundA!=NULL) delete[] dataQSoundA;
|
||||||
|
@ -711,7 +712,7 @@ void DivSample::render() {
|
||||||
}
|
}
|
||||||
if (depth!=3) { // YMZ ADPCM
|
if (depth!=3) { // YMZ ADPCM
|
||||||
if (!initInternal(3,samples)) return;
|
if (!initInternal(3,samples)) return;
|
||||||
ymz_encode(data16,dataZ,(samples+511)&(~0x1ff));
|
ymz_encode(data16,dataZ,(samples+7)&(~0x7));
|
||||||
}
|
}
|
||||||
if (depth!=4) { // QSound ADPCM
|
if (depth!=4) { // QSound ADPCM
|
||||||
if (!initInternal(4,samples)) return;
|
if (!initInternal(4,samples)) return;
|
||||||
|
|
|
@ -86,7 +86,7 @@ struct DivSample {
|
||||||
|
|
||||||
unsigned int length8, length16, length1, lengthDPCM, lengthZ, lengthQSoundA, lengthA, lengthB, lengthBRR, lengthVOX;
|
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 off8, off16, off1, offDPCM, offZ, offQSoundA, offA, offB, offBRR, offVOX;
|
||||||
unsigned int offSegaPCM, offQSound, offX1_010, offSU, offRF5C68;
|
unsigned int offSegaPCM, offQSound, offX1_010, offSU, offYMZ280B, offRF5C68;
|
||||||
|
|
||||||
unsigned int samples;
|
unsigned int samples;
|
||||||
|
|
||||||
|
|
|
@ -107,6 +107,7 @@ enum DivSystem {
|
||||||
DIV_SYSTEM_SOUND_UNIT,
|
DIV_SYSTEM_SOUND_UNIT,
|
||||||
DIV_SYSTEM_MSM6295,
|
DIV_SYSTEM_MSM6295,
|
||||||
DIV_SYSTEM_MSM6258,
|
DIV_SYSTEM_MSM6258,
|
||||||
|
DIV_SYSTEM_YMZ280B,
|
||||||
DIV_SYSTEM_NAMCO,
|
DIV_SYSTEM_NAMCO,
|
||||||
DIV_SYSTEM_NAMCO_15XX,
|
DIV_SYSTEM_NAMCO_15XX,
|
||||||
DIV_SYSTEM_NAMCO_CUS30,
|
DIV_SYSTEM_NAMCO_CUS30,
|
||||||
|
|
|
@ -1966,6 +1966,15 @@ void DivEngine::registerSystems() {
|
||||||
{DIV_INS_AMIGA}
|
{DIV_INS_AMIGA}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
sysDefs[DIV_SYSTEM_YMZ280B]=new DivSysDef(
|
||||||
|
"Yamaha YMZ280B", NULL, 0xb8, 0, 8, false, true, 0x151, false,
|
||||||
|
"used in some arcade boards. Can play back either 4-bit ADPCM, 8-bit PCM or 16-bit PCM.",
|
||||||
|
{"PCM 1", "PCM 2", "PCM 3", "PCM 4", "PCM 5", "PCM 6", "PCM 7", "PCM 8"},
|
||||||
|
{"1", "2", "3", "4", "5", "6", "7", "8"},
|
||||||
|
{DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
|
||||||
|
{DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}
|
||||||
|
);
|
||||||
|
|
||||||
sysDefs[DIV_SYSTEM_NAMCO]=new DivSysDef(
|
sysDefs[DIV_SYSTEM_NAMCO]=new DivSysDef(
|
||||||
"Namco WSG", NULL, 0xb9, 0, 3, false, true, 0, false,
|
"Namco WSG", NULL, 0xb9, 0, 3, false, true, 0, false,
|
||||||
"a wavetable sound chip used in Pac-Man, among other early Namco arcade games.",
|
"a wavetable sound chip used in Pac-Man, among other early Namco arcade games.",
|
||||||
|
|
|
@ -769,6 +769,11 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
|
||||||
logW("SCC+: writing to unmapped address %.2x!",write.addr);
|
logW("SCC+: writing to unmapped address %.2x!",write.addr);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case DIV_SYSTEM_YMZ280B:
|
||||||
|
w->writeC(0x0d|baseAddr1);
|
||||||
|
w->writeC(write.addr&0xff);
|
||||||
|
w->writeC(write.val&0xff);
|
||||||
|
break;
|
||||||
case DIV_SYSTEM_RF5C68:
|
case DIV_SYSTEM_RF5C68:
|
||||||
w->writeC(rf5c68Addr);
|
w->writeC(rf5c68Addr);
|
||||||
w->writeC(write.addr&0xff);
|
w->writeC(write.addr&0xff);
|
||||||
|
@ -895,6 +900,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
||||||
int writeSegaPCM=0;
|
int writeSegaPCM=0;
|
||||||
DivDispatch* writeX1010[2]={NULL,NULL};
|
DivDispatch* writeX1010[2]={NULL,NULL};
|
||||||
DivDispatch* writeQSound[2]={NULL,NULL};
|
DivDispatch* writeQSound[2]={NULL,NULL};
|
||||||
|
DivDispatch* writeZ280[2]={NULL,NULL};
|
||||||
DivDispatch* writeRF5C68[2]={NULL,NULL};
|
DivDispatch* writeRF5C68[2]={NULL,NULL};
|
||||||
|
|
||||||
for (int i=0; i<song.systemLen; i++) {
|
for (int i=0; i<song.systemLen; i++) {
|
||||||
|
@ -1257,6 +1263,19 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
||||||
howManyChips++;
|
howManyChips++;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case DIV_SYSTEM_YMZ280B:
|
||||||
|
if (!hasZ280) {
|
||||||
|
hasZ280=disCont[i].dispatch->chipClock;
|
||||||
|
willExport[i]=true;
|
||||||
|
writeZ280[0]=disCont[i].dispatch;
|
||||||
|
} else if (!(hasZ280&0x40000000)) {
|
||||||
|
isSecond[i]=true;
|
||||||
|
willExport[i]=true;
|
||||||
|
writeZ280[1]=disCont[i].dispatch;
|
||||||
|
hasZ280|=0x40000000;
|
||||||
|
howManyChips++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case DIV_SYSTEM_RF5C68:
|
case DIV_SYSTEM_RF5C68:
|
||||||
// here's the dumb part: VGM thinks RF5C68 and RF5C164 are different
|
// here's the dumb part: VGM thinks RF5C68 and RF5C164 are different
|
||||||
// chips even though the only difference is the output resolution
|
// chips even though the only difference is the output resolution
|
||||||
|
@ -1273,7 +1292,6 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
||||||
hasRFC=disCont[i].dispatch->chipClock;
|
hasRFC=disCont[i].dispatch->chipClock;
|
||||||
willExport[i]=true;
|
willExport[i]=true;
|
||||||
writeRF5C68[0]=disCont[i].dispatch;
|
writeRF5C68[0]=disCont[i].dispatch;
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -1537,8 +1555,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
||||||
delete[] pcmMem;
|
delete[] pcmMem;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ADPCM (OPNA)
|
|
||||||
for (int i=0; i<2; i++) {
|
for (int i=0; i<2; i++) {
|
||||||
|
// ADPCM (OPNA)
|
||||||
if (writeADPCM_OPNA[i]!=NULL && writeADPCM_OPNA[i]->getSampleMemUsage(0)>0) {
|
if (writeADPCM_OPNA[i]!=NULL && writeADPCM_OPNA[i]->getSampleMemUsage(0)>0) {
|
||||||
w->writeC(0x67);
|
w->writeC(0x67);
|
||||||
w->writeC(0x66);
|
w->writeC(0x66);
|
||||||
|
@ -1548,10 +1566,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
||||||
w->writeI(0);
|
w->writeI(0);
|
||||||
w->write(writeADPCM_OPNA[i]->getSampleMem(0),writeADPCM_OPNA[i]->getSampleMemUsage(0));
|
w->write(writeADPCM_OPNA[i]->getSampleMem(0),writeADPCM_OPNA[i]->getSampleMemUsage(0));
|
||||||
}
|
}
|
||||||
}
|
// ADPCM-A (OPNB)
|
||||||
|
|
||||||
// ADPCM-A (OPNB)
|
|
||||||
for (int i=0; i<2; i++) {
|
|
||||||
if (writeADPCM_OPNB[i]!=NULL && writeADPCM_OPNB[i]->getSampleMemUsage(0)>0) {
|
if (writeADPCM_OPNB[i]!=NULL && writeADPCM_OPNB[i]->getSampleMemUsage(0)>0) {
|
||||||
w->writeC(0x67);
|
w->writeC(0x67);
|
||||||
w->writeC(0x66);
|
w->writeC(0x66);
|
||||||
|
@ -1561,10 +1576,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
||||||
w->writeI(0);
|
w->writeI(0);
|
||||||
w->write(writeADPCM_OPNB[i]->getSampleMem(0),writeADPCM_OPNB[i]->getSampleMemUsage(0));
|
w->write(writeADPCM_OPNB[i]->getSampleMem(0),writeADPCM_OPNB[i]->getSampleMemUsage(0));
|
||||||
}
|
}
|
||||||
}
|
// ADPCM-B (OPNB)
|
||||||
|
|
||||||
// ADPCM-B (OPNB)
|
|
||||||
for (int i=0; i<2; i++) {
|
|
||||||
if (writeADPCM_OPNB[i]!=NULL && writeADPCM_OPNB[i]->getSampleMemUsage(1)>0) {
|
if (writeADPCM_OPNB[i]!=NULL && writeADPCM_OPNB[i]->getSampleMemUsage(1)>0) {
|
||||||
w->writeC(0x67);
|
w->writeC(0x67);
|
||||||
w->writeC(0x66);
|
w->writeC(0x66);
|
||||||
|
@ -1574,10 +1586,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
||||||
w->writeI(0);
|
w->writeI(0);
|
||||||
w->write(writeADPCM_OPNB[i]->getSampleMem(1),writeADPCM_OPNB[i]->getSampleMemUsage(1));
|
w->write(writeADPCM_OPNB[i]->getSampleMem(1),writeADPCM_OPNB[i]->getSampleMemUsage(1));
|
||||||
}
|
}
|
||||||
}
|
// ADPCM (Y8950)
|
||||||
|
|
||||||
// ADPCM (Y8950)
|
|
||||||
for (int i=0; i<2; i++) {
|
|
||||||
if (writeADPCM_Y8950[i]!=NULL && writeADPCM_Y8950[i]->getSampleMemUsage(0)>0) {
|
if (writeADPCM_Y8950[i]!=NULL && writeADPCM_Y8950[i]->getSampleMemUsage(0)>0) {
|
||||||
w->writeC(0x67);
|
w->writeC(0x67);
|
||||||
w->writeC(0x66);
|
w->writeC(0x66);
|
||||||
|
@ -1587,9 +1596,6 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
||||||
w->writeI(0);
|
w->writeI(0);
|
||||||
w->write(writeADPCM_Y8950[i]->getSampleMem(0),writeADPCM_Y8950[i]->getSampleMemUsage(0));
|
w->write(writeADPCM_Y8950[i]->getSampleMem(0),writeADPCM_Y8950[i]->getSampleMemUsage(0));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for (int i=0; i<2; i++) {
|
|
||||||
if (writeQSound[i]!=NULL && writeQSound[i]->getSampleMemUsage()>0) {
|
if (writeQSound[i]!=NULL && writeQSound[i]->getSampleMemUsage()>0) {
|
||||||
unsigned int blockSize=(writeQSound[i]->getSampleMemUsage()+0xffff)&(~0xffff);
|
unsigned int blockSize=(writeQSound[i]->getSampleMemUsage()+0xffff)&(~0xffff);
|
||||||
if (blockSize > 0x1000000) {
|
if (blockSize > 0x1000000) {
|
||||||
|
@ -1603,9 +1609,6 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
||||||
w->writeI(0);
|
w->writeI(0);
|
||||||
w->write(writeQSound[i]->getSampleMem(),blockSize);
|
w->write(writeQSound[i]->getSampleMem(),blockSize);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for (int i=0; i<2; i++) {
|
|
||||||
if (writeX1010[i]!=NULL && writeX1010[i]->getSampleMemUsage()>0) {
|
if (writeX1010[i]!=NULL && writeX1010[i]->getSampleMemUsage()>0) {
|
||||||
w->writeC(0x67);
|
w->writeC(0x67);
|
||||||
w->writeC(0x66);
|
w->writeC(0x66);
|
||||||
|
@ -1615,6 +1618,15 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
||||||
w->writeI(0);
|
w->writeI(0);
|
||||||
w->write(writeX1010[i]->getSampleMem(),writeX1010[i]->getSampleMemUsage());
|
w->write(writeX1010[i]->getSampleMem(),writeX1010[i]->getSampleMemUsage());
|
||||||
}
|
}
|
||||||
|
if (writeZ280[i]!=NULL && writeZ280[i]->getSampleMemUsage()>0) {
|
||||||
|
w->writeC(0x67);
|
||||||
|
w->writeC(0x66);
|
||||||
|
w->writeC(0x86);
|
||||||
|
w->writeI((writeZ280[i]->getSampleMemUsage()+8)|(i*0x80000000));
|
||||||
|
w->writeI(writeZ280[i]->getSampleMemCapacity());
|
||||||
|
w->writeI(0);
|
||||||
|
w->write(writeZ280[i]->getSampleMem(),writeZ280[i]->getSampleMemUsage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i=0; i<2; i++) {
|
for (int i=0; i<2; i++) {
|
||||||
|
|
|
@ -880,6 +880,7 @@ const int availableSystems[]={
|
||||||
DIV_SYSTEM_MMC5,
|
DIV_SYSTEM_MMC5,
|
||||||
DIV_SYSTEM_SCC,
|
DIV_SYSTEM_SCC,
|
||||||
DIV_SYSTEM_SCC_PLUS,
|
DIV_SYSTEM_SCC_PLUS,
|
||||||
|
DIV_SYSTEM_YMZ280B,
|
||||||
DIV_SYSTEM_MSM6258,
|
DIV_SYSTEM_MSM6258,
|
||||||
DIV_SYSTEM_MSM6295,
|
DIV_SYSTEM_MSM6295,
|
||||||
DIV_SYSTEM_NAMCO,
|
DIV_SYSTEM_NAMCO,
|
||||||
|
|
|
@ -260,6 +260,12 @@ void FurnaceGUI::initSystemPresets() {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
cat.systems.push_back(FurnaceGUISysDef(
|
||||||
|
"Yamaha YMZ280B", {
|
||||||
|
DIV_SYSTEM_YMZ280B, 64, 0, 0,
|
||||||
|
0
|
||||||
|
}
|
||||||
|
));
|
||||||
cat.systems.push_back(FurnaceGUISysDef(
|
cat.systems.push_back(FurnaceGUISysDef(
|
||||||
"Ricoh RF5C68", {
|
"Ricoh RF5C68", {
|
||||||
DIV_SYSTEM_RF5C68, 64, 0, 0,
|
DIV_SYSTEM_RF5C68, 64, 0, 0,
|
||||||
|
|
|
@ -427,6 +427,7 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool
|
||||||
case DIV_SYSTEM_PET:
|
case DIV_SYSTEM_PET:
|
||||||
case DIV_SYSTEM_SCC:
|
case DIV_SYSTEM_SCC:
|
||||||
case DIV_SYSTEM_SCC_PLUS:
|
case DIV_SYSTEM_SCC_PLUS:
|
||||||
|
case DIV_SYSTEM_YMZ280B:
|
||||||
ImGui::Text("nothing to configure");
|
ImGui::Text("nothing to configure");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
Loading…
Reference in a new issue