Merge branch 'master' of https://github.com/tildearrow/furnace into rf5c68

This commit is contained in:
Natt Akuma 2022-05-21 03:02:08 +07:00
commit 27a412c134
20 changed files with 2468 additions and 74 deletions

View file

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

View file

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

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

View 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

View file

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

View file

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

View file

@ -0,0 +1,326 @@
// license:BSD-3-Clause
// copyright-holders:Barry Rodewald
/**********************************************************************************************
*
* OKI MSM6258 ADPCM
*
* TODO:
* 3-bit ADPCM support
* Recording?
*
**********************************************************************************************/
#include "emu.h"
#include "okim6258.h"
#define COMMAND_STOP (1 << 0)
#define COMMAND_PLAY (1 << 1)
#define COMMAND_RECORD (1 << 2)
#define STATUS_PLAYING (1 << 1)
#define STATUS_RECORDING (1 << 2)
static const int dividers[4] = { 1024, 768, 512, 512 };
/* step size index shift table */
static const int index_shift[8] = { -1, -1, -1, -1, 2, 4, 6, 8 };
/* lookup table for the precomputed difference */
static int diff_lookup[49*16];
/* tables computed? */
static int tables_computed = 0;
// device type definition
DEFINE_DEVICE_TYPE(OKIM6258, okim6258_device, "okim6258", "OKI MSM6258 ADPCM")
//**************************************************************************
// LIVE DEVICE
//**************************************************************************
//-------------------------------------------------
// okim6258_device - constructor
//-------------------------------------------------
okim6258_device::okim6258_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: device_t(mconfig, OKIM6258, tag, owner, clock),
device_sound_interface(mconfig, *this),
m_status(0),
m_start_divider(0),
m_divider(512),
m_adpcm_type(0),
m_data_in(0),
m_nibble_shift(0),
m_stream(nullptr),
m_output_bits(0),
m_signal(0),
m_step(0)
{
}
/**********************************************************************************************
compute_tables -- compute the difference tables
***********************************************************************************************/
static void compute_tables()
{
/* nibble to bit map */
static const int nbl2bit[16][4] =
{
{ 1, 0, 0, 0}, { 1, 0, 0, 1}, { 1, 0, 1, 0}, { 1, 0, 1, 1},
{ 1, 1, 0, 0}, { 1, 1, 0, 1}, { 1, 1, 1, 0}, { 1, 1, 1, 1},
{-1, 0, 0, 0}, {-1, 0, 0, 1}, {-1, 0, 1, 0}, {-1, 0, 1, 1},
{-1, 1, 0, 0}, {-1, 1, 0, 1}, {-1, 1, 1, 0}, {-1, 1, 1, 1}
};
int step, nib;
/* loop over all possible steps */
for (step = 0; step <= 48; step++)
{
/* compute the step value */
int stepval = floor(16.0 * pow(11.0 / 10.0, (double)step));
/* loop over all nibbles and compute the difference */
for (nib = 0; nib < 16; nib++)
{
diff_lookup[step*16 + nib] = nbl2bit[nib][0] *
(stepval * nbl2bit[nib][1] +
stepval/2 * nbl2bit[nib][2] +
stepval/4 * nbl2bit[nib][3] +
stepval/8);
}
}
tables_computed = 1;
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void okim6258_device::device_start()
{
compute_tables();
m_divider = dividers[m_start_divider];
m_stream = stream_alloc(0, 1, clock()/m_divider);
m_signal = -2;
m_step = 0;
state_save_register();
}
//-------------------------------------------------
// device_reset - device-specific reset
//-------------------------------------------------
void okim6258_device::device_reset()
{
m_stream->update();
m_signal = -2;
m_step = 0;
m_status = 0;
}
//-------------------------------------------------
// sound_stream_update - handle a stream update
//-------------------------------------------------
void okim6258_device::sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs)
{
auto &buffer = outputs[0];
if (m_status & STATUS_PLAYING)
{
int nibble_shift = m_nibble_shift;
for (int sampindex = 0; sampindex < buffer.samples(); sampindex++)
{
/* Compute the new amplitude and update the current step */
int nibble = (m_data_in >> nibble_shift) & 0xf;
/* Output to the buffer */
int16_t sample = clock_adpcm(nibble);
nibble_shift ^= 4;
buffer.put_int(sampindex, sample, 32768);
}
/* Update the parameters */
m_nibble_shift = nibble_shift;
}
else
{
buffer.fill(0);
}
}
/**********************************************************************************************
state save support for MAME
***********************************************************************************************/
void okim6258_device::state_save_register()
{
save_item(NAME(m_status));
save_item(NAME(m_divider));
save_item(NAME(m_data_in));
save_item(NAME(m_nibble_shift));
save_item(NAME(m_signal));
save_item(NAME(m_step));
}
int16_t okim6258_device::clock_adpcm(uint8_t nibble)
{
int32_t max = (1 << (m_output_bits - 1)) - 1;
int32_t min = -(1 << (m_output_bits - 1));
m_signal += diff_lookup[m_step * 16 + (nibble & 15)];
/* clamp to the maximum */
if (m_signal > max)
m_signal = max;
else if (m_signal < min)
m_signal = min;
/* adjust the step size and clamp */
m_step += index_shift[nibble & 7];
if (m_step > 48)
m_step = 48;
else if (m_step < 0)
m_step = 0;
/* return the signal scaled up to 32767 */
return m_signal << 4;
}
/**********************************************************************************************
okim6258::set_divider -- set the master clock divider
***********************************************************************************************/
void okim6258_device::set_divider(int val)
{
m_divider = dividers[val];
notify_clock_changed();
}
/**********************************************************************************************
okim6258::set_clock -- set the master clock
***********************************************************************************************/
void okim6258_device::device_clock_changed()
{
m_stream->set_sample_rate(clock() / m_divider);
}
/**********************************************************************************************
okim6258::get_vclk -- get the VCLK/sampling frequency
***********************************************************************************************/
int okim6258_device::get_vclk()
{
return (clock() / m_divider);
}
/**********************************************************************************************
okim6258_status_r -- read the status port of an OKIM6258-compatible chip
***********************************************************************************************/
uint8_t okim6258_device::status_r()
{
m_stream->update();
return (m_status & STATUS_PLAYING) ? 0x00 : 0x80;
}
/**********************************************************************************************
okim6258_data_w -- write to the control port of an OKIM6258-compatible chip
***********************************************************************************************/
void okim6258_device::data_w(uint8_t data)
{
/* update the stream */
m_stream->update();
m_data_in = data;
m_nibble_shift = 0;
}
/**********************************************************************************************
okim6258_ctrl_w -- write to the control port of an OKIM6258-compatible chip
***********************************************************************************************/
void okim6258_device::ctrl_w(uint8_t data)
{
m_stream->update();
if (data & COMMAND_STOP)
{
m_status &= ~(STATUS_PLAYING | STATUS_RECORDING);
return;
}
if (data & COMMAND_PLAY)
{
if (!(m_status & STATUS_PLAYING))
{
m_status |= STATUS_PLAYING;
/* Also reset the ADPCM parameters */
m_signal = -2;
m_step = 0;
m_nibble_shift = 0;
}
}
else
{
m_status &= ~STATUS_PLAYING;
}
if (data & COMMAND_RECORD)
{
logerror("M6258: Record enabled\n");
m_status |= STATUS_RECORDING;
}
else
{
m_status &= ~STATUS_RECORDING;
}
}

View file

@ -0,0 +1,74 @@
// license:BSD-3-Clause
// copyright-holders:Barry Rodewald
#ifndef MAME_SOUND_OKIM6258_H
#define MAME_SOUND_OKIM6258_H
#pragma once
//**************************************************************************
// TYPE DEFINITIONS
//**************************************************************************
// ======================> okim6258_device
class okim6258_device : public device_t,
public device_sound_interface
{
public:
static constexpr int FOSC_DIV_BY_1024 = 0;
static constexpr int FOSC_DIV_BY_768 = 1;
static constexpr int FOSC_DIV_BY_512 = 2;
static constexpr int TYPE_3BITS = 0;
static constexpr int TYPE_4BITS = 1;
static constexpr int OUTPUT_10BITS = 10;
static constexpr int OUTPUT_12BITS = 12;
okim6258_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
// configuration
void set_start_div(int div) { m_start_divider = div; }
void set_type(int type) { m_adpcm_type = type; }
void set_outbits(int outbit) { m_output_bits = outbit; }
uint8_t status_r();
void data_w(uint8_t data);
void ctrl_w(uint8_t data);
void set_divider(int val);
int get_vclk();
protected:
// device-level overrides
virtual void device_start() override;
virtual void device_reset() override;
virtual void device_clock_changed() override;
// sound stream update overrides
virtual void sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs) override;
private:
void state_save_register();
int16_t clock_adpcm(uint8_t nibble);
uint8_t m_status;
uint32_t m_start_divider;
uint32_t m_divider; /* master clock divider */
uint8_t m_adpcm_type; /* 3/4 bit ADPCM select */
uint8_t m_data_in; /* ADPCM data-in register */
uint8_t m_nibble_shift; /* nibble select */
sound_stream *m_stream; /* which stream are we playing on? */
uint8_t m_output_bits; /* D/A precision is 10-bits but 12-bit data can be
output serially to an external DAC */
int32_t m_signal;
int32_t m_step;
};
DECLARE_DEVICE_TYPE(OKIM6258, okim6258_device)
#endif // MAME_SOUND_OKIM6258_H

View file

@ -0,0 +1,785 @@
// license:BSD-3-Clause
// copyright-holders:Aaron Giles
/*
Yamaha YMZ280B driver
by Aaron Giles
YMZ280B 8-Channel PCMD8 PCM/ADPCM Decoder
Features as listed in LSI-4MZ280B3 data sheet:
Voice data stored in external memory can be played back simultaneously for up to eight voices
Voice data format can be selected from 4-bit ADPCM, 8-bit PCM and 16-bit PCM
Control of voice data external memory
Up to 16M bytes of ROM or SRAM (x 8 bits, access time 150ms max) can be connected
Continuous access is possible
Loop playback between selective addresses is possible
Voice data playback frequency control
4-bit ADPCM ................ 0.172 to 44.1kHz in 256 steps
8-bit PCM, 16-bit PCM ...... 0.172 to 88.2kHz in 512 steps
256 steps total level and 16 steps panpot can be set
Voice signal is output in stereo 16-bit 2's complement MSB-first format
TODO:
- Is memory handling 100% correct? At the moment, Konami firebeat.c is the only
hardware currently emulated that uses external handlers.
It also happens to be the only one using 16-bit PCM.
Some other drivers (eg. bishi.cpp, bfm_sc4/5.cpp) also use ROM readback.
*/
#include "ymz280b.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
#define MAX_SAMPLE_CHUNK 10000
static constexpr unsigned FRAC_BITS = 8;
static constexpr s32 FRAC_ONE = 1 << FRAC_BITS;
/* step size index shift table */
static constexpr int index_scale[8] = { 0x0e6, 0x0e6, 0x0e6, 0x0e6, 0x133, 0x199, 0x200, 0x266 };
/* lookup table for the precomputed difference */
static int diff_lookup[16];
void ymz280b_device::update_step(struct YMZ280BVoice *voice)
{
int frequency;
/* compute the frequency */
if (voice->mode == 1)
frequency = voice->fnum & 0x0ff;
else
frequency = voice->fnum & 0x1ff;
voice->output_step = frequency + 1; // ((fnum + 1) * (input clock / 384)) / 256
}
void ymz280b_device::update_volumes(struct YMZ280BVoice *voice)
{
if (voice->pan == 8)
{
voice->output_left = voice->level;
voice->output_right = voice->level;
}
else if (voice->pan < 8)
{
voice->output_left = voice->level;
/* pan 1 is hard-left, what's pan 0? for now assume same as pan 1 */
voice->output_right = (voice->pan == 0) ? 0 : voice->level * (voice->pan - 1) / 7;
}
else
{
voice->output_left = voice->level * (15 - voice->pan) / 7;
voice->output_right = voice->level;
}
}
/**********************************************************************************************
compute_tables -- compute the difference tables
***********************************************************************************************/
static void compute_tables()
{
/* loop over all nibbles and compute the difference */
for (int nib = 0; nib < 16; nib++)
{
int value = (nib & 0x07) * 2 + 1;
diff_lookup[nib] = (nib & 0x08) ? -value : value;
}
}
/**********************************************************************************************
generate_adpcm -- general ADPCM decoding routine
***********************************************************************************************/
int ymz280b_device::generate_adpcm(struct YMZ280BVoice *voice, s16 *buffer, int samples)
{
u32 position = voice->position;
int signal = voice->signal;
int step = voice->step;
int val;
/* two cases: first cases is non-looping */
if (!voice->looping)
{
/* loop while we still have samples to generate */
while (samples)
{
/* compute the new amplitude and update the current step */
val = m_ext_mem[position / 2] >> ((~position & 1) << 2);
signal += (step * diff_lookup[val & 15]) / 8;
/* clamp to the maximum */
if (signal > 32767)
signal = 32767;
else if (signal < -32768)
signal = -32768;
/* adjust the step size and clamp */
step = (step * index_scale[val & 7]) >> 8;
if (step > 0x6000)
step = 0x6000;
else if (step < 0x7f)
step = 0x7f;
/* output to the buffer, scaling by the volume */
*buffer++ = signal;
samples--;
/* next! */
position++;
if (position >= voice->stop)
{
voice->ended = true;
break;
}
}
}
/* second case: looping */
else
{
/* loop while we still have samples to generate */
while (samples)
{
/* compute the new amplitude and update the current step */
val = m_ext_mem[position / 2] >> ((~position & 1) << 2);
signal += (step * diff_lookup[val & 15]) / 8;
/* clamp to the maximum */
if (signal > 32767)
signal = 32767;
else if (signal < -32768)
signal = -32768;
/* adjust the step size and clamp */
step = (step * index_scale[val & 7]) >> 8;
if (step > 0x6000)
step = 0x6000;
else if (step < 0x7f)
step = 0x7f;
/* output to the buffer, scaling by the volume */
*buffer++ = signal;
samples--;
/* next! */
position++;
if (position == voice->loop_start && voice->loop_count == 0)
{
voice->loop_signal = signal;
voice->loop_step = step;
}
if (position >= voice->loop_end)
{
if (voice->keyon)
{
position = voice->loop_start;
signal = voice->loop_signal;
step = voice->loop_step;
voice->loop_count++;
}
}
if (position >= voice->stop)
{
voice->ended = true;
break;
}
}
}
/* update the parameters */
voice->position = position;
voice->signal = signal;
voice->step = step;
return samples;
}
/**********************************************************************************************
generate_pcm8 -- general 8-bit PCM decoding routine
***********************************************************************************************/
int ymz280b_device::generate_pcm8(struct YMZ280BVoice *voice, s16 *buffer, int samples)
{
u32 position = voice->position;
int val;
/* two cases: first cases is non-looping */
if (!voice->looping)
{
/* loop while we still have samples to generate */
while (samples)
{
/* fetch the current value */
val = m_ext_mem[position / 2];
/* output to the buffer, scaling by the volume */
*buffer++ = (s8)val * 256;
samples--;
/* next! */
position += 2;
if (position >= voice->stop)
{
voice->ended = true;
break;
}
}
}
/* second case: looping */
else
{
/* loop while we still have samples to generate */
while (samples)
{
/* fetch the current value */
val = m_ext_mem[position / 2];
/* output to the buffer, scaling by the volume */
*buffer++ = (s8)val * 256;
samples--;
/* next! */
position += 2;
if (position >= voice->loop_end)
{
if (voice->keyon)
position = voice->loop_start;
}
if (position >= voice->stop)
{
voice->ended = true;
break;
}
}
}
/* update the parameters */
voice->position = position;
return samples;
}
/**********************************************************************************************
generate_pcm16 -- general 16-bit PCM decoding routine
***********************************************************************************************/
int ymz280b_device::generate_pcm16(struct YMZ280BVoice *voice, s16 *buffer, int samples)
{
u32 position = voice->position;
int val;
/* two cases: first cases is non-looping */
if (!voice->looping)
{
/* loop while we still have samples to generate */
while (samples)
{
/* fetch the current value */
val = (s16)((m_ext_mem[position / 2 + 1] << 8) + m_ext_mem[position / 2 + 0]);
/* output to the buffer, scaling by the volume */
*buffer++ = val;
samples--;
/* next! */
position += 4;
if (position >= voice->stop)
{
voice->ended = true;
break;
}
}
}
/* second case: looping */
else
{
/* loop while we still have samples to generate */
while (samples)
{
/* fetch the current value */
val = (s16)((m_ext_mem[position / 2 + 1] << 8) + m_ext_mem[position / 2 + 0]);
/* output to the buffer, scaling by the volume */
*buffer++ = val;
samples--;
/* next! */
position += 4;
if (position >= voice->loop_end)
{
if (voice->keyon)
position = voice->loop_start;
}
if (position >= voice->stop)
{
voice->ended = true;
break;
}
}
}
/* update the parameters */
voice->position = position;
return samples;
}
//-------------------------------------------------
// sound_stream_update - handle a stream update
//-------------------------------------------------
void ymz280b_device::sound_stream_update(s16 **outputs, int samples)
{
int v;
/* loop over voices */
for (v = 0; v < 8; v++)
{
struct YMZ280BVoice *voice = &m_voice[v];
s16 prev = voice->last_sample;
s16 curr = voice->curr_sample;
s16 *curr_data = m_scratch.get();
s16 *ldest = outputs[v*2];
s16 *rdest = outputs[v*2+1];
s32 sampindex = 0;
u32 new_samples, samples_left;
u32 final_pos;
int remaining = samples;
int lvol = voice->output_left;
int rvol = voice->output_right;
/* quick out if we're not playing and we're at 0 */
if (!voice->playing && curr == 0 && prev == 0)
{
memset(ldest, 0, samples * sizeof(s16));
memset(rdest, 0, samples * sizeof(s16));
/* make sure next sound plays immediately */
voice->output_pos = FRAC_ONE;
continue;
}
/* finish off the current sample */
/* interpolate */
while (remaining > 0 && voice->output_pos < FRAC_ONE)
{
s32 interp_sample = ((s32(prev) * (FRAC_ONE - voice->output_pos)) + (s32(curr) * voice->output_pos)) >> FRAC_BITS;
ldest[sampindex] = (s16)(interp_sample * lvol / 256);
rdest[sampindex] = (s16)(interp_sample * rvol / 256);
sampindex++;
voice->output_pos += voice->output_step;
remaining--;
}
/* if we're over, continue; otherwise, we're done */
if (voice->output_pos >= FRAC_ONE)
voice->output_pos -= FRAC_ONE;
else
continue;
/* compute how many new samples we need */
final_pos = voice->output_pos + remaining * voice->output_step;
new_samples = (final_pos + FRAC_ONE) >> FRAC_BITS;
if (new_samples > MAX_SAMPLE_CHUNK)
new_samples = MAX_SAMPLE_CHUNK;
samples_left = new_samples;
/* generate them into our buffer */
switch (voice->playing << 7 | voice->mode)
{
case 0x81: samples_left = generate_adpcm(voice, m_scratch.get(), new_samples); break;
case 0x82: samples_left = generate_pcm8(voice, m_scratch.get(), new_samples); break;
case 0x83: samples_left = generate_pcm16(voice, m_scratch.get(), new_samples); break;
default: samples_left = 0; memset(m_scratch.get(), 0, new_samples * sizeof(m_scratch[0])); break;
}
if (samples_left || voice->ended)
{
voice->ended = false;
/* if there are leftovers, ramp back to 0 */
int base = new_samples - samples_left;
int t = (base == 0) ? curr : m_scratch[base - 1];
for (u32 i = 0; i < samples_left; i++)
{
if (t < 0) t = -((-t * 15) >> 4);
else if (t > 0) t = (t * 15) >> 4;
m_scratch[base + i] = t;
}
/* if we hit the end and IRQs are enabled, signal it */
if (base != 0)
{
voice->playing = 0;
/* set update_irq_state_timer. IRQ is signaled on next CPU execution. */
voice->irq_schedule = 1;
}
}
/* advance forward one sample */
prev = curr;
curr = *curr_data++;
/* then sample-rate convert with linear interpolation */
while (remaining > 0)
{
/* interpolate */
while (remaining > 0 && voice->output_pos < FRAC_ONE)
{
int interp_sample = ((s32(prev) * (FRAC_ONE - voice->output_pos)) + (s32(curr) * voice->output_pos)) >> FRAC_BITS;
ldest[sampindex] = (s16)(interp_sample * lvol / 256);
rdest[sampindex] = (s16)(interp_sample * rvol / 256);
sampindex++;
voice->output_pos += voice->output_step;
remaining--;
}
/* if we're over, grab the next samples */
if (voice->output_pos >= FRAC_ONE)
{
voice->output_pos -= FRAC_ONE;
prev = curr;
curr = *curr_data++;
}
}
/* remember the last samples */
voice->last_sample = prev;
voice->curr_sample = curr;
}
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void ymz280b_device::device_start(u8 *ext_mem)
{
m_ext_mem = ext_mem;
/* compute ADPCM tables */
compute_tables();
/* allocate memory */
assert(MAX_SAMPLE_CHUNK < 0x10000);
m_scratch = std::make_unique<s16[]>(MAX_SAMPLE_CHUNK);
for (auto & elem : m_voice)
{
update_step(&elem);
}
}
//-------------------------------------------------
// device_reset - device-specific reset
//-------------------------------------------------
void ymz280b_device::device_reset()
{
/* initial clear registers */
for (int i = 0xff; i >= 0; i--)
{
m_current_register = i;
write_to_register(0);
}
m_current_register = 0;
m_status_register = 0;
m_ext_mem_address = 0;
/* clear other voice parameters */
for (auto &elem : m_voice)
{
struct YMZ280BVoice *voice = &elem;
voice->curr_sample = 0;
voice->last_sample = 0;
voice->output_pos = FRAC_ONE;
voice->playing = 0;
}
}
/**********************************************************************************************
write_to_register -- handle a write to the current register
***********************************************************************************************/
void ymz280b_device::write_to_register(int data)
{
struct YMZ280BVoice *voice;
int i;
/* lower registers follow a pattern */
if (m_current_register < 0x80)
{
voice = &m_voice[(m_current_register >> 2) & 7];
switch (m_current_register & 0xe3)
{
case 0x00: /* pitch low 8 bits */
voice->fnum = (voice->fnum & 0x100) | (data & 0xff);
update_step(voice);
break;
case 0x01: /* pitch upper 1 bit, loop, key on, mode */
voice->fnum = (voice->fnum & 0xff) | ((data & 0x01) << 8);
voice->looping = (data & 0x10) >> 4;
if ((data & 0x60) == 0) data &= 0x7f; /* ignore mode setting and set to same state as KON=0 */
else voice->mode = (data & 0x60) >> 5;
if (!voice->keyon && (data & 0x80) && m_keyon_enable)
{
voice->playing = 1;
voice->position = voice->start;
voice->signal = voice->loop_signal = 0;
voice->step = voice->loop_step = 0x7f;
voice->loop_count = 0;
/* if update_irq_state_timer is set, cancel it. */
voice->irq_schedule = 0;
}
else if (voice->keyon && !(data & 0x80))
{
voice->playing = 0;
/* if update_irq_state_timer is set, cancel it. */
voice->irq_schedule = 0;
}
voice->keyon = (data & 0x80) >> 7;
update_step(voice);
break;
case 0x02: /* total level */
voice->level = data;
update_volumes(voice);
break;
case 0x03: /* pan */
voice->pan = data & 0x0f;
update_volumes(voice);
break;
case 0x20: /* start address high */
voice->start = (voice->start & (0x00ffff << 1)) | (data << 17);
break;
case 0x21: /* loop start address high */
voice->loop_start = (voice->loop_start & (0x00ffff << 1)) | (data << 17);
break;
case 0x22: /* loop end address high */
voice->loop_end = (voice->loop_end & (0x00ffff << 1)) | (data << 17);
break;
case 0x23: /* stop address high */
voice->stop = (voice->stop & (0x00ffff << 1)) | (data << 17);
break;
case 0x40: /* start address middle */
voice->start = (voice->start & (0xff00ff << 1)) | (data << 9);
break;
case 0x41: /* loop start address middle */
voice->loop_start = (voice->loop_start & (0xff00ff << 1)) | (data << 9);
break;
case 0x42: /* loop end address middle */
voice->loop_end = (voice->loop_end & (0xff00ff << 1)) | (data << 9);
break;
case 0x43: /* stop address middle */
voice->stop = (voice->stop & (0xff00ff << 1)) | (data << 9);
break;
case 0x60: /* start address low */
voice->start = (voice->start & (0xffff00 << 1)) | (data << 1);
break;
case 0x61: /* loop start address low */
voice->loop_start = (voice->loop_start & (0xffff00 << 1)) | (data << 1);
break;
case 0x62: /* loop end address low */
voice->loop_end = (voice->loop_end & (0xffff00 << 1)) | (data << 1);
break;
case 0x63: /* stop address low */
voice->stop = (voice->stop & (0xffff00 << 1)) | (data << 1);
break;
default:
if (data != 0)
printf("YMZ280B: unknown register write %02X = %02X\n", m_current_register, data);
break;
}
}
/* upper registers are special */
else
{
switch (m_current_register)
{
/* DSP related (not implemented yet) */
case 0x80: // d0-2: DSP Rch, d3: enable Rch (0: yes, 1: no), d4-6: DSP Lch, d7: enable Lch (0: yes, 1: no)
case 0x81: // d0: enable control of $82 (0: yes, 1: no)
case 0x82: // DSP data
//printf("YMZ280B: DSP register write %02X = %02X\n", m_current_register, data);
break;
case 0x84: /* ROM readback / RAM write (high) */
m_ext_mem_address_hi = data << 16;
break;
case 0x85: /* ROM readback / RAM write (middle) */
m_ext_mem_address_mid = data << 8;
break;
case 0x86: /* ROM readback / RAM write (low) -> update latch */
m_ext_mem_address = m_ext_mem_address_hi | m_ext_mem_address_mid | data;
if (m_ext_mem_enable)
m_ext_readlatch = m_ext_mem[m_ext_mem_address];
break;
case 0x87: /* RAM write */
if (m_ext_mem_enable)
{
m_ext_mem[m_ext_mem_address] = data;
m_ext_mem_address = (m_ext_mem_address + 1) & 0xffffff;
}
break;
case 0xfe: /* IRQ mask */
m_irq_mask = data;
break;
case 0xff: /* IRQ enable, test, etc */
m_ext_mem_enable = (data & 0x40) >> 6;
m_irq_enable = (data & 0x10) >> 4;
if (m_keyon_enable && !(data & 0x80))
{
for (i = 0; i < 8; i++)
{
m_voice[i].playing = 0;
/* if update_irq_state_timer is set, cancel it. */
m_voice[i].irq_schedule = 0;
}
}
else if (!m_keyon_enable && (data & 0x80))
{
for (i = 0; i < 8; i++)
{
if (m_voice[i].keyon && m_voice[i].looping)
m_voice[i].playing = 1;
}
}
m_keyon_enable = (data & 0x80) >> 7;
break;
default:
if (data != 0)
printf("YMZ280B: unknown register write %02X = %02X\n", m_current_register, data);
break;
}
}
}
/**********************************************************************************************
compute_status -- determine the status bits
***********************************************************************************************/
int ymz280b_device::compute_status()
{
u8 result;
result = m_status_register;
/* clear the IRQ state */
m_status_register = 0;
return result;
}
/**********************************************************************************************
read/write -- handle external accesses
***********************************************************************************************/
u8 ymz280b_device::read(offs_t offset)
{
if ((offset & 1) == 0)
{
if (!m_ext_mem_enable)
return 0xff;
/* read from external memory */
u8 ret = m_ext_readlatch;
m_ext_readlatch = m_ext_mem[m_ext_mem_address];
m_ext_mem_address = (m_ext_mem_address + 1) & 0xffffff;
return ret;
}
else
return compute_status();
}
void ymz280b_device::write(offs_t offset, u8 data)
{
if ((offset & 1) == 0)
m_current_register = data;
else
{
write_to_register(data);
}
}
ymz280b_device::ymz280b_device()
: m_current_register(0)
, m_status_register(0)
, m_irq_mask(0)
, m_irq_enable(0)
, m_keyon_enable(0)
, m_ext_mem_enable(0)
, m_ext_readlatch(0)
, m_ext_mem_address_hi(0)
, m_ext_mem_address_mid(0)
, m_ext_mem_address(0)
{
memset(m_voice, 0, sizeof(m_voice));
}

View file

@ -0,0 +1,104 @@
// license:BSD-3-Clause
// copyright-holders:Aaron Giles
/**********************************************************************************************
*
* Yamaha YMZ280B driver
* by Aaron Giles
*
**********************************************************************************************/
#include <memory>
#ifndef MAME_SOUND_YMZ280B_H
#define MAME_SOUND_YMZ280B_H
#pragma once
namespace ymz280b
{
typedef unsigned char u8;
typedef signed char s8;
typedef unsigned short u16;
typedef signed short s16;
typedef unsigned int u32;
typedef signed int s32;
typedef signed int offs_t;
}
using namespace ymz280b;
class ymz280b_device
{
public:
ymz280b_device();
u8 read(offs_t offset);
void write(offs_t offset, u8 data);
void device_start(u8 *ext_mem);
void device_reset();
void sound_stream_update(s16 **outputs, int samples);
private:
/* struct describing a single playing ADPCM voice */
struct YMZ280BVoice
{
u8 playing; /* 1 if we are actively playing */
bool ended; /* indicate voice has ended in case samples_left is 0 */
u8 keyon; /* 1 if the key is on */
u8 looping; /* 1 if looping is enabled */
u8 mode; /* current playback mode */
u16 fnum; /* frequency */
u8 level; /* output level */
u8 pan; /* panning */
u32 start; /* start address, in nibbles */
u32 stop; /* stop address, in nibbles */
u32 loop_start; /* loop start address, in nibbles */
u32 loop_end; /* loop end address, in nibbles */
u32 position; /* current position, in nibbles */
s32 signal; /* current ADPCM signal */
s32 step; /* current ADPCM step */
s32 loop_signal; /* signal at loop start */
s32 loop_step; /* step at loop start */
u32 loop_count; /* number of loops so far */
s32 output_left; /* output volume (left) */
s32 output_right; /* output volume (right) */
s32 output_step; /* step value for frequency conversion */
s32 output_pos; /* current fractional position */
s16 last_sample; /* last sample output */
s16 curr_sample; /* current sample target */
u8 irq_schedule; /* 1 if the IRQ state is updated by timer */
};
void update_step(struct YMZ280BVoice *voice);
void update_volumes(struct YMZ280BVoice *voice);
int generate_adpcm(struct YMZ280BVoice *voice, s16 *buffer, int samples);
int generate_pcm8(struct YMZ280BVoice *voice, s16 *buffer, int samples);
int generate_pcm16(struct YMZ280BVoice *voice, s16 *buffer, int samples);
void write_to_register(int data);
int compute_status();
// internal state
struct YMZ280BVoice m_voice[8]; /* the 8 voices */
u8 m_current_register; /* currently accessible register */
u8 m_status_register; /* current status register */
u8 m_irq_mask; /* current IRQ mask */
u8 m_irq_enable; /* current IRQ enable */
u8 m_keyon_enable; /* key on enable */
u8 m_ext_mem_enable; /* external memory enable */
u8 m_ext_readlatch; /* external memory prefetched data */
u32 m_ext_mem_address_hi;
u32 m_ext_mem_address_mid;
u32 m_ext_mem_address; /* where the CPU can read the ROM */
u8 *m_ext_mem;
std::unique_ptr<s16[]> m_scratch;
};
#endif // MAME_SOUND_YMZ280B_H

View file

@ -0,0 +1,460 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "ymz280b.h"
#include "../engine.h"
#include "../../ta-log.h"
#include <math.h>
#include <map>
#define CHIP_FREQBASE 98304
#define rWrite(a,v) {if(!skipRegisterWrites) {ymz280b.write(0,a); ymz280b.write(1,v); regPool[a]=v; if(dumpWrites) addWrite(a,v); }}
const char* regCheatSheetYMZ280B[]={
"CHx_Freq", "00+x*4",
"CHx_Control", "01+x*4",
"CHx_Volume", "02+x*4",
"CHx_Panning", "03+x*4",
"CHx_StartH", "20+x*4",
"CHx_LoopStartH", "21+x*4",
"CHx_LoopEndH", "22+x*4",
"CHx_EndH", "23+x*4",
"CHx_StartM", "40+x*4",
"CHx_LoopStartM", "41+x*4",
"CHx_LoopEndM", "42+x*4",
"CHx_EndM", "43+x*4",
"CHx_StartL", "60+x*4",
"CHx_LoopStartL", "61+x*4",
"CHx_LoopEndL", "62+x*4",
"CHx_EndL", "63+x*4",
"DSP_Channel", "80",
"DSP_Enable", "81",
"DSP_Data", "82",
"RAM_AddrH", "84",
"RAM_AddrM", "85",
"RAM_AddrL", "86",
"RAM_Data", "87",
"IRQ_Enable", "E0",
"Enable", "FF",
NULL
};
const char** DivPlatformYMZ280B::getRegisterSheet() {
return regCheatSheetYMZ280B;
}
const char* DivPlatformYMZ280B::getEffectName(unsigned char effect) {
return NULL;
}
void DivPlatformYMZ280B::acquire(short* bufL, short* bufR, size_t start, size_t len) {
short buf[16][256];
short *bufPtrs[16]={
buf[0],buf[1],buf[2],buf[3],buf[4],buf[5],buf[6],buf[7],
buf[8],buf[9],buf[10],buf[11],buf[12],buf[13],buf[14],buf[15]
};
size_t pos=start;
while (len > 0) {
size_t blockLen = MIN(len, 256);
ymz280b.sound_stream_update(bufPtrs, blockLen);
for (size_t i=0; i<blockLen; i++) {
int dataL=0;
int dataR=0;
for (int j=0; j<8; j++) {
dataL+=buf[j*2][i];
dataR+=buf[j*2+1][i];
oscBuf[j]->data[oscBuf[j]->needle++]=(short)(((int)buf[j*2][i]+buf[j*2+1][i])/2);
}
bufL[pos]=(short)(dataL/8);
bufR[pos]=(short)(dataR/8);
pos++;
}
len-=blockLen;
}
}
void DivPlatformYMZ280B::tick(bool sysTick) {
for (int i=0; i<8; i++) {
chan[i].std.next();
if (chan[i].std.vol.had) {
chan[i].outVol=((chan[i].vol&0xff)*chan[i].std.vol.val)>>6;
writeOutVol(i);
}
if (chan[i].std.arp.had) {
if (!chan[i].inPorta) {
if (chan[i].std.arp.mode) {
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
} else {
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val);
}
}
chan[i].freqChanged=true;
} else {
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note);
chan[i].freqChanged=true;
}
}
if (chan[i].std.pitch.had) {
if (chan[i].std.pitch.mode) {
chan[i].pitch2+=chan[i].std.pitch.val;
CLAMP_VAR(chan[i].pitch2,-2048,2048);
} else {
chan[i].pitch2=chan[i].std.pitch.val;
}
chan[i].freqChanged=true;
}
if (chan[i].std.panL.had) { // panning
chan[i].panning=MIN((chan[i].std.panL.val*15/16+15)/2+1,15);
rWrite(0x03+i*4,chan[i].panning);
}
if (chan[i].setPos) {
// force keyon
chan[i].keyOn=true;
chan[i].setPos=false;
} else {
chan[i].audPos=0;
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
DivSample* s=parent->getSample(chan[i].sample);
unsigned char ctrl;
switch (s->depth) {
case 3: ctrl=0x20; break;
case 8: ctrl=0x40; break;
case 16: ctrl=0x60; break;
default: ctrl=0;
}
double off=(s->centerRate>=1)?((double)s->centerRate/8363.0):1.0;
chan[i].freq=(int)(off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE))-1;
if (chan[i].freq<0) chan[i].freq=0;
if (chan[i].freq>511) chan[i].freq=511;
// ADPCM has half the range
if (s->depth==3 && chan[i].freq>255) chan[i].freq=255;
ctrl|=(chan[i].active?0x80:0)|((s->loopStart>=0)?0x10:0)|(chan[i].freq>>8);
if (chan[i].keyOn) {
unsigned int start=s->offYMZ280B;
unsigned int loop=0;
unsigned int end=MIN(start+s->getCurBufLen(),getSampleMemCapacity()-1);
if (chan[i].audPos>0) {
switch (s->depth) {
case 3: start+=chan[i].audPos/2; break;
case 8: start+=chan[i].audPos; break;
case 16: start+=chan[i].audPos*2; break;
}
start=MIN(start,end);
}
if (s->loopStart>=0) {
switch (s->depth) {
case 3: loop=start+s->loopStart/2; break;
case 8: loop=start+s->loopStart; break;
case 16: loop=start+s->loopStart*2; break;
}
loop=MIN(loop,end);
}
rWrite(0x01+i*4,ctrl&~0x80); // force keyoff first
rWrite(0x20+i*4,(start>>16)&0xff);
rWrite(0x21+i*4,(loop>>16)&0xff);
rWrite(0x22+i*4,(end>>16)&0xff);
rWrite(0x23+i*4,(end>>16)&0xff);
rWrite(0x40+i*4,(start>>8)&0xff);
rWrite(0x41+i*4,(loop>>8)&0xff);
rWrite(0x42+i*4,(end>>8)&0xff);
rWrite(0x43+i*4,(end>>8)&0xff);
rWrite(0x60+i*4,start&0xff);
rWrite(0x61+i*4,loop&0xff);
rWrite(0x62+i*4,end&0xff);
rWrite(0x63+i*4,end&0xff);
if (!chan[i].std.vol.had) {
chan[i].outVol=chan[i].vol;
writeOutVol(i);
}
chan[i].keyOn=false;
}
if (chan[i].keyOff) {
chan[i].keyOff=false;
}
if (chan[i].freqChanged) {
rWrite(0x00+i*4,chan[i].freq&0xff);
chan[i].freqChanged=false;
}
rWrite(0x01+i*4,ctrl);
}
}
}
int DivPlatformYMZ280B::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
chan[c.chan].sample=ins->amiga.getSample(c.value);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
}
if (chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) {
chan[c.chan].sample=-1;
}
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
chan[c.chan].macroInit(ins);
break;
}
case DIV_CMD_NOTE_OFF:
chan[c.chan].sample=-1;
chan[c.chan].active=false;
chan[c.chan].keyOff=true;
chan[c.chan].macroInit(NULL);
break;
case DIV_CMD_NOTE_OFF_ENV:
case DIV_CMD_ENV_RELEASE:
chan[c.chan].std.release();
break;
case DIV_CMD_INSTRUMENT:
if (chan[c.chan].ins!=c.value || c.value2==1) {
chan[c.chan].ins=c.value;
}
break;
case DIV_CMD_VOLUME:
if (chan[c.chan].vol!=c.value) {
chan[c.chan].vol=c.value;
if (!chan[c.chan].std.vol.has) {
chan[c.chan].outVol=c.value;
writeOutVol(c.chan);
}
}
break;
case DIV_CMD_GET_VOLUME:
if (chan[c.chan].std.vol.has) {
return chan[c.chan].vol;
}
return chan[c.chan].outVol;
break;
case DIV_CMD_PANNING:
chan[c.chan].panning=MIN(parent->convertPanSplitToLinearLR(c.value,c.value2,15)+1,15);
rWrite(0x03+c.chan*4,chan[c.chan].panning);
break;
case DIV_CMD_PITCH:
chan[c.chan].pitch=c.value;
chan[c.chan].freqChanged=true;
break;
case DIV_CMD_NOTE_PORTA: {
int destFreq=NOTE_FREQUENCY(c.value2);
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
chan[c.chan].baseFreq+=c.value;
if (chan[c.chan].baseFreq>=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
} else {
chan[c.chan].baseFreq-=c.value;
if (chan[c.chan].baseFreq<=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
}
chan[c.chan].freqChanged=true;
if (return2) {
chan[c.chan].inPorta=false;
return 2;
}
break;
}
case DIV_CMD_LEGATO: {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val-12):(0)));
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
break;
}
case DIV_CMD_PRE_PORTA:
if (chan[c.chan].active && c.value2) {
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA));
}
chan[c.chan].inPorta=c.value;
break;
case DIV_CMD_SAMPLE_POS:
chan[c.chan].audPos=c.value;
chan[c.chan].setPos=true;
break;
case DIV_CMD_GET_VOLMAX:
return 255;
break;
case DIV_ALWAYS_SET_VOLUME:
return 1;
break;
default:
break;
}
return 1;
}
void DivPlatformYMZ280B::writeOutVol(int ch) {
unsigned char val=isMuted[ch]?0:chan[ch].outVol;
rWrite(0x02+ch*4,val);
}
void DivPlatformYMZ280B::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
writeOutVol(ch);
}
void DivPlatformYMZ280B::forceIns() {
for (int i=0; i<8; i++) {
chan[i].insChanged=true;
chan[i].freqChanged=true;
chan[i].sample=-1;
}
}
void* DivPlatformYMZ280B::getChanState(int ch) {
return &chan[ch];
}
DivDispatchOscBuffer* DivPlatformYMZ280B::getOscBuffer(int ch) {
return oscBuf[ch];
}
void DivPlatformYMZ280B::reset() {
memset(regPool,0,256);
ymz280b.device_reset();
rWrite(0xff,0x80); // enable keyon
for (int i=0; i<8; i++) {
chan[i]=DivPlatformYMZ280B::Channel();
chan[i].std.setEngine(parent);
rWrite(0x02+i*4,255);
rWrite(0x03+i*4,8);
}
}
bool DivPlatformYMZ280B::isStereo() {
return true;
}
void DivPlatformYMZ280B::notifyInsChange(int ins) {
for (int i=0; i<8; i++) {
if (chan[i].ins==ins) {
chan[i].insChanged=true;
}
}
}
void DivPlatformYMZ280B::notifyWaveChange(int wave) {
// TODO when wavetables are added
// TODO they probably won't be added unless the samples reside in RAM
}
void DivPlatformYMZ280B::notifyInsDeletion(void* ins) {
for (int i=0; i<8; i++) {
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
}
}
void DivPlatformYMZ280B::poke(unsigned int addr, unsigned short val) {
rWrite(addr,val);
}
void DivPlatformYMZ280B::poke(std::vector<DivRegWrite>& wlist) {
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
}
unsigned char* DivPlatformYMZ280B::getRegisterPool() {
return regPool;
}
int DivPlatformYMZ280B::getRegisterPoolSize() {
return 256;
}
float DivPlatformYMZ280B::getPostAmp() {
// according to MAME core's mixing
return 4.0f;
}
const void* DivPlatformYMZ280B::getSampleMem(int index) {
return index == 0 ? sampleMem : NULL;
}
size_t DivPlatformYMZ280B::getSampleMemCapacity(int index) {
return index == 0 ? 16777216 : 0;
}
size_t DivPlatformYMZ280B::getSampleMemUsage(int index) {
return index == 0 ? sampleMemLen : 0;
}
void DivPlatformYMZ280B::renderSamples() {
memset(sampleMem,0,getSampleMemCapacity());
size_t memPos=0;
for (int i=0; i<parent->song.sampleLen; i++) {
DivSample* s=parent->song.sample[i];
int length=s->getCurBufLen();
unsigned char* src=(unsigned char*)s->getCurBuf();
int actualLength=MIN((int)(getSampleMemCapacity()-memPos),length);
if (actualLength>0) {
memcpy(&sampleMem[memPos],src,actualLength);
s->offYMZ280B=memPos;
memPos+=length;
}
if (actualLength<length) {
logW("out of YMZ280B PCM memory for sample %d!",i);
break;
}
}
sampleMemLen=memPos;
}
void DivPlatformYMZ280B::setChipModel(int type) {
chipType=type;
}
int DivPlatformYMZ280B::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
parent=p;
dumpWrites=false;
skipRegisterWrites=false;
for (int i=0; i<8; i++) {
isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer;
}
setFlags(flags);
rate=(chipType==759)?32000:44100;
chipClock=rate*384;
sampleMem=new unsigned char[getSampleMemCapacity()];
sampleMemLen=0;
ymz280b.device_start(sampleMem);
reset();
for (int i=0; i<8; i++) {
oscBuf[i]->rate=rate;
}
return 8;
}
void DivPlatformYMZ280B::quit() {
delete[] sampleMem;
for (int i=0; i<8; i++) {
delete oscBuf[i];
}
}

View file

@ -0,0 +1,104 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef _YMZ280B_H
#define _YMZ280B_H
#include "../dispatch.h"
#include <queue>
#include "../macroInt.h"
#include "sound/ymz280b.h"
class DivPlatformYMZ280B: public DivDispatch {
struct Channel {
int freq, baseFreq, pitch, pitch2;
unsigned int audPos;
int sample, wave, ins;
int note;
int panning;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, setPos;
int vol, outVol;
DivMacroInt std;
void macroInit(DivInstrument* which) {
std.init(which);
pitch2=0;
}
Channel():
freq(0),
baseFreq(0),
pitch(0),
pitch2(0),
audPos(0),
sample(-1),
ins(-1),
note(0),
panning(8),
active(false),
insChanged(true),
freqChanged(false),
keyOn(false),
keyOff(false),
inPorta(false),
setPos(false),
vol(255),
outVol(255) {}
};
Channel chan[8];
DivDispatchOscBuffer* oscBuf[8];
bool isMuted[8];
int chipType;
unsigned char* sampleMem;
size_t sampleMemLen;
ymz280b_device ymz280b;
unsigned char regPool[256];
friend void putDispatchChan(void*,int,int);
public:
void acquire(short* bufL, short* bufR, size_t start, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
DivDispatchOscBuffer* getOscBuffer(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();
void reset();
void forceIns();
void tick(bool sysTick=true);
void muteChannel(int ch, bool mute);
float getPostAmp();
bool isStereo();
void setChipModel(int type);
void notifyInsChange(int ins);
void notifyWaveChange(int wave);
void notifyInsDeletion(void* ins);
void poke(unsigned int addr, unsigned short val);
void poke(std::vector<DivRegWrite>& wlist);
const char** getRegisterSheet();
const char* getEffectName(unsigned char effect);
const void* getSampleMem(int index = 0);
size_t getSampleMemCapacity(int index = 0);
size_t getSampleMemUsage(int index = 0);
void renderSamples();
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
void quit();
private:
void writeOutVol(int ch);
};
#endif

View file

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

View file

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

View file

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

View file

@ -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.",

View file

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

View file

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

View file

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

View file

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