Add PET support

This commit is contained in:
Natt Akuma 2022-03-21 21:02:51 +07:00
parent b45d2be312
commit 47c5c34e1c
7 changed files with 410 additions and 3 deletions

View file

@ -320,6 +320,7 @@ src/engine/platform/lynx.cpp
src/engine/platform/swan.cpp src/engine/platform/swan.cpp
src/engine/platform/vera.cpp src/engine/platform/vera.cpp
src/engine/platform/bubsyswsg.cpp src/engine/platform/bubsyswsg.cpp
src/engine/platform/pet.cpp
src/engine/platform/dummy.cpp src/engine/platform/dummy.cpp
) )

View file

@ -46,6 +46,7 @@
#include "platform/swan.h" #include "platform/swan.h"
#include "platform/lynx.h" #include "platform/lynx.h"
#include "platform/bubsyswsg.h" #include "platform/bubsyswsg.h"
#include "platform/pet.h"
#include "platform/dummy.h" #include "platform/dummy.h"
#include "../ta-log.h" #include "../ta-log.h"
#include "song.h" #include "song.h"
@ -275,6 +276,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
case DIV_SYSTEM_BUBSYS_WSG: case DIV_SYSTEM_BUBSYS_WSG:
dispatch=new DivPlatformBubSysWSG; dispatch=new DivPlatformBubSysWSG;
break; break;
case DIV_SYSTEM_PET:
dispatch=new DivPlatformPET;
break;
default: default:
logW("this system is not supported yet! using dummy platform.\n"); logW("this system is not supported yet! using dummy platform.\n");
dispatch=new DivPlatformDummy; dispatch=new DivPlatformDummy;

311
src/engine/platform/pet.cpp Normal file
View file

@ -0,0 +1,311 @@
/**
* 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 "pet.h"
#include "../engine.h"
#include <math.h>
#define rWrite(a,v) {regPool[(a)]=(v)&0xff; if((a)==10) {chan.sreg=(v); chan.cnt=2;}}
#define CHIP_DIVIDER 16
#define SAMP_DIVIDER 4
const char* regCheatSheet6522[]={
"T2L", "08",
"SR", "0A",
"ACR", "0B",
NULL
};
const char** DivPlatformPET::getRegisterSheet() {
return regCheatSheet6522;
}
const char* DivPlatformPET::getEffectName(unsigned char effect) {
switch (effect) {
case 0x10:
return "10xx: Change waveform";
break;
}
return NULL;
}
void DivPlatformPET::acquire(short* bufL, short* bufR, size_t start, size_t len) {
// high-level emulation of 6522 shift register for now
int t2=regPool[8]*2+4;
if (((regPool[11]>>2)&7)==4) {
for (size_t h=start; h<start+len; h++) {
int cycs=SAMP_DIVIDER;
while (cycs>0) {
int adv=MIN(cycs,chan.cnt);
chan.cnt-=adv;
cycs-=adv;
if (chan.cnt==0) {
chan.out=(chan.sreg&1)*32767;
chan.sreg=(chan.sreg>>1)|((chan.sreg&1)<<7);
chan.cnt=t2;
}
}
bufL[h]=chan.out;
bufR[h]=chan.out;
}
} else {
chan.out=0;
for (size_t h=start; h<start+len; h++) {
bufL[h]=0;
bufR[h]=0;
}
}
}
void DivPlatformPET::updateWave() {
DivWavetable* wt=parent->getWave(chan.wave);
if (wt->max<1 || wt->len<1) {
rWrite(10,0);
} else {
unsigned char sr=0;
for (int i=0; i<8; i++) {
sr=(sr<<1)|((wt->data[i*wt->len/8]*2)/(wt->max+1));
}
rWrite(10,sr);
}
}
void DivPlatformPET::writeOutVol() {
if (chan.active && !isMuted && chan.outVol>0) {
if (regPool[11]!=16) {
rWrite(11,16);
rWrite(10,regPool[10]);
}
} else {
rWrite(11,0);
}
}
void DivPlatformPET::tick() {
chan.std.next();
if (chan.std.hadVol) {
chan.outVol=chan.std.vol&chan.vol;
writeOutVol();
}
if (chan.std.hadArp) {
if (!chan.inPorta) {
if (chan.std.arpMode) {
chan.baseFreq=NOTE_PERIODIC(chan.std.arp);
} else {
chan.baseFreq=NOTE_PERIODIC(chan.note+chan.std.arp);
}
}
chan.freqChanged=true;
} else {
if (chan.std.arpMode && chan.std.finishedArp) {
chan.baseFreq=NOTE_PERIODIC(chan.note);
chan.freqChanged=true;
}
}
if (chan.std.hadWave) {
if (chan.wave!=chan.std.wave) {
chan.wave=chan.std.wave;
updateWave();
}
}
if (chan.freqChanged || chan.keyOn || chan.keyOff) {
chan.freq=parent->calcFreq(chan.baseFreq,chan.pitch,true);
if (chan.freq>257) chan.freq=257;
if (chan.freq<2) chan.freq=2;
rWrite(8,chan.freq-2);
if (chan.keyOn) {
if (!chan.std.willVol) {
chan.outVol=chan.vol;
writeOutVol();
}
if (chan.wave<0) {
chan.wave=0;
updateWave();
}
chan.keyOn=false;
}
if (chan.keyOff) {
rWrite(11,0);
chan.keyOff=false;
}
chan.freqChanged=false;
}
}
int DivPlatformPET::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan.ins);
if (c.value!=DIV_NOTE_NULL) {
chan.baseFreq=NOTE_PERIODIC(c.value);
chan.freqChanged=true;
chan.note=c.value;
}
chan.active=true;
chan.keyOn=true;
chan.std.init(ins);
break;
}
case DIV_CMD_NOTE_OFF:
chan.active=false;
chan.keyOff=true;
chan.std.init(NULL);
break;
case DIV_CMD_NOTE_OFF_ENV:
case DIV_CMD_ENV_RELEASE:
chan.std.release();
break;
case DIV_CMD_INSTRUMENT:
if (chan.ins!=c.value || c.value2==1) {
chan.ins=c.value;
}
break;
case DIV_CMD_VOLUME:
if (chan.vol!=c.value) {
chan.vol=c.value;
if (!chan.std.hadVol) {
chan.outVol=chan.vol;
writeOutVol();
}
}
break;
case DIV_CMD_GET_VOLUME:
return chan.vol;
break;
case DIV_CMD_PITCH:
chan.pitch=c.value;
chan.freqChanged=true;
break;
case DIV_CMD_WAVE:
chan.wave=c.value;
updateWave();
chan.keyOn=true;
break;
case DIV_CMD_NOTE_PORTA: {
int destFreq=NOTE_PERIODIC(c.value2);
bool return2=false;
if (destFreq>chan.baseFreq) {
chan.baseFreq+=c.value;
if (chan.baseFreq>=destFreq) {
chan.baseFreq=destFreq;
return2=true;
}
} else {
chan.baseFreq-=c.value;
if (chan.baseFreq<=destFreq) {
chan.baseFreq=destFreq;
return2=true;
}
}
chan.freqChanged=true;
if (return2) {
chan.inPorta=false;
return 2;
}
break;
}
case DIV_CMD_LEGATO:
chan.baseFreq=NOTE_PERIODIC(c.value+((chan.std.willArp && !chan.std.arpMode)?(chan.std.arp):(0)));
chan.freqChanged=true;
chan.note=c.value;
break;
case DIV_CMD_PRE_PORTA:
if (chan.active && c.value2) {
if (parent->song.resetMacroOnPorta) chan.std.init(parent->getIns(chan.ins));
}
chan.inPorta=c.value;
break;
case DIV_CMD_GET_VOLMAX:
return 1;
break;
case DIV_ALWAYS_SET_VOLUME:
return 1;
break;
default:
break;
}
return 1;
}
void DivPlatformPET::muteChannel(int ch, bool mute) {
isMuted=mute;
writeOutVol();
}
void DivPlatformPET::forceIns() {
chan.insChanged=true;
chan.freqChanged=true;
updateWave();
writeOutVol();
}
void* DivPlatformPET::getChanState(int ch) {
return &chan;
}
unsigned char* DivPlatformPET::getRegisterPool() {
return regPool;
}
int DivPlatformPET::getRegisterPoolSize() {
return 16;
}
void DivPlatformPET::reset() {
memset(regPool,0,16);
chan=Channel();
chan.vol=1;
}
bool DivPlatformPET::isStereo() {
return false;
}
void DivPlatformPET::notifyWaveChange(int wave) {
if (chan.wave==wave) {
updateWave();
}
}
void DivPlatformPET::notifyInsDeletion(void* ins) {
chan.std.notifyInsDeletion((DivInstrument*)ins);
}
void DivPlatformPET::poke(unsigned int addr, unsigned short val) {
rWrite(addr,val);
}
void DivPlatformPET::poke(std::vector<DivRegWrite>& wlist) {
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
}
int DivPlatformPET::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
parent=p;
dumpWrites=false;
skipRegisterWrites=false;
chipClock=1000000;
rate=chipClock/SAMP_DIVIDER; // = 250000kHz
isMuted=false;
reset();
return 1;
}
DivPlatformPET::~DivPlatformPET() {
}

84
src/engine/platform/pet.h Normal file
View file

@ -0,0 +1,84 @@
/**
* 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 _PET_H
#define _PET_H
#include "../dispatch.h"
#include "../macroInt.h"
class DivPlatformPET: public DivDispatch {
struct Channel {
int freq, baseFreq, pitch, note;
unsigned char ins;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta;
int vol, outVol, wave;
unsigned char sreg;
int cnt;
short out;
DivMacroInt std;
Channel():
freq(0),
baseFreq(0),
pitch(0),
note(0),
ins(-1),
active(false),
insChanged(true),
freqChanged(false),
keyOn(false),
keyOff(false),
inPorta(false),
vol(1),
outVol(1),
wave(-1),
sreg(0),
cnt(0),
out(0) {}
};
Channel chan;
bool isMuted;
unsigned char regPool[16];
void updateWave();
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 forceIns();
void tick();
void muteChannel(int ch, bool mute);
void notifyWaveChange(int wave);
void notifyInsDeletion(void* ins);
bool isStereo();
void poke(unsigned int addr, unsigned short val);
void poke(std::vector<DivRegWrite>& wlist);
const char** getRegisterSheet();
const char* getEffectName(unsigned char effect);
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
~DivPlatformPET();
private:
void writeOutVol();
};
#endif

View file

@ -314,6 +314,7 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe
} }
break; break;
case DIV_SYSTEM_BUBSYS_WSG: case DIV_SYSTEM_BUBSYS_WSG:
case DIV_SYSTEM_PET:
switch (effect) { switch (effect) {
case 0x10: // select waveform case 0x10: // select waveform
dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal));

View file

@ -5575,6 +5575,7 @@ bool FurnaceGUI::loop() {
sysAddOption(DIV_SYSTEM_SWAN); sysAddOption(DIV_SYSTEM_SWAN);
sysAddOption(DIV_SYSTEM_VERA); sysAddOption(DIV_SYSTEM_VERA);
sysAddOption(DIV_SYSTEM_BUBSYS_WSG); sysAddOption(DIV_SYSTEM_BUBSYS_WSG);
sysAddOption(DIV_SYSTEM_PET);
ImGui::EndMenu(); ImGui::EndMenu();
} }
if (ImGui::BeginMenu("configure system...")) { if (ImGui::BeginMenu("configure system...")) {
@ -5918,6 +5919,7 @@ bool FurnaceGUI::loop() {
case DIV_SYSTEM_YM2610B: case DIV_SYSTEM_YM2610B:
case DIV_SYSTEM_YM2610B_EXT: case DIV_SYSTEM_YM2610B_EXT:
case DIV_SYSTEM_YMU759: case DIV_SYSTEM_YMU759:
case DIV_SYSTEM_PET:
ImGui::Text("nothing to configure"); ImGui::Text("nothing to configure");
break; break;
default: default:
@ -5973,6 +5975,7 @@ bool FurnaceGUI::loop() {
sysChangeOption(i,DIV_SYSTEM_SWAN); sysChangeOption(i,DIV_SYSTEM_SWAN);
sysChangeOption(i,DIV_SYSTEM_VERA); sysChangeOption(i,DIV_SYSTEM_VERA);
sysChangeOption(i,DIV_SYSTEM_BUBSYS_WSG); sysChangeOption(i,DIV_SYSTEM_BUBSYS_WSG);
sysChangeOption(i,DIV_SYSTEM_PET);
ImGui::EndMenu(); ImGui::EndMenu();
} }
} }

View file

@ -1550,6 +1550,9 @@ void FurnaceGUI::drawInsEdit() {
if (ins->type==DIV_INS_GB) { if (ins->type==DIV_INS_GB) {
volMax=0; volMax=0;
} }
if (ins->type==DIV_INS_PET) {
volMax=1;
}
bool arpMode=ins->std.arpMacroMode; bool arpMode=ins->std.arpMacroMode;
@ -1579,7 +1582,7 @@ void FurnaceGUI::drawInsEdit() {
if (ins->type==DIV_INS_AY8930) { if (ins->type==DIV_INS_AY8930) {
dutyMax=255; dutyMax=255;
} }
if (ins->type==DIV_INS_TIA || ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_SCC) { if (ins->type==DIV_INS_TIA || ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_SCC || ins->type==DIV_INS_PET) {
dutyMax=0; dutyMax=0;
} }
if (ins->type==DIV_INS_PCE) { if (ins->type==DIV_INS_PCE) {
@ -2002,7 +2005,7 @@ void FurnaceGUI::drawWaveEdit() {
DivWavetable* wave=e->song.wave[curWave]; DivWavetable* wave=e->song.wave[curWave];
ImGui::Text("Width"); ImGui::Text("Width");
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("use a width of 32 on Game Boy, PC Engine and WonderSwan.\nany other widths will be scaled during playback."); ImGui::SetTooltip("use a width of:\n- 8 for PET\n- 32 on Game Boy, PC Engine and WonderSwan.\nany other widths will be scaled during playback.");
} }
ImGui::SameLine(); ImGui::SameLine();
ImGui::SetNextItemWidth(128.0f*dpiScale); ImGui::SetNextItemWidth(128.0f*dpiScale);
@ -2016,7 +2019,7 @@ void FurnaceGUI::drawWaveEdit() {
ImGui::SameLine(); ImGui::SameLine();
ImGui::Text("Height"); ImGui::Text("Height");
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("use a height of:\n- 15 for Game Boy and WonderSwan\n- 31 for PC Engine\nany other heights will be scaled during playback."); ImGui::SetTooltip("use a height of:\n- 1 for PET\n- 15 for Game Boy and WonderSwan\n- 31 for PC Engine\nany other heights will be scaled during playback.");
} }
ImGui::SameLine(); ImGui::SameLine();
ImGui::SetNextItemWidth(128.0f*dpiScale); ImGui::SetNextItemWidth(128.0f*dpiScale);