diff --git a/CMakeLists.txt b/CMakeLists.txt index 014d7e84e..094e9ce00 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -321,6 +321,8 @@ src/engine/platform/sound/scc/scc.cpp src/engine/platform/sound/ymz280b.cpp +src/engine/platform/sound/rf5c68.cpp + src/engine/platform/oplAInterface.cpp src/engine/platform/ym2608Interface.cpp src/engine/platform/ym2610Interface.cpp @@ -388,6 +390,7 @@ src/engine/platform/vrc6.cpp src/engine/platform/scc.cpp src/engine/platform/ymz280b.cpp src/engine/platform/namcowsg.cpp +src/engine/platform/rf5c68.cpp src/engine/platform/dummy.cpp ) diff --git a/TODO.md b/TODO.md index 625edbb6e..f44e15fe5 100644 --- a/TODO.md +++ b/TODO.md @@ -1,6 +1,5 @@ # to-do for 0.6pre1 -- RF5C68 system - ADPCM chips - Game Boy envelope macro/sequence - drag-and-drop ins/wave/sample loading diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 2ab533c7f..6ac2623c0 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -60,6 +60,7 @@ #include "platform/mmc5.h" #include "platform/scc.h" #include "platform/ymz280b.h" +#include "platform/rf5c68.h" #include "platform/dummy.h" #include "../ta-log.h" #include "platform/zxbeeper.h" @@ -356,6 +357,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do dispatch=new DivPlatformYMZ280B; ((DivPlatformYMZ280B*)dispatch)->setChipModel(280); break; + case DIV_SYSTEM_RF5C68: + dispatch=new DivPlatformRF5C68; + break; case DIV_SYSTEM_SOUND_UNIT: dispatch=new DivPlatformSoundUnit; break; diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index 066e4744b..81a360482 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -1150,14 +1150,12 @@ void DivPlatformGenesis::setYMFM(bool use) { } void DivPlatformGenesis::setFlags(unsigned int flags) { - if (flags==3) { - chipClock=COLOR_NTSC*12.0/7.0; - } else if (flags==2) { - chipClock=8000000.0; - } else if (flags==1) { - chipClock=COLOR_PAL*12.0/7.0; - } else { - chipClock=COLOR_NTSC*15.0/7.0; + switch (flags) { + case 1: chipClock=COLOR_PAL*12.0/7.0; break; + case 2: chipClock=8000000.0; break; + case 3: chipClock=COLOR_NTSC*12.0/7.0; break; + case 4: chipClock=COLOR_NTSC*9.0/4.0; break; + default: chipClock=COLOR_NTSC*15.0/7.0; break; } ladder=flags&0x80000000; OPN2_SetChipType(ladder?ym3438_mode_ym2612:0); diff --git a/src/engine/platform/rf5c68.cpp b/src/engine/platform/rf5c68.cpp new file mode 100644 index 000000000..b9ce1a185 --- /dev/null +++ b/src/engine/platform/rf5c68.cpp @@ -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 + +#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; jdata[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<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& 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; isong.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; jdata8[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 +#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& 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 diff --git a/src/engine/platform/sound/rf5c68.cpp b/src/engine/platform/sound/rf5c68.cpp new file mode 100644 index 000000000..dfcc420ea --- /dev/null +++ b/src/engine/platform/sound/rf5c68.cpp @@ -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 + + +//************************************************************************** +// 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; +} diff --git a/src/engine/platform/sound/rf5c68.h b/src/engine/platform/sound/rf5c68.h new file mode 100644 index 000000000..b6416a2fa --- /dev/null +++ b/src/engine/platform/sound/rf5c68.h @@ -0,0 +1,87 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert,Aaron Giles +/*********************************************************/ +/* ricoh RF5C68(or clone) PCM controller */ +/*********************************************************/ + +#include + +#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 m_mixleft; + std::vector m_mixright; + u8 *m_ext_mem; +}; + +class rf5c164_device : public rf5c68_device +{ +public: + rf5c164_device(); +}; + +#endif // MAME_SOUND_RF5C68_H diff --git a/src/engine/sample.h b/src/engine/sample.h index 1b553b295..d9ad633c9 100644 --- a/src/engine/sample.h +++ b/src/engine/sample.h @@ -86,7 +86,7 @@ struct DivSample { unsigned int length8, length16, length1, lengthDPCM, lengthZ, lengthQSoundA, lengthA, lengthB, lengthBRR, lengthVOX; unsigned int off8, off16, off1, offDPCM, offZ, offQSoundA, offA, offB, offBRR, offVOX; - unsigned int offSegaPCM, offQSound, offX1_010, offSU, offYMZ280B; + unsigned int offSegaPCM, offQSound, offX1_010, offSU, offYMZ280B, offRF5C68; unsigned int samples; @@ -248,6 +248,7 @@ struct DivSample { offQSound(0), offX1_010(0), offSU(0), + offRF5C68(0), samples(0) {} ~DivSample(); }; diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index ce0612b7a..09f189ee1 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1401,7 +1401,7 @@ void DivEngine::registerSystems() { ); sysDefs[DIV_SYSTEM_RF5C68]=new DivSysDef( - "Ricoh RF5C68", NULL, 0x95, 0, 8, false, true, 0, false, + "Ricoh RF5C68", NULL, 0x95, 0, 8, false, true, 0x151, false, "this is like SNES' sound chip but without interpolation and the rest of nice bits.", {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"}, {"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"}, diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index e5db2a4da..137791ac8 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -29,6 +29,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write unsigned char baseAddr2=isSecond?0x80:0; unsigned short baseAddr2S=isSecond?0x8000:0; unsigned char smsAddr=isSecond?0x30:0x50; + unsigned char rf5c68Addr=isSecond?0xb1:0xb0; if (write.addr==0xffffffff) { // Furnace fake reset switch (sys) { case DIV_SYSTEM_YM2612: @@ -480,6 +481,13 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeC(0); w->writeC(0); break; + case DIV_SYSTEM_RF5C68: + w->writeC(rf5c68Addr); + w->writeC(7); + w->writeC(0); + w->writeC(rf5c68Addr); + w->writeC(8); + w->writeC(0xff); default: break; } @@ -766,6 +774,11 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeC(write.addr&0xff); w->writeC(write.val&0xff); break; + case DIV_SYSTEM_RF5C68: + w->writeC(rf5c68Addr); + w->writeC(write.addr&0xff); + w->writeC(write.val); + break; default: logW("write not handled!"); break; @@ -888,6 +901,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { DivDispatch* writeX1010[2]={NULL,NULL}; DivDispatch* writeQSound[2]={NULL,NULL}; DivDispatch* writeZ280[2]={NULL,NULL}; + DivDispatch* writeRF5C68[2]={NULL,NULL}; for (int i=0; i>4)==1) { + if (!hasRFC1) { + hasRFC1=disCont[i].dispatch->chipClock; + isSecond[i]=true; + willExport[i]=true; + writeRF5C68[1]=disCont[i].dispatch; + } + } else if (!hasRFC) { + hasRFC=disCont[i].dispatch->chipClock; + willExport[i]=true; + writeRF5C68[0]=disCont[i].dispatch; + } + break; default: break; } @@ -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 int streamID=0; for (int i=0; i>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_SWAN: case DIV_SYSTEM_VERA: