From 9abf872ff3ef08633d3a6c324bc27b097d4d78bc Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Fri, 4 Mar 2022 18:13:49 +0700 Subject: [PATCH 01/12] Add VERA support for Commander X16 --- CMakeLists.txt | 1 + papers/doc/4-instrument/README.md | 1 + papers/doc/4-instrument/vera.md | 8 + src/engine/dispatchContainer.cpp | 4 + src/engine/engine.cpp | 2 + src/engine/instrument.h | 1 + src/engine/platform/vera.cpp | 404 ++++++++++++++++++++++++++++++ src/engine/platform/vera.h | 74 ++++++ src/engine/playback.cpp | 12 + src/engine/song.h | 3 +- src/engine/sysDef.cpp | 36 ++- src/gui/gui.cpp | 4 +- src/gui/insEdit.cpp | 28 ++- 13 files changed, 565 insertions(+), 13 deletions(-) create mode 100644 papers/doc/4-instrument/vera.md create mode 100644 src/engine/platform/vera.cpp create mode 100644 src/engine/platform/vera.h diff --git a/CMakeLists.txt b/CMakeLists.txt index af3e3a7d1..7211eb3d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -303,6 +303,7 @@ src/engine/platform/saa.cpp src/engine/platform/amiga.cpp src/engine/platform/segapcm.cpp src/engine/platform/qsound.cpp +src/engine/platform/vera.cpp src/engine/platform/dummy.cpp src/engine/platform/lynx.cpp ) diff --git a/papers/doc/4-instrument/README.md b/papers/doc/4-instrument/README.md index b9d4b8f40..499a5ee14 100644 --- a/papers/doc/4-instrument/README.md +++ b/papers/doc/4-instrument/README.md @@ -23,6 +23,7 @@ depending on the instrument type, there are currently 10 different types of an i - [AY-3-8910](ay8910.md) - for use with AY-3-8910 PSG sound source and SSG portion in YM2610. - [Amiga/sample](amiga.md) for controlling Amiga and other sample based synthsizers like YM2612's Channel 6 PCM mode, NES channel 5, Sega PCM and PC Engine's sample playback mode. - [Atari Lynx](lynx.md) - for use with Atari Lynx handheld console. +- [VERA](vera.md) - for use with Commander X16 VERA. # macros diff --git a/papers/doc/4-instrument/vera.md b/papers/doc/4-instrument/vera.md new file mode 100644 index 000000000..b577ccd23 --- /dev/null +++ b/papers/doc/4-instrument/vera.md @@ -0,0 +1,8 @@ +# VERA instrument editor + +VERA instrument editor consists of only four macros: + +- [Volume] - volume sequence +- [Arpeggio] - pitch sequence +- [Duty cycle] - pulse duty cycle sequence +- [Waveform] - select the waveform used by instrument diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 8398ca3d0..e34eebbb8 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -39,6 +39,7 @@ #include "platform/amiga.h" #include "platform/segapcm.h" #include "platform/qsound.h" +#include "platform/vera.h" #include "platform/dummy.h" #include "platform/lynx.h" #include "../ta-log.h" @@ -226,6 +227,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_SEGAPCM_COMPAT: dispatch=new DivPlatformSegaPCM; break; + case DIV_SYSTEM_VERA: + dispatch = new DivPlatformVERA; + break; default: logW("this system is not supported yet! using dummy platform.\n"); dispatch=new DivPlatformDummy; diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index a8b023d7d..4f387b272 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -951,6 +951,8 @@ int DivEngine::getEffectiveSampleRate(int rate) { return (24038*MIN(65535,(rate*4096/24038)))/4096; case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610_FULL_EXT: case DIV_SYSTEM_YM2610B: case DIV_SYSTEM_YM2610B_EXT: return 18518; + case DIV_SYSTEM_VERA: + return (48828*MIN(128,(rate*128/48828)))/128; default: break; } diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 27ce2a4ce..5f11df65a 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -48,6 +48,7 @@ enum DivInstrumentType { DIV_INS_BEEPER=21, DIV_INS_SWAN=22, DIV_INS_MIKEY=23, + DIV_INS_VERA=24, }; // FM operator structure: diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp new file mode 100644 index 000000000..47f4888d6 --- /dev/null +++ b/src/engine/platform/vera.cpp @@ -0,0 +1,404 @@ +/** + * 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 "vera.h" +#include "../engine.h" +#include +#include + +#define rWrite(c,a,d) {regPool[(c)*4+(a)]=(d);} +#define rWriteLo(c,a,d) rWrite(c,a,(regPool[(c)*4+(a)]&(~0x3f))|((d)&0x3f)) +#define rWriteHi(c,a,d) rWrite(c,a,(regPool[(c)*4+(a)]&(~0xc0))|(((d)<<6)&0xc0)) +#define rWriteFIFOVol(d) rWrite(16,0,(regPool[64]&(~0x3f))|((d)&0x3f)) + +const char* regCheatSheetVERA[]={ + "CHxFreq", "00+x*4", + "CHxVol", "02+x*4", + "CHxWave", "03+x*4", + + "AUDIO_CTRL", "40", + "AUDIO_RATE", "41", + + NULL +}; + +const char** DivPlatformVERA::getRegisterSheet() { + return regCheatSheetVERA; +} + +const char* DivPlatformVERA::getEffectName(unsigned char effect) { + switch (effect) { + case 0x20: + return "20xx: Change waveform"; + break; + case 0x22: + return "22xx: Set duty cycle (0 to 63)"; + break; + } + return NULL; +} + +void DivPlatformVERA::acquire(short* bufL, short* bufR, size_t start, size_t len) { + // taken from the official X16 emulator's source code, (c) 2020 Frank van den Hoef + const uint8_t volume_lut_psg[64]={0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 7, 7, 7, 8, 8, 9, 9, 10, 11, 11, 12, 13, 14, 14, 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 28, 29, 31, 33, 35, 37, 39, 42, 44, 47, 50, 52, 56, 59, 63}; + const uint8_t volume_lut_pcm[16]={0, 1, 2, 3, 4, 5, 6, 8, 11, 14, 18, 23, 30, 38, 49, 64}; + for (size_t pos=start; pos>6) { + case 0: val=(new_accum>>10)>(regPool[i*4+3]&(unsigned)0x3f)?0:63; break; + case 1: val=(new_accum>>11); break; + case 2: val=(new_accum&0x10000)?(~(new_accum>>10)&0x3f):((new_accum>>10)&0x3f); break; + case 3: val=chan[i].noiseval; break; + } + val=(val-0x20)*volume_lut_psg[regPool[i*4+2]&0x3f]; + lout+=(regPool[i*4+2]&0x40)?val:0; + rout+=(regPool[i*4+2]&0x80)?val:0; + } + // PCM + // simple one-channel sample player, actual hardware is essentially a DAC + // with buffering + if (chan[16].pcm.sample>=0) { + chan[16].accum+=regPool[65]; + if (chan[16].accum>=128) { + DivSample* s=parent->getSample(chan[16].pcm.sample); + if (s->samples>0) { + // TODO stereo samples once DivSample has a support for it + switch (s->depth) { + case 8: + chan[16].pcm.out_l=chan[16].pcm.pan&1?(s->data8[chan[16].pcm.pos]*256):0; + chan[16].pcm.out_r=chan[16].pcm.pan&2?(s->data8[chan[16].pcm.pos]*256):0; + regPool[64]|=0x20; // for register viewer purposes + break; + case 16: + chan[16].pcm.out_l=chan[16].pcm.pan&1?(s->data16[chan[16].pcm.pos]):0; + chan[16].pcm.out_r=chan[16].pcm.pan&2?(s->data16[chan[16].pcm.pos]):0; + regPool[64]&=~0x20; + break; + } + } else { + chan[16].pcm.sample=-1; + } + chan[16].accum&=0x7f; + chan[16].pcm.pos++; + if (chan[16].pcm.pos>=s->samples) { + if (s->loopStart>=0 && s->loopStart<=(int)s->samples) { + chan[16].pcm.pos=s->loopStart; + } else { + chan[16].pcm.sample=-1; + } + } + } + } + int pcmvol=volume_lut_pcm[regPool[64]&0x0f]; + lout+=chan[16].pcm.out_l*pcmvol/64; + rout+=chan[16].pcm.out_r*pcmvol/64; + + bufL[pos]=(short)(lout/2); + bufR[pos]=(short)(rout/2); + } +} + +void DivPlatformVERA::reset() { + for (int i=0; i<17; i++) { + chan[i]=Channel(); + } + memset(regPool,0,66); + for (int i=0; i<16; i++) { + chan[i].vol=63; + rWriteHi(i,2,3); // default pan + } + chan[16].vol=15; +} + +int DivPlatformVERA::calcNoteFreq(int ch, int note) { + if (ch<16) { + return parent->calcBaseFreq(chipClock,2097152,note,false); + } else { + double off=1.0; + if (chan[ch].pcm.sample>=0 && chan[ch].pcm.samplesong.sampleLen) { + DivSample* s=parent->getSample(chan[ch].pcm.sample); + if (s->centerRate<1) { + off=1.0; + } else { + off=s->centerRate/8363.0; + } + } + return (int)(off*parent->calcBaseFreq(chipClock,65536,note,false)); + } +} + +void DivPlatformVERA::tick() { + for (int i=0; i<17; i++) { + chan[i].std.next(); + if (chan[i].std.hadVol) { + if (i<16) { + chan[i].outVol=MAX(chan[i].vol+chan[i].std.vol-63,0); + rWriteLo(i,2,isMuted[i]?0:(chan[i].outVol&63)); + } else { + // NB this is currently assuming Amiga instrument type with a 0-64 + // (inclusive) volume range. This envelope is then scaled and added to + // the channel volume. Is this a better way to handle this instead of + // making another identical Amiga instrument type but with a 0-15 + // volume range? + chan[16].outVol=MAX(chan[16].vol+MIN(chan[16].std.vol/4,15)-15,0); + rWriteFIFOVol(isMuted[16]?0:(chan[16].outVol&15)); + } + } + if (chan[i].std.hadArp) { + if (!chan[i].inPorta) { + if (chan[i].std.arpMode) { + chan[i].baseFreq=calcNoteFreq(0,chan[i].std.arp); + } else { + chan[i].baseFreq=calcNoteFreq(0,chan[i].note+chan[i].std.arp); + } + } + chan[i].freqChanged=true; + } else { + if (chan[i].std.arpMode && chan[i].std.finishedArp) { + chan[i].baseFreq=calcNoteFreq(0,chan[i].note); + chan[i].freqChanged=true; + } + } + if (chan[i].std.hadDuty && i<16) { + rWriteLo(i,3,chan[i].std.duty); + } + if (chan[i].std.hadWave && i<16) { + rWriteHi(i,3,chan[i].std.wave); + } + if (chan[i].freqChanged) { + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8); + if (i<16) { + if (chan[i].freq>65535) chan[i].freq=65535; + rWrite(i,0,chan[i].freq&0xff); + rWrite(i,1,(chan[i].freq>>8)&0xff); + } else { + if (chan[i].freq>128) chan[i].freq=128; + rWrite(16,1,chan[i].freq&0xff); + } + chan[i].freqChanged=false; + } + } + // PCM + chan[16].std.next(); + if (chan[16].std.hadVol) { + } + if (chan[16].std.hadArp) { + if (!chan[16].inPorta) { + if (chan[16].std.arpMode) { + chan[16].baseFreq=calcNoteFreq(16,chan[16].std.arp); + } else { + chan[16].baseFreq=calcNoteFreq(16,chan[16].note+chan[16].std.arp); + } + } + chan[16].freqChanged=true; + } else { + if (chan[16].std.arpMode && chan[16].std.finishedArp) { + chan[16].baseFreq=calcNoteFreq(16,chan[16].note); + chan[16].freqChanged=true; + } + } +} + +int DivPlatformVERA::dispatch(DivCommand c) { + int tmp; + switch (c.cmd) { + case DIV_CMD_NOTE_ON: + tmp = isMuted[c.chan]?0:chan[c.chan].vol; + if(c.chan<16) { + rWriteLo(c.chan,2,tmp) + } else { + chan[c.chan].pcm.sample=parent->getIns(chan[16].ins)->amiga.initSample; + if (chan[c.chan].pcm.sample<0 || chan[c.chan].pcm.sample>=parent->song.sampleLen) { + chan[c.chan].pcm.sample=-1; + } + chan[16].pcm.pos=0; + rWriteFIFOVol(tmp); + } + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=calcNoteFreq(c.chan,c.value); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].active=true; + chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + break; + case DIV_CMD_NOTE_OFF: + chan[c.chan].active=false; + if(c.chan<16) { + rWriteLo(c.chan,2,0) + } else { + chan[16].pcm.sample=-1; + rWriteFIFOVol(0); + rWrite(16,1,0); + } + chan[c.chan].std.init(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_INSTRUMENT: + chan[c.chan].ins=(unsigned char)c.value; + break; + case DIV_CMD_VOLUME: + if (c.chan<16) { + tmp=c.value&0x3f; + chan[c.chan].vol=tmp; + rWriteLo(c.chan,2,(isMuted[c.chan]?0:tmp)); + } else { + tmp=c.value&0x0f; + chan[c.chan].vol=tmp; + rWriteFIFOVol(isMuted[c.chan]?0:tmp); + } + break; + case DIV_CMD_GET_VOLUME: + return chan[c.chan].vol; + break; + case DIV_CMD_PITCH: + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_NOTE_PORTA: { + int destFreq=calcNoteFreq(c.chan,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=calcNoteFreq(c.chan,c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(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].std.init(parent->getIns(chan[c.chan].ins)); + } + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_STD_NOISE_MODE: + if (c.chan<16) rWriteLo(c.chan,3,c.value); + break; + case DIV_CMD_WAVE: + if (c.chan<16) rWriteHi(c.chan,3,c.value); + break; + case DIV_CMD_PANNING: { + tmp=0; + tmp|=(c.value&0x10)?1:0; + tmp|=(c.value&0x01)?2:0; + if (c.chan<16) { + rWriteHi(c.chan,2,tmp); + } else { + chan[c.chan].pcm.pan = tmp&3; + } + break; + } + case DIV_CMD_GET_VOLMAX: + if(c.chan<16) { + return 63; + } else { + return 15; + } + break; + case DIV_ALWAYS_SET_VOLUME: + return 0; + break; + default: + break; + } + return 1; +} + +void* DivPlatformVERA::getChanState(int ch) { + return &chan[ch]; +} + +unsigned char* DivPlatformVERA::getRegisterPool() { + return regPool; +} + +int DivPlatformVERA::getRegisterPoolSize() { + return 66; +} + +void DivPlatformVERA::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; +} + +bool DivPlatformVERA::isStereo() { + return true; +} + +void DivPlatformVERA::notifyInsDeletion(void* ins) { + for (int i=0; i<2; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformVERA::poke(unsigned int addr, unsigned short val) { + regPool[addr] = (unsigned char)val; +} + +void DivPlatformVERA::poke(std::vector& wlist) { + for (auto &i: wlist) regPool[i.addr] = (unsigned char)i.val; +} + +int DivPlatformVERA::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + for (int i=0; i<17; i++) { + isMuted[i]=false; + } + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + chipClock=25000000; + rate=chipClock/512; + reset(); + return 17; +} + +DivPlatformVERA::~DivPlatformVERA() { +} diff --git a/src/engine/platform/vera.h b/src/engine/platform/vera.h new file mode 100644 index 000000000..c6a208b90 --- /dev/null +++ b/src/engine/platform/vera.h @@ -0,0 +1,74 @@ +/** + * 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 _VERA_H +#define _VERA_H +#include "../dispatch.h" +#include "../instrument.h" +#include "../macroInt.h" + +class DivPlatformVERA: public DivDispatch { + protected: + struct Channel { + int freq, baseFreq, pitch, note; + unsigned char ins; + bool active, freqChanged, inPorta; + int vol, outVol; + unsigned accum; + int noiseval; + DivMacroInt std; + + struct PCMChannel { + int sample; + int out_l, out_r; + unsigned pos; + unsigned len; + unsigned char freq, pan; + PCMChannel(): sample(-1), out_l(0), out_r(0), pos(0), len(0), freq(0), pan(3) {} + } pcm; + Channel(): freq(0), baseFreq(0), pitch(0), note(0), ins(-1), active(false), freqChanged(false), inPorta(false), vol(0), outVol(0), accum(0), noiseval(0) {} + }; + Channel chan[17]; + bool isMuted[17]; + unsigned char regPool[66]; + + 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); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void tick(); + void muteChannel(int ch, bool mute); + void notifyInsDeletion(void* ins); + bool isStereo(); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + const char* getEffectName(unsigned char effect); + int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + ~DivPlatformVERA(); + + private: + int calcNoteFreq(int ch, int note); +}; +#endif diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 23cb54e1c..e1a7e3a4c 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -258,6 +258,18 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe break; } break; + case DIV_SYSTEM_VERA: + switch (effect) { + case 0x20: // select waveform + dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); + break; + case 0x22: // duty + dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); + break; + default: + return false; + } + break; default: return false; } diff --git a/src/engine/song.h b/src/engine/song.h index fff1065fe..133175c0a 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -90,8 +90,9 @@ enum DivSystem { DIV_SYSTEM_OPLL_DRUMS, DIV_SYSTEM_LYNX, DIV_SYSTEM_QSOUND, + DIV_SYSTEM_VERA, DIV_SYSTEM_YM2610B_EXT, - DIV_SYSTEM_SEGAPCM_COMPAT + DIV_SYSTEM_SEGAPCM_COMPAT, }; struct DivSong { diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 3c988ceb8..385c1609b 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -135,6 +135,8 @@ DivSystem DivEngine::systemFromFile(unsigned char val) { return DIV_SYSTEM_LYNX; case 0xa9: return DIV_SYSTEM_SEGAPCM_COMPAT; + case 0xaa: + return DIV_SYSTEM_VERA; case 0xde: return DIV_SYSTEM_YM2610B_EXT; case 0xe0: @@ -258,6 +260,8 @@ unsigned char DivEngine::systemToFile(DivSystem val) { return 0xa8; case DIV_SYSTEM_SEGAPCM_COMPAT: return 0xa9; + case DIV_SYSTEM_VERA: + return 0xaa; case DIV_SYSTEM_YM2610B_EXT: return 0xde; case DIV_SYSTEM_QSOUND: @@ -383,6 +387,8 @@ int DivEngine::getChannelCount(DivSystem sys) { case DIV_SYSTEM_YM2610B_EXT: case DIV_SYSTEM_QSOUND: return 19; + case DIV_SYSTEM_VERA: + return 17; } return 0; } @@ -522,6 +528,10 @@ const char* DivEngine::getSongSystemName() { if (song.system[0]==DIV_SYSTEM_AY8910 && song.system[1]==DIV_SYSTEM_AY8910) { return "Bally Midway MCR"; } + + if (song.system[0]==DIV_SYSTEM_YM2151 && song.system[1]==DIV_SYSTEM_VERA) { + return "Commander X16"; + } break; case 3: break; @@ -650,6 +660,8 @@ const char* DivEngine::getSystemName(DivSystem sys) { return "Taito Arcade Extended Channel 3"; case DIV_SYSTEM_QSOUND: return "Capcom QSound"; + case DIV_SYSTEM_VERA: + return "VERA"; } return "Unknown"; } @@ -775,6 +787,8 @@ const char* DivEngine::getSystemChips(DivSystem sys) { return "Yamaha YM2610B Extended Channel 3"; case DIV_SYSTEM_QSOUND: return "Capcom DL-1425"; + case DIV_SYSTEM_VERA: + return "VERA"; } return "Unknown"; } @@ -858,7 +872,7 @@ bool DivEngine::isSTDSystem(DivSystem sys) { } const char* chanNames[38][24]={ - {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "PCM"}, // YMU759/SegaPCM + {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "PCM"}, // YMU759/SegaPCM/VERA {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Noise"}, // Genesis {"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Noise"}, // Genesis (extended channel 3) {"Square 1", "Square 2", "Square 3", "Noise"}, // SMS @@ -899,7 +913,7 @@ const char* chanNames[38][24]={ }; const char* chanShortNames[38][24]={ - {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "PCM"}, // YMU759 + {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "PCM"}, // YMU759/VERA {"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "NO"}, // Genesis {"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "S4"}, // Genesis (extended channel 3) {"S1", "S2", "S3", "NO"}, // SMS @@ -939,7 +953,7 @@ const char* chanShortNames[38][24]={ {"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, // YM2610B (extended channel 3) }; -const int chanTypes[38][24]={ +const int chanTypes[39][24]={ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4}, // YMU759 {0, 0, 0, 0, 0, 0, 1, 1, 1, 2}, // Genesis {0, 0, 5, 5, 5, 5, 0, 0, 0, 1, 1, 1, 2}, // Genesis (extended channel 3) @@ -978,9 +992,10 @@ const int chanTypes[38][24]={ {0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2}, // OPL3 4-op + drums {3, 3, 3, 3}, //Lynx {0, 0, 5, 5, 5, 5, 0, 0, 0, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4}, // YM2610B (extended channel 3) + {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4}, // VERA }; -const DivInstrumentType chanPrefType[44][24]={ +const DivInstrumentType chanPrefType[45][24]={ {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM}, // YMU759 {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}, // Genesis {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}, // Genesis (extended channel 3) @@ -1025,6 +1040,7 @@ const DivInstrumentType chanPrefType[44][24]={ {DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ}, // Z {DIV_INS_MIKEY, DIV_INS_MIKEY, DIV_INS_MIKEY, DIV_INS_MIKEY}, // Lynx {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // YM2610B (extended channel 3) + {DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_AMIGA}, // VERA }; const char* DivEngine::getChannelName(int chan) { @@ -1162,6 +1178,9 @@ const char* DivEngine::getChannelName(int chan) { case DIV_SYSTEM_QSOUND: return chanNames[36][dispatchChanOfChan[chan]]; break; + case DIV_SYSTEM_VERA: + return chanNames[0][dispatchChanOfChan[chan]]; + break; } return "??"; } @@ -1301,6 +1320,9 @@ const char* DivEngine::getChannelShortName(int chan) { case DIV_SYSTEM_QSOUND: return chanShortNames[36][dispatchChanOfChan[chan]]; break; + case DIV_SYSTEM_VERA: + return chanShortNames[0][dispatchChanOfChan[chan]]; + break; } return "??"; } @@ -1438,6 +1460,9 @@ int DivEngine::getChannelType(int chan) { case DIV_SYSTEM_LYNX: return chanTypes[36][dispatchChanOfChan[chan]]; break; + case DIV_SYSTEM_VERA: + return chanTypes[38][dispatchChanOfChan[chan]]; + break; } return 1; } @@ -1588,6 +1613,9 @@ DivInstrumentType DivEngine::getPreferInsType(int chan) { case DIV_SYSTEM_LYNX: return chanPrefType[42][dispatchChanOfChan[chan]]; break; + case DIV_SYSTEM_VERA: + return chanPrefType[44][dispatchChanOfChan[chan]]; + break; } return DIV_INS_FM; } diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 58e95003f..f4fd48a28 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4597,6 +4597,7 @@ bool FurnaceGUI::loop() { sysAddOption(DIV_SYSTEM_AY8930); sysAddOption(DIV_SYSTEM_LYNX); sysAddOption(DIV_SYSTEM_QSOUND); + sysAddOption(DIV_SYSTEM_VERA); ImGui::EndMenu(); } if (ImGui::BeginMenu("configure system...")) { @@ -4715,7 +4716,7 @@ bool FurnaceGUI::loop() { break; } case DIV_SYSTEM_YM2151: - if (ImGui::RadioButton("NTSC (3.58MHz)",flags==0)) { + if (ImGui::RadioButton("NTSC/X16 (3.58MHz)",flags==0)) { e->setSysFlags(i,0,restart); updateWindowTitle(); } @@ -4913,6 +4914,7 @@ bool FurnaceGUI::loop() { sysChangeOption(i,DIV_SYSTEM_AY8930); sysChangeOption(i,DIV_SYSTEM_LYNX); sysChangeOption(i,DIV_SYSTEM_QSOUND); + sysChangeOption(i,DIV_SYSTEM_VERA); ImGui::EndMenu(); } } diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 4afa67a5c..1650fd151 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -27,7 +27,7 @@ #include #include "plot_nolerp.h" -const char* insTypes[24]={ +const char* insTypes[25]={ "Standard", "FM (4-operator)", "Game Boy", @@ -51,7 +51,8 @@ const char* insTypes[24]={ "POKEY", "PC Beeper", "WonderSwan", - "Atari Lynx" + "Atari Lynx", + "VERA" }; const char* ssgEnvTypes[8]={ @@ -783,9 +784,9 @@ void FurnaceGUI::drawInsEdit() { } else { DivInstrument* ins=e->song.ins[curIns]; ImGui::InputText("Name",&ins->name); - if (ins->type<0 || ins->type>23) ins->type=DIV_INS_FM; + if (ins->type<0 || ins->type>24) ins->type=DIV_INS_FM; int insType=ins->type; - if (ImGui::Combo("Type",&insType,insTypes,24,24)) { + if (ImGui::Combo("Type",&insType,insTypes,25,24)) { ins->type=(DivInstrumentType)insType; } @@ -1304,7 +1305,7 @@ void FurnaceGUI::drawInsEdit() { float loopIndicator[256]; const char* volumeLabel="Volume"; - int volMax=(ins->type==DIV_INS_PCE || ins->type==DIV_INS_AY8930)?31:15; + int volMax=15; int volMin=0; if (ins->type==DIV_INS_C64) { if (ins->c64.volIsCutoff) { @@ -1317,6 +1318,12 @@ void FurnaceGUI::drawInsEdit() { } } } + if ((ins->type==DIV_INS_PCE || ins->type==DIV_INS_AY8930)) { + volMax=31; + } + if (ins->type==DIV_INS_VERA) { + volMax=63; + } if (ins->type==DIV_INS_AMIGA) { volMax=64; } @@ -1330,7 +1337,7 @@ void FurnaceGUI::drawInsEdit() { bool arpMode=ins->std.arpMacroMode; const char* dutyLabel="Duty/Noise"; - int dutyMax=(ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)?31:3; + int dutyMax=3; if (ins->type==DIV_INS_C64) { dutyLabel="Duty"; if (ins->c64.dutyIsAbs) { @@ -1342,6 +1349,9 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_FM) { dutyMax=32; } + if ((ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)) { + dutyMax=31; + } if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_FM) { dutyLabel="Noise Freq"; } @@ -1361,9 +1371,13 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPL) { dutyMax=0; } + if (ins->type==DIV_INS_VERA) { + dutyLabel="Duty"; + dutyMax=63; + } bool dutyIsRel=(ins->type==DIV_INS_C64 && !ins->c64.dutyIsAbs); - int waveMax=(ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)?3:63; + int waveMax=(ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_VERA)?3:63; bool bitMode=false; if (ins->type==DIV_INS_C64 || ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_SAA1099) { bitMode=true; From d209a45b92f93b3b6fb6b9701617e517d6ffd1c5 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Sat, 5 Mar 2022 03:11:11 +0700 Subject: [PATCH 02/12] Change sound chip ID to 0xac --- src/engine/sysDef.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 385c1609b..ee771115e 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -135,7 +135,7 @@ DivSystem DivEngine::systemFromFile(unsigned char val) { return DIV_SYSTEM_LYNX; case 0xa9: return DIV_SYSTEM_SEGAPCM_COMPAT; - case 0xaa: + case 0xac: return DIV_SYSTEM_VERA; case 0xde: return DIV_SYSTEM_YM2610B_EXT; @@ -261,7 +261,7 @@ unsigned char DivEngine::systemToFile(DivSystem val) { case DIV_SYSTEM_SEGAPCM_COMPAT: return 0xa9; case DIV_SYSTEM_VERA: - return 0xaa; + return 0xac; case DIV_SYSTEM_YM2610B_EXT: return 0xde; case DIV_SYSTEM_QSOUND: From 252dc16492da387afc72c622c99a50660de6abf9 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Mon, 7 Mar 2022 16:45:34 +0700 Subject: [PATCH 03/12] Add X16 to the New menu --- src/gui/gui.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index f4fd48a28..79335dfdf 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -6457,6 +6457,13 @@ FurnaceGUI::FurnaceGUI(): 0 } ));*/ + cat.systems.push_back(FurnaceGUISysDef( + "Commander X16", { + DIV_SYSTEM_YM2151, 64, 0, 0, + DIV_SYSTEM_VERA, 64, 0, 0, + 0 + } + )); sysCategories.push_back(cat); cat=FurnaceGUISysCategory("Arcade systems"); From 7f3519b9708957b8e556b0d2314ac88b73e678fd Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Mon, 7 Mar 2022 16:46:12 +0700 Subject: [PATCH 04/12] Implement VERA noise generation instead of rand() --- src/engine/platform/vera.cpp | 11 ++++++++--- src/engine/platform/vera.h | 5 ++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index 47f4888d6..ce45341be 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -62,14 +62,17 @@ void DivPlatformVERA::acquire(short* bufL, short* bufR, size_t start, size_t len int32_t lout=0; int32_t rout=0; // PSG + // TODO this is a currently speculated noise generation + // as the hardware and sources for it are not out in the public + // and the official emulator just uses rand() + noiseState=(noiseState<<1)|(((noiseState>>1)^(noiseState>>2)^(noiseState>>4)^(noiseState>>15))&1); + noiseOut=((noiseOut<<1)|(noiseState&1))&63; for (int i=0; i<16; i++) { unsigned freq=regPool[i*4+0] | (regPool[i*4+1] << 8); unsigned old_accum=chan[i].accum; unsigned new_accum=old_accum+freq; int val=0x20; - // TODO actually emulate the LFSR, it's currently unknown publicly - // and the official emulator just uses this: - if ((old_accum^new_accum)&0x10000) chan[i].noiseval=rand()&63; + if ((old_accum^new_accum)&0x10000) chan[i].noiseval=noiseOut; new_accum&=0x1ffff; chan[i].accum=new_accum; switch (regPool[i*4+3]>>6) { @@ -136,6 +139,8 @@ void DivPlatformVERA::reset() { rWriteHi(i,2,3); // default pan } chan[16].vol=15; + noiseState=1; + noiseOut=0; } int DivPlatformVERA::calcNoteFreq(int ch, int note) { diff --git a/src/engine/platform/vera.h b/src/engine/platform/vera.h index c6a208b90..64a8ee7c1 100644 --- a/src/engine/platform/vera.h +++ b/src/engine/platform/vera.h @@ -46,8 +46,10 @@ class DivPlatformVERA: public DivDispatch { }; Channel chan[17]; bool isMuted[17]; + unsigned noiseState, noiseOut; unsigned char regPool[66]; + int calcNoteFreq(int ch, int note); friend void putDispatchChan(void*,int,int); public: @@ -67,8 +69,5 @@ class DivPlatformVERA: public DivDispatch { const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); ~DivPlatformVERA(); - - private: - int calcNoteFreq(int ch, int note); }; #endif From a86a7f766be4325d73913d279951e66079859b28 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Tue, 8 Mar 2022 15:06:11 +0700 Subject: [PATCH 05/12] VERA doesn't have config flags --- src/gui/gui.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index f13ad55df..4a64bb3b5 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4928,6 +4928,7 @@ bool FurnaceGUI::loop() { break; } case DIV_SYSTEM_GB: + case DIV_SYSTEM_VERA: case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL: From e05052d9d7fbe0fdca88b3eedd7d2cf835473b27 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Tue, 8 Mar 2022 15:44:14 +0700 Subject: [PATCH 06/12] Properly case PCM channel --- src/engine/platform/vera.cpp | 39 +++++++++++++++--------------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index ce45341be..4d0355731 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -161,21 +161,11 @@ int DivPlatformVERA::calcNoteFreq(int ch, int note) { } void DivPlatformVERA::tick() { - for (int i=0; i<17; i++) { + for (int i=0; i<16; i++) { chan[i].std.next(); if (chan[i].std.hadVol) { - if (i<16) { - chan[i].outVol=MAX(chan[i].vol+chan[i].std.vol-63,0); - rWriteLo(i,2,isMuted[i]?0:(chan[i].outVol&63)); - } else { - // NB this is currently assuming Amiga instrument type with a 0-64 - // (inclusive) volume range. This envelope is then scaled and added to - // the channel volume. Is this a better way to handle this instead of - // making another identical Amiga instrument type but with a 0-15 - // volume range? - chan[16].outVol=MAX(chan[16].vol+MIN(chan[16].std.vol/4,15)-15,0); - rWriteFIFOVol(isMuted[16]?0:(chan[16].outVol&15)); - } + chan[i].outVol=MAX(chan[i].vol+chan[i].std.vol-63,0); + rWriteLo(i,2,isMuted[i]?0:(chan[i].outVol&63)); } if (chan[i].std.hadArp) { if (!chan[i].inPorta) { @@ -192,28 +182,25 @@ void DivPlatformVERA::tick() { chan[i].freqChanged=true; } } - if (chan[i].std.hadDuty && i<16) { + if (chan[i].std.hadDuty) { rWriteLo(i,3,chan[i].std.duty); } - if (chan[i].std.hadWave && i<16) { + if (chan[i].std.hadWave) { rWriteHi(i,3,chan[i].std.wave); } if (chan[i].freqChanged) { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8); - if (i<16) { - if (chan[i].freq>65535) chan[i].freq=65535; - rWrite(i,0,chan[i].freq&0xff); - rWrite(i,1,(chan[i].freq>>8)&0xff); - } else { - if (chan[i].freq>128) chan[i].freq=128; - rWrite(16,1,chan[i].freq&0xff); - } + if (chan[i].freq>65535) chan[i].freq=65535; + rWrite(i,0,chan[i].freq&0xff); + rWrite(i,1,(chan[i].freq>>8)&0xff); chan[i].freqChanged=false; } } // PCM chan[16].std.next(); if (chan[16].std.hadVol) { + chan[16].outVol=MAX(chan[16].vol+MIN(chan[16].std.vol/4,15)-15,0); + rWriteFIFOVol(isMuted[16]?0:(chan[16].outVol&15)); } if (chan[16].std.hadArp) { if (!chan[16].inPorta) { @@ -230,6 +217,12 @@ void DivPlatformVERA::tick() { chan[16].freqChanged=true; } } + if (chan[16].freqChanged) { + chan[16].freq=parent->calcFreq(chan[16].baseFreq,chan[16].pitch,false,8); + if (chan[16].freq>128) chan[16].freq=128; + rWrite(16,1,chan[16].freq&0xff); + chan[16].freqChanged=false; + } } int DivPlatformVERA::dispatch(DivCommand c) { From eb3a73c38b552d010c9e621341159a1beb42310a Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Wed, 9 Mar 2022 00:58:21 +0700 Subject: [PATCH 07/12] Mute on pan registers instead --- src/engine/platform/vera.cpp | 57 +++++++++++++++++++++--------------- src/engine/platform/vera.h | 8 ++--- 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index 4d0355731..49255be80 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -93,19 +93,27 @@ void DivPlatformVERA::acquire(short* bufL, short* bufR, size_t start, size_t len if (chan[16].accum>=128) { DivSample* s=parent->getSample(chan[16].pcm.sample); if (s->samples>0) { - // TODO stereo samples once DivSample has a support for it - switch (s->depth) { - case 8: - chan[16].pcm.out_l=chan[16].pcm.pan&1?(s->data8[chan[16].pcm.pos]*256):0; - chan[16].pcm.out_r=chan[16].pcm.pan&2?(s->data8[chan[16].pcm.pos]*256):0; - regPool[64]|=0x20; // for register viewer purposes - break; - case 16: - chan[16].pcm.out_l=chan[16].pcm.pan&1?(s->data16[chan[16].pcm.pos]):0; - chan[16].pcm.out_r=chan[16].pcm.pan&2?(s->data16[chan[16].pcm.pos]):0; - regPool[64]&=~0x20; - break; + int tmp_l=0; + int tmp_r=0; + if (!isMuted[16]) { + // TODO stereo samples once DivSample has a support for it + switch (s->depth) { + case 8: + tmp_l=s->data8[chan[16].pcm.pos]*256; + tmp_r=tmp_l; + regPool[64]|=0x20; // for register viewer purposes + break; + case 16: + tmp_l=s->data16[chan[16].pcm.pos]; + tmp_r=tmp_l; + regPool[64]&=~0x20; + break; + } + if (!(chan[16].pan&1)) tmp_l=0; + if (!(chan[16].pan&2)) tmp_r=0; } + chan[16].pcm.out_l=tmp_l; + chan[16].pcm.out_r=tmp_r; } else { chan[16].pcm.sample=-1; } @@ -136,9 +144,11 @@ void DivPlatformVERA::reset() { memset(regPool,0,66); for (int i=0; i<16; i++) { chan[i].vol=63; - rWriteHi(i,2,3); // default pan + chan[i].pan=3; + rWriteHi(i,2,isMuted[i]?0:3); } chan[16].vol=15; + chan[16].pan=3; noiseState=1; noiseOut=0; } @@ -165,7 +175,7 @@ void DivPlatformVERA::tick() { chan[i].std.next(); if (chan[i].std.hadVol) { chan[i].outVol=MAX(chan[i].vol+chan[i].std.vol-63,0); - rWriteLo(i,2,isMuted[i]?0:(chan[i].outVol&63)); + rWriteLo(i,2,chan[i].outVol); } if (chan[i].std.hadArp) { if (!chan[i].inPorta) { @@ -200,7 +210,7 @@ void DivPlatformVERA::tick() { chan[16].std.next(); if (chan[16].std.hadVol) { chan[16].outVol=MAX(chan[16].vol+MIN(chan[16].std.vol/4,15)-15,0); - rWriteFIFOVol(isMuted[16]?0:(chan[16].outVol&15)); + rWriteFIFOVol(chan[16].outVol&15); } if (chan[16].std.hadArp) { if (!chan[16].inPorta) { @@ -229,16 +239,15 @@ int DivPlatformVERA::dispatch(DivCommand c) { int tmp; switch (c.cmd) { case DIV_CMD_NOTE_ON: - tmp = isMuted[c.chan]?0:chan[c.chan].vol; if(c.chan<16) { - rWriteLo(c.chan,2,tmp) + rWriteLo(c.chan,2,chan[c.chan].vol) } else { chan[c.chan].pcm.sample=parent->getIns(chan[16].ins)->amiga.initSample; if (chan[c.chan].pcm.sample<0 || chan[c.chan].pcm.sample>=parent->song.sampleLen) { chan[c.chan].pcm.sample=-1; } chan[16].pcm.pos=0; - rWriteFIFOVol(tmp); + rWriteFIFOVol(chan[c.chan].vol); } if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=calcNoteFreq(c.chan,c.value); @@ -270,11 +279,11 @@ int DivPlatformVERA::dispatch(DivCommand c) { if (c.chan<16) { tmp=c.value&0x3f; chan[c.chan].vol=tmp; - rWriteLo(c.chan,2,(isMuted[c.chan]?0:tmp)); + rWriteLo(c.chan,2,tmp); } else { tmp=c.value&0x0f; chan[c.chan].vol=tmp; - rWriteFIFOVol(isMuted[c.chan]?0:tmp); + rWriteFIFOVol(tmp); } break; case DIV_CMD_GET_VOLUME: @@ -328,10 +337,9 @@ int DivPlatformVERA::dispatch(DivCommand c) { tmp=0; tmp|=(c.value&0x10)?1:0; tmp|=(c.value&0x01)?2:0; + chan[c.chan].pan=tmp&3; if (c.chan<16) { - rWriteHi(c.chan,2,tmp); - } else { - chan[c.chan].pcm.pan = tmp&3; + rWriteHi(c.chan,2,isMuted[c.chan]?0:chan[c.chan].pan); } break; } @@ -365,6 +373,9 @@ int DivPlatformVERA::getRegisterPoolSize() { void DivPlatformVERA::muteChannel(int ch, bool mute) { isMuted[ch]=mute; + if (ch<16) { + rWriteHi(ch,2,mute?0:chan[ch].pan); + } } bool DivPlatformVERA::isStereo() { diff --git a/src/engine/platform/vera.h b/src/engine/platform/vera.h index 64a8ee7c1..e5cb2ad9e 100644 --- a/src/engine/platform/vera.h +++ b/src/engine/platform/vera.h @@ -27,7 +27,7 @@ class DivPlatformVERA: public DivDispatch { protected: struct Channel { int freq, baseFreq, pitch, note; - unsigned char ins; + unsigned char ins, pan; bool active, freqChanged, inPorta; int vol, outVol; unsigned accum; @@ -39,10 +39,10 @@ class DivPlatformVERA: public DivDispatch { int out_l, out_r; unsigned pos; unsigned len; - unsigned char freq, pan; - PCMChannel(): sample(-1), out_l(0), out_r(0), pos(0), len(0), freq(0), pan(3) {} + unsigned char freq; + PCMChannel(): sample(-1), out_l(0), out_r(0), pos(0), len(0), freq(0) {} } pcm; - Channel(): freq(0), baseFreq(0), pitch(0), note(0), ins(-1), active(false), freqChanged(false), inPorta(false), vol(0), outVol(0), accum(0), noiseval(0) {} + Channel(): freq(0), baseFreq(0), pitch(0), note(0), ins(-1), pan(0), active(false), freqChanged(false), inPorta(false), vol(0), outVol(0), accum(0), noiseval(0) {} }; Channel chan[17]; bool isMuted[17]; From 6bb9843fb96b446f403e75564c85f13e91379f91 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Wed, 9 Mar 2022 18:06:07 +0700 Subject: [PATCH 08/12] Fix wrong noise sampling operation This really shouldn't have an effect on anything though... --- src/engine/platform/vera.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index 49255be80..3376b0a39 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -65,8 +65,8 @@ void DivPlatformVERA::acquire(short* bufL, short* bufR, size_t start, size_t len // TODO this is a currently speculated noise generation // as the hardware and sources for it are not out in the public // and the official emulator just uses rand() - noiseState=(noiseState<<1)|(((noiseState>>1)^(noiseState>>2)^(noiseState>>4)^(noiseState>>15))&1); noiseOut=((noiseOut<<1)|(noiseState&1))&63; + noiseState=(noiseState<<1)|(((noiseState>>1)^(noiseState>>2)^(noiseState>>4)^(noiseState>>15))&1); for (int i=0; i<16; i++) { unsigned freq=regPool[i*4+0] | (regPool[i*4+1] << 8); unsigned old_accum=chan[i].accum; From 587fecd11d41777bc5b7fe119e903eeb77787a00 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 10 Mar 2022 14:40:45 -0500 Subject: [PATCH 09/12] temporarily strip out emulation code --- src/engine/platform/vera.cpp | 80 ------------------------------------ 1 file changed, 80 deletions(-) diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index 3376b0a39..e58266dd6 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -55,86 +55,6 @@ const char* DivPlatformVERA::getEffectName(unsigned char effect) { } void DivPlatformVERA::acquire(short* bufL, short* bufR, size_t start, size_t len) { - // taken from the official X16 emulator's source code, (c) 2020 Frank van den Hoef - const uint8_t volume_lut_psg[64]={0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 7, 7, 7, 8, 8, 9, 9, 10, 11, 11, 12, 13, 14, 14, 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 28, 29, 31, 33, 35, 37, 39, 42, 44, 47, 50, 52, 56, 59, 63}; - const uint8_t volume_lut_pcm[16]={0, 1, 2, 3, 4, 5, 6, 8, 11, 14, 18, 23, 30, 38, 49, 64}; - for (size_t pos=start; pos>1)^(noiseState>>2)^(noiseState>>4)^(noiseState>>15))&1); - for (int i=0; i<16; i++) { - unsigned freq=regPool[i*4+0] | (regPool[i*4+1] << 8); - unsigned old_accum=chan[i].accum; - unsigned new_accum=old_accum+freq; - int val=0x20; - if ((old_accum^new_accum)&0x10000) chan[i].noiseval=noiseOut; - new_accum&=0x1ffff; - chan[i].accum=new_accum; - switch (regPool[i*4+3]>>6) { - case 0: val=(new_accum>>10)>(regPool[i*4+3]&(unsigned)0x3f)?0:63; break; - case 1: val=(new_accum>>11); break; - case 2: val=(new_accum&0x10000)?(~(new_accum>>10)&0x3f):((new_accum>>10)&0x3f); break; - case 3: val=chan[i].noiseval; break; - } - val=(val-0x20)*volume_lut_psg[regPool[i*4+2]&0x3f]; - lout+=(regPool[i*4+2]&0x40)?val:0; - rout+=(regPool[i*4+2]&0x80)?val:0; - } - // PCM - // simple one-channel sample player, actual hardware is essentially a DAC - // with buffering - if (chan[16].pcm.sample>=0) { - chan[16].accum+=regPool[65]; - if (chan[16].accum>=128) { - DivSample* s=parent->getSample(chan[16].pcm.sample); - if (s->samples>0) { - int tmp_l=0; - int tmp_r=0; - if (!isMuted[16]) { - // TODO stereo samples once DivSample has a support for it - switch (s->depth) { - case 8: - tmp_l=s->data8[chan[16].pcm.pos]*256; - tmp_r=tmp_l; - regPool[64]|=0x20; // for register viewer purposes - break; - case 16: - tmp_l=s->data16[chan[16].pcm.pos]; - tmp_r=tmp_l; - regPool[64]&=~0x20; - break; - } - if (!(chan[16].pan&1)) tmp_l=0; - if (!(chan[16].pan&2)) tmp_r=0; - } - chan[16].pcm.out_l=tmp_l; - chan[16].pcm.out_r=tmp_r; - } else { - chan[16].pcm.sample=-1; - } - chan[16].accum&=0x7f; - chan[16].pcm.pos++; - if (chan[16].pcm.pos>=s->samples) { - if (s->loopStart>=0 && s->loopStart<=(int)s->samples) { - chan[16].pcm.pos=s->loopStart; - } else { - chan[16].pcm.sample=-1; - } - } - } - } - int pcmvol=volume_lut_pcm[regPool[64]&0x0f]; - lout+=chan[16].pcm.out_l*pcmvol/64; - rout+=chan[16].pcm.out_r*pcmvol/64; - - bufL[pos]=(short)(lout/2); - bufR[pos]=(short)(rout/2); - } } void DivPlatformVERA::reset() { From 9bd15bd513fab980706383f00cab338c649388d6 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 10 Mar 2022 15:51:27 -0500 Subject: [PATCH 10/12] VERA: bring up actual emulation core --- CMakeLists.txt | 2 + src/engine/platform/sound/vera_pcm.c | 130 +++++++++++++++++++++++++++ src/engine/platform/sound/vera_pcm.h | 31 +++++++ src/engine/platform/sound/vera_psg.c | 98 ++++++++++++++++++++ src/engine/platform/sound/vera_psg.h | 27 ++++++ src/engine/platform/vera.cpp | 11 ++- src/engine/platform/vera.h | 7 +- 7 files changed, 303 insertions(+), 3 deletions(-) create mode 100644 src/engine/platform/sound/vera_pcm.c create mode 100644 src/engine/platform/sound/vera_pcm.h create mode 100644 src/engine/platform/sound/vera_psg.c create mode 100644 src/engine/platform/sound/vera_psg.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 487d8617c..e3dbdbbaa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -234,6 +234,8 @@ src/engine/platform/sound/gb/apu.c src/engine/platform/sound/gb/timing.c src/engine/platform/sound/pce_psg.cpp src/engine/platform/sound/nes/apu.c +src/engine/platform/sound/vera_psg.c +src/engine/platform/sound/vera_pcm.c src/engine/platform/sound/c64/sid.cc src/engine/platform/sound/c64/voice.cc diff --git a/src/engine/platform/sound/vera_pcm.c b/src/engine/platform/sound/vera_pcm.c new file mode 100644 index 000000000..2a95ff04e --- /dev/null +++ b/src/engine/platform/sound/vera_pcm.c @@ -0,0 +1,130 @@ +// Commander X16 Emulator +// Copyright (c) 2020 Frank van den Hoef +// All rights reserved. License: 2-clause BSD + +#include "vera_pcm.h" +#include + +static uint8_t volume_lut[16] = {0, 1, 2, 3, 4, 5, 6, 8, 11, 14, 18, 23, 30, 38, 49, 64}; + +static void +fifo_reset(struct VERA_PCM* pcm) +{ + pcm->fifo_wridx = 0; + pcm->fifo_rdidx = 0; + pcm->fifo_cnt = 0; +} + +void +pcm_reset(struct VERA_PCM* pcm) +{ + fifo_reset(pcm); + pcm->ctrl = 0; + pcm->rate = 0; + pcm->cur_l = 0; + pcm->cur_r = 0; + pcm->phase = 0; +} + +void +pcm_write_ctrl(struct VERA_PCM* pcm, uint8_t val) +{ + if (val & 0x80) { + fifo_reset(pcm); + } + + pcm->ctrl = val & 0x3F; +} + +uint8_t +pcm_read_ctrl(struct VERA_PCM* pcm) +{ + uint8_t result = pcm->ctrl; + if (pcm->fifo_cnt == sizeof(pcm->fifo)) { + result |= 0x80; + } + return result; +} + +void +pcm_write_rate(struct VERA_PCM* pcm, uint8_t val) +{ + pcm->rate = val; +} + +uint8_t +pcm_read_rate(struct VERA_PCM* pcm) +{ + return pcm->rate; +} + +void +pcm_write_fifo(struct VERA_PCM* pcm, uint8_t val) +{ + if (pcm->fifo_cnt < sizeof(pcm->fifo)) { + pcm->fifo[pcm->fifo_wridx++] = val; + if (pcm->fifo_wridx == sizeof(pcm->fifo)) { + pcm->fifo_wridx = 0; + } + pcm->fifo_cnt++; + } +} + +static uint8_t +read_fifo(struct VERA_PCM* pcm) +{ + if (pcm->fifo_cnt == 0) { + return 0; + } + uint8_t result = pcm->fifo[pcm->fifo_rdidx++]; + if (pcm->fifo_rdidx == sizeof(pcm->fifo)) { + pcm->fifo_rdidx = 0; + } + pcm->fifo_cnt--; + return result; +} + +bool +pcm_is_fifo_almost_empty(struct VERA_PCM* pcm) +{ + return pcm->fifo_cnt < 1024; +} + +void +pcm_render(struct VERA_PCM* pcm, int16_t* buf_l, int16_t* buf_r, unsigned num_samples) +{ + while (num_samples--) { + uint8_t old_phase = pcm->phase; + pcm->phase += pcm->rate; + if ((old_phase & 0x80) != (pcm->phase & 0x80)) { + switch ((pcm->ctrl >> 4) & 3) { + case 0: { // mono 8-bit + pcm->cur_l = (int16_t)read_fifo(pcm) << 8; + pcm->cur_r = pcm->cur_l; + break; + } + case 1: { // stereo 8-bit + pcm->cur_l = read_fifo(pcm) << 8; + pcm->cur_r = read_fifo(pcm) << 8; + break; + } + case 2: { // mono 16-bit + pcm->cur_l = read_fifo(pcm); + pcm->cur_l |= read_fifo(pcm) << 8; + pcm->cur_r = pcm->cur_l; + break; + } + case 3: { // stereo 16-bit + pcm->cur_l = read_fifo(pcm); + pcm->cur_l |= read_fifo(pcm) << 8; + pcm->cur_r = read_fifo(pcm); + pcm->cur_r |= read_fifo(pcm) << 8; + break; + } + } + } + + *(buf_l++) = ((int)pcm->cur_l * (int)volume_lut[pcm->ctrl & 0xF]) >> 6; + *(buf_r++) = ((int)pcm->cur_r * (int)volume_lut[pcm->ctrl & 0xF]) >> 6; + } +} diff --git a/src/engine/platform/sound/vera_pcm.h b/src/engine/platform/sound/vera_pcm.h new file mode 100644 index 000000000..d9b600d0b --- /dev/null +++ b/src/engine/platform/sound/vera_pcm.h @@ -0,0 +1,31 @@ +// Commander X16 Emulator +// Copyright (c) 2020 Frank van den Hoef +// All rights reserved. License: 2-clause BSD + +#pragma once + +#include +#include + +struct VERA_PCM { + uint8_t fifo[4096 - 1]; // Actual hardware FIFO is 4kB, but you can only use 4095 bytes. + unsigned fifo_wridx; + unsigned fifo_rdidx; + unsigned fifo_cnt; + + uint8_t ctrl; + uint8_t rate; + + int16_t cur_l, cur_r; + uint8_t phase; +}; + + +void pcm_reset(struct VERA_PCM* pcm); +void pcm_write_ctrl(struct VERA_PCM* pcm, uint8_t val); +uint8_t pcm_read_ctrl(struct VERA_PCM* pcm); +void pcm_write_rate(struct VERA_PCM* pcm, uint8_t val); +uint8_t pcm_read_rate(struct VERA_PCM* pcm); +void pcm_write_fifo(struct VERA_PCM* pcm, uint8_t val); +void pcm_render(struct VERA_PCM* pcm, int16_t* buf_l, int16_t* buf_r, unsigned num_samples); +bool pcm_is_fifo_almost_empty(struct VERA_PCM* pcm); diff --git a/src/engine/platform/sound/vera_psg.c b/src/engine/platform/sound/vera_psg.c new file mode 100644 index 000000000..92dac698c --- /dev/null +++ b/src/engine/platform/sound/vera_psg.c @@ -0,0 +1,98 @@ +// Commander X16 Emulator +// Copyright (c) 2020 Frank van den Hoef +// All rights reserved. License: 2-clause BSD + +#include "vera_psg.h" + +#include +#include + +enum waveform { + WF_PULSE = 0, + WF_SAWTOOTH, + WF_TRIANGLE, + WF_NOISE, +}; + +static uint8_t volume_lut[64] = {0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 7, 7, 7, 8, 8, 9, 9, 10, 11, 11, 12, 13, 14, 14, 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 28, 29, 31, 33, 35, 37, 39, 42, 44, 47, 50, 52, 56, 59, 63}; + +void +psg_reset(struct VERA_PSG* psg) +{ + memset(psg->channels, 0, sizeof(psg->channels)); +} + +void +psg_writereg(struct VERA_PSG* psg, uint8_t reg, uint8_t val) +{ + reg &= 0x3f; + + int ch = reg / 4; + int idx = reg & 3; + + switch (idx) { + case 0: psg->channels[ch].freq = (psg->channels[ch].freq & 0xFF00) | val; break; + case 1: psg->channels[ch].freq = (psg->channels[ch].freq & 0x00FF) | (val << 8); break; + case 2: { + psg->channels[ch].right = (val & 0x80) != 0; + psg->channels[ch].left = (val & 0x40) != 0; + psg->channels[ch].volume = volume_lut[val & 0x3F]; + break; + } + case 3: { + psg->channels[ch].pw = val & 0x3F; + psg->channels[ch].waveform = val >> 6; + break; + } + } +} + +static inline void +render(struct VERA_PSG* psg, int16_t *left, int16_t *right) +{ + int l = 0; + int r = 0; + + for (int i = 0; i < 16; i++) { + struct VERAChannel *ch = &psg->channels[i]; + + unsigned new_phase = (ch->phase + ch->freq) & 0x1FFFF; + if ((ch->phase & 0x10000) != (new_phase & 0x10000)) { + ch->noiseval = rand() & 63; + } + ch->phase = new_phase; + + uint8_t v = 0; + switch (ch->waveform) { + case WF_PULSE: v = (ch->phase >> 10) > ch->pw ? 0 : 63; break; + case WF_SAWTOOTH: v = ch->phase >> 11; break; + case WF_TRIANGLE: v = (ch->phase & 0x10000) ? (~(ch->phase >> 10) & 0x3F) : ((ch->phase >> 10) & 0x3F); break; + case WF_NOISE: v = ch->noiseval; break; + } + int8_t sv = (v ^ 0x20); + if (sv & 0x20) { + sv |= 0xC0; + } + + int val = (int)sv * (int)ch->volume; + + if (ch->left) { + l += val; + } + if (ch->right) { + r += val; + } + } + + *left = l; + *right = r; +} + +void +psg_render(struct VERA_PSG* psg, int16_t *buf, unsigned num_samples) +{ + while (num_samples--) { + render(psg, &buf[0], &buf[1]); + buf += 2; + } +} diff --git a/src/engine/platform/sound/vera_psg.h b/src/engine/platform/sound/vera_psg.h new file mode 100644 index 000000000..9f94bb578 --- /dev/null +++ b/src/engine/platform/sound/vera_psg.h @@ -0,0 +1,27 @@ +// Commander X16 Emulator +// Copyright (c) 2020 Frank van den Hoef +// All rights reserved. License: 2-clause BSD + +#pragma once + +#include +#include + +struct VERAChannel { + uint16_t freq; + uint8_t volume; + bool left, right; + uint8_t pw; + uint8_t waveform; + + unsigned phase; + uint8_t noiseval; +}; + +struct VERA_PSG { + struct VERAChannel channels[16]; +}; + +void psg_reset(struct VERA_PSG* psg); +void psg_writereg(struct VERA_PSG* psg, uint8_t reg, uint8_t val); +void psg_render(struct VERA_PSG* psg, int16_t *buf, unsigned num_samples); diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index e58266dd6..99c6517ea 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -22,6 +22,9 @@ #include #include +#include "sound/vera_psg.h" +#include "sound/vera_pcm.h" + #define rWrite(c,a,d) {regPool[(c)*4+(a)]=(d);} #define rWriteLo(c,a,d) rWrite(c,a,(regPool[(c)*4+(a)]&(~0x3f))|((d)&0x3f)) #define rWriteHi(c,a,d) rWrite(c,a,(regPool[(c)*4+(a)]&(~0xc0))|(((d)<<6)&0xc0)) @@ -69,8 +72,6 @@ void DivPlatformVERA::reset() { } chan[16].vol=15; chan[16].pan=3; - noiseState=1; - noiseOut=0; } int DivPlatformVERA::calcNoteFreq(int ch, int note) { @@ -321,6 +322,8 @@ int DivPlatformVERA::init(DivEngine* p, int channels, int sugRate, unsigned int isMuted[i]=false; } parent=p; + psg=new struct VERA_PSG; + pcm=new struct VERA_PCM; dumpWrites=false; skipRegisterWrites=false; chipClock=25000000; @@ -329,5 +332,9 @@ int DivPlatformVERA::init(DivEngine* p, int channels, int sugRate, unsigned int return 17; } +void DivPlatformVERA::quit() { + delete psg; + delete pcm; +} DivPlatformVERA::~DivPlatformVERA() { } diff --git a/src/engine/platform/vera.h b/src/engine/platform/vera.h index e5cb2ad9e..e1369301b 100644 --- a/src/engine/platform/vera.h +++ b/src/engine/platform/vera.h @@ -23,6 +23,9 @@ #include "../instrument.h" #include "../macroInt.h" +struct VERA_PSG; +struct VERA_PCM; + class DivPlatformVERA: public DivDispatch { protected: struct Channel { @@ -46,8 +49,9 @@ class DivPlatformVERA: public DivDispatch { }; Channel chan[17]; bool isMuted[17]; - unsigned noiseState, noiseOut; unsigned char regPool[66]; + struct VERA_PSG* psg; + struct VERA_PCM* pcm; int calcNoteFreq(int ch, int note); friend void putDispatchChan(void*,int,int); @@ -68,6 +72,7 @@ class DivPlatformVERA: public DivDispatch { const char** getRegisterSheet(); const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + void quit(); ~DivPlatformVERA(); }; #endif From 2f02e24a2f9185e001dd5531ab44edf5e1182f8a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 10 Mar 2022 16:52:59 -0500 Subject: [PATCH 11/12] VERA: get rid of rand() and adapt code --- src/engine/platform/sound/vera_psg.c | 16 ++++++++++++---- src/engine/platform/sound/vera_psg.h | 3 ++- src/engine/platform/vera.cpp | 9 ++++++--- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/engine/platform/sound/vera_psg.c b/src/engine/platform/sound/vera_psg.c index 92dac698c..f61bd26a2 100644 --- a/src/engine/platform/sound/vera_psg.c +++ b/src/engine/platform/sound/vera_psg.c @@ -20,6 +20,8 @@ void psg_reset(struct VERA_PSG* psg) { memset(psg->channels, 0, sizeof(psg->channels)); + psg->noiseState=1; + psg->noiseOut=0; } void @@ -52,13 +54,18 @@ render(struct VERA_PSG* psg, int16_t *left, int16_t *right) { int l = 0; int r = 0; + // TODO this is a currently speculated noise generation + // as the hardware and sources for it are not out in the public + // and the official emulator just uses rand() + psg->noiseOut=((psg->noiseOut<<1)|(psg->noiseState&1))&63; + psg->noiseState=(psg->noiseState<<1)|(((psg->noiseState>>1)^(psg->noiseState>>2)^(psg->noiseState>>4)^(psg->noiseState>>15))&1); for (int i = 0; i < 16; i++) { struct VERAChannel *ch = &psg->channels[i]; unsigned new_phase = (ch->phase + ch->freq) & 0x1FFFF; if ((ch->phase & 0x10000) != (new_phase & 0x10000)) { - ch->noiseval = rand() & 63; + ch->noiseval = psg->noiseOut; } ch->phase = new_phase; @@ -89,10 +96,11 @@ render(struct VERA_PSG* psg, int16_t *left, int16_t *right) } void -psg_render(struct VERA_PSG* psg, int16_t *buf, unsigned num_samples) +psg_render(struct VERA_PSG* psg, int16_t *bufL, int16_t *bufR, unsigned num_samples) { while (num_samples--) { - render(psg, &buf[0], &buf[1]); - buf += 2; + render(psg, bufL, bufR); + bufL++; + bufR++; } } diff --git a/src/engine/platform/sound/vera_psg.h b/src/engine/platform/sound/vera_psg.h index 9f94bb578..7a6a7f01d 100644 --- a/src/engine/platform/sound/vera_psg.h +++ b/src/engine/platform/sound/vera_psg.h @@ -19,9 +19,10 @@ struct VERAChannel { }; struct VERA_PSG { + unsigned int noiseState, noiseOut; struct VERAChannel channels[16]; }; void psg_reset(struct VERA_PSG* psg); void psg_writereg(struct VERA_PSG* psg, uint8_t reg, uint8_t val); -void psg_render(struct VERA_PSG* psg, int16_t *buf, unsigned num_samples); +void psg_render(struct VERA_PSG* psg, int16_t *bufL, int16_t *bufR, unsigned num_samples); diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index 99c6517ea..f6fc22f70 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -22,10 +22,12 @@ #include #include -#include "sound/vera_psg.h" -#include "sound/vera_pcm.h" +extern "C" { + #include "sound/vera_psg.h" + #include "sound/vera_pcm.h" +} -#define rWrite(c,a,d) {regPool[(c)*4+(a)]=(d);} +#define rWrite(c,a,d) {regPool[(c)*4+(a)]=(d); psg_writereg(psg,((c)*4+(a)),(d));} #define rWriteLo(c,a,d) rWrite(c,a,(regPool[(c)*4+(a)]&(~0x3f))|((d)&0x3f)) #define rWriteHi(c,a,d) rWrite(c,a,(regPool[(c)*4+(a)]&(~0xc0))|(((d)<<6)&0xc0)) #define rWriteFIFOVol(d) rWrite(16,0,(regPool[64]&(~0x3f))|((d)&0x3f)) @@ -58,6 +60,7 @@ const char* DivPlatformVERA::getEffectName(unsigned char effect) { } void DivPlatformVERA::acquire(short* bufL, short* bufR, size_t start, size_t len) { + psg_render(psg,bufL+start,bufR+start,len); } void DivPlatformVERA::reset() { From 6d9befaf278a8d7aa3e02222b0e6a9009ae56a27 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 10 Mar 2022 17:30:34 -0500 Subject: [PATCH 12/12] yay --- src/engine/platform/sound/vera_pcm.c | 7 +++++-- src/engine/platform/vera.cpp | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/sound/vera_pcm.c b/src/engine/platform/sound/vera_pcm.c index 2a95ff04e..857ba46e2 100644 --- a/src/engine/platform/sound/vera_pcm.c +++ b/src/engine/platform/sound/vera_pcm.c @@ -124,7 +124,10 @@ pcm_render(struct VERA_PCM* pcm, int16_t* buf_l, int16_t* buf_r, unsigned num_sa } } - *(buf_l++) = ((int)pcm->cur_l * (int)volume_lut[pcm->ctrl & 0xF]) >> 6; - *(buf_r++) = ((int)pcm->cur_r * (int)volume_lut[pcm->ctrl & 0xF]) >> 6; + *(buf_l) += ((int)pcm->cur_l * (int)volume_lut[pcm->ctrl & 0xF]) >> 6; + *(buf_r) += ((int)pcm->cur_r * (int)volume_lut[pcm->ctrl & 0xF]) >> 6; + + buf_l++; + buf_r++; } } diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index f6fc22f70..3546ac1fc 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -59,8 +59,10 @@ const char* DivPlatformVERA::getEffectName(unsigned char effect) { return NULL; } +// TODO: wire up PCM. void DivPlatformVERA::acquire(short* bufL, short* bufR, size_t start, size_t len) { psg_render(psg,bufL+start,bufR+start,len); + pcm_render(pcm,bufL+start,bufR+start,len); } void DivPlatformVERA::reset() {