diff --git a/CMakeLists.txt b/CMakeLists.txt index 2490246a3..f12a98629 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -606,6 +606,8 @@ src/engine/platform/sound/ted-sound.c src/engine/platform/sound/c140_c219.c +src/engine/platform/sound/dave/dave.cpp + src/engine/platform/oplAInterface.cpp src/engine/platform/ym2608Interface.cpp src/engine/platform/ym2610Interface.cpp diff --git a/papers/format.md b/papers/format.md index 149c49869..43a4dd78d 100644 --- a/papers/format.md +++ b/papers/format.md @@ -238,6 +238,7 @@ size | description | - 0xd1: ESFM - 18 channels | - 0xd2: Ensoniq ES5503 (hard pan) - 32 channels (UNAVAILABLE) | - 0xd4: PowerNoise - 4 channels + | - 0xd5: Dave - 6 channels (UNAVAILABLE) | - 0xde: YM2610B extended - 19 channels | - 0xe0: QSound - 19 channels | - 0xfc: Pong - 1 channel diff --git a/src/engine/platform/dave.cpp b/src/engine/platform/dave.cpp new file mode 100644 index 000000000..c98469b36 --- /dev/null +++ b/src/engine/platform/dave.cpp @@ -0,0 +1,631 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2024 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 "dave.h" +#include "../engine.h" +#include "furIcons.h" +#include + +//#define rWrite(a,v) pendingWrites[a]=v; +#define rWrite(a,v) if (!skipRegisterWrites) {writes.push(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);} } +#define chWrite(c,a,v) \ + if (!skipRegisterWrites) { \ + if (curChan!=c) { \ + curChan=c; \ + rWrite(0,curChan); \ + } \ + regPool[16+((c)<<4)+((a)&0x0f)]=v; \ + rWrite(a,v); \ + } + +#define CHIP_DIVIDER 32 + +const char* regCheatSheetDave[]={ + "Freq0", "00", + "Control0", "01", + "Freq1", "02", + "Control1", "03", + "Freq2", "04", + "Control2", "05", + "Control3", "06", + "SoundCtrl", "07", + "Vol0L", "08", + "Vol1L", "09", + "Vol2L", "0A", + "Vol3L", "0B", + "Vol0R", "0C", + "Vol1R", "0D", + "Vol2R", "0E", + "Vol3R", "0F", + "ClockDiv", "1F", + NULL +}; + +const char** DivPlatformDave::getRegisterSheet() { + return regCheatSheetDave; +} + +void DivPlatformDave::acquire(short** buf, size_t len) { + for (size_t h=0; hrate) { + DivSample* s=parent->getSample(chan[i].dacSample); + if (s->samples<=0) { + chan[i].dacSample=-1; + continue; + } + chWrite(i,0x07,0); + signed char dacData=((signed char)((unsigned char)s->data8[chan[i].dacPos]^0x80))>>3; + chan[i].dacOut=CLAMP(dacData,-16,15); + if (!isMuted[i]) { + chWrite(i,0x04,parent->song.disableSampleMacro?0xdf:(0xc0|chan[i].outVol)); + chWrite(i,0x06,chan[i].dacOut&0x1f); + } else { + chWrite(i,0x04,0xc0); + chWrite(i,0x06,0x10); + } + chan[i].dacPos++; + if (s->isLoopable() && chan[i].dacPos>=(unsigned int)s->loopEnd) { + chan[i].dacPos=s->loopStart; + } else if (chan[i].dacPos>=s->samples) { + chan[i].dacSample=-1; + } + chan[i].dacPeriod-=rate; + } + } + } + + // PCE part + cycles=0; + while (!writes.empty() && cycles<24) { + QueuedWrite w=writes.front(); + pce->Write(cycles,w.addr,w.val); + regPool[w.addr&0x0f]=w.val; + //cycles+=2; + writes.pop(); + } + memset(tempL,0,24*sizeof(int)); + memset(tempR,0,24*sizeof(int)); + pce->Update(24); + pce->ResetTS(0); + + for (int i=0; i<6; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(pce->channel[i].blip_prev_samp[0]+pce->channel[i].blip_prev_samp[1],-32768,32767); + } + + tempL[0]=(tempL[0]>>1)+(tempL[0]>>2); + tempR[0]=(tempR[0]>>1)+(tempR[0]>>2); + + if (tempL[0]<-32768) tempL[0]=-32768; + if (tempL[0]>32767) tempL[0]=32767; + if (tempR[0]<-32768) tempR[0]=-32768; + if (tempR[0]>32767) tempR[0]=32767; + + //printf("tempL: %d tempR: %d\n",tempL,tempR); + buf[0][h]=tempL[0]; + buf[1][h]=tempR[0]; + } +} + +// TODO: in octave 6 the noise table changes to a tonal one +static unsigned char noiseFreq[12]={ + 4,13,15,18,21,23,25,27,29,31,0,2 +}; + +void DivPlatformDave::tick(bool sysTick) { + for (int i=0; i<6; i++) { + chan[i].std.next(); + if (chan[i].std.vol.had) { + chan[i].outVol=VOL_SCALE_LOG_BROKEN(chan[i].vol&31,MIN(31,chan[i].std.vol.val),31); + if (chan[i].furnaceDac && chan[i].pcm) { + // ignore for now + } else { + chWrite(i,0x04,0x80|chan[i].outVol); + } + } + if (chan[i].std.duty.had && i>=4) { + chan[i].noise=chan[i].std.duty.val; + chan[i].freqChanged=true; + } + if (NEW_ARP_STRAT) { + chan[i].handleArp(); + } else if (chan[i].std.arp.had) { + if (!chan[i].inPorta) { + int noiseSeek=parent->calcArp(chan[i].note,chan[i].std.arp.val); + chan[i].baseFreq=NOTE_PERIODIC(noiseSeek); + if (noiseSeek<0) noiseSeek=0; + chan[i].noiseSeek=noiseSeek; + } + chan[i].freqChanged=true; + } + if (chan[i].std.wave.had && !chan[i].pcm) { + // TODO: this + } + if (chan[i].std.panL.had) { + chan[i].pan&=0x0f; + chan[i].pan|=(chan[i].std.panL.val&15)<<4; + } + if (chan[i].std.panR.had) { + chan[i].pan&=0xf0; + chan[i].pan|=chan[i].std.panR.val&15; + } + if (chan[i].std.panL.had || chan[i].std.panR.had) { + chWrite(i,0x05,isMuted[i]?0:chan[i].pan); + } + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-32768,32767); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } + if (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1) { + if (chan[i].furnaceDac && chan[i].pcm) { + if (chan[i].active && chan[i].dacSample>=0 && chan[i].dacSamplesong.sampleLen) { + chan[i].dacPos=0; + chan[i].dacPeriod=0; + chWrite(i,0x04,parent->song.disableSampleMacro?0xdf:(0xc0|chan[i].vol)); + addWrite(0xffff0000+(i<<8),chan[i].dacSample); + chan[i].keyOn=true; + } + } + } + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { + //DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_PCE); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); + if (chan[i].furnaceDac && chan[i].pcm) { + double off=1.0; + if (chan[i].dacSample>=0 && chan[i].dacSamplesong.sampleLen) { + DivSample* s=parent->getSample(chan[i].dacSample); + if (s->centerRate<1) { + off=1.0; + } else { + off=8363.0/(double)s->centerRate; + } + } + chan[i].dacRate=((double)chipClock/2)/MAX(1,off*chan[i].freq); + if (dumpWrites) addWrite(0xffff0001+(i<<8),chan[i].dacRate); + } + if (chan[i].freq<1) chan[i].freq=1; + if (chan[i].freq>4095) chan[i].freq=4095; + chWrite(i,0x02,chan[i].freq&0xff); + chWrite(i,0x03,chan[i].freq>>8); + + if (i>=4) { + int noiseSeek=(chan[i].fixedArp?chan[i].baseNoteOverride:(chan[i].note+chan[i].arpOff))+chan[i].pitch2; + if (!parent->song.properNoiseLayout && noiseSeek<0) noiseSeek=0; + if (!NEW_ARP_STRAT) { + noiseSeek=chan[i].noiseSeek; + } + chWrite(i,0x07,chan[i].noise?(0x80|(parent->song.properNoiseLayout?(noiseSeek&31):noiseFreq[noiseSeek%12])):0); + } + if (chan[i].keyOn) { + //rWrite(16+i*5,0x80); + //chWrite(i,0x04,0x80|chan[i].vol); + } + if (chan[i].keyOff) { + chWrite(i,0x04,0); + } + if (chan[i].keyOn) chan[i].keyOn=false; + if (chan[i].keyOff) chan[i].keyOff=false; + chan[i].freqChanged=false; + } + } + if (updateLFO) { + rWrite(0x08,lfoSpeed); + rWrite(0x09,lfoMode); + updateLFO=false; + } +} + +int DivPlatformDave::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_PCE); + chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:31; + if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) { + chan[c.chan].pcm=true; + } else if (chan[c.chan].furnaceDac) { + chan[c.chan].pcm=false; + chan[c.chan].sampleNote=DIV_NOTE_NULL; + chan[c.chan].sampleNoteDelta=0; + } + if (chan[c.chan].pcm) { + if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) { + chan[c.chan].furnaceDac=true; + if (skipRegisterWrites) break; + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].dacSample=ins->amiga.getSample(c.value); + chan[c.chan].sampleNote=c.value; + c.value=ins->amiga.getFreq(c.value); + chan[c.chan].sampleNoteDelta=c.value-chan[c.chan].sampleNote; + } else if (chan[c.chan].sampleNote!=DIV_NOTE_NULL) { + chan[c.chan].dacSample=ins->amiga.getSample(chan[c.chan].sampleNote); + c.value=ins->amiga.getFreq(chan[c.chan].sampleNote); + } + if (chan[c.chan].dacSample<0 || chan[c.chan].dacSample>=parent->song.sampleLen) { + chan[c.chan].dacSample=-1; + if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); + break; + } else { + if (dumpWrites) { + chWrite(c.chan,0x04,parent->song.disableSampleMacro?0xdf:(0xc0|chan[c.chan].vol)); + addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dacSample); + } + } + chan[c.chan].dacPos=0; + chan[c.chan].dacPeriod=0; + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].active=true; + chan[c.chan].macroInit(ins); + if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + chan[c.chan].outVol=chan[c.chan].vol; + } + //chan[c.chan].keyOn=true; + } else { + chan[c.chan].furnaceDac=false; + chan[c.chan].sampleNote=DIV_NOTE_NULL; + chan[c.chan].sampleNoteDelta=0; + if (skipRegisterWrites) break; + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].note=c.value; + } + chan[c.chan].dacSample=12*sampleBank+chan[c.chan].note%12; + if (chan[c.chan].dacSample>=parent->song.sampleLen) { + chan[c.chan].dacSample=-1; + if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); + break; + } else { + if (dumpWrites) addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dacSample); + } + chan[c.chan].dacPos=0; + chan[c.chan].dacPeriod=0; + chan[c.chan].dacRate=parent->getSample(chan[c.chan].dacSample)->rate; + if (dumpWrites) { + chWrite(c.chan,0x04,parent->song.disableSampleMacro?0xdf:(0xc0|chan[c.chan].vol)); + addWrite(0xffff0001+(c.chan<<8),chan[c.chan].dacRate); + } + } + break; + } + chan[c.chan].sampleNote=DIV_NOTE_NULL; + chan[c.chan].sampleNoteDelta=0; + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + chan[c.chan].noiseSeek=c.value; + if (chan[c.chan].noiseSeek<0) chan[c.chan].noiseSeek=0; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + chWrite(c.chan,0x04,0x80|chan[c.chan].vol); + chan[c.chan].macroInit(ins); + if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + chan[c.chan].outVol=chan[c.chan].vol; + } + chan[c.chan].insChanged=false; + break; + } + case DIV_CMD_NOTE_OFF: + chan[c.chan].dacSample=-1; + if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); + chan[c.chan].pcm=false; + chan[c.chan].active=false; + chan[c.chan].keyOff=true; + chan[c.chan].macroInit(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value || c.value2==1) { + chan[c.chan].ins=c.value; + chan[c.chan].insChanged=true; + } + break; + case DIV_CMD_VOLUME: + if (chan[c.chan].vol!=c.value) { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.vol.has) { + chan[c.chan].outVol=c.value; + if (chan[c.chan].active && !chan[c.chan].pcm) { + chWrite(c.chan,0x04,0x80|chan[c.chan].outVol); + } + } + } + break; + case DIV_CMD_GET_VOLUME: + if (chan[c.chan].std.vol.has) { + return chan[c.chan].vol; + } + return chan[c.chan].outVol; + break; + case DIV_CMD_PITCH: + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_WAVE: + chan[c.chan].wave=c.value; + chan[c.chan].keyOn=true; + break; + case DIV_CMD_PCE_LFO_MODE: + if (c.value==0) { + lfoMode=0; + } else { + lfoMode=c.value; + } + updateLFO=true; + break; + case DIV_CMD_PCE_LFO_SPEED: + lfoSpeed=255-c.value; + updateLFO=true; + break; + case DIV_CMD_NOTE_PORTA: { + int destFreq=NOTE_PERIODIC(c.value2+chan[c.chan].sampleNoteDelta); + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value; + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value; + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_STD_NOISE_MODE: + chan[c.chan].noise=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_SAMPLE_MODE: + chan[c.chan].pcm=c.value; + break; + case DIV_CMD_SAMPLE_BANK: + sampleBank=c.value; + if (sampleBank>(parent->song.sample.size()/12)) { + sampleBank=parent->song.sample.size()/12; + } + break; + case DIV_CMD_PANNING: { + chan[c.chan].pan=(c.value&0xf0)|(c.value2>>4); + chWrite(c.chan,0x05,isMuted[c.chan]?0:chan[c.chan].pan); + break; + } + case DIV_CMD_LEGATO: + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+chan[c.chan].sampleNoteDelta+((HACKY_LEGATO_MESS)?(chan[c.chan].std.arp.val):(0))); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + break; + case DIV_CMD_PRE_PORTA: + if (chan[c.chan].active && c.value2) { + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_PCE)); + } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_GET_VOLMAX: + return 31; + break; + case DIV_CMD_MACRO_OFF: + chan[c.chan].std.mask(c.value,true); + break; + case DIV_CMD_MACRO_ON: + chan[c.chan].std.mask(c.value,false); + break; + case DIV_CMD_MACRO_RESTART: + chan[c.chan].std.restart(c.value); + break; + default: + break; + } + return 1; +} + +void DivPlatformDave::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + chWrite(ch,0x05,isMuted[ch]?0:chan[ch].pan); + if (!isMuted[ch] && (chan[ch].pcm && chan[ch].dacSample!=-1)) { + chWrite(ch,0x04,parent->song.disableSampleMacro?0xdf:(0xc0|chan[ch].outVol)); + chWrite(ch,0x06,chan[ch].dacOut&0x1f); + } +} + +void DivPlatformDave::forceIns() { + for (int i=0; i<6; i++) { + chan[i].insChanged=true; + chan[i].freqChanged=true; + updateWave(i); + chWrite(i,0x05,isMuted[i]?0:chan[i].pan); + } +} + +void* DivPlatformDave::getChanState(int ch) { + return &chan[ch]; +} + +DivMacroInt* DivPlatformDave::getChanMacroInt(int ch) { + return &chan[ch].std; +} + +unsigned short DivPlatformDave::getPan(int ch) { + return ((chan[ch].pan&0xf0)<<4)|(chan[ch].pan&15); +} + +DivChannelPair DivPlatformDave::getPaired(int ch) { + if (ch==1 && lfoMode>0) { + return DivChannelPair("mod",0); + } + return DivChannelPair(); +} + +DivChannelModeHints DivPlatformDave::getModeHints(int ch) { + DivChannelModeHints ret; + if (ch<4) return ret; + ret.count=1; + ret.hint[0]=ICON_FUR_NOISE; + ret.type[0]=0; + + if (chan[ch].noise) ret.type[0]=4; + + return ret; +} + +DivSamplePos DivPlatformDave::getSamplePos(int ch) { + if (ch>=6) return DivSamplePos(); + if (!chan[ch].pcm) return DivSamplePos(); + return DivSamplePos( + chan[ch].dacSample, + chan[ch].dacPos, + chan[ch].dacRate + ); +} + +DivDispatchOscBuffer* DivPlatformDave::getOscBuffer(int ch) { + return oscBuf[ch]; +} + +int DivPlatformDave::mapVelocity(int ch, float vel) { + return round(31.0*pow(vel,0.22)); +} + +unsigned char* DivPlatformDave::getRegisterPool() { + return regPool; +} + +int DivPlatformDave::getRegisterPoolSize() { + return 112; +} + +void DivPlatformDave::reset() { + writes.clear(); + memset(regPool,0,128); + for (int i=0; i<6; i++) { + chan[i]=DivPlatformDave::Channel(); + chan[i].std.setEngine(parent); + } + if (dumpWrites) { + addWrite(0xffffffff,0); + } + pce->Power(0); + lastPan=0xff; + memset(tempL,0,32*sizeof(int)); + memset(tempR,0,32*sizeof(int)); + cycles=0; + curChan=-1; + sampleBank=0; + lfoMode=0; + lfoSpeed=255; + // set global volume + rWrite(0,0); + rWrite(0x01,0xff); + // set LFO + updateLFO=true; + // set per-channel initial panning + for (int i=0; i<6; i++) { + chWrite(i,0x05,isMuted[i]?0:chan[i].pan); + } + delay=500; +} + +int DivPlatformDave::getOutputCount() { + return 2; +} + +bool DivPlatformDave::keyOffAffectsArp(int ch) { + return true; +} + +void DivPlatformDave::notifyInsDeletion(void* ins) { + for (int i=0; i<6; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformDave::setFlags(const DivConfig& flags) { + chipClock=8000000.0; + CHECK_CUSTOM_CLOCK; + antiClickEnabled=!flags.getBool("noAntiClick",false); + rate=chipClock/12; + for (int i=0; i<6; i++) { + oscBuf[i]->rate=rate; + } + + if (pce!=NULL) { + delete pce; + pce=NULL; + } + pce=new PCE_PSG(tempL,tempR,flags.getInt("chipType",0)?PCE_PSG::REVISION_HUC6280A:PCE_PSG::REVISION_HUC6280); +} + +void DivPlatformDave::poke(unsigned int addr, unsigned short val) { + rWrite(addr,val); +} + +void DivPlatformDave::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); +} + +int DivPlatformDave::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + updateLFO=false; + for (int i=0; i<6; i++) { + isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; + } + pce=NULL; + setFlags(flags); + reset(); + return 6; +} + +void DivPlatformDave::quit() { + for (int i=0; i<6; i++) { + delete oscBuf[i]; + } + if (pce!=NULL) { + delete pce; + pce=NULL; + } +} + +DivPlatformDave::~DivPlatformDave() { +} diff --git a/src/engine/platform/dave.h b/src/engine/platform/dave.h new file mode 100644 index 000000000..e61d5ab4d --- /dev/null +++ b/src/engine/platform/dave.h @@ -0,0 +1,103 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2024 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 _PCE_H +#define _PCE_H + +#include "../dispatch.h" +#include "../../fixedQueue.h" +#include "sound/dave/dave.hpp" + +class DivPlatformDave: public DivDispatch { + struct Channel: public SharedChannel { + int dacPeriod, dacRate, dacOut; + unsigned int dacPos; + int dacSample; + unsigned char pan; + bool noise, pcm, furnaceDac, deferredWaveUpdate; + signed short wave; + int macroVolMul, noiseSeek; + Channel(): + SharedChannel(31), + dacPeriod(0), + dacRate(0), + dacOut(0), + dacPos(0), + dacSample(-1), + pan(255), + noise(false), + pcm(false), + furnaceDac(false), + deferredWaveUpdate(false), + wave(-1), + macroVolMul(31), + noiseSeek(0) {} + }; + Channel chan[6]; + DivDispatchOscBuffer* oscBuf[6]; + bool isMuted[6]; + bool antiClickEnabled; + bool updateLFO; + struct QueuedWrite { + unsigned char addr; + unsigned char val; + QueuedWrite(): addr(0), val(9) {} + QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {} + }; + FixedQueue writes; + unsigned char lastPan; + + int cycles, curChan, delay; + int tempL[32]; + int tempR[32]; + unsigned char sampleBank, lfoMode, lfoSpeed; + Ep128::Dave* dave; + unsigned char regPool[128]; + friend void putDispatchChip(void*,int); + friend void putDispatchChan(void*,int,int); + public: + void acquire(short** buf, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); + unsigned short getPan(int chan); + DivChannelPair getPaired(int chan); + DivChannelModeHints getModeHints(int chan); + DivSamplePos getSamplePos(int ch); + DivDispatchOscBuffer* getOscBuffer(int chan); + int mapVelocity(int ch, float vel); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void forceIns(); + void tick(bool sysTick=true); + void muteChannel(int ch, bool mute); + int getOutputCount(); + bool keyOffAffectsArp(int ch); + void setFlags(const DivConfig& flags); + void notifyInsDeletion(void* ins); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); + void quit(); + ~DivPlatformDave(); +}; + +#endif diff --git a/src/engine/platform/sound/dave/dave.cpp b/src/engine/platform/sound/dave/dave.cpp new file mode 100644 index 000000000..59d2f5f20 --- /dev/null +++ b/src/engine/platform/sound/dave/dave.cpp @@ -0,0 +1,813 @@ + +// ep128emu -- portable Enterprise 128 emulator +// Copyright (C) 2003-2016 Istvan Varga +// https://github.com/istvan-v/ep128emu/ +// +// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +#include "dave.hpp" +#include + +#define EP128EMU_UNLIKELY(x) x + +// Generate polynomial counter of log2(m0) + 1 bits length, and store +// (m0 * 2 - 1) samples at 'tabptr' in reverse order. +// log2(m1) is the second bit to be used in the XOR operation when +// calculating the next bit of output. + +static void calculate_polycnt(uint8_t *tabptr, uint32_t m0, uint32_t m1) +{ + uint32_t sr = 0xFFFFFFFFU; + int n = int(m0 << 1) - 1; + while (--n >= 0) { + uint8_t b0 = uint8_t(bool(sr & m0)); + uint8_t b1 = uint8_t(bool(sr & m1)); + tabptr[n] = b0; + sr = (sr << 1) | uint32_t(b0 ^ b1); + } +} + +namespace Ep128 { + DaveTables::DaveTables() + { + static const uint32_t polycnt_params[14] = { + 0x00000008U, 0x00000004U, // 4-bit: poly = 11001 + 0x00000010U, 0x00000004U, // 5-bit: poly = 101001 + 0x00000040U, 0x00000020U, // 7-bit: poly = 11000001 + 0x00000100U, 0x00000010U, // 9-bit: poly = 1000100001 + 0x00000400U, 0x00000100U, // 11-bit: poly = 101000000001 + 0x00004000U, 0x00002000U, // 15-bit: poly = 1100000000000001 + 0x00010000U, 0x00002000U // 17-bit: poly = 100100000000000001 + }; + polycnt4_table = new uint8_t[15 + 31 + 127 + 511 + 2047 + 32767 + 131071]; + polycnt5_table = &(polycnt4_table[15]); + polycnt7_table = &(polycnt5_table[31]); + polycnt9_table = &(polycnt7_table[127]); + polycnt11_table = &(polycnt9_table[511]); + polycnt15_table = &(polycnt11_table[2047]); + polycnt17_table = &(polycnt15_table[32767]); + uint8_t *bufp = polycnt4_table; + for (int i = 0; i < 14; i += 2) { + calculate_polycnt(bufp, polycnt_params[i], polycnt_params[i + 1]); + bufp = bufp + ((polycnt_params[i] << 1) - 1U); + } + } + + DaveTables::~DaveTables() + { + delete[] polycnt4_table; + } + + // handle timer interrupts + + inline void Dave::triggerIntSnd() + { + // trigger interrupt on edge only + if (int_snd_active) + return; + // mark as active + int_snd_active = 1; + interruptRequest(); + } + + inline void Dave::triggerInt1Hz() + { + // trigger interrupt on edge only + if (int_1hz_active) + return; + // mark as active + int_1hz_active = 1; + interruptRequest(); + } + + // run DAVE emulation, and also trigger any sound or timer interrupts + + uint32_t Dave::runOneCycle_() + { + // update polynomial counters + if (--polycnt4_phase < 0) // 4-bit + polycnt4_phase = 14; + polycnt4_state = (int) t.polycnt4_table[polycnt4_phase]; + if (--polycnt5_phase < 0) // 5-bit + polycnt5_phase = 30; + polycnt5_state = (int) t.polycnt5_table[polycnt5_phase]; + if (!noise_polycnt_is_7bit) { + // channel 3 uses the variable length polynomial counter + if (--polycnt7_phase < 0) // 7-bit + polycnt7_phase = 126; + polycnt7_state = (int) t.polycnt7_table[polycnt7_phase]; + // channel 3 polynomial counter: updated on negative edge + if (*chn3_clk_source < chn3_clk_source_prv) { + if (--polycntVL_phase < 0) // variable length + polycntVL_phase = polycntVL_maxphase; + polycntVL_state = (int) polycntVL_table[polycntVL_phase]; + chn3_state1 = polycntVL_state; // input signal to channel 3 + if (!chn3_lp_2) + chn3_state2 = polycntVL_state; + } + chn3_clk_source_prv = *chn3_clk_source; + } + else { + // channel 3 uses the 7-bit polynomial counter + if (*chn3_clk_source < chn3_clk_source_prv) { + // update on negative edge + if (--polycnt7_phase < 0) // 7-bit + polycnt7_phase = 126; + polycnt7_state = (int) t.polycnt7_table[polycnt7_phase]; + chn3_state1 = polycnt7_state; // input signal to channel 3 + if (!chn3_lp_2) + chn3_state2 = polycnt7_state; + } + chn3_clk_source_prv = *chn3_clk_source; + if (--polycntVL_phase < 0) // variable length + polycntVL_phase = polycntVL_maxphase; + polycntVL_state = (int) polycntVL_table[polycntVL_phase]; + } + + // update the phase of all oscillators + clk_62500_phase--; + clk_1000_phase--; + chn0_phase -= chn0_run; + chn1_phase -= chn1_run; + chn2_phase -= chn2_run; + + // reload phase counters if necessary + if (EP128EMU_UNLIKELY(clk_1000_phase < 0)) { + clk_50_phase--; + // trigger interrupts if enabled + if ((*int_snd_phase) < 0) { + // will reload counter later + int_snd_state = (int_snd_state & 1) ^ 1; // invert state + if (enable_int_snd) + triggerIntSnd(); + } + clk_1000_phase = clk_1000_frq; + if (EP128EMU_UNLIKELY(clk_50_phase < 0)) { + clk_50_phase = clk_50_frq; + clk_1_phase--; + if (EP128EMU_UNLIKELY(clk_1_phase < 0)) { + clk_1_phase = clk_1_frq; // reload counter + int_1hz_state = (int_1hz_state & 1) ^ 1; // invert state + if (enable_int_1hz) + triggerInt1Hz(); + } + } + } + else if (EP128EMU_UNLIKELY((*int_snd_phase) < 0)) { + // trigger interrupt if enabled + int_snd_state = (int_snd_state & 1) ^ 1; // invert state + if (enable_int_snd) + triggerIntSnd(); + } + + // calculate oscillator outputs + if (clk_62500_phase < 0) { + // simple 31250 Hz oscillator + clk_62500_phase = clk_62500_frq; // reload counter + clk_62500_state = (clk_62500_state & 1) ^ 1; // invert state + } + // ---- channel 3 ---- + chn3_prv = chn3_state; // save previous output + if (chn3_lp_2 && (chn2_state < chn2_prv)) { + // lowpass filter holds signal until negative edge in channel 2 + chn3_state2 = chn3_state1; + } + if (chn3_hp_0 && (chn0_state < chn0_prv)) { + // highpass filter: sets level to 0 on negative edge in channel 0 + chn3_state2 = 0; + } + // store final output signal in chn3_state + chn3_state = chn3_state2; + if (chn3_rm_1) { + // ring modulation: XNOR by channel 1 + chn3_state ^= (chn1_state ^ 1); + } + // ---- channel 2 ---- + chn2_prv = chn2_state; // save previous output + if (chn2_phase < 0) { + chn2_phase = chn2_frqcode; // reload counter + if (chn2_input_polycnt == NULL) { + // square wave + chn2_state1 = (chn2_state1 & 1) ^ 1; + } + else { + // get input from polynomial counter + chn2_state1 = *chn2_input_polycnt; + } + } + if (chn2_hp_3 && (chn3_state < chn3_prv)) { + // highpass filter: sets level to 0 on negative edge in channel 3 + chn2_state1 = 0; + } + // store final output signal in chn2_state + chn2_state = chn2_state1; + if (chn2_rm_0) { + // ring modulation: XNOR by channel 0 + chn2_state ^= (chn0_state ^ 1); + } + // ---- channel 1 ---- + chn1_prv = chn1_state; // save previous output + if (chn1_phase < 0) { + chn1_phase = chn1_frqcode; // reload counter + if (chn1_input_polycnt == NULL) { + // square wave + chn1_state1 = (chn1_state1 & 1) ^ 1; + } + else { + // get input from polynomial counter + chn1_state1 = *chn1_input_polycnt; + } + } + if (chn1_hp_2 && (chn2_state < chn2_prv)) { + // highpass filter: sets level to 0 on negative edge in channel 2 + chn1_state1 = 0; + } + // store final output signal in chn1_state + chn1_state = chn1_state1; + if (chn1_rm_3) { + // ring modulation: XNOR by channel 3 + chn1_state ^= (chn3_state ^ 1); + } + // ---- channel 0 ---- + chn0_prv = chn0_state; // save previous output + if (chn0_phase < 0) { + chn0_phase = chn0_frqcode; // reload counter + if (chn0_input_polycnt == NULL) { + // square wave + chn0_state1 = (chn0_state1 & 1) ^ 1; + } + else { + // get input from polynomial counter + chn0_state1 = *chn0_input_polycnt; + } + } + if (chn0_hp_1 && (chn1_state < chn1_prv)) { + // highpass filter: sets level to 0 on negative edge in channel 1 + chn0_state1 = 0; + } + // store final output signal in chn0_state + chn0_state = chn0_state1; + if (chn0_rm_2) { + // ring modulation: XNOR by channel 2 + chn0_state ^= (chn2_state ^ 1); + } + + // and now the final DAC output (left/right) values + // total output range (not including tape feedback): 0 to 252 + unsigned int lval = + ((tape_feedback & tape_input) == 0 ? 0U : 0x3FU); // tape feedback + unsigned int rval = lval; + if (dac_mode_left) { + lval += (chn0_left << 2); + if (dac_mode_right) { + // simplest case: both channels in DAC mode + rval += (chn0_right << 2); + } + else { + // left channel is in DAC mode, but right is not + if (chn0_state) + rval += chn0_right; + if (chn1_state) + rval += chn1_right; + if (chn2_state) + rval += chn2_right; + if (chn3_state) + rval += chn3_right; + } + } + else if (dac_mode_right) { + // right channel is in DAC mode, but left is not + rval += (chn0_right << 2); + if (chn0_state) + lval += chn0_left; + if (chn1_state) + lval += chn1_left; + if (chn2_state) + lval += chn2_left; + if (chn3_state) + lval += chn3_left; + } + else { + // neither channel is in DAC mode + if (chn0_state) { + lval += chn0_left; + rval += chn0_right; + } + if (chn1_state) { + lval += chn1_left; + rval += chn1_right; + } + if (chn2_state) { + lval += chn2_left; + rval += chn2_right; + } + if (chn3_state) { + lval += chn3_left; + rval += chn3_right; + } + } + + audioOutput = uint32_t(lval + (rval << 16)) << 7; + return audioOutput; + } + + // returns pointer to the polynomial counter for channels 0, 1, and 2 + // selected by 'n' (allowed values for 'n' are 0x00, 0x10, 0x20, and 0x30) + + int * Dave::findPolycntForToneChannel(int n) + { + switch (n) { + case 0x10: + return (&polycnt4_state); // 4-bit + case 0x20: + return (&polycnt5_state); // 5-bit + case 0x30: + if (!noise_polycnt_is_7bit) { + return (&polycnt7_state); // 7-bit + } + else { + return (&polycntVL_state); // variable length + } + } + // default to square wave + return (int*) NULL; + } + + // write to DAVE registers + + void Dave::writePort(uint16_t addr, uint8_t value) + { + switch (uint8_t(addr & 0x1F)) { + case 0x00: + // channel 0 frequency + chn0_frqcode = (chn0_frqcode & 0x0F00) | (int) value; + break; + case 0x01: + // channel 0 frequency and mode + chn0_frqcode = (chn0_frqcode & 0x00FF) | (((int) value & 0x0F) << 8); + // select distortion mode + chn0_input_polycnt = findPolycntForToneChannel((int) value & 0x30); + chn0_hp_1 = ((int) value & 0x40 ? 1 : 0); // highpass + chn0_rm_2 = ((int) value & 0x80 ? 1 : 0); // ringmod + break; + case 0x02: + // channel 1 frequency + chn1_frqcode = (chn1_frqcode & 0x0F00) | (int) value; + break; + case 0x03: + // channel 1 frequency and mode + chn1_frqcode = (chn1_frqcode & 0x00FF) | (((int) value & 0x0F) << 8); + // select distortion mode + chn1_input_polycnt = findPolycntForToneChannel((int) value & 0x30); + chn1_hp_2 = ((int) value & 0x40 ? 1 : 0); // highpass + chn1_rm_3 = ((int) value & 0x80 ? 1 : 0); // ringmod + break; + case 0x04: + // channel 2 frequency + chn2_frqcode = (chn2_frqcode & 0x0F00) | (int) value; + break; + case 0x05: + // channel 2 frequency and mode + chn2_frqcode = (chn2_frqcode & 0x00FF) | (((int) value & 0x0F) << 8); + // select distortion mode + chn2_input_polycnt = findPolycntForToneChannel((int) value & 0x30); + chn2_hp_3 = ((int) value & 0x40 ? 1 : 0); // highpass + chn2_rm_0 = ((int) value & 0x80 ? 1 : 0); // ringmod + break; + case 0x06: + // channel 3 parameters + switch ((int) value & 0x03) { + // polynomial counter clock source + case 0x00: chn3_clk_source = &clk_62500_state; break; + case 0x01: chn3_clk_source = &chn0_state; break; + case 0x02: chn3_clk_source = &chn1_state; break; + case 0x03: chn3_clk_source = &chn2_state; break; + } + // select variable length polynomial counter + switch ((int) value & 0x0C) { + case 0x00: + polycntVL_table = t.polycnt17_table; // 17-bit + polycntVL_maxphase = 131070; + break; + case 0x04: + polycntVL_table = t.polycnt15_table; // 15-bit + polycntVL_maxphase = 32766; + break; + case 0x08: + polycntVL_table = t.polycnt11_table; // 11-bit + polycntVL_maxphase = 2046; + break; + case 0x0C: + polycntVL_table = t.polycnt9_table; // 9-bit + polycntVL_maxphase = 510; + break; + } + // wrap the phase of variable length polynomial counter to table length + polycntVL_phase = polycntVL_phase % (polycntVL_maxphase + 1); + // bit 4: swap 7-bit and variable length polynomial counters if set + if ((int) value & 0x10) { + noise_polycnt_is_7bit = 1; + chn3_input_polycnt = &polycnt7_state; + if (chn0_input_polycnt == &polycnt7_state) + chn0_input_polycnt = &polycntVL_state; + if (chn1_input_polycnt == &polycnt7_state) + chn1_input_polycnt = &polycntVL_state; + if (chn2_input_polycnt == &polycnt7_state) + chn2_input_polycnt = &polycntVL_state; + } + else { + noise_polycnt_is_7bit = 0; + chn3_input_polycnt = &polycntVL_state; + if (chn0_input_polycnt == &polycntVL_state) + chn0_input_polycnt = &polycnt7_state; + if (chn1_input_polycnt == &polycntVL_state) + chn1_input_polycnt = &polycnt7_state; + if (chn2_input_polycnt == &polycntVL_state) + chn2_input_polycnt = &polycnt7_state; + } + chn3_lp_2 = ((int) value & 0x20 ? 1 : 0); // lowpass with channel 2 + chn3_hp_0 = ((int) value & 0x40 ? 1 : 0); // highpass with channel 0 + chn3_rm_1 = ((int) value & 0x80 ? 1 : 0); // ring mod. with channel 1 + break; + case 0x07: + // sound/interrupt control register + if ((int) value & 0x01) { + chn0_run = 0; // channel 0 sync + chn0_state1 = 0; + } + else if (!chn0_run) { + chn0_phase = chn0_frqcode; // reset phase + chn0_run = 1; + } + if ((int) value & 0x02) { + chn1_run = 0; // channel 1 sync + chn1_state1 = 0; + } + else if (!chn1_run) { + chn1_phase = chn1_frqcode; // reset phase + chn1_run = 1; + } + if ((int) value & 0x04) { + chn2_run = 0; // channel 2 sync + chn2_state1 = 0; + } + else if (!chn2_run) { + chn2_phase = chn2_frqcode; // reset phase + chn2_run = 1; + } + dac_mode_left = ((int) value & 0x08 ? 1 : 0); // analogue mode + dac_mode_right = ((int) value & 0x10 ? 1 : 0); + switch ((int) value & 0x60) { + // sound interrupt mode + case 0x00: + int_snd_phase = &clk_1000_phase; + break; + case 0x20: + int_snd_phase = &clk_50_phase; + break; + case 0x40: + int_snd_phase = &chn0_phase; + break; + case 0x60: + int_snd_phase = &chn1_phase; + break; + } + break; + case 0x08: + // channel 0 left volume + chn0_left = int(value & 0x3F); + break; + case 0x09: + // channel 1 left volume + chn1_left = int(value & 0x3F); + break; + case 0x0A: + // channel 2 left volume + chn2_left = int(value & 0x3F); + break; + case 0x0B: + // channel 3 left volume + chn3_left = int(value & 0x3F); + break; + case 0x0C: + // channel 0 right volume + chn0_right = int(value & 0x3F); + break; + case 0x0D: + // channel 1 right volume + chn1_right = int(value & 0x3F); + break; + case 0x0E: + // channel 2 right volume + chn2_right = int(value & 0x3F); + break; + case 0x0F: + // channel 3 right volume + chn3_right = int(value & 0x3F); + break; + case 0x10: + // memory page 0 + page0Segment = value; + setMemoryPage(0, value); + break; + case 0x11: + // memory page 1 + page1Segment = value; + setMemoryPage(1, value); + break; + case 0x12: + // memory page 2 + page2Segment = value; + setMemoryPage(2, value); + break; + case 0x13: + // memory page 3 + page3Segment = value; + setMemoryPage(3, value); + break; + case 0x14: + // interrupt control register + { + int prv = (int_snd_active | int_1hz_active + | int_1_active | int_2_active); + uint8_t tmp = (uint8_t) value ^ (uint8_t) 0x55; + // sound/timer interrupt + enable_int_snd = (tmp & (uint8_t) 0x01 ? 0 : 1); + if (tmp & (uint8_t) 0x03) + int_snd_active = 0; + // 1 Hz interrupt + enable_int_1hz = (tmp & (uint8_t) 0x04 ? 0 : 1); + if (tmp & (uint8_t) 0x0C) + int_1hz_active = 0; + // INT 1 (video interrupt) + enable_int_1 = (tmp & (uint8_t) 0x10 ? 0 : 1); + if (tmp & (uint8_t) 0x30) + int_1_active = 0; + // INT 2 + enable_int_2 = (tmp & (uint8_t) 0x40 ? 0 : 1); + if (tmp & (uint8_t) 0xC0) + int_2_active = 0; + if (prv && !(int_snd_active | int_1hz_active + | int_1_active | int_2_active)) { + // no more active interrupts: clear request to CPU + clearInterruptRequest(); + } + } + break; + case 0x15: + // select keyboard row + keyboardRow = int(value & 0x0F); + tape_feedback = int(!(value & 0x20)); // tape control + setRemote1State(value & 0x40 ? 1 : 0); + setRemote2State(value & 0x80 ? 1 : 0); + break; + case 0x1F: // system configuration register + { + // CPU wait cycle control + setMemoryWaitMode(int(value & 0x0C) >> 2); + // input clock frequency (note: the frequency is always 8 MHz, + // this bit sets the assumed value) + // 0: 8 MHz, dave_clock_freq = input_freq / 32 + // 1: 12 MHz, dave_clock_freq = input_freq / 48 + clockDiv = ((value & 0x02) == 0 ? 2 : 3); + } + break; + } + } + + // read from DAVE registers + + uint8_t Dave::readPort(uint16_t addr) const + { + switch (uint8_t(addr & 0x1F)) { + case 0x10: + return page0Segment; + case 0x11: + return page1Segment; + case 0x12: + return page2Segment; + case 0x13: + return page3Segment; + case 0x14: + { + // interrupt state + return uint8_t((int_snd_state | (int_snd_active << 1)) + | ((int_1hz_state | (int_1hz_active << 1)) << 2) + | ((int_1_state | (int_1_active << 1)) << 4) + | ((int_2_state | (int_2_active << 1)) << 6)); + } + break; + case 0x15: + // read currently selected keyboard row + return (keyboardRow < 10 ? keyboardState[keyboardRow] : 0xFF); + case 0x16: + { + // tape input + uint8_t n = + uint8_t(((tape_input_level - 1) & 0x40) | ((tape_input - 1) & 0x80) + | 0x0F); + if (keyboardRow < 5) { + if (keyboardRow == 0) { + // EnterMice buttons (left and right) + n &= uint8_t(0xF9 | (mouseInput >> 3)); + // EXT1 joystick fire buttons + n &= uint8_t(0xF8 | (keyboardState[14] >> 4)); + } + else { + if (!(mouseInput & 0x80)) // EnterMice data input on column K + n &= uint8_t(0xFD | ((mouseInput << 1) >> (keyboardRow - 1))); + // EXT1 joystick (mapped to row 14) + n &= uint8_t(0xFE | (keyboardState[14] >> (4 - keyboardRow))); + } + } + else if (keyboardRow < 10) { + // external joystick 2 (mapped to keyboard row 15) + if (keyboardRow == 5) // fire buttons + n &= uint8_t(0xF8 | (keyboardState[15] >> 4)); + else + n &= uint8_t(0xFE | (keyboardState[15] >> (9 - keyboardRow))); + } + return n; + } + } + // anything else is either handled elsewhere, or is write-only + return 0xFF; + } + + // set hardware interrupt 1 state, and (possibly) trigger interrupt + + void Dave::setInt1State(int new_state) + { + int prv = int_1_state; + // set new state + int_1_state = (new_state ? 1 : 0); + // on negative edge, trigger CPU interrupt + // (assuming it is enabled, and not active already) + if (!enable_int_1) + return; // disabled + if (int_1_state || !prv) + return; // not on negative edge + if (int_1_active) + return; // already active + // now active + int_1_active = 1; + // send request to CPU + interruptRequest(); + } + + // set hardware interrupt 2 state, and (possibly) trigger interrupt + + void Dave::setInt2State(int new_state) + { + int prv = int_2_state; + // set new state + int_2_state = (new_state ? 1 : 0); + // on negative edge, trigger CPU interrupt + // (assuming it is enabled, and not active already) + if (!enable_int_2) + return; // disabled + if (int_2_state || !prv) + return; // not on negative edge + if (int_2_active) + return; // already active + // now active + int_2_active = 1; + // send request to CPU + interruptRequest(); + } + + Dave::Dave() + { + clockDiv = 2; + clockCnt = 1; + polycnt4_state = 0; + polycnt5_state = 0; + polycnt7_state = 0; + polycntVL_state = 0; + clk_62500_state = 0; + chn0_state = 0; + chn0_prv = 0; + chn0_state1 = 0; + chn0_frqcode = 0; + chn0_run = 1; + chn1_state = 0; + chn1_prv = 0; + chn1_state1 = 0; + chn1_frqcode = 0; + chn1_run = 1; + chn2_state = 0; + chn2_prv = 0; + chn2_state1 = 0; + chn2_frqcode = 0; + chn2_run = 1; + chn3_state = 0; + chn3_prv = 0; + chn3_state1 = 0; + chn3_state2 = 0; + chn3_clk_source_prv = 0; + int_snd_state = 0; + int_1hz_state = 0; + int_1_state = 1; + int_2_state = 1; + int_snd_active = 0; + int_1hz_active = 0; + int_1_active = 0; + int_2_active = 0; + audioOutput = 0; + tape_input = 0; + tape_input_level = 0; + this->reset(true); + } + + Dave::~Dave() + { + } + + void Dave::reset(bool isColdReset) + { + polycnt4_phase = 0; + polycnt5_phase = 0; + polycnt7_phase = 0; + polycntVL_phase = 0; + chn0_phase = 0; + chn1_phase = 0; + chn2_phase = 0; + clk_62500_phase = 0; + clk_1000_phase = 0; + clk_50_phase = 0; + clk_1_phase = 0; + // initialize registers + // this will also reset many variables to the default value + for (uint16_t i = 0x00; i < 0x20; i++) + writePort(i, 0); + // clear all interrupts + writePort(0x14, 0xAA); + if (isColdReset) { + // reset keyboard state + for (int i = 0; i < 16; i++) + keyboardState[i] = 0xFF; + } + mouseInput = 0xFF; + } + + void Dave::setTapeInput(int state, int level) + { + tape_input = (state ? 1 : 0); + tape_input_level = (level > 0 ? 1 : 0); + } + + void Dave::setKeyboardState(int keyCode, int state) + { + int row = (keyCode & 0x78) >> 3; + uint8_t mask = uint8_t(1 << (keyCode & 0x07)); + if (!state) + keyboardState[row] |= mask; + else + keyboardState[row] &= (~mask); + } + + // -------------------------------------------------------------------------- + + void Dave::setMemoryPage(uint8_t page, uint8_t segment) + { + (void) page; + (void) segment; + } + + void Dave::setMemoryWaitMode(int mode) + { + (void) mode; + } + + void Dave::setRemote1State(int state) + { + (void) state; + } + + void Dave::setRemote2State(int state) + { + (void) state; + } + + void Dave::interruptRequest() + { + } + + void Dave::clearInterruptRequest() + { + } + +} // namespace Ep128 + diff --git a/src/engine/platform/sound/dave/dave.hpp b/src/engine/platform/sound/dave/dave.hpp new file mode 100644 index 000000000..8e44ca49a --- /dev/null +++ b/src/engine/platform/sound/dave/dave.hpp @@ -0,0 +1,251 @@ + +// ep128emu -- portable Enterprise 128 emulator +// Copyright (C) 2003-2016 Istvan Varga +// https://github.com/istvan-v/ep128emu/ +// +// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +#ifndef EP128EMU_DAVE_HPP +#define EP128EMU_DAVE_HPP + +#include + +namespace Ep128 { + + class DaveTables { + public: + // tables for polynomial counters + // (note: the table data is stored in reverse order) + uint8_t *polycnt4_table; // length = 15, poly = 0x0000000C + uint8_t *polycnt5_table; // length = 31, poly = 0x00000014 + uint8_t *polycnt7_table; // length = 127, poly = 0x00000060 + uint8_t *polycnt9_table; // length = 511, poly = 0x00000110 + uint8_t *polycnt11_table; // length = 2047, poly = 0x00000500 + uint8_t *polycnt15_table; // length = 32767, poly = 0x00006000 + uint8_t *polycnt17_table; // length = 131071, poly = 0x00012000 + // ---------------- + DaveTables(); + ~DaveTables(); + }; + + class Dave { + private: + DaveTables t; + int clockDiv; // 2 if bit 1 of port 0xBF is 0, 3 otherwise + int clockCnt; // counts from 'clockDiv' towards zero + // variable length counter uses one of the 9, 11, 15, and 17 bit tables + uint8_t *polycntVL_table; + // polynomial counters + int polycnt4_phase; // 4-bit counter phase (14 -> 0) + int polycnt5_phase; // 5-bit counter phase (30 -> 0) + int polycnt7_phase; // 7-bit counter phase (126 -> 0) + int polycntVL_phase; // variable length counter phase ... + int polycntVL_maxphase; // ... counts from this value to zero + int polycnt4_state; // 4-bit counter output + int polycnt5_state; // 5-bit counter output + int polycnt7_state; // 7-bit counter output + int polycntVL_state; // variable length counter output + // fixed frequency counters (f = 250000 / (n + 1)) + static const int clk_62500_frq = 3; + static const int clk_1000_frq = 249; + static const int clk_50_frq = 19; // clocked by the 1 kHz counter + static const int clk_1_frq = 49; // clocked by the 50 Hz counter + int clk_62500_phase; + int clk_1000_phase; + int clk_50_phase; + int clk_1_phase; + int clk_62500_state; + // channel 0 parameters + int chn0_state; // current output state + int chn0_prv; // previous output state + int chn0_state1; // oscillator output + int chn0_phase; // phase (frqcode -> 0) + int chn0_frqcode; // frequency code (0 - 4095) + int *chn0_input_polycnt; // polynomial counter + int chn0_hp_1; // enable highpass filter + int chn0_rm_2; // enable ring modulation + int chn0_run; // 1: oscillator is running + int chn0_left; // left volume (0 - 63) + int chn0_right; // right volume (0 - 63) + // channel 1 parameters + int chn1_state; // current output state + int chn1_prv; // previous output state + int chn1_state1; // oscillator output + int chn1_phase; // phase (frqcode -> 0) + int chn1_frqcode; // frequency code (0 - 4095) + int *chn1_input_polycnt; // polynomial counter + int chn1_hp_2; // enable highpass filter + int chn1_rm_3; // enable ring modulation + int chn1_run; // 1: oscillator is running + int chn1_left; // left volume (0 - 63) + int chn1_right; // right volume (0 - 63) + // channel 2 parameters + int chn2_state; // current output state + int chn2_prv; // previous output state + int chn2_state1; // oscillator output + int chn2_phase; // phase (frqcode -> 0) + int chn2_frqcode; // frequency code (0 - 4095) + int *chn2_input_polycnt; // polynomial counter + int chn2_hp_3; // enable highpass filter + int chn2_rm_0; // enable ring modulation + int chn2_run; // 1: oscillator is running + int chn2_left; // left volume (0 - 63) + int chn2_right; // right volume (0 - 63) + // channel 3 (noise) parameters + int chn3_state; // current output state + int chn3_prv; // previous output state + int chn3_state1; // polynomial counter output + int chn3_state2; // lowpass filter output + int *chn3_clk_source; // clock input signal + int chn3_clk_source_prv; // previous clock input + int noise_polycnt_is_7bit; // 0xA6 port bit 4 + int *chn3_input_polycnt; // polynomial counter + int chn3_lp_2; // enable lowpass filter + int chn3_hp_0; // enable highpass filter + int chn3_rm_1; // enable ring modulation + int chn3_left; // left volume (0 - 63) + int chn3_right; // right volume (0 - 63) + // enable DAC mode for left/right channel + int dac_mode_left; + int dac_mode_right; + // interrupts + int *int_snd_phase; + int enable_int_snd; + int enable_int_1hz; + int enable_int_1; + int enable_int_2; + int int_snd_state; + int int_1hz_state; + int int_1_state; + int int_2_state; + int int_snd_active; + int int_1hz_active; + int int_1_active; + int int_2_active; + uint32_t audioOutput; + uint8_t page0Segment; + uint8_t page1Segment; + uint8_t page2Segment; + uint8_t page3Segment; + int tape_feedback; + int tape_input; + int tape_input_level; + int keyboardRow; + uint8_t keyboardState[16]; + // b0..b3 = current nibble + // b4 = button 1 (left) state (0 = pressed) + // b5 = button 2 (right) state + uint8_t mouseInput; // b7 == 1: mouse data bits inactive + // ---------------- + inline void triggerIntSnd(); + inline void triggerInt1Hz(); + int * findPolycntForToneChannel(int n); + uint32_t runOneCycle_(); + public: + Dave(); + virtual ~Dave(); + protected: + virtual void setMemoryPage(uint8_t page, uint8_t segment); + virtual void setMemoryWaitMode(int mode); + virtual void setRemote1State(int state); + virtual void setRemote2State(int state); + virtual void interruptRequest(); + virtual void clearInterruptRequest(); + public: + /*! + * Run DAVE emulation for 2 us (clock frequency = 500 kHz). + * Return value is audio output in left_channel + (right_channel << 16) + * format, where the range for a single channel is 0 to 40320 (sum of 4 + * sound generators and tape feedback, 0 to 8064 each). + */ + inline uint32_t runOneCycle() + { + if (--clockCnt > 0) + return audioOutput; + clockCnt = clockDiv; + return runOneCycle_(); + } + /*! + * Write to a DAVE register. + */ + void writePort(uint16_t addr, uint8_t value); + /*! + * Read from a DAVE register. + */ + uint8_t readPort(uint16_t addr) const; + /*! + * Set hardware interrupt 1 state, and (possibly) trigger interrupt. + */ + void setInt1State(int new_state); + /*! + * Set hardware interrupt 2 state, and (possibly) trigger interrupt. + */ + void setInt2State(int new_state); + /*! + * Set tape input state, and level (0: low, 1: high). + */ + void setTapeInput(int state, int level); + /*! + * Set state of key 'keyCode' (0 to 127, see table below) to pressed + * (state != 0) or released (state == 0). + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * | 0x00 | 0x01 | 0x02 | 0x03 | 0x04 | 0x05 | 0x06 | 0x07 | + * | 0x08 | 0x09 | 0x0A | 0x0B | 0x0C | 0x0D | 0x0E | 0x0F | + * +------+-------+-------+-------+-------+-------+-------+-------+-------+ + * | 0x00 | N | \ | B | C | V | X | Z | SHF_L | + * +------+-------+-------+-------+-------+-------+-------+-------+-------+ + * | 0x08 | H | LOCK | G | D | F | S | A | CTRL | + * +------+-------+-------+-------+-------+-------+-------+-------+-------+ + * | 0x10 | U | Q | Y | R | T | E | W | TAB | + * +------+-------+-------+-------+-------+-------+-------+-------+-------+ + * | 0x18 | 7 | 1 | 6 | 4 | 5 | 3 | 2 | ESC | + * +------+-------+-------+-------+-------+-------+-------+-------+-------+ + * | 0x20 | F4 | F8 | F3 | F6 | F5 | F7 | F2 | F1 | + * +------+-------+-------+-------+-------+-------+-------+-------+-------+ + * | 0x28 | 8 | | 9 | - | 0 | ^ | ERASE | | + * +------+-------+-------+-------+-------+-------+-------+-------+-------+ + * | 0x30 | J | | K | ; | L | : | ] | | + * +------+-------+-------+-------+-------+-------+-------+-------+-------+ + * | 0x38 | STOP | DOWN | RIGHT | UP | HOLD | LEFT | ENTER | ALT | + * +------+-------+-------+-------+-------+-------+-------+-------+-------+ + * | 0x40 | M | DEL | , | / | . | SHF_R | SPACE | INS | + * +------+-------+-------+-------+-------+-------+-------+-------+-------+ + * | 0x48 | I | | O | @ | P | [ | | | + * +------+-------+-------+-------+-------+-------+-------+-------+-------+ + * | 0x70 | JOY1R | JOY1L | JOY1D | JOY1U | JOY1F | JOY1F2| JOY1F3| | + * +------+-------+-------+-------+-------+-------+-------+-------+-------+ + * | 0x78 | JOY2R | JOY2L | JOY2D | JOY2U | JOY2F | JOY2F2| JOY2F3| | + * +------+-------+-------+-------+-------+-------+-------+-------+-------+ + */ + void setKeyboardState(int keyCode, int state); + inline void setMouseInput(uint8_t value) + { + mouseInput = value; + } + inline void clearMouseInput() + { + // clear data bits, but not the buttons + mouseInput = mouseInput | 0xCF; + } + /*! + * Reset DAVE. + */ + void reset(bool isColdReset = false); + }; + +} // namespace Ep128 + +#endif // EP128EMU_DAVE_HPP +