Merge pull request #476 from akumanatt/rf5c68

Add RF5C68 and RF5C164 support
This commit is contained in:
tildearrow 2022-05-20 17:28:14 -05:00 committed by GitHub
commit 08203bf2a3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 1019 additions and 15 deletions

View file

@ -321,6 +321,8 @@ src/engine/platform/sound/scc/scc.cpp
src/engine/platform/sound/ymz280b.cpp src/engine/platform/sound/ymz280b.cpp
src/engine/platform/sound/rf5c68.cpp
src/engine/platform/oplAInterface.cpp src/engine/platform/oplAInterface.cpp
src/engine/platform/ym2608Interface.cpp src/engine/platform/ym2608Interface.cpp
src/engine/platform/ym2610Interface.cpp src/engine/platform/ym2610Interface.cpp
@ -388,6 +390,7 @@ src/engine/platform/vrc6.cpp
src/engine/platform/scc.cpp src/engine/platform/scc.cpp
src/engine/platform/ymz280b.cpp src/engine/platform/ymz280b.cpp
src/engine/platform/namcowsg.cpp src/engine/platform/namcowsg.cpp
src/engine/platform/rf5c68.cpp
src/engine/platform/dummy.cpp src/engine/platform/dummy.cpp
) )

View file

@ -1,6 +1,5 @@
# to-do for 0.6pre1 # to-do for 0.6pre1
- RF5C68 system
- ADPCM chips - ADPCM chips
- Game Boy envelope macro/sequence - Game Boy envelope macro/sequence
- drag-and-drop ins/wave/sample loading - drag-and-drop ins/wave/sample loading

View file

@ -60,6 +60,7 @@
#include "platform/mmc5.h" #include "platform/mmc5.h"
#include "platform/scc.h" #include "platform/scc.h"
#include "platform/ymz280b.h" #include "platform/ymz280b.h"
#include "platform/rf5c68.h"
#include "platform/dummy.h" #include "platform/dummy.h"
#include "../ta-log.h" #include "../ta-log.h"
#include "platform/zxbeeper.h" #include "platform/zxbeeper.h"
@ -356,6 +357,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
dispatch=new DivPlatformYMZ280B; dispatch=new DivPlatformYMZ280B;
((DivPlatformYMZ280B*)dispatch)->setChipModel(280); ((DivPlatformYMZ280B*)dispatch)->setChipModel(280);
break; break;
case DIV_SYSTEM_RF5C68:
dispatch=new DivPlatformRF5C68;
break;
case DIV_SYSTEM_SOUND_UNIT: case DIV_SYSTEM_SOUND_UNIT:
dispatch=new DivPlatformSoundUnit; dispatch=new DivPlatformSoundUnit;
break; break;

View file

@ -1150,14 +1150,12 @@ void DivPlatformGenesis::setYMFM(bool use) {
} }
void DivPlatformGenesis::setFlags(unsigned int flags) { void DivPlatformGenesis::setFlags(unsigned int flags) {
if (flags==3) { switch (flags) {
chipClock=COLOR_NTSC*12.0/7.0; case 1: chipClock=COLOR_PAL*12.0/7.0; break;
} else if (flags==2) { case 2: chipClock=8000000.0; break;
chipClock=8000000.0; case 3: chipClock=COLOR_NTSC*12.0/7.0; break;
} else if (flags==1) { case 4: chipClock=COLOR_NTSC*9.0/4.0; break;
chipClock=COLOR_PAL*12.0/7.0; default: chipClock=COLOR_NTSC*15.0/7.0; break;
} else {
chipClock=COLOR_NTSC*15.0/7.0;
} }
ladder=flags&0x80000000; ladder=flags&0x80000000;
OPN2_SetChipType(ladder?ym3438_mode_ym2612:0); OPN2_SetChipType(ladder?ym3438_mode_ym2612:0);

View file

@ -0,0 +1,434 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "rf5c68.h"
#include "../engine.h"
#include "../../ta-log.h"
#include <math.h>
#define rWrite(a,v) {if(!skipRegisterWrites) {rf5c68.rf5c68_w(a,v); regPool[a]=v; if(dumpWrites) addWrite(a,v);}}
#define CHIP_FREQBASE 786432
const char* regCheatSheetRF5C68[]={
"Volume", "0",
"Panning", "1",
"FreqL", "2",
"FreqH", "3",
"LoopStartL", "4",
"LoopStartH", "5",
"StartH", "6",
"Control", "7",
"Disable", "8",
NULL
};
const char** DivPlatformRF5C68::getRegisterSheet() {
return regCheatSheetRF5C68;
}
const char* DivPlatformRF5C68::getEffectName(unsigned char effect) {
return NULL;
}
void DivPlatformRF5C68::chWrite(unsigned char ch, unsigned int addr, unsigned char val) {
if (!skipRegisterWrites) {
if (curChan!=ch) {
curChan=ch;
rWrite(7,curChan|0xc0);
}
regPool[16+((ch)<<4)+((addr)&0x0f)]=val;
rWrite(addr,val);
}
}
void DivPlatformRF5C68::acquire(short* bufL, short* bufR, size_t start, size_t len) {
short buf[16][256];
short* chBufPtrs[16]={
buf[0],buf[1],buf[2],buf[3],buf[4],buf[5],buf[6],buf[7],
buf[8],buf[9],buf[10],buf[11],buf[12],buf[13],buf[14],buf[15]
};
size_t pos=start;
while (len > 0) {
size_t blockLen=MIN(len,256);
short* bufPtrs[2]={&bufL[pos],&bufR[pos]};
rf5c68.sound_stream_update(bufPtrs,chBufPtrs,blockLen);
for (int i=0; i<8; i++) {
for (size_t j=0; j<blockLen; j++) {
oscBuf[i]->data[oscBuf[i]->needle++]=buf[i*2][j]+buf[i*2+1][j];
}
}
pos+=blockLen;
len-=blockLen;
}
}
void DivPlatformRF5C68::tick(bool sysTick) {
for (int i=0; i<8; i++) {
chan[i].std.next();
if (chan[i].std.vol.had) {
chan[i].outVol=((chan[i].vol&0xff)*chan[i].std.vol.val)>>6;
chWrite(i,0,chan[i].outVol);
}
if (chan[i].std.arp.had) {
if (!chan[i].inPorta) {
if (chan[i].std.arp.mode) {
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
} else {
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val);
}
}
chan[i].freqChanged=true;
} else {
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note);
chan[i].freqChanged=true;
}
}
if (chan[i].std.pitch.had) {
if (chan[i].std.pitch.mode) {
chan[i].pitch2+=chan[i].std.pitch.val;
CLAMP_VAR(chan[i].pitch2,-2048,2048);
} else {
chan[i].pitch2=chan[i].std.pitch.val;
}
chan[i].freqChanged=true;
}
// panning registers are reversed in this chip
if (chan[i].std.panL.had) {
chan[i].panning&=0xf0;
chan[i].panning|=chan[i].std.panL.val&15;
}
if (chan[i].std.panR.had) {
chan[i].panning&=0x0f;
chan[i].panning|=(chan[i].std.panR.val&15)<<4;
}
if (chan[i].std.panL.had || chan[i].std.panR.had) {
chWrite(i,0x05,isMuted[i]?0:chan[i].panning);
}
if (chan[i].setPos) {
// force keyon
chan[i].keyOn=true;
chan[i].setPos=false;
} else {
chan[i].audPos=0;
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
unsigned char keyon=regPool[8]&~(1<<i);
unsigned char keyoff=keyon|(1<<i);
DivSample* s=parent->getSample(chan[i].sample);
double off=(s->centerRate>=1)?((double)s->centerRate/8363.0):1.0;
chan[i].freq=(int)(off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE));
if (chan[i].freq>65535) chan[i].freq=65535;
if (chan[i].keyOn) {
unsigned int start=s->offRF5C68;
unsigned int loop=start+s->length8;
if (chan[i].audPos>0) {
start=start+MIN(chan[i].audPos,s->length8);
}
if (s->loopStart>=0) {
loop=start+s->loopStart;
}
start=MIN(start,getSampleMemCapacity()-31);
loop=MIN(loop,getSampleMemCapacity()-31);
rWrite(8,keyoff); // force keyoff first
chWrite(i,6,start>>8);
chWrite(i,4,loop&0xff);
chWrite(i,5,loop>>8);
if (!chan[i].std.vol.had) {
chan[i].outVol=chan[i].vol;
chWrite(i,0,chan[i].outVol);
}
rWrite(8,keyon);
chan[i].keyOn=false;
}
if (chan[i].keyOff) {
rWrite(8,keyoff);
chan[i].keyOff=false;
}
if (chan[i].freqChanged) {
chWrite(i,2,chan[i].freq&0xff);
chWrite(i,3,chan[i].freq>>8);
chan[i].freqChanged=false;
}
}
}
}
int DivPlatformRF5C68::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
chan[c.chan].sample=ins->amiga.getSample(c.value);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
}
if (chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) {
chan[c.chan].sample=-1;
}
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
chan[c.chan].macroInit(ins);
break;
}
case DIV_CMD_NOTE_OFF:
chan[c.chan].sample=-1;
chan[c.chan].active=false;
chan[c.chan].keyOff=true;
chan[c.chan].macroInit(NULL);
break;
case DIV_CMD_NOTE_OFF_ENV:
case DIV_CMD_ENV_RELEASE:
chan[c.chan].std.release();
break;
case DIV_CMD_INSTRUMENT:
if (chan[c.chan].ins!=c.value || c.value2==1) {
chan[c.chan].ins=c.value;
}
break;
case DIV_CMD_VOLUME:
if (chan[c.chan].vol!=c.value) {
chan[c.chan].vol=c.value;
if (!chan[c.chan].std.vol.has) {
chan[c.chan].outVol=c.value;
chWrite(c.chan,0,chan[c.chan].outVol);
}
}
break;
case DIV_CMD_GET_VOLUME:
if (chan[c.chan].std.vol.has) {
return chan[c.chan].vol;
}
return chan[c.chan].outVol;
break;
case DIV_CMD_PANNING:
chan[c.chan].panning=(c.value>>4)|(c.value2&0xf0);
chWrite(c.chan,1,isMuted[c.chan]?0:chan[c.chan].panning);
break;
case DIV_CMD_PITCH:
chan[c.chan].pitch=c.value;
chan[c.chan].freqChanged=true;
break;
case DIV_CMD_NOTE_PORTA: {
int destFreq=NOTE_FREQUENCY(c.value2);
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
chan[c.chan].baseFreq+=c.value;
if (chan[c.chan].baseFreq>=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
} else {
chan[c.chan].baseFreq-=c.value;
if (chan[c.chan].baseFreq<=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
}
chan[c.chan].freqChanged=true;
if (return2) {
chan[c.chan].inPorta=false;
return 2;
}
break;
}
case DIV_CMD_LEGATO: {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val-12):(0)));
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
break;
}
case DIV_CMD_PRE_PORTA:
if (chan[c.chan].active && c.value2) {
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA));
}
chan[c.chan].inPorta=c.value;
break;
case DIV_CMD_SAMPLE_POS:
chan[c.chan].audPos=c.value;
chan[c.chan].setPos=true;
break;
case DIV_CMD_GET_VOLMAX:
return 255;
break;
case DIV_ALWAYS_SET_VOLUME:
return 1;
break;
default:
break;
}
return 1;
}
void DivPlatformRF5C68::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
chWrite(ch,1,mute?0:chan[ch].panning);
}
void DivPlatformRF5C68::forceIns() {
for (int i=0; i<8; i++) {
chan[i].insChanged=true;
chan[i].freqChanged=true;
chan[i].sample=-1;
}
}
void* DivPlatformRF5C68::getChanState(int ch) {
return &chan[ch];
}
DivDispatchOscBuffer* DivPlatformRF5C68::getOscBuffer(int ch) {
return oscBuf[ch];
}
void DivPlatformRF5C68::reset() {
memset(regPool,0,144);
rf5c68.device_reset();
rWrite(0x08,0xff); // keyoff all channels
for (int i=0; i<8; i++) {
chan[i]=DivPlatformRF5C68::Channel();
chan[i].std.setEngine(parent);
chWrite(i,0,255);
chWrite(i,1,isMuted[i]?0:255);
}
}
bool DivPlatformRF5C68::isStereo() {
return true;
}
void DivPlatformRF5C68::notifyInsChange(int ins) {
for (int i=0; i<8; i++) {
if (chan[i].ins==ins) {
chan[i].insChanged=true;
}
}
}
void DivPlatformRF5C68::notifyWaveChange(int wave) {
// TODO when wavetables are added
// TODO they probably won't be added unless the samples reside in RAM
}
void DivPlatformRF5C68::notifyInsDeletion(void* ins) {
for (int i=0; i<8; i++) {
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
}
}
void DivPlatformRF5C68::setFlags(unsigned int flags) {
switch (flags&0x0f) {
case 1: chipClock=10000000; break;
case 2: chipClock=12500000; break;
default: chipClock=8000000; break;
}
chipType=flags>>4;
rate=chipClock/384;
for (int i=0; i<8; i++) {
oscBuf[i]->rate=rate;
}
rf5c68=(chipType==1)?rf5c164_device():rf5c68_device();
rf5c68.device_start(sampleMem);
}
void DivPlatformRF5C68::poke(unsigned int addr, unsigned short val) {
rWrite(addr&0x0f,val);
}
void DivPlatformRF5C68::poke(std::vector<DivRegWrite>& wlist) {
for (DivRegWrite& i: wlist) rWrite(i.addr&0x0f,i.val);
}
unsigned char* DivPlatformRF5C68::getRegisterPool() {
return regPool;
}
int DivPlatformRF5C68::getRegisterPoolSize() {
return 144;
}
const void* DivPlatformRF5C68::getSampleMem(int index) {
return index == 0 ? sampleMem : NULL;
}
size_t DivPlatformRF5C68::getSampleMemCapacity(int index) {
return index == 0 ? 65536 : 0;
}
size_t DivPlatformRF5C68::getSampleMemUsage(int index) {
return index == 0 ? sampleMemLen : 0;
}
void DivPlatformRF5C68::renderSamples() {
memset(sampleMem,0,getSampleMemCapacity());
size_t memPos=0;
for (int i=0; i<parent->song.sampleLen; i++) {
DivSample* s=parent->song.sample[i];
int length=s->length8;
int actualLength=MIN((int)(getSampleMemCapacity()-memPos)-31,length);
if (actualLength>0) {
s->offRF5C68=memPos;
for (int j=0; j<actualLength; j++) {
// convert to signed magnitude
signed char val=s->data8[j];
CLAMP_VAR(val,-127,126);
sampleMem[memPos++]=(val>0)?(val|0x80):(0-val);
}
// write end of sample marker
memset(&sampleMem[memPos],0xff,31);
memPos+=31;
}
if (actualLength<length) {
logW("out of RF5C68 PCM memory for sample %d!",i);
break;
}
// align memPos to 256-byte boundary
memPos=(memPos+0xff)&~0xff;
}
sampleMemLen=memPos;
}
int DivPlatformRF5C68::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
parent=p;
dumpWrites=false;
skipRegisterWrites=false;
for (int i=0; i<8; i++) {
isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer;
}
sampleMem=new unsigned char[getSampleMemCapacity()];
sampleMemLen=0;
setFlags(flags);
reset();
return 8;
}
void DivPlatformRF5C68::quit() {
delete[] sampleMem;
for (int i=0; i<8; i++) {
delete oscBuf[i];
}
}

View file

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

View file

@ -0,0 +1,255 @@
// license:BSD-3-Clause
// copyright-holders:Olivier Galibert,Aaron Giles
/*********************************************************/
/* ricoh RF5C68(or clone) PCM controller */
/* */
/* TODO: Verify RF5C105,164 (Sega CD/Mega CD) */
/* differences */
/*********************************************************/
#include "rf5c68.h"
#include <algorithm>
//**************************************************************************
// LIVE DEVICE
//**************************************************************************
//-------------------------------------------------
// rf5c68_device - constructor
//-------------------------------------------------
rf5c68_device::rf5c68_device(int output_bits)
: m_cbank(0)
, m_wbank(0)
, m_enable(0)
, m_output_bits(output_bits)
, m_ext_mem(nullptr)
{
}
rf5c68_device::rf5c68_device()
: rf5c68_device(10)
{
}
//-------------------------------------------------
// rf5c164_device - constructor
//-------------------------------------------------
rf5c164_device::rf5c164_device()
: rf5c68_device(16)
{
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void rf5c68_device::device_start(u8 *ext_mem)
{
m_ext_mem = ext_mem;
}
void rf5c68_device::device_reset()
{
m_cbank = 0;
m_wbank = 0;
m_enable = 0;
for (pcm_channel &chan : m_chan) {
chan.enable = 0;
chan.env = 0;
chan.pan = 0;
chan.start = 0;
chan.addr = 0;
chan.step = 0;
chan.loopst = 0;
}
}
//-------------------------------------------------
// sound_stream_update - handle a stream update
//-------------------------------------------------
void rf5c68_device::sound_stream_update(s16 **outputs, s16 **channel_outputs, u32 samples)
{
/* bail if not enabled */
if (!m_enable)
{
std::fill_n(outputs[0], samples, 0);
std::fill_n(outputs[1], samples, 0);
for (unsigned int i = 0; i < NUM_CHANNELS; i++) {
std::fill_n(channel_outputs[i*2], samples, 0);
std::fill_n(channel_outputs[i*2+1], samples, 0);
}
return;
}
if (m_mixleft.size() < samples)
m_mixleft.resize(samples);
if (m_mixright.size() < samples)
m_mixright.resize(samples);
std::fill_n(&m_mixleft[0], samples, 0);
std::fill_n(&m_mixright[0], samples, 0);
/* loop over channels */
for (unsigned int i = 0; i < NUM_CHANNELS; i++)
{
pcm_channel &chan = m_chan[i];
/* if this channel is active, accumulate samples */
if (chan.enable)
{
int lv = (chan.pan & 0x0f) * chan.env;
int rv = ((chan.pan >> 4) & 0x0f) * chan.env;
/* loop over the sample buffer */
for (u32 j = 0; j < samples; j++)
{
int sample;
/* fetch the sample and handle looping */
sample = m_ext_mem[(chan.addr >> 11) & 0xffff];
if (sample == 0xff)
{
chan.addr = chan.loopst << 11;
sample = m_ext_mem[(chan.addr >> 11) & 0xffff];
/* if we loop to a loop point, we're effectively dead */
if (sample == 0xff)
break;
}
chan.addr += chan.step;
/* add to the buffer */
s32 left_out = ((sample & 0x7f) * lv) >> 5;
s32 right_out = ((sample & 0x7f) * rv) >> 5;
if ((sample & 0x80) == 0)
{
left_out = -left_out;
right_out = -right_out;
}
channel_outputs[i*2][j] = (s16)left_out;
channel_outputs[i*2+1][j] = (s16)right_out;
m_mixleft[j] += left_out;
m_mixright[j] += right_out;
}
}
else
{
std::fill_n(channel_outputs[i*2], samples, 0);
std::fill_n(channel_outputs[i*2+1], samples, 0);
}
}
/*
now clamp and shift the result (output is only 10 bits for RF5C68, 16 bits for RF5C164)
reference: Mega CD hardware manual, RF5C68 datasheet
*/
const u8 output_shift = (m_output_bits > 16) ? 0 : (16 - m_output_bits);
const s32 output_nandmask = (1 << output_shift) - 1;
for (u32 j = 0; j < samples; j++)
{
s32 outleft = std::min(std::max(m_mixleft[j], -32768), 32767);
s32 outright = std::min(std::max(m_mixright[j], -32768), 32767);
outputs[0][j] = outleft & ~output_nandmask;
outputs[1][j] = outright & ~output_nandmask;
}
}
//-------------------------------------------------
// RF5C68 write register
//-------------------------------------------------
// TODO: RF5C164 only?
u8 rf5c68_device::rf5c68_r(offs_t offset)
{
u8 shift;
shift = (offset & 1) ? 11 + 8 : 11;
// printf("%08x\n",(m_chan[(offset & 0x0e) >> 1].addr));
return (m_chan[(offset & 0x0e) >> 1].addr) >> (shift);
}
void rf5c68_device::rf5c68_w(offs_t offset, u8 data)
{
pcm_channel &chan = m_chan[m_cbank];
int i;
/* switch off the address */
switch (offset)
{
case 0x00: /* envelope */
chan.env = data;
break;
case 0x01: /* pan */
chan.pan = data;
break;
case 0x02: /* FDL */
chan.step = (chan.step & 0xff00) | (data & 0x00ff);
break;
case 0x03: /* FDH */
chan.step = (chan.step & 0x00ff) | ((data << 8) & 0xff00);
break;
case 0x04: /* LSL */
chan.loopst = (chan.loopst & 0xff00) | (data & 0x00ff);
break;
case 0x05: /* LSH */
chan.loopst = (chan.loopst & 0x00ff) | ((data << 8) & 0xff00);
break;
case 0x06: /* ST */
chan.start = data;
if (!chan.enable)
chan.addr = chan.start << (8 + 11);
break;
case 0x07: /* control reg */
m_enable = (data >> 7) & 1;
if (data & 0x40)
m_cbank = data & 7;
else
m_wbank = (data & 0xf) << 12;
break;
case 0x08: /* channel on/off reg */
for (i = 0; i < 8; i++)
{
m_chan[i].enable = (~data >> i) & 1;
if (!m_chan[i].enable)
m_chan[i].addr = m_chan[i].start << (8 + 11);
}
break;
}
}
//-------------------------------------------------
// RF5C68 read memory
//-------------------------------------------------
u8 rf5c68_device::rf5c68_mem_r(offs_t offset)
{
return m_ext_mem[m_wbank | offset];
}
//-------------------------------------------------
// RF5C68 write memory
//-------------------------------------------------
void rf5c68_device::rf5c68_mem_w(offs_t offset, u8 data)
{
m_ext_mem[m_wbank | offset] = data;
}

View file

@ -0,0 +1,87 @@
// license:BSD-3-Clause
// copyright-holders:Olivier Galibert,Aaron Giles
/*********************************************************/
/* ricoh RF5C68(or clone) PCM controller */
/*********************************************************/
#include <vector>
#ifndef MAME_SOUND_RF5C68_H
#define MAME_SOUND_RF5C68_H
#pragma once
namespace rf5c68
{
typedef unsigned char u8;
typedef signed char s8;
typedef unsigned short u16;
typedef signed short s16;
typedef unsigned int u32;
typedef signed int s32;
typedef signed int offs_t;
}
//**************************************************************************
// TYPE DEFINITIONS
//**************************************************************************
#define RF5C68_SAMPLE_END_CB_MEMBER(_name) void _name(int channel)
// ======================> rf5c68_device
using namespace rf5c68;
class rf5c68_device
{
public:
rf5c68_device();
u8 rf5c68_r(offs_t offset);
void rf5c68_w(offs_t offset, u8 data);
u8 rf5c68_mem_r(offs_t offset);
void rf5c68_mem_w(offs_t offset, u8 data);
void device_start(u8 *ext_mem);
void device_reset();
void sound_stream_update(s16 **outputs, s16 **channel_outputs, u32 samples);
protected:
rf5c68_device(int output_bits);
private:
static constexpr unsigned NUM_CHANNELS = 8;
struct pcm_channel
{
pcm_channel() { }
u8 enable;
u8 env;
u8 pan;
u8 start;
u32 addr;
u16 step;
u16 loopst;
};
pcm_channel m_chan[NUM_CHANNELS];
u8 m_cbank;
u16 m_wbank;
u8 m_enable;
int m_output_bits;
std::vector<s32> m_mixleft;
std::vector<s32> m_mixright;
u8 *m_ext_mem;
};
class rf5c164_device : public rf5c68_device
{
public:
rf5c164_device();
};
#endif // MAME_SOUND_RF5C68_H

View file

@ -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, offYMZ280B; unsigned int offSegaPCM, offQSound, offX1_010, offSU, offYMZ280B, offRF5C68;
unsigned int samples; unsigned int samples;
@ -248,6 +248,7 @@ struct DivSample {
offQSound(0), offQSound(0),
offX1_010(0), offX1_010(0),
offSU(0), offSU(0),
offRF5C68(0),
samples(0) {} samples(0) {}
~DivSample(); ~DivSample();
}; };

View file

@ -1401,7 +1401,7 @@ void DivEngine::registerSystems() {
); );
sysDefs[DIV_SYSTEM_RF5C68]=new DivSysDef( sysDefs[DIV_SYSTEM_RF5C68]=new DivSysDef(
"Ricoh RF5C68", NULL, 0x95, 0, 8, false, true, 0, false, "Ricoh RF5C68", NULL, 0x95, 0, 8, false, true, 0x151, false,
"this is like SNES' sound chip but without interpolation and the rest of nice bits.", "this is like SNES' sound chip but without interpolation and the rest of nice bits.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"}, {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"},
{"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"}, {"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"},

View file

@ -29,6 +29,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
unsigned char baseAddr2=isSecond?0x80:0; unsigned char baseAddr2=isSecond?0x80:0;
unsigned short baseAddr2S=isSecond?0x8000:0; unsigned short baseAddr2S=isSecond?0x8000:0;
unsigned char smsAddr=isSecond?0x30:0x50; unsigned char smsAddr=isSecond?0x30:0x50;
unsigned char rf5c68Addr=isSecond?0xb1:0xb0;
if (write.addr==0xffffffff) { // Furnace fake reset if (write.addr==0xffffffff) { // Furnace fake reset
switch (sys) { switch (sys) {
case DIV_SYSTEM_YM2612: case DIV_SYSTEM_YM2612:
@ -480,6 +481,13 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
w->writeC(0); w->writeC(0);
w->writeC(0); w->writeC(0);
break; break;
case DIV_SYSTEM_RF5C68:
w->writeC(rf5c68Addr);
w->writeC(7);
w->writeC(0);
w->writeC(rf5c68Addr);
w->writeC(8);
w->writeC(0xff);
default: default:
break; break;
} }
@ -766,6 +774,11 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
w->writeC(write.addr&0xff); w->writeC(write.addr&0xff);
w->writeC(write.val&0xff); w->writeC(write.val&0xff);
break; break;
case DIV_SYSTEM_RF5C68:
w->writeC(rf5c68Addr);
w->writeC(write.addr&0xff);
w->writeC(write.val);
break;
default: default:
logW("write not handled!"); logW("write not handled!");
break; break;
@ -888,6 +901,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
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* writeZ280[2]={NULL,NULL};
DivDispatch* writeRF5C68[2]={NULL,NULL};
for (int i=0; i<song.systemLen; i++) { for (int i=0; i<song.systemLen; i++) {
willExport[i]=false; willExport[i]=false;
@ -1262,6 +1276,24 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
howManyChips++; howManyChips++;
} }
break; break;
case DIV_SYSTEM_RF5C68:
// here's the dumb part: VGM thinks RF5C68 and RF5C164 are different
// chips even though the only difference is the output resolution
// these system types are currently handled by reusing isSecond flag
// also this system is not dual-able
if ((song.systemFlags[i]>>4)==1) {
if (!hasRFC1) {
hasRFC1=disCont[i].dispatch->chipClock;
isSecond[i]=true;
willExport[i]=true;
writeRF5C68[1]=disCont[i].dispatch;
}
} else if (!hasRFC) {
hasRFC=disCont[i].dispatch->chipClock;
willExport[i]=true;
writeRF5C68[0]=disCont[i].dispatch;
}
break;
default: default:
break; break;
} }
@ -1598,6 +1630,18 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
} }
} }
for (int i=0; i<2; i++) {
if (writeRF5C68[i]!=NULL && writeRF5C68[i]->getSampleMemUsage()>0) {
w->writeC(0x67);
w->writeC(0x66);
w->writeC(0xc0+i);
w->writeI(writeRF5C68[i]->getSampleMemUsage()+8);
w->writeI(writeRF5C68[i]->getSampleMemCapacity());
w->writeI(0);
w->write(writeRF5C68[i]->getSampleMem(),writeRF5C68[i]->getSampleMemUsage());
}
}
// initialize streams // initialize streams
int streamID=0; int streamID=0;
for (int i=0; i<song.systemLen; i++) { for (int i=0; i<song.systemLen; i++) {

View file

@ -886,6 +886,7 @@ const int availableSystems[]={
DIV_SYSTEM_NAMCO, DIV_SYSTEM_NAMCO,
DIV_SYSTEM_NAMCO_15XX, DIV_SYSTEM_NAMCO_15XX,
DIV_SYSTEM_NAMCO_CUS30, DIV_SYSTEM_NAMCO_CUS30,
DIV_SYSTEM_RF5C68,
0 // don't remove this last one! 0 // don't remove this last one!
}; };

View file

@ -266,6 +266,12 @@ void FurnaceGUI::initSystemPresets() {
0 0
} }
)); ));
cat.systems.push_back(FurnaceGUISysDef(
"Ricoh RF5C68", {
DIV_SYSTEM_RF5C68, 64, 0, 0,
0
}
));
sysCategories.push_back(cat); sysCategories.push_back(cat);
cat=FurnaceGUISysCategory("Wavetable","chips which use user-specified waveforms to generate sound."); cat=FurnaceGUISysCategory("Wavetable","chips which use user-specified waveforms to generate sound.");
@ -405,6 +411,22 @@ void FurnaceGUI::initSystemPresets() {
0 0
} }
)); ));
cat.systems.push_back(FurnaceGUISysDef(
"Sega Genesis (with Sega CD)", {
DIV_SYSTEM_YM2612, 64, 0, 0,
DIV_SYSTEM_SMS, 24, 0, 0,
DIV_SYSTEM_RF5C68, 64, 0, 18,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Sega Genesis (extended channel 3 with Sega CD)", {
DIV_SYSTEM_YM2612_EXT, 64, 0, 0,
DIV_SYSTEM_SMS, 24, 0, 0,
DIV_SYSTEM_RF5C68, 64, 0, 18,
0
}
));
cat.systems.push_back(FurnaceGUISysDef( cat.systems.push_back(FurnaceGUISysDef(
"Sega Master System", { "Sega Master System", {
DIV_SYSTEM_SMS, 64, 0, 0, DIV_SYSTEM_SMS, 64, 0, 0,
@ -853,6 +875,13 @@ void FurnaceGUI::initSystemPresets() {
0 0
} }
));*/ ));*/
cat.systems.push_back(FurnaceGUISysDef(
"FM Towns", {
DIV_SYSTEM_YM2612, 64, 0, 2,
DIV_SYSTEM_RF5C68, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef( cat.systems.push_back(FurnaceGUISysDef(
"Commander X16", { "Commander X16", {
DIV_SYSTEM_VERA, 64, 0, 0, DIV_SYSTEM_VERA, 64, 0, 0,
@ -884,6 +913,22 @@ void FurnaceGUI::initSystemPresets() {
0 0
} }
)); ));
cat.systems.push_back(FurnaceGUISysDef(
"Sega System 18", {
DIV_SYSTEM_YM2612, 64, 0, 2,
DIV_SYSTEM_YM2612, 64, 0, 2,
DIV_SYSTEM_RF5C68, 64, 0, 1,
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Sega System 32", {
DIV_SYSTEM_YM2612, 64, 0, 4,
DIV_SYSTEM_YM2612, 64, 0, 4,
DIV_SYSTEM_RF5C68, 64, 0, 2,
0
}
));
cat.systems.push_back(FurnaceGUISysDef( cat.systems.push_back(FurnaceGUISysDef(
"Neo Geo MVS", { "Neo Geo MVS", {
DIV_SYSTEM_YM2610_FULL, 64, 0, 0, DIV_SYSTEM_YM2610_FULL, 64, 0, 0,

View file

@ -26,18 +26,21 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool
switch (type) { switch (type) {
case DIV_SYSTEM_YM2612: case DIV_SYSTEM_YM2612:
case DIV_SYSTEM_YM2612_EXT: { case DIV_SYSTEM_YM2612_EXT: {
if (ImGui::RadioButton("NTSC (7.67MHz)",(flags&3)==0)) { if (ImGui::RadioButton("NTSC (7.67MHz)",(flags&7)==0)) {
copyOfFlags=(flags&0x80000000)|0; copyOfFlags=(flags&0x80000000)|0;
} }
if (ImGui::RadioButton("PAL (7.61MHz)",(flags&3)==1)) { if (ImGui::RadioButton("PAL (7.61MHz)",(flags&7)==1)) {
copyOfFlags=(flags&0x80000000)|1; copyOfFlags=(flags&0x80000000)|1;
} }
if (ImGui::RadioButton("FM Towns (8MHz)",(flags&3)==2)) { if (ImGui::RadioButton("FM Towns (8MHz)",(flags&7)==2)) {
copyOfFlags=(flags&0x80000000)|2; copyOfFlags=(flags&0x80000000)|2;
} }
if (ImGui::RadioButton("AtGames Genesis (6.13MHz)",(flags&3)==3)) { if (ImGui::RadioButton("AtGames Genesis (6.13MHz)",(flags&7)==3)) {
copyOfFlags=(flags&0x80000000)|3; copyOfFlags=(flags&0x80000000)|3;
} }
if (ImGui::RadioButton("Sega System 32 (8.05MHz)",(flags&7)==4)) {
copyOfFlags=(flags&0x80000000)|4;
}
bool ladder=flags&0x80000000; bool ladder=flags&0x80000000;
if (ImGui::Checkbox("Enable DAC distortion",&ladder)) { if (ImGui::Checkbox("Enable DAC distortion",&ladder)) {
copyOfFlags=(flags&(~0x80000000))|(ladder?0x80000000:0); copyOfFlags=(flags&(~0x80000000))|(ladder?0x80000000:0);
@ -385,6 +388,31 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool
} }
break; break;
} }
case DIV_SYSTEM_RF5C68: {
ImGui::Text("Clock rate:");
if (ImGui::RadioButton("8MHz (FM Towns)",(flags&15)==0)) {
copyOfFlags=(flags&(~15))|0;
}
if (ImGui::RadioButton("10MHz (Sega System 18)",(flags&15)==1)) {
copyOfFlags=(flags&(~15))|1;
}
if (ImGui::RadioButton("12.5MHz (Sega CD/System 32)",(flags&15)==2)) {
copyOfFlags=(flags&(~15))|2;
}
ImGui::Text("Chip type:");
if (ImGui::RadioButton("RF5C68 (10-bit output)",((flags>>4)&15)==0)) {
copyOfFlags=(flags&(~240))|0;
}
if (ImGui::RadioButton("RF5C164 (16-bit output)",((flags>>4)&15)==1)) {
copyOfFlags=(flags&(~240))|16;
}
break;
}
case DIV_SYSTEM_GB: case DIV_SYSTEM_GB:
case DIV_SYSTEM_SWAN: case DIV_SYSTEM_SWAN:
case DIV_SYSTEM_VERA: case DIV_SYSTEM_VERA: