Merge branch 'master' into es5506_alt

This commit is contained in:
cam900 2022-12-24 12:01:03 +09:00 committed by GitHub
commit a793bed56d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
65 changed files with 4981 additions and 395 deletions

View file

@ -424,6 +424,9 @@ src/engine/platform/sound/ymfm/ymfm_ssg.cpp
src/engine/platform/sound/lynx/Mikey.cpp
src/engine/platform/sound/pokey/mzpokeysnd.c
src/engine/platform/sound/pokey/AltASAP.cpp
src/engine/platform/sound/qsound.c
src/engine/platform/sound/swan.cpp
@ -508,6 +511,7 @@ src/engine/platform/pcspkr.cpp
src/engine/platform/segapcm.cpp
src/engine/platform/qsound.cpp
src/engine/platform/x1_010.cpp
src/engine/platform/pokey.cpp
src/engine/platform/lynx.cpp
src/engine/platform/su.cpp
src/engine/platform/swan.cpp

View file

@ -1,9 +1,3 @@
# to-do for 0.6pre2
- POKEY
- Pokémon Mini
- register layout
- confirm emulation
- (maybe) YM2612 CSM (no DualPCM)
- port op macro code to all other OPN chips
- bug fixes
- bug fixes

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
demos/snes/Breezy.fur Normal file

Binary file not shown.

BIN
demos/specs2/object.fur Normal file

Binary file not shown.

View file

@ -0,0 +1,34 @@
# POKEY
a sound and input chip developed by Atari for their 8-bit computers (Atari 400, 800, XL/XE and so on). 4 channels of signature Atari sounds.
# effects
- 10xx: set waveform.
- 0: harsh noise (poly5+17)
- 1: square buzz (poly5)
- 2: weird noise (poly4+5)
- 3: square buzz (poly5)
- 4: soft noise (poly17)
- 5: square
- 6: bass (poly4)
- 7: buzz (poly4)
- 11xx: set AUDCTL. `xx` is a bitmask.
- bit 7: 9-bit poly mode. shortens noise.
- bit 6: high channel 1 clock (~1.79MHz on NTSC).
- overrides 15KHz mode.
- bit 5: high channel 3 clock (~1.79MHz on NTSC).
- overrides 15KHz mode.
- bit 4: join channels 1 and 2 for a wide period range.
- use with conjunction with bit 6.
- channel 2 becomes inaccessible when this is on.
- bit 3: join channels 3 and 4 for a wide period range.
- use with conjunction with bit 5.
- channel 4 becomes inaccessible when this is on.
- bit 2: high-pass filter (channels 1 and 3).
- filtered output on channel 1 (I suggest you to set channel 3 volume to 0).
- use for PWM effects (not automatic!).
- bit 1: high-pass filter (channels 2 and 4).
- filtered output on channel 2 (I suggest you to set channel 4 volume to 0).
- use for PWM effects (not automatic!).
- bit 0: 15KHz mode.

View file

@ -215,8 +215,8 @@ size | description
| - 0x8a: FDS - 1 channel
| - 0x8b: MMC5 - 3 channels
| - 0x8c: Namco 163 - 8 channels
| - 0x8d: OPN (YM2203) - 6 channels
| - 0x8e: PC-98 (YM2608) - 16 channels
| - 0x8d: YM2203 - 6 channels
| - 0x8e: YM2608 - 16 channels
| - 0x8f: OPL (YM3526) - 9 channels
| - 0x90: OPL2 (YM3812) - 9 channels
| - 0x91: OPL3 (YMF262) - 18 channels
@ -256,8 +256,8 @@ size | description
| - 0xb3: Yamaha Y8950 drums - 12 channels
| - 0xb4: Konami SCC+ - 5 channels
| - 0xb5: tildearrow Sound Unit - 8 channels
| - 0xb6: OPN extended - 9 channels
| - 0xb7: PC-98 extended - 19 channels
| - 0xb6: YM2203 extended - 9 channels
| - 0xb7: YM2608 extended - 19 channels
| - 0xb8: YMZ280B - 8 channels
| - 0xb9: Namco WSG - 3 channels
| - 0xba: Namco 15xx - 8 channels
@ -269,8 +269,8 @@ size | description
| - 0xc0: PCM DAC - 1 channel
| - 0xc1: YM2612 CSM - 10 channels
| - 0xc2: Neo Geo CSM (YM2610) - 18 channels
| - 0xc3: OPN CSM - 10 channels
| - 0xc4: PC-98 CSM - 20 channels
| - 0xc3: YM2203 CSM - 10 channels
| - 0xc4: YM2608 CSM - 20 channels
| - 0xc5: YM2610B CSM - 20 channels
| - 0xc6: K007232 - 2 channels
| - 0xc7: GA20 - 4 channels
@ -1441,7 +1441,7 @@ chips which aren't on this list don't have any flags.
- bit 4-6: channels (int)
- bit 7: multiplex (bool)
## 0x8d: OPN (YM2203) and 0xb6: OPN extended
## 0x8d: YM2203 and 0xb6: YM2203 extended
- bit 0-4: clockSel (int)
- 0: NTSC
@ -1455,7 +1455,7 @@ chips which aren't on this list don't have any flags.
- 1: /3
- 2: /2
## 0x8e: PC-98 (YM2608) and 0xb7: PC-98 extended
## 0x8e: YM2608 and 0xb7: YM2608 extended
- bit 0-4: clockSel (int)
- 0: 8MHz

View file

@ -54,6 +54,10 @@ String DivConfig::toBase64() {
return taEncodeBase64(data);
}
const std::map<String,String>& DivConfig::configMap() {
return conf;
}
void DivConfig::parseLine(const char* line) {
String key="";
String value="";
@ -171,6 +175,15 @@ String DivConfig::getString(String key, String fallback) const {
return fallback;
}
bool DivConfig::has(String key) {
try {
String test=conf.at(key);
} catch (std::out_of_range& e) {
return false;
}
return true;
}
void DivConfig::set(String key, bool value) {
if (value) {
conf[key]="true";

View file

@ -35,6 +35,9 @@ class DivConfig {
String toBase64();
bool save(const char* path);
// get the map
const std::map<String,String>& configMap();
// get a config value
bool getBool(String key, bool fallback) const;
int getInt(String key, int fallback) const;
@ -42,6 +45,9 @@ class DivConfig {
double getDouble(String key, double fallback) const;
String getString(String key, String fallback) const;
// check for existence
bool has(String key);
// set a config value
void set(String key, bool value);
void set(String key, int value);

View file

@ -23,6 +23,7 @@
#ifdef _WIN32
#include "winStuff.h"
#define CONFIG_FILE "\\furnace.cfg"
#define LOG_FILE "\\furnace.log"
#else
#ifdef __HAIKU__
#include <support/SupportDefs.h>

View file

@ -57,6 +57,7 @@
#include "platform/su.h"
#include "platform/swan.h"
#include "platform/lynx.h"
#include "platform/pokey.h"
#include "platform/zxbeeper.h"
#include "platform/bubsyswsg.h"
#include "platform/n163.h"
@ -201,12 +202,18 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
((DivPlatformGenesisExt*)dispatch)->setYMFM(eng->getConfInt("ym2612Core",0));
((DivPlatformGenesisExt*)dispatch)->setSoftPCM(false);
break;
case DIV_SYSTEM_YM2612_FRAC:
case DIV_SYSTEM_YM2612_CSM:
dispatch=new DivPlatformGenesisExt;
((DivPlatformGenesisExt*)dispatch)->setYMFM(eng->getConfInt("ym2612Core",0));
((DivPlatformGenesisExt*)dispatch)->setSoftPCM(false);
((DivPlatformGenesisExt*)dispatch)->setCSMChannel(6);
break;
case DIV_SYSTEM_YM2612_DUALPCM:
dispatch=new DivPlatformGenesis;
((DivPlatformGenesis*)dispatch)->setYMFM(eng->getConfInt("ym2612Core",0));
((DivPlatformGenesis*)dispatch)->setSoftPCM(true);
break;
case DIV_SYSTEM_YM2612_FRAC_EXT:
case DIV_SYSTEM_YM2612_DUALPCM_EXT:
dispatch=new DivPlatformGenesisExt;
((DivPlatformGenesisExt*)dispatch)->setYMFM(eng->getConfInt("ym2612Core",0));
((DivPlatformGenesisExt*)dispatch)->setSoftPCM(true);
@ -269,16 +276,16 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
case DIV_SYSTEM_TIA:
dispatch=new DivPlatformTIA;
break;
case DIV_SYSTEM_OPN:
case DIV_SYSTEM_YM2203:
dispatch=new DivPlatformYM2203;
break;
case DIV_SYSTEM_OPN_EXT:
case DIV_SYSTEM_YM2203_EXT:
dispatch=new DivPlatformYM2203Ext;
break;
case DIV_SYSTEM_PC98:
case DIV_SYSTEM_YM2608:
dispatch=new DivPlatformYM2608;
break;
case DIV_SYSTEM_PC98_EXT:
case DIV_SYSTEM_YM2608_EXT:
dispatch=new DivPlatformYM2608Ext;
break;
case DIV_SYSTEM_OPLL:
@ -339,6 +346,10 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
case DIV_SYSTEM_LYNX:
dispatch=new DivPlatformLynx;
break;
case DIV_SYSTEM_POKEY:
dispatch=new DivPlatformPOKEY;
((DivPlatformPOKEY*)dispatch)->setAltASAP(eng->getConfInt("pokeyCore",1)==1);
break;
case DIV_SYSTEM_QSOUND:
dispatch=new DivPlatformQSound;
break;

View file

@ -3295,7 +3295,7 @@ DivSample* DivEngine::sampleFromFile(const char* path) {
#endif
}
DivSample* DivEngine::sampleFromFileRaw(const char* path, DivSampleDepth depth, int channels, bool bigEndian, bool unsign) {
DivSample* DivEngine::sampleFromFileRaw(const char* path, DivSampleDepth depth, int channels, bool bigEndian, bool unsign, bool swapNibbles) {
if (song.sample.size()>=256) {
lastError="too many samples!";
return NULL;
@ -3461,6 +3461,14 @@ DivSample* DivEngine::sampleFromFileRaw(const char* path, DivSampleDepth depth,
}
delete[] buf;
// swap nibbles if needed
if (swapNibbles) {
unsigned char* b=(unsigned char*)sample->getCurBuf();
for (unsigned int i=0; i<sample->getCurBufLen(); i++) {
b[i]=(b[i]<<4)|(b[i]>>4);
}
}
BUSY_END;
return sample;
}
@ -4215,7 +4223,7 @@ bool DivEngine::deinitAudioBackend(bool dueToSwitchMaster) {
return true;
}
bool DivEngine::init() {
void DivEngine::preInit() {
// register systems
if (!systemsRegistered) registerSystems();
@ -4223,8 +4231,13 @@ bool DivEngine::init() {
initConfDir();
logD("config path: %s",configPath.c_str());
String logPath=configPath+DIR_SEPARATOR_STR+"furnace.log";
startLogFile(logPath.c_str());
loadConf();
}
bool DivEngine::init() {
loadSampleROMs();
// set default system preset

View file

@ -796,7 +796,7 @@ class DivEngine {
DivSample* sampleFromFile(const char* path);
// get raw sample
DivSample* sampleFromFileRaw(const char* path, DivSampleDepth depth, int channels, bool bigEndian, bool unsign);
DivSample* sampleFromFileRaw(const char* path, DivSampleDepth depth, int channels, bool bigEndian, bool unsign, bool swapNibbles);
// delete sample
void delSample(int index);
@ -1012,6 +1012,9 @@ class DivEngine {
// quit dispatch
void quitDispatch();
// pre-initialize the engine.
void preInit();
// initialize the engine.
bool init();

View file

@ -1207,8 +1207,8 @@ void DivEngine::convertOldFlags(unsigned int oldFlags, DivConfig& newFlags, DivS
break;
case DIV_SYSTEM_YM2612:
case DIV_SYSTEM_YM2612_EXT:
case DIV_SYSTEM_YM2612_FRAC:
case DIV_SYSTEM_YM2612_FRAC_EXT:
case DIV_SYSTEM_YM2612_DUALPCM:
case DIV_SYSTEM_YM2612_DUALPCM_EXT:
switch (oldFlags&0x7fffffff) {
case 0:
newFlags.set("clockSel",0);
@ -1295,8 +1295,8 @@ void DivEngine::convertOldFlags(unsigned int oldFlags, DivConfig& newFlags, DivS
newFlags.set("channels",(int)((oldFlags>>4)&7));
if (oldFlags&128) newFlags.set("multiplex",true);
break;
case DIV_SYSTEM_OPN:
case DIV_SYSTEM_OPN_EXT:
case DIV_SYSTEM_YM2203:
case DIV_SYSTEM_YM2203_EXT:
switch (oldFlags&31) {
case 0:
newFlags.set("clockSel",0);
@ -1329,8 +1329,8 @@ void DivEngine::convertOldFlags(unsigned int oldFlags, DivConfig& newFlags, DivS
break;
}
break;
case DIV_SYSTEM_PC98:
case DIV_SYSTEM_PC98_EXT:
case DIV_SYSTEM_YM2608:
case DIV_SYSTEM_YM2608_EXT:
switch (oldFlags&31) {
case 0:
newFlags.set("clockSel",0);
@ -2484,14 +2484,14 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
case DIV_SYSTEM_YM2610_FULL_EXT:
case DIV_SYSTEM_YM2610B:
case DIV_SYSTEM_YM2610B_EXT:
case DIV_SYSTEM_OPN:
case DIV_SYSTEM_OPN_EXT:
case DIV_SYSTEM_PC98:
case DIV_SYSTEM_PC98_EXT:
case DIV_SYSTEM_YM2203:
case DIV_SYSTEM_YM2203_EXT:
case DIV_SYSTEM_YM2608:
case DIV_SYSTEM_YM2608_EXT:
case DIV_SYSTEM_YM2612:
case DIV_SYSTEM_YM2612_EXT:
case DIV_SYSTEM_YM2612_FRAC:
case DIV_SYSTEM_YM2612_FRAC_EXT:
case DIV_SYSTEM_YM2612_DUALPCM:
case DIV_SYSTEM_YM2612_DUALPCM_EXT:
opnCount++;
break;
default:
@ -2514,12 +2514,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
if (ds.version<125) {
for (int i=0; i<ds.systemLen; i++) {
if (ds.system[i]==DIV_SYSTEM_YM2612_EXT ||
ds.system[i]==DIV_SYSTEM_YM2612_FRAC_EXT ||
ds.system[i]==DIV_SYSTEM_YM2612_DUALPCM_EXT ||
ds.system[i]==DIV_SYSTEM_YM2610_EXT ||
ds.system[i]==DIV_SYSTEM_YM2610_FULL_EXT ||
ds.system[i]==DIV_SYSTEM_YM2610B_EXT ||
ds.system[i]==DIV_SYSTEM_OPN_EXT ||
ds.system[i]==DIV_SYSTEM_PC98_EXT) {
ds.system[i]==DIV_SYSTEM_YM2203_EXT ||
ds.system[i]==DIV_SYSTEM_YM2608_EXT) {
ds.systemFlags[i].set("noExtMacros",true);
}
}

View file

@ -24,6 +24,7 @@
float* DivFilterTables::cubicTable=NULL;
float* DivFilterTables::sincTable=NULL;
float* DivFilterTables::sincTable8=NULL;
float* DivFilterTables::sincIntegralTable=NULL;
// portions from Schism Tracker (scripts/lutgen.c)
@ -44,7 +45,7 @@ float* DivFilterTables::getCubicTable() {
return cubicTable;
}
float* DivFilterTables:: getSincTable() {
float* DivFilterTables::getSincTable() {
if (sincTable==NULL) {
logD("initializing sinc table.");
sincTable=new float[65536];
@ -64,6 +65,26 @@ float* DivFilterTables:: getSincTable() {
return sincTable;
}
float* DivFilterTables::getSincTable8() {
if (sincTable8==NULL) {
logD("initializing sinc table (8).");
sincTable8=new float[32768];
sincTable8[0]=1.0f;
for (int i=1; i<32768; i++) {
int mapped=((i&8191)<<2)|(i>>13);
double x=(double)i*M_PI/8192.0;
sincTable8[mapped]=sin(x)/x;
}
for (int i=0; i<32768; i++) {
int mapped=((i&8191)<<2)|(i>>13);
sincTable8[mapped]*=pow(cos(M_PI*(double)i/65536.0),2.0);
}
}
return sincTable8;
}
float* DivFilterTables::getSincIntegralTable() {
if (sincIntegralTable==NULL) {
logD("initializing sinc integral table.");

View file

@ -21,6 +21,7 @@ class DivFilterTables {
public:
static float* cubicTable;
static float* sincTable;
static float* sincTable8;
static float* sincIntegralTable;
/**
@ -35,6 +36,12 @@ class DivFilterTables {
*/
static float* getSincTable();
/**
* get a 8192x4 one-side sine-windowed sinc table.
* @return the table.
*/
static float* getSincTable8();
/**
* get a 8192x8 one-side sine-windowed sinc integral table.
* @return the table.

View file

@ -353,9 +353,9 @@ void DivPlatformArcade::tick(bool sysTick) {
chan[i].freq=chan[i].baseFreq+(chan[i].pitch>>1)-64+chan[i].pitch2;
if (!parent->song.oldArpStrategy) {
if (chan[i].fixedArp) {
chan[i].freq=(chan[i].baseNoteOverride<<7)+(chan[i].pitch>>1)-64+chan[i].pitch2;
chan[i].freq=(chan[i].baseNoteOverride<<6)+(chan[i].pitch>>1)-64+chan[i].pitch2;
} else {
chan[i].freq+=chan[i].arpOff<<7;
chan[i].freq+=chan[i].arpOff<<6;
}
}
if (chan[i].freq<0) chan[i].freq=0;

View file

@ -830,6 +830,9 @@ void DivPlatformAY8910::setFlags(const DivConfig& flags) {
case 14:
chipClock=1536000;
break;
case 15:
chipClock=38400*13*4; // 31948800/16
break;
default:
chipClock=COLOR_NTSC/2.0;
break;

View file

@ -83,6 +83,8 @@
return 2; \
}
#define IS_EXTCH_MUTED (isOpMuted[0] && isOpMuted[1] && isOpMuted[2] && isOpMuted[3])
class DivPlatformOPN: public DivPlatformFMBase {
protected:
const unsigned short ADDR_MULT_DT=0x30;
@ -147,17 +149,21 @@ class DivPlatformOPN: public DivPlatformFMBase {
double fmFreqBase;
unsigned int fmDivBase;
unsigned int ayDiv;
unsigned char csmChan;
unsigned char lfoValue;
bool extSys;
DivConfig ayFlags;
friend void putDispatchChip(void*,int);
friend void putDispatchChan(void*,int,int);
DivPlatformOPN(double f=9440540.0, unsigned int d=72, unsigned int a=32, bool isExtSys=false):
DivPlatformOPN(double f=9440540.0, unsigned int d=72, unsigned int a=32, bool isExtSys=false, unsigned char cc=255):
DivPlatformFMBase(),
fmFreqBase(f),
fmDivBase(d),
ayDiv(a),
csmChan(cc),
lfoValue(0),
extSys(isExtSys) {}
};

View file

@ -479,7 +479,7 @@ void DivPlatformGenesis::tick(bool sysTick) {
}
}
for (int i=0; i<7; i++) {
for (int i=0; i<csmChan; i++) {
if (i==2 && extMode) continue;
if (chan[i].freqChanged) {
if (parent->song.linearPitch==2) {
@ -554,7 +554,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM);
if (c.chan==7 && extMode && softPCM) { // CSM
if (c.chan==csmChan && extMode) { // CSM
chan[c.chan].macroInit(ins);
chan[c.chan].insChanged=false;
@ -691,7 +691,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
break;
}
case DIV_CMD_NOTE_OFF:
if (c.chan>=5 && c.chan<7) {
if (c.chan>=5 && c.chan<csmChan) {
chan[c.chan].dacSample=-1;
if (dumpWrites) addWrite(0xffff0002,0);
if (parent->song.brokenDACMode) {
@ -786,7 +786,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
}
break;
}
if (c.chan==7) {
if (c.chan==csmChan) {
int destFreq=NOTE_PERIODIC(c.value2);
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
@ -855,7 +855,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
break;
}
case DIV_CMD_LEGATO: {
if (c.chan==7) {
if (c.chan==csmChan) {
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
} else if (c.chan>=5 && chan[c.chan].furnaceDac && chan[c.chan].dacMode) {
chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value,false);
@ -1222,7 +1222,7 @@ void DivPlatformGenesis::poke(std::vector<DivRegWrite>& wlist) {
}
int DivPlatformGenesis::getPortaFloor(int ch) {
return (ch>5)?12:0;
return 0;
}
void DivPlatformGenesis::setYMFM(bool use) {

View file

@ -86,8 +86,6 @@ class DivPlatformGenesis: public DivPlatformOPN {
ymfm::ym2612* fm_ymfm;
ymfm::ym2612::output_data out_ymfm;
DivYM2612Interface iface;
unsigned char lfoValue;
int softPCMTimer;
@ -133,7 +131,7 @@ class DivPlatformGenesis: public DivPlatformOPN {
int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags);
void quit();
DivPlatformGenesis():
DivPlatformOPN(9440540.0, 72, 32) {}
DivPlatformOPN(9440540.0, 72, 32, false, 7) {}
~DivPlatformGenesis();
};
#endif

View file

@ -25,7 +25,6 @@
#define CHIP_DIVIDER fmDivBase
#define IS_REALLY_MUTED(x) (isMuted[x] && (x<5 || !softPCM || (isMuted[5] && isMuted[6])))
#define IS_EXTCH_MUTED (isOpMuted[0] && isOpMuted[1] && isOpMuted[2] && isOpMuted[3])
int DivPlatformGenesisExt::dispatch(DivCommand c) {
if (c.chan<2) {
@ -220,14 +219,14 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
rWrite(chanOffs[2]+ADDR_FB_ALG,(chan[2].state.alg&7)|(chan[2].state.fb<<3));
break;
}
case DIV_CMD_FM_MULT: { // TODO
case DIV_CMD_FM_MULT: {
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
op.mult=c.value2&15;
rWrite(baseAddr+0x30,(op.mult&15)|(dtTable[op.dt&7]<<4));
break;
}
case DIV_CMD_FM_TL: { // TODO
case DIV_CMD_FM_TL: {
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
op.tl=c.value2;
@ -454,7 +453,7 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
}
}
if (writeSomething) {
if (chan[7].active) { // CSM
if (chan[csmChan].active) { // CSM
writeMask^=0xf0;
}
/*printf(
@ -588,18 +587,18 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
}
}
if (extMode && softPCM) {
if (chan[7].freqChanged) {
chan[7].freq=parent->calcFreq(chan[7].baseFreq,chan[7].pitch,chan[7].fixedArp?chan[7].baseNoteOverride:chan[7].arpOff,chan[7].fixedArp,true,0,chan[7].pitch2,chipClock,CHIP_DIVIDER);
if (chan[7].freq<1) chan[7].freq=1;
if (chan[7].freq>1024) chan[7].freq=1024;
int wf=0x400-chan[7].freq;
if (extMode) {
if (chan[csmChan].freqChanged) {
chan[csmChan].freq=parent->calcFreq(chan[csmChan].baseFreq,chan[csmChan].pitch,chan[csmChan].fixedArp?chan[csmChan].baseNoteOverride:chan[csmChan].arpOff,chan[csmChan].fixedArp,true,0,chan[csmChan].pitch2,chipClock,CHIP_DIVIDER);
if (chan[csmChan].freq<1) chan[csmChan].freq=1;
if (chan[csmChan].freq>1024) chan[csmChan].freq=1024;
int wf=0x400-chan[csmChan].freq;
immWrite(0x24,wf>>2);
immWrite(0x25,wf&3);
chan[7].freqChanged=false;
chan[csmChan].freqChanged=false;
}
if (chan[7].keyOff || chan[7].keyOn) {
if (chan[csmChan].keyOff || chan[csmChan].keyOn) {
writeNoteOn=true;
for (int i=0; i<4; i++) {
writeMask|=opChan[i].active<<(4+i);
@ -608,7 +607,7 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
}
if (writeNoteOn) {
if (chan[7].active) { // CSM
if (chan[csmChan].active) { // CSM
writeMask^=0xf0;
}
/*printf(
@ -621,14 +620,14 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
immWrite(0x28,writeMask);
}
if (extMode && softPCM) {
if (chan[7].keyOn) {
if (extMode) {
if (chan[csmChan].keyOn) {
immWrite(0x27,0x81);
chan[7].keyOn=false;
chan[csmChan].keyOn=false;
}
if (chan[7].keyOff) {
if (chan[csmChan].keyOff) {
immWrite(0x27,0x40);
chan[7].keyOff=false;
chan[csmChan].keyOff=false;
}
}
}
@ -686,10 +685,10 @@ void DivPlatformGenesisExt::forceIns() {
opChan[i].freqChanged=true;
}
}
if (extMode && softPCM && chan[7].active) { // CSM
chan[7].insChanged=true;
chan[7].freqChanged=true;
chan[7].keyOn=true;
if (extMode && chan[csmChan].active) { // CSM
chan[csmChan].insChanged=true;
chan[csmChan].freqChanged=true;
chan[csmChan].keyOn=true;
}
}
@ -701,7 +700,7 @@ void* DivPlatformGenesisExt::getChanState(int ch) {
DivMacroInt* DivPlatformGenesisExt::getChanMacroInt(int ch) {
if (ch>=6) return &chan[ch-3].std;
if (ch>=2) return NULL; // currently not implemented
if (ch>=2) return &opChan[ch-2].std;
return &chan[ch].std;
}
@ -747,6 +746,10 @@ int DivPlatformGenesisExt::getPortaFloor(int ch) {
return (ch>8)?12:0;
}
void DivPlatformGenesisExt::setCSMChannel(unsigned char ch) {
csmChan=ch;
}
int DivPlatformGenesisExt::init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags) {
DivPlatformGenesis::init(parent,channels,sugRate,flags);
for (int i=0; i<4; i++) {

View file

@ -42,6 +42,7 @@ class DivPlatformGenesisExt: public DivPlatformGenesis {
bool keyOffAffectsPorta(int ch);
void notifyInsChange(int ins);
int getPortaFloor(int ch);
void setCSMChannel(unsigned char ch);
int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags);
void quit();
~DivPlatformGenesisExt();

View file

@ -529,10 +529,10 @@ void DivPlatformK007232::renderSamples(int sysID) {
}
const int length=s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT);
int actualLength=MIN((int)(getSampleMemCapacity()-memPos)-1,length);
int actualLength=MIN((int)(getSampleMemCapacity()-memPos)-2,length);
if (actualLength>0) {
if (actualLength>131072-1) {
actualLength=131072-1;
if (actualLength>131072-2) {
actualLength=131072-2;
}
if ((memPos&0xfe0000)!=((memPos+actualLength+1)&0xfe0000)) {
memPos=(memPos+0x1ffff)&0xfe0000;

View file

@ -356,7 +356,7 @@ void DivPlatformMSM6295::renderSamples(int sysID) {
// sample data
size_t memPos=128*8;
int sampleCount=parent->song.sampleLen;
if (sampleCount>128) sampleCount=128;
if (sampleCount>127) sampleCount=127;
for (int i=0; i<sampleCount; i++) {
DivSample* s=parent->song.sample[i];
if (!s->renderOn[0][sysID]) {

View file

@ -20,6 +20,7 @@
#define _USE_MATH_DEFINES
#include "pcmdac.h"
#include "../engine.h"
#include "../filter.h"
#include <math.h>
// to ease the driver, freqency register is a 8.16 counter relative to output sample rate
@ -103,7 +104,50 @@ void DivPlatformPCMDAC::acquire(short* bufL, short* bufR, size_t start, size_t l
}
}
if (chan[0].audPos>=0 && chan[0].audPos<(int)s->samples) {
output=s->data16[chan[0].audPos];
int s_4=((chan[0].audPos-4)>=0)?s->data16[chan[0].audPos-4]:0;
int s_3=((chan[0].audPos-3)>=0)?s->data16[chan[0].audPos-3]:0;
int s_2=((chan[0].audPos-2)>=0)?s->data16[chan[0].audPos-2]:0;
int s_1=((chan[0].audPos-1)>=0)?s->data16[chan[0].audPos-1]:0;
int s0=s->data16[chan[0].audPos];
int s1=((chan[0].audPos+1)<(int)s->samples)?s->data16[chan[0].audPos+1]:0;
int s2=((chan[0].audPos+2)<(int)s->samples)?s->data16[chan[0].audPos+2]:0;
int s3=((chan[0].audPos+3)<(int)s->samples)?s->data16[chan[0].audPos+3]:0;
switch (interp) {
case 1: // linear
output=s0+((s1-s0)*(chan[0].audSub&0xffff)>>16);
break;
case 2: { // cubic
float* cubicTable=DivFilterTables::getCubicTable();
float* t=&cubicTable[((chan[0].audSub&0xffff)>>6)<<2];
float result=(float)s_1*t[0]+(float)s0*t[1]+(float)s1*t[2]+(float)s2*t[3];
if (result<-32768) result=-32768;
if (result>32767) result=32767;
output=result;
break;
}
case 3: { // sinc
float* sincTable=DivFilterTables::getSincTable8();
float* t1=&sincTable[(8191-((chan[0].audSub&0xffff)>>3))<<2];
float* t2=&sincTable[((chan[0].audSub&0xffff)>>3)<<2];
float result=(
s_4*t2[3]+
s_3*t2[2]+
s_2*t2[1]+
s_1*t2[0]+
s0*t1[0]+
s1*t1[1]+
s2*t1[2]+
s3*t1[3]
);
if (result<-32768) result=-32768;
if (result>32767) result=32767;
output=result;
break;
}
default: // none
output=s0;
break;
}
}
} else {
chan[0].sample=-1;
@ -398,6 +442,8 @@ void DivPlatformPCMDAC::setFlags(const DivConfig& flags) {
chipClock=rate;
outDepth=(flags.getInt("outDepth",15))&15;
outStereo=flags.getBool("stereo",true);
interp=flags.getInt("interpolation",0);
oscBuf->rate=rate;
}
int DivPlatformPCMDAC::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {

View file

@ -55,6 +55,12 @@ class DivPlatformPCMDAC: public DivDispatch {
DivDispatchOscBuffer* oscBuf;
bool isMuted;
int outDepth;
// valid values:
// - 0: none
// - 1: linear
// - 2: cubic spline
// - 3: sinc
int interp;
bool outStereo;
friend void putDispatchChip(void*,int);

View file

@ -26,7 +26,20 @@
#define CHIP_DIVIDER 1
const char* regCheatSheetPokeMini[]={
"Period", "0",
"TMR3_SCALE", "1C",
"TMR3_OSC", "1D",
"TMR3_CTRL_L", "48",
"TMR3_CTRL_H", "49",
"TMR3_PRE_L", "4A",
"TMR3_PRE_H", "4B",
"TMR3_PVT_L", "4C",
"TMR3_PVT_H", "4D",
"TMR3_CNT_L", "4E",
"TMR3_CNT_H", "4F",
"IO_DIR", "60",
"IO_DATA", "61",
"AUD_CTRL", "70",
"AUD_VOL", "71",
NULL
};
@ -42,6 +55,37 @@ const char** DivPlatformPokeMini::getRegisterSheet() {
return regCheatSheetPokeMini;
}
void DivPlatformPokeMini::rWrite(unsigned char addr, unsigned char val) {
if (addr<128) regPool[addr]=val;
switch (addr) {
case 0x1c:
// ignore
break;
case 0x1d:
// ignore
break;
case 0x48: case 0x49:
on=val&4;
if (val&2) pos=0;
break;
case 0x4a:
preset=(preset&0xff00)|val;
break;
case 0x4b:
preset=(preset&0xff)|(val<<8);
break;
case 0x4c:
pivot=(pivot&0xff00)|val;
break;
case 0x4d:
pivot=(pivot&0xff)|(val<<8);
break;
case 0x71:
vol=val&3;
break;
}
}
void DivPlatformPokeMini::acquire(short* bufL, short* bufR, size_t start, size_t len) {
int out=0;
for (size_t i=start; i<start+len; i++) {
@ -72,7 +116,7 @@ void DivPlatformPokeMini::tick(bool sysTick) {
chan[i].std.next();
if (chan[i].std.vol.had) {
chan[i].outVol=VOL_SCALE_LINEAR(chan[i].vol,chan[i].std.vol.val,2);
vol=(chan[i].outVol==2)?3:chan[i].outVol;
rWrite(0x71,(chan[i].outVol==2)?3:chan[i].outVol);
}
if (NEW_ARP_STRAT) {
chan[i].handleArp();
@ -100,13 +144,16 @@ void DivPlatformPokeMini::tick(bool sysTick) {
if (chan[i].freq<0) chan[i].freq=0;
if (chan[i].freq>65535) chan[i].freq=65535;
if (chan[i].keyOn) {
on=true;
rWrite(0x48,4);
}
if (chan[i].keyOff) {
on=false;
rWrite(0x48,0);
}
preset=chan[i].freq;
pivot=(chan[i].duty*preset)>>8;
rWrite(0x4a,chan[i].freq&0xff);
rWrite(0x4b,chan[i].freq>>8);
int pvt=(chan[i].duty*chan[i].freq)>>8;
rWrite(0x4c,pvt&0xff);
rWrite(0x4d,pvt>>8);
if (chan[i].keyOn) chan[i].keyOn=false;
if (chan[i].keyOff) chan[i].keyOff=false;
chan[i].freqChanged=false;
@ -122,7 +169,7 @@ int DivPlatformPokeMini::dispatch(DivCommand c) {
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
}
vol=(chan[c.chan].outVol==2)?3:chan[c.chan].outVol;
rWrite(0x71,(chan[c.chan].outVol==2)?3:chan[c.chan].outVol);
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_POKEMINI));
@ -151,7 +198,7 @@ int DivPlatformPokeMini::dispatch(DivCommand c) {
chan[c.chan].outVol=c.value;
}
if (chan[c.chan].active) {
on=chan[c.chan].vol;
rWrite(0x71,(chan[c.chan].outVol==2)?3:chan[c.chan].outVol);
}
}
break;
@ -186,14 +233,13 @@ int DivPlatformPokeMini::dispatch(DivCommand c) {
break;
}
case DIV_CMD_LEGATO:
if (c.chan==3) break;
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((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_BEEPER));
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_POKEMINI));
}
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;
@ -239,18 +285,11 @@ DivDispatchOscBuffer* DivPlatformPokeMini::getOscBuffer(int ch) {
}
unsigned char* DivPlatformPokeMini::getRegisterPool() {
if (on) {
regPool[0]=preset;
regPool[1]=preset>>8;
} else {
regPool[0]=0;
regPool[1]=0;
}
return regPool;
}
int DivPlatformPokeMini::getRegisterPoolSize() {
return 2;
return 128;
}
void DivPlatformPokeMini::reset() {
@ -272,7 +311,7 @@ void DivPlatformPokeMini::reset() {
pivot=0;
elapsedMain=0;
memset(regPool,0,2);
memset(regPool,0,128);
}
bool DivPlatformPokeMini::keyOffAffectsArp(int ch) {
@ -294,11 +333,11 @@ void DivPlatformPokeMini::notifyInsDeletion(void* ins) {
}
void DivPlatformPokeMini::poke(unsigned int addr, unsigned short val) {
// ???
rWrite(addr,val);
}
void DivPlatformPokeMini::poke(std::vector<DivRegWrite>& wlist) {
// ???
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
}
int DivPlatformPokeMini::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {

View file

@ -37,9 +37,11 @@ class DivPlatformPokeMini: public DivDispatch {
int pos;
unsigned char timerScale, vol;
unsigned short preset, pivot;
unsigned char regPool[2];
unsigned char regPool[128];
unsigned short elapsedMain;
void rWrite(unsigned char addr, unsigned char val);
friend void putDispatchChip(void*,int);
friend void putDispatchChan(void*,int,int);

View file

@ -0,0 +1,504 @@
/**
* 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 "pokey.h"
#include "../engine.h"
#include "../../ta-log.h"
#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} }
#define CHIP_DIVIDER 1
const char* regCheatSheetPOKEY[]={
"AUDF1", "0",
"AUDC1", "1",
"AUDF2", "2",
"AUDC2", "3",
"AUDF3", "4",
"AUDC3", "5",
"AUDF4", "6",
"AUDC4", "7",
"AUDCTL", "8",
NULL
};
// LLsLSsLLsSLsLLn
const unsigned char snapPeriodLong[15]={
0, 1, 1, 3, 3, 6, 6, 7, 7, 10, 10, 12, 12, 13, 13
};
const unsigned char snapPeriodShort[15]={
2, 2, 2, 2, 5, 5, 5, 8, 8, 11, 11, 11, 11, 17, 17
};
// LsSLsLLnLLsLSsL
const unsigned char snapPeriodLong16[15]={
0, 0, 3, 3, 3, 5, 6, 6, 8, 9, 9, 11, 11, 14, 14
};
const unsigned char snapPeriodShort16[15]={
1, 1, 1, 4, 4, 4, 4, 4, 10, 10, 10, 10, 13, 13, 13
};
const unsigned char waveMap[8]={
0, 1, 2, 3, 4, 5, 6, 6
};
const char** DivPlatformPOKEY::getRegisterSheet() {
return regCheatSheetPOKEY;
}
void DivPlatformPOKEY::acquire(short* bufL, short* bufR, size_t start, size_t len) {
if (useAltASAP) {
acquireASAP(bufL, start, len);
} else {
acquireMZ(bufL, start, len);
}
}
void DivPlatformPOKEY::acquireMZ(short* buf, size_t start, size_t len) {
for (size_t h=start; h<start+len; h++) {
while (!writes.empty()) {
QueuedWrite w=writes.front();
Update_pokey_sound_mz(&pokey,w.addr,w.val,0);
regPool[w.addr&0x0f]=w.val;
writes.pop();
}
mzpokeysnd_process_16(&pokey,&buf[h],1);
if (++oscBufDelay>=14) {
oscBufDelay=0;
oscBuf[0]->data[oscBuf[0]->needle++]=pokey.outvol_0<<11;
oscBuf[1]->data[oscBuf[1]->needle++]=pokey.outvol_1<<11;
oscBuf[2]->data[oscBuf[2]->needle++]=pokey.outvol_2<<11;
oscBuf[3]->data[oscBuf[3]->needle++]=pokey.outvol_3<<11;
}
}
}
void DivPlatformPOKEY::acquireASAP(short* buf, size_t start, size_t len) {
while (!writes.empty()) {
QueuedWrite w=writes.front();
altASAP.write(w.addr, w.val);
writes.pop();
}
for (size_t h=start; h<start+len; h++) {
if (++oscBufDelay>=2) {
oscBufDelay=0;
buf[h]=altASAP.sampleAudio(oscBuf);
} else {
buf[h]=altASAP.sampleAudio();
}
}
}
void DivPlatformPOKEY::tick(bool sysTick) {
for (int i=0; i<4; i++) {
chan[i].std.next();
if (chan[i].std.vol.had) {
chan[i].outVol=VOL_SCALE_LINEAR(chan[i].vol&15,MIN(15,chan[i].std.vol.val),15);
chan[i].ctlChanged=true;
}
if (NEW_ARP_STRAT) {
chan[i].handleArp();
} else if (chan[i].std.arp.had) {
if (!chan[i].inPorta) {
chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val));
}
chan[i].freqChanged=true;
}
if (chan[i].std.duty.had) {
audctl=chan[i].std.duty.val;
audctlChanged=true;
}
if (chan[i].std.wave.had) {
chan[i].wave=chan[i].std.wave.val;
chan[i].ctlChanged=true;
chan[i].freqChanged=true;
}
if (chan[i].std.pitch.had) {
if (chan[i].std.pitch.mode) {
chan[i].pitch2+=chan[i].std.pitch.val;
CLAMP_VAR(chan[i].pitch2,-32768,32767);
} else {
chan[i].pitch2=chan[i].std.pitch.val;
}
chan[i].freqChanged=true;
}
}
if (audctlChanged) {
audctlChanged=false;
rWrite(8,audctl);
for (int i=0; i<4; i++) {
chan[i].freqChanged=true;
chan[i].ctlChanged=true;
}
}
for (int i=0; i<4; i++) {
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
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 ((i==0 && !(audctl&64)) || (i==2 && !(audctl&32)) || i==1 || i==3) {
chan[i].freq/=7;
switch (chan[i].wave) {
case 6:
chan[i].freq/=5;
chan[i].freq>>=1;
break;
case 7:
if (audctl&1) {
chan[i].freq/=5;
} else {
chan[i].freq/=15;
}
chan[i].freq>>=1;
break;
default:
chan[i].freq>>=2;
break;
}
} else if ((i==0 && audctl&64) || (i==2 && audctl&32)) {
switch (chan[i].wave) {
case 6:
chan[i].freq<<=1;
chan[i].freq/=5;
break;
case 7:
chan[i].freq<<=1;
chan[i].freq/=15;
break;
}
}
if (audctl&1 && !((i==0 && audctl&64) || (i==2 && audctl&32))) {
chan[i].freq>>=2;
}
if (--chan[i].freq<0) chan[i].freq=0;
// snap buzz periods
int minFreq8=255;
if (chan[i].wave==7) {
if ((i==0 && audctl&64) || (i==2 && audctl&32)) {
chan[i].freq=15*(chan[i].freq/15)+snapPeriodLong16[(chan[i].freq%15)]+1;
} else {
if (!(audctl&1)) chan[i].freq=15*(chan[i].freq/15)+snapPeriodLong[(chan[i].freq%15)];
}
} else if (chan[i].wave==6) {
if ((i==0 && audctl&64) || (i==2 && audctl&32)) {
chan[i].freq=15*(chan[i].freq/15)+snapPeriodShort16[(chan[i].freq%15)]+1;
} else {
if (!(audctl&1)) chan[i].freq=15*(chan[i].freq/15)+snapPeriodShort[(chan[i].freq%15)];
}
minFreq8=251;
}
if ((i==0 && audctl&16) || (i==2 && audctl&8)) {
if (chan[i].freq>65535) chan[i].freq=65535;
} else {
if (chan[i].freq>minFreq8) chan[i].freq=minFreq8;
}
// write frequency
if ((i==1 && audctl&16) || (i==3 && audctl&8)) {
// ignore - channel is paired
} else {
rWrite(i<<1,chan[i].freq&0xff);
if ((i==0 && audctl&16) || (i==2 && audctl&8)) {
rWrite((1+i)<<1,chan[i].freq>>8);
}
}
if (chan[i].keyOff) {
chan[i].ctlChanged=true;
}
if (chan[i].keyOn) chan[i].keyOn=false;
if (chan[i].keyOff) chan[i].keyOff=false;
chan[i].freqChanged=false;
}
if (chan[i].ctlChanged) {
unsigned char val=((chan[i].active && !isMuted[i])?(chan[i].outVol&15):0)|(waveMap[chan[i].wave&7]<<5);
chan[i].ctlChanged=false;
if ((i==1 && audctl&16) || (i==3 && audctl&8)) {
// ignore - channel is paired
} else if ((i==0 && audctl&16) || (i==0 && audctl&8)) {
rWrite(1+(i<<1),0);
rWrite(3+(i<<1),val);
} else {
rWrite(1+(i<<1),val);
}
}
}
}
int DivPlatformPOKEY::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_POKEY);
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].keyOn=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].ctlChanged=true;
}
chan[c.chan].insChanged=false;
break;
}
case DIV_CMD_NOTE_OFF:
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].ctlChanged=true;
}
}
}
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].ctlChanged=true;
break;
case DIV_CMD_STD_NOISE_MODE:
audctl=c.value&0xff;
audctlChanged=true;
break;
case DIV_CMD_NOTE_PORTA: {
int destFreq=NOTE_PERIODIC(c.value2);
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
chan[c.chan].baseFreq+=c.value;
if (chan[c.chan].baseFreq>=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
} else {
chan[c.chan].baseFreq-=c.value;
if (chan[c.chan].baseFreq<=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
}
chan[c.chan].freqChanged=true;
if (return2) {
chan[c.chan].inPorta=false;
return 2;
}
break;
}
case DIV_CMD_LEGATO:
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
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_POKEY));
}
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 15;
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_ALWAYS_SET_VOLUME:
return 1;
break;
default:
break;
}
return 1;
}
void DivPlatformPOKEY::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
chan[ch].ctlChanged=true;
}
void DivPlatformPOKEY::forceIns() {
for (int i=0; i<4; i++) {
chan[i].insChanged=true;
chan[i].ctlChanged=true;
chan[i].freqChanged=true;
}
audctlChanged=true;
}
void* DivPlatformPOKEY::getChanState(int ch) {
return &chan[ch];
}
DivMacroInt* DivPlatformPOKEY::getChanMacroInt(int ch) {
return &chan[ch].std;
}
DivDispatchOscBuffer* DivPlatformPOKEY::getOscBuffer(int ch) {
return oscBuf[ch];
}
unsigned char* DivPlatformPOKEY::getRegisterPool() {
if (useAltASAP) {
return const_cast<unsigned char*>(altASAP.getRegisterPool());
} else {
return regPool;
}
}
int DivPlatformPOKEY::getRegisterPoolSize() {
return 9;
}
void DivPlatformPOKEY::reset() {
while (!writes.empty()) writes.pop();
memset(regPool,0,16);
for (int i=0; i<4; i++) {
chan[i]=DivPlatformPOKEY::Channel();
chan[i].std.setEngine(parent);
}
if (dumpWrites) {
addWrite(0xffffffff,0);
}
if (useAltASAP) {
altASAP.reset();
} else {
ResetPokeyState(&pokey);
}
audctl=0;
audctlChanged=true;
}
bool DivPlatformPOKEY::keyOffAffectsArp(int ch) {
return true;
}
float DivPlatformPOKEY::getPostAmp() {
return 2.0f;
}
void DivPlatformPOKEY::notifyInsDeletion(void* ins) {
for (int i=0; i<4; i++) {
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
}
}
void DivPlatformPOKEY::setFlags(const DivConfig& flags) {
if (flags.getInt("clockSel",0)) {
chipClock=COLOR_PAL*2.0/5.0;
} else {
chipClock=COLOR_NTSC/2.0;
}
CHECK_CUSTOM_CLOCK;
if (useAltASAP) {
rate=chipClock/7;
for (int i=0; i<4; i++) {
oscBuf[i]->rate=rate/2;
}
altASAP.init(chipClock,rate);
} else {
rate=chipClock;
for (int i=0; i<4; i++) {
oscBuf[i]->rate=rate/14;
}
}
}
void DivPlatformPOKEY::poke(unsigned int addr, unsigned short val) {
rWrite(addr,val);
}
void DivPlatformPOKEY::poke(std::vector<DivRegWrite>& wlist) {
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
}
int DivPlatformPOKEY::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {
parent=p;
dumpWrites=false;
skipRegisterWrites=false;
oscBufDelay=0;
for (int i=0; i<4; i++) {
isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer;
}
if (!useAltASAP) {
MZPOKEYSND_Init(&pokey);
}
setFlags(flags);
reset();
return 6;
}
void DivPlatformPOKEY::quit() {
for (int i=0; i<4; i++) {
delete oscBuf[i];
}
}
void DivPlatformPOKEY::setAltASAP(bool value) {
useAltASAP=value;
}
DivPlatformPOKEY::~DivPlatformPOKEY() {
}

View file

@ -0,0 +1,87 @@
/**
* 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 _POKEY_H
#define _POKEY_H
#include "../dispatch.h"
#include <queue>
extern "C" {
#include "sound/pokey/mzpokeysnd.h"
}
#include "sound/pokey/AltASAP.hpp"
class DivPlatformPOKEY: public DivDispatch {
struct Channel: public SharedChannel<int> {
unsigned char wave;
bool ctlChanged;
Channel():
SharedChannel<int>(15),
wave(5),
ctlChanged(true) {}
};
Channel chan[4];
DivDispatchOscBuffer* oscBuf[4];
bool isMuted[4];
struct QueuedWrite {
unsigned char addr;
unsigned char val;
QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {}
};
std::queue<QueuedWrite> writes;
unsigned char audctl;
bool audctlChanged;
unsigned char oscBufDelay;
PokeyState pokey;
AltASAP::Pokey altASAP;
bool useAltASAP;
unsigned char regPool[16];
friend void putDispatchChip(void*,int);
friend void putDispatchChan(void*,int,int);
public:
void acquire(short* bufL, short* bufR, size_t start, size_t len);
void acquireMZ(short* buf, size_t start, size_t len);
void acquireASAP(short* buf, size_t start, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
DivMacroInt* getChanMacroInt(int ch);
DivDispatchOscBuffer* getOscBuffer(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();
void reset();
void forceIns();
void tick(bool sysTick=true);
void muteChannel(int ch, bool mute);
bool keyOffAffectsArp(int ch);
float getPostAmp();
void setFlags(const DivConfig& flags);
void notifyInsDeletion(void* ins);
void poke(unsigned int addr, unsigned short val);
void poke(std::vector<DivRegWrite>& wlist);
const char** getRegisterSheet();
int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags);
void quit();
void setAltASAP(bool useAltASAP);
~DivPlatformPOKEY();
};
#endif

View file

@ -61,6 +61,11 @@ void DivPlatformRF5C68::acquire(short* bufL, short* bufR, size_t start, size_t l
buf[8],buf[9],buf[10],buf[11],buf[12],buf[13],buf[14],buf[15]
};
size_t pos=start;
for (int i=0; i<16; i++) {
memset(buf[i],0,256*sizeof(short));
}
while (len > 0) {
size_t blockLen=MIN(len,256);
short* bufPtrs[2]={&bufL[pos],&bufR[pos]};

View file

@ -96,24 +96,16 @@ void DivPlatformSegaPCM::tick(bool sysTick) {
}
if (parent->song.newSegaPCM) if (chan[i].std.panL.had) {
if (chan[i].isNewSegaPCM) {
chan[i].chPanL=chan[i].std.panL.val&127;
chan[i].chVolL=(chan[i].outVol*chan[i].chPanL)/127;
} else {
chan[i].chVolL=chan[i].std.panL.val&127;
}
chan[i].chPanL=chan[i].std.panL.val&127;
chan[i].chVolL=(chan[i].outVol*chan[i].chPanL)/127;
if (dumpWrites) {
addWrite(0x10002+(i<<3),chan[i].chVolL);
}
}
if (parent->song.newSegaPCM) if (chan[i].std.panR.had) {
if (chan[i].isNewSegaPCM) {
chan[i].chPanR=chan[i].std.panR.val&127;
chan[i].chVolR=(chan[i].outVol*chan[i].chPanR)/127;
} else {
chan[i].chVolR=chan[i].std.panR.val&127;
}
chan[i].chPanR=chan[i].std.panR.val&127;
chan[i].chVolR=(chan[i].outVol*chan[i].chPanR)/127;
if (dumpWrites) {
addWrite(0x10003+(i<<3),chan[i].chVolR);
}
@ -287,7 +279,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
if (!chan[c.chan].std.vol.has) {
chan[c.chan].outVol=c.value;
}
if (parent->song.newSegaPCM && chan[c.chan].isNewSegaPCM) {
if (parent->song.newSegaPCM) {
chan[c.chan].chVolL=(c.value*chan[c.chan].chPanL)/127;
chan[c.chan].chVolR=(c.value*chan[c.chan].chPanR)/127;
} else {
@ -311,7 +303,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
chan[c.chan].ins=c.value;
break;
case DIV_CMD_PANNING: {
if (parent->song.newSegaPCM && chan[c.chan].isNewSegaPCM) {
if (parent->song.newSegaPCM) {
chan[c.chan].chPanL=c.value>>1;
chan[c.chan].chPanR=c.value2>>1;
chan[c.chan].chVolL=(chan[c.chan].outVol*chan[c.chan].chPanL)/127;

View file

@ -0,0 +1,653 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
*
* Original author: Piotr Fusik (http://asap.sourceforge.net)
* Rewritten based on Mikey emulation by Waldemar Pawlaszek
*
* 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 "AltASAP.hpp"
#include <array>
#include <vector>
#include <cassert>
#include <algorithm>
#include <limits>
namespace AltASAP
{
namespace
{
static constexpr int64_t CNT_MAX = std::numeric_limits<int64_t>::max() & ~7;
static constexpr int MuteFrequency = 1;
static constexpr int MuteInit = 2;
static constexpr int MuteSerialInput = 8;
//just some magick value to match the audio level of mzpokeysnd
static constexpr int16_t MAGICK_VOLUME_BOOSTER = 160;
static constexpr int16_t MAGICK_OSC_VOLUME_BOOSTER = 4;
struct PokeyBase
{
int64_t mPolyIndex;
int mAudctl;
int mSkctl;
bool mInit;
std::array<uint8_t, 511> mPoly9Lookup;
std::array<uint8_t, 16385> mPoly17Lookup;
PokeyBase() : mPolyIndex{ 15 * 31 * 131071 }, mAudctl{ 0 }, mSkctl{ 3 }, mInit{ false }, mPoly9Lookup{}, mPoly17Lookup{}
{
int reg = 0x1ff;
for ( int i = 0; i < 511; i++ )
{
reg = ( ( ( reg >> 5 ^ reg ) & 1 ) << 8 ) + ( reg >> 1 );
mPoly9Lookup[i] = reg & 0xff;
}
reg = 0x1ffff;
for ( int i = 0; i < 16385; i++ )
{
reg = ( ( ( reg >> 5 ^ reg ) & 0xff ) << 9 ) + ( reg >> 8 );
mPoly17Lookup[i] = reg >> 1 & 0xff;
}
}
};
/*
"Queue" holding event timepoints.
- 4 channel timer fire points
- 1 sample point
Three LSBs are used to encode event kind: 0-3 are channels, 4 is sampling.
Channel sequence is 2,3,0,1
*/
class ActionQueue
{
public:
ActionQueue() : mTab{ CNT_MAX | 0, CNT_MAX | 1, CNT_MAX | 2, CNT_MAX | 3, CNT_MAX | 4 }
{
}
void insert( int idx, int64_t value )
{
assert( idx < (int)mTab.size() );
idx ^= 2;
mTab[idx] = value | idx;
}
void insertSampling( int64_t value )
{
mTab[4] = value | 4;
}
void disable( int idx )
{
assert( idx < (int)mTab.size() );
idx ^= 2;
mTab[idx] = CNT_MAX | idx;
}
bool enqueued( int idx )
{
assert( idx < (int)mTab.size() );
idx ^= 2;
return mTab[idx] < CNT_MAX;
}
int64_t pop()
{
int64_t min1 = std::min( mTab[0], mTab[1] );
int64_t min2 = std::min( mTab[2], mTab[3] );
int64_t min3 = std::min( min1, mTab[4] );
int64_t min4 = std::min( min2, min3 );
return min4 ^ 2;
}
private:
std::array<int64_t, 5> mTab;
};
class AudioChannel
{
public:
AudioChannel( ActionQueue& queue, uint32_t number ) : mQueue{ queue }, mNumber{ number }, mAudf{ 0 }, mAudc{ 0 }, mPeriodCycles{ 28 }, mMute{ MuteFrequency }, mDelta{ 0 }, mOut{ 0 }
{
}
int16_t getOutput() const
{
return (int16_t)mDelta;
}
void fillRegisterPool( uint8_t* regs )
{
regs[0] = (uint8_t)mAudf;
regs[1] = (uint8_t)mAudc;
}
void renewTrigger( int64_t cycle )
{
overrideTrigger( cycle + mPeriodCycles );
}
void overrideTrigger( int64_t cycle )
{
mQueue.insert( mNumber, cycle << 3 );
}
void doStimer( int64_t cycle )
{
if ( mQueue.enqueued( mNumber ) )
renewTrigger( cycle );
}
void trigger( int64_t cycle, PokeyBase const& pokey )
{
renewTrigger( cycle );
if ( ( mAudc & 0xb0 ) == 0xa0 )
mOut ^= 1;
else if ( ( mAudc & 0x10 ) != 0 || pokey.mInit )
return;
else
{
int64_t poly = cycle + pokey.mPolyIndex - mNumber;
if ( mAudc < 0x80 && ( 0x65bd44e0 & 1 << ( poly % 31 ) ) == 0 ) // 0000011100100010101111011010011
return;
if ( ( mAudc & 0x20 ) != 0 )
mOut ^= 1;
else
{
uint32_t newOut;
if ( ( mAudc & 0x40 ) != 0 )
newOut = 0x5370u >> (int)( poly % 15 ); // 000011101100101
else if ( pokey.mAudctl < 0x80 )
{
poly %= 131071;
newOut = pokey.mPoly17Lookup[poly >> 3] >> ( poly & 7 );
}
else
newOut = pokey.mPoly9Lookup[poly % 511];
newOut &= 1;
if ( mOut == newOut )
return;
mOut = newOut;
}
}
toggle();
}
uint32_t mute() const
{
return mMute;
}
uint32_t audf() const
{
return mAudf;
}
uint32_t audc() const
{
return mAudc;
}
void setAudf( uint32_t value )
{
mAudf = value;
}
void setAudc( int64_t cycle, uint32_t value )
{
if ( mAudc == value )
return;
mAudc = value;
int32_t volume = value & 0x0f;
if ( ( value & 0x10 ) != 0 )
{
mDelta = volume * MAGICK_VOLUME_BOOSTER;
}
else
{
muteUltrasound( cycle );
if ( mDelta > 0 )
mDelta = volume * MAGICK_VOLUME_BOOSTER;
else
mDelta = -volume * MAGICK_VOLUME_BOOSTER;
}
}
void toggle()
{
mDelta = -mDelta;
}
void setPeriodCycles( int64_t value )
{
mPeriodCycles = value;
}
void setMute( bool enable, int mask, int64_t cycle )
{
if ( enable )
{
mMute |= mask;
mQueue.disable( mNumber );
}
else
{
mMute &= ~mask;
if ( mMute == 0 && !mQueue.enqueued( mNumber ) )
overrideTrigger( cycle );
}
}
void muteUltrasound( int64_t cycle )
{
static constexpr int UltrasoundCycles = 112;
setMute( mPeriodCycles <= UltrasoundCycles && ( mAudc & 0xb0 ) == 0xa0, MuteFrequency, cycle );
}
private:
ActionQueue& mQueue;
uint32_t mNumber;
uint32_t mAudf;
uint32_t mAudc;
int64_t mPeriodCycles;
uint32_t mMute;
int32_t mDelta;
uint32_t mOut;
};
}
class PokeyPimpl : public PokeyBase
{
public:
PokeyPimpl( uint32_t pokeyClock, uint32_t sampleRate ) : PokeyBase{}, mQueue{ std::make_unique<ActionQueue>() },
mAudioChannels{ AudioChannel{ *mQueue, 0u }, AudioChannel{ *mQueue, 1u }, AudioChannel{ *mQueue, 2u }, AudioChannel{ *mQueue, 3u } },
mRegisterPool{}, mTick{}, mNextTick{},
mReloadCycles1{ 28 }, mReloadCycles3{ 28 }, mDivCycles{ 28 }, mSampleRate{ sampleRate }, mSamplesRemainder{},
mTicksPerSample{ ( pokeyClock * 8 ) / mSampleRate, ( pokeyClock * 8 ) % mSampleRate }
{
std::fill_n( mRegisterPool.data(), mRegisterPool.size(), (uint8_t)0xff );
enqueueSampling();
}
~PokeyPimpl() {}
void write( uint8_t address, uint8_t value )
{
auto cycle = mTick >> 3;
switch ( address & 0xf )
{
case 0x00:
if ( value == mAudioChannels[0].audf() )
break;
mAudioChannels[0].setAudf( value );
switch ( mAudctl & 0x50 )
{
case 0x00:
mAudioChannels[0].setPeriodCycles( mDivCycles * ( value + 1 ) );
break;
case 0x10:
mAudioChannels[1].setPeriodCycles( mDivCycles * ( value + ( mAudioChannels[1].audf() << 8 ) + 1 ) );
mReloadCycles1 = mDivCycles * ( value + 1 );
mAudioChannels[1].muteUltrasound( cycle );
break;
case 0x40:
mAudioChannels[0].setPeriodCycles( value + 4 );
break;
case 0x50:
mAudioChannels[1].setPeriodCycles( value + ( mAudioChannels[1].audf() << 8 ) + 7 );
mReloadCycles1 = value + 4;
mAudioChannels[1].muteUltrasound( cycle );
break;
default:
assert( false );
}
mAudioChannels[0].muteUltrasound( cycle );
break;
case 0x01:
mAudioChannels[0].setAudc( cycle, value );
break;
case 0x02:
if ( value == mAudioChannels[1].audf() )
break;
mAudioChannels[1].setAudf( value );
switch ( mAudctl & 0x50 )
{
case 0x00:
case 0x40:
mAudioChannels[1].setPeriodCycles( mDivCycles * ( value + 1 ) );
break;
case 0x10:
mAudioChannels[1].setPeriodCycles( mDivCycles * ( mAudioChannels[0].audf() + ( value << 8 ) + 1 ) );
break;
case 0x50:
mAudioChannels[1].setPeriodCycles( mAudioChannels[0].audf() + ( value << 8 ) + 7 );
break;
default:
assert( false );
}
mAudioChannels[1].muteUltrasound( cycle );
break;
case 0x03:
mAudioChannels[1].setAudc( cycle, value );
break;
case 0x04:
if ( value == mAudioChannels[2].audf() )
break;
mAudioChannels[2].setAudf( value );
switch ( mAudctl & 0x28 )
{
case 0x00:
mAudioChannels[2].setPeriodCycles( mDivCycles * ( value + 1 ) );
break;
case 0x08:
mAudioChannels[3].setPeriodCycles( mDivCycles * ( value + ( mAudioChannels[3].audf() << 8 ) + 1 ) );
mReloadCycles3 = mDivCycles * ( value + 1 );
mAudioChannels[3].muteUltrasound( cycle );
break;
case 0x20:
mAudioChannels[2].setPeriodCycles( value + 4 );
break;
case 0x28:
mAudioChannels[3].setPeriodCycles( value + ( mAudioChannels[3].audf() << 8 ) + 7 );
mReloadCycles3 = value + 4;
mAudioChannels[3].muteUltrasound( cycle );
break;
default:
assert( false );
}
mAudioChannels[2].muteUltrasound( cycle );
break;
case 0x05:
mAudioChannels[2].setAudc( cycle, value );
break;
case 0x06:
if ( value == mAudioChannels[3].audf() )
break;
mAudioChannels[3].setAudf( value );
switch ( mAudctl & 0x28 )
{
case 0x00:
case 0x20:
mAudioChannels[3].setPeriodCycles( mDivCycles * ( value + 1 ) );
break;
case 0x08:
mAudioChannels[3].setPeriodCycles( mDivCycles * ( mAudioChannels[2].audf() + ( value << 8 ) + 1 ) );
break;
case 0x28:
mAudioChannels[3].setPeriodCycles( mAudioChannels[2].audf() + ( value << 8 ) + 7 );
break;
default:
assert( false );
}
mAudioChannels[3].muteUltrasound( cycle );
break;
case 0x07:
mAudioChannels[3].setAudc( cycle, value );
break;
case 0x08:
if ( value == mAudctl )
break;
mAudctl = value;
mDivCycles = ( value & 1 ) != 0 ? 114 : 28;
switch ( value & 0x50 )
{
case 0x00:
mAudioChannels[0].setPeriodCycles( mDivCycles * ( mAudioChannels[0].audf() + 1 ) );
mAudioChannels[1].setPeriodCycles( mDivCycles * ( mAudioChannels[1].audf() + 1 ) );
break;
case 0x10:
mAudioChannels[0].setPeriodCycles( (int64_t)mDivCycles << 8 );
mAudioChannels[1].setPeriodCycles( mDivCycles * ( mAudioChannels[0].audf() + ( mAudioChannels[1].audf() << 8 ) + 1 ) );
mReloadCycles1 = mDivCycles * ( mAudioChannels[0].audf() + 1 );
break;
case 0x40:
mAudioChannels[0].setPeriodCycles( mAudioChannels[0].audf() + 4 );
mAudioChannels[1].setPeriodCycles( mDivCycles * ( mAudioChannels[1].audf() + 1 ) );
break;
case 0x50:
mAudioChannels[0].setPeriodCycles( 256 );
mAudioChannels[1].setPeriodCycles( mAudioChannels[0].audf() + ( mAudioChannels[1].audf() << 8 ) + 7 );
mReloadCycles1 = mAudioChannels[0].audf() + 4;
break;
default:
assert( false );
}
mAudioChannels[0].muteUltrasound( cycle );
mAudioChannels[1].muteUltrasound( cycle );
switch ( value & 0x28 )
{
case 0x00:
mAudioChannels[2].setPeriodCycles( mDivCycles * ( mAudioChannels[2].audf() + 1 ) );
mAudioChannels[3].setPeriodCycles( mDivCycles * ( mAudioChannels[3].audf() + 1 ) );
break;
case 0x08:
mAudioChannels[2].setPeriodCycles( (int64_t)mDivCycles << 8 );
mAudioChannels[3].setPeriodCycles( mDivCycles * ( mAudioChannels[2].audf() + ( mAudioChannels[3].audf() << 8 ) + 1 ) );
mReloadCycles3 = mDivCycles * ( mAudioChannels[2].audf() + 1 );
break;
case 0x20:
mAudioChannels[2].setPeriodCycles( mAudioChannels[2].audf() + 4 );
mAudioChannels[3].setPeriodCycles( mDivCycles * ( mAudioChannels[3].audf() + 1 ) );
break;
case 0x28:
mAudioChannels[2].setPeriodCycles( 256 );
mAudioChannels[3].setPeriodCycles( mAudioChannels[2].audf() + ( mAudioChannels[3].audf() << 8 ) + 7 );
mReloadCycles3 = mAudioChannels[2].audf() + 4;
break;
default:
assert( false );
}
mAudioChannels[2].muteUltrasound( cycle );
mAudioChannels[3].muteUltrasound( cycle );
initMute( cycle );
break;
case 0x09:
for ( int i = 0; i < 4; i++ )
mAudioChannels[i].doStimer( cycle );
break;
case 0x0f:
{
if ( value == mSkctl )
break;
mSkctl = value;
bool init = ( value & 3 ) == 0;
if ( mInit && !init )
mPolyIndex = ( ( mAudctl & 0x80 ) != 0 ? 15 * 31 * 511 - 1 : 15 * 31 * 131071 - 1 ) - cycle;
mInit = init;
initMute( cycle );
mAudioChannels[2].setMute( ( value & 0x10 ) != 0, MuteSerialInput, cycle );
mAudioChannels[3].setMute( ( value & 0x10 ) != 0, MuteSerialInput, cycle );
break;
}
default:
break;
}
}
int16_t sampleAudio( DivDispatchOscBuffer** oscb )
{
for ( ;; )
{
int64_t value = mQueue->pop();
if ( ( value & 7 ) == 6 ) // 6 == 4 ^ 2
{
int16_t ch0 = mAudioChannels[0].getOutput();
int16_t ch1 = mAudioChannels[1].getOutput();
int16_t ch2 = mAudioChannels[2].getOutput();
int16_t ch3 = mAudioChannels[3].getOutput();
if ( oscb != nullptr )
{
oscb[0]->data[oscb[0]->needle++]=ch0 * MAGICK_OSC_VOLUME_BOOSTER;
oscb[1]->data[oscb[1]->needle++]=ch1 * MAGICK_OSC_VOLUME_BOOSTER;
oscb[2]->data[oscb[2]->needle++]=ch2 * MAGICK_OSC_VOLUME_BOOSTER;
oscb[3]->data[oscb[3]->needle++]=ch3 * MAGICK_OSC_VOLUME_BOOSTER;
}
enqueueSampling();
return ch0 + ch1 + ch2 + ch3;
}
else
{
fireTimer( value );
}
}
}
uint8_t const* getRegisterPool()
{
for ( size_t i = 0; i < mAudioChannels.size(); ++i )
{
mAudioChannels[i].fillRegisterPool( mRegisterPool.data() + 2 * i );
}
mRegisterPool[8] = mAudctl;
return mRegisterPool.data();
}
private:
void initMute( int64_t cycle )
{
mAudioChannels[0].setMute( mInit && ( mAudctl & 0x40 ) == 0, MuteInit, cycle );
mAudioChannels[1].setMute( mInit && ( mAudctl & 0x50 ) != 0x50, MuteInit, cycle );
mAudioChannels[2].setMute( mInit && ( mAudctl & 0x20 ) == 0, MuteInit, cycle );
mAudioChannels[3].setMute( mInit && ( mAudctl & 0x28 ) != 0x28, MuteInit, cycle );
}
void fireTimer( int64_t tick )
{
mTick = tick & ~7;
size_t ch = tick & 3;
auto cycle = tick >> 3;
switch ( ch )
{
case 0:
if ( ( mSkctl & 0x88 ) == 8 ) // two-tone, sending 1 (i.e. timer1)
mAudioChannels[1].renewTrigger( cycle );
mAudioChannels[0].trigger( cycle, *this );
break;
case 1:
if ( ( mAudctl & 0x10 ) != 0 )
mAudioChannels[0].overrideTrigger( cycle + mReloadCycles1 );
else if ( ( mSkctl & 8 ) != 0 ) // two-tone
mAudioChannels[0].renewTrigger( cycle );
mAudioChannels[1].trigger( cycle, *this );
break;
case 2:
if ( ( mAudctl & 4 ) != 0 && mAudioChannels[0].getOutput() > 0 && mAudioChannels[0].mute() == 0 )
mAudioChannels[0].toggle();
mAudioChannels[2].trigger( cycle, *this );
break;
case 3:
if ( ( mAudctl & 8 ) != 0 )
mAudioChannels[2].overrideTrigger( cycle + mReloadCycles3 );
if ( ( mAudctl & 2 ) != 0 && mAudioChannels[1].getOutput() > 0 && mAudioChannels[1].mute() == 0 )
mAudioChannels[1].toggle();
mAudioChannels[3].trigger( cycle, *this );
break;
default:
break;
}
}
void enqueueSampling()
{
mTick = mNextTick & ~7;
mNextTick = mNextTick + mTicksPerSample.first;
mSamplesRemainder += mTicksPerSample.second;
if ( mSamplesRemainder > mSampleRate )
{
mSamplesRemainder %= mSampleRate;
mNextTick += 1;
}
mQueue->insertSampling( mNextTick & ~7 );
}
private:
std::unique_ptr<ActionQueue> mQueue;
std::array<AudioChannel, 4> mAudioChannels;
std::array<uint8_t, 4 * 2 + 1> mRegisterPool;
uint64_t mTick;
uint64_t mNextTick;
int64_t mReloadCycles1;
int64_t mReloadCycles3;
int64_t mDivCycles;
uint32_t mSampleRate;
uint32_t mSamplesRemainder;
std::pair<uint32_t, uint32_t> mTicksPerSample;
};
//Initializing periods with safe defaults
Pokey::Pokey() : mPokeyClock{ (uint32_t)COLOR_NTSC / 2 }, mSampleRate{ mPokeyClock / 7 }, mPokey{}
{
}
void Pokey::init( uint32_t pokeyClock, uint32_t sampleRate )
{
mPokey.reset();
mPokeyClock = pokeyClock;
mSampleRate = sampleRate;
}
void Pokey::reset()
{
mPokey = std::make_unique<PokeyPimpl>( mPokeyClock, mSampleRate );
}
Pokey::~Pokey()
{
}
void Pokey::write( uint8_t address, uint8_t value )
{
assert( mPokey );
mPokey->write( address, value );
}
int16_t Pokey::sampleAudio( DivDispatchOscBuffer** oscb )
{
assert( mPokey );
return mPokey->sampleAudio( oscb );
}
uint8_t const* Pokey::getRegisterPool()
{
assert( mPokey );
return mPokey->getRegisterPool();
}
}

View file

@ -0,0 +1,35 @@
#pragma once
#include <cstdint>
#include <memory>
// can you forgive me
#include "../../../dispatch.h"
namespace AltASAP
{
class PokeyPimpl;
class Pokey
{
public:
Pokey();
void init( uint32_t pokeyClock, uint32_t sampleRate );
~Pokey();
void write( uint8_t address, uint8_t value );
int16_t sampleAudio( DivDispatchOscBuffer** oscb = nullptr );
uint8_t const* getRegisterPool();
void reset();
private:
uint32_t mPokeyClock;
uint32_t mSampleRate;
std::unique_ptr<PokeyPimpl> mPokey;
};
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,159 @@
#ifndef MZPOKEYSND_H_
#define MZPOKEYSND_H_
#include <stdlib.h>
struct stPokeyState;
typedef int (*readout_t)(struct stPokeyState* ps);
typedef void (*event_t)(struct stPokeyState* ps, int p5v, int p4v, int p917v);
#ifdef NONLINEAR_MIXING
/* Change queue event value type */
typedef double qev_t;
#else
typedef unsigned char qev_t;
#endif
/* State variables for single Pokey Chip */
typedef struct stPokeyState
{
int curtick;
/* Poly positions */
int poly4pos;
int poly5pos;
int poly17pos;
int poly9pos;
/* Main divider (64khz/15khz) */
int mdivk; /* 28 for 64khz, 114 for 15khz */
/* Main switches */
int selpoly9;
int c0_hf;
int c1_f0;
int c2_hf;
int c3_f2;
/* SKCTL for two-tone mode */
int skctl;
/* Main output state */
qev_t outvol_all;
int forcero; /* Force readout */
/* channel 0 state */
readout_t readout_0;
event_t event_0;
int c0divpos;
int c0divstart; /* AUDF0 recalculated */
int c0divstart_p; /* start value when c1_f0 */
int c0diva; /* AUDF0 register */
int c0t1; /* D - 5bit, Q goes to sw3 */
int c0t2; /* D - out sw2, Q goes to sw4 and t3 */
int c0t3; /* D - out t2, q goes to xor */
int c0sw1; /* in1 - 4bit, in2 - 17bit, out goes to sw2 */
int c0sw2; /* in1 - /Q t2, in2 - out sw1, out goes to t2 */
int c0sw3; /* in1 - +5, in2 - Q t1, out goes to C t2 */
int c0sw4; /* hi-pass sw */
int c0vo; /* volume only */
#ifndef NONLINEAR_MIXING
int c0stop; /* channel counter stopped */
#endif
int vol0;
int outvol_0;
/* channel 1 state */
readout_t readout_1;
event_t event_1;
int c1divpos;
int c1divstart;
int c1diva;
int c1t1;
int c1t2;
int c1t3;
int c1sw1;
int c1sw2;
int c1sw3;
int c1sw4;
int c1vo;
#ifndef NONLINEAR_MIXING
int c1stop; /* channel counter stopped */
#endif
int vol1;
int outvol_1;
/* channel 2 state */
readout_t readout_2;
event_t event_2;
int c2divpos;
int c2divstart;
int c2divstart_p; /* start value when c1_f0 */
int c2diva;
int c2t1;
int c2t2;
int c2sw1;
int c2sw2;
int c2sw3;
int c2vo;
#ifndef NONLINEAR_MIXING
int c2stop; /* channel counter stopped */
#endif
int vol2;
int outvol_2;
/* channel 3 state */
readout_t readout_3;
event_t event_3;
int c3divpos;
int c3divstart;
int c3diva;
int c3t1;
int c3t2;
int c3sw1;
int c3sw2;
int c3sw3;
int c3vo;
#ifndef NONLINEAR_MIXING
int c3stop; /* channel counter stopped */
#endif
int vol3;
int outvol_3;
} PokeyState;
void mzpokeysnd_process_16(PokeyState* ps, void* sndbuffer, int sndn);
void Update_pokey_sound_mz(PokeyState* ps, unsigned short addr, unsigned char val, unsigned char gain);
void ResetPokeyState(PokeyState* ps);
int MZPOKEYSND_Init(PokeyState* ps);
#endif /* MZPOKEYSND_H_ */

View file

@ -0,0 +1,78 @@
#ifndef POKEY_H_
#define POKEY_H_
#define POKEY_OFFSET_AUDF1 0x00
#define POKEY_OFFSET_AUDC1 0x01
#define POKEY_OFFSET_AUDF2 0x02
#define POKEY_OFFSET_AUDC2 0x03
#define POKEY_OFFSET_AUDF3 0x04
#define POKEY_OFFSET_AUDC3 0x05
#define POKEY_OFFSET_AUDF4 0x06
#define POKEY_OFFSET_AUDC4 0x07
#define POKEY_OFFSET_AUDCTL 0x08
#define POKEY_OFFSET_STIMER 0x09
#define POKEY_OFFSET_SKRES 0x0a
#define POKEY_OFFSET_POTGO 0x0b
#define POKEY_OFFSET_SEROUT 0x0d
#define POKEY_OFFSET_IRQEN 0x0e
#define POKEY_OFFSET_SKCTL 0x0f
#define POKEY_OFFSET_POT0 0x00
#define POKEY_OFFSET_POT1 0x01
#define POKEY_OFFSET_POT2 0x02
#define POKEY_OFFSET_POT3 0x03
#define POKEY_OFFSET_POT4 0x04
#define POKEY_OFFSET_POT5 0x05
#define POKEY_OFFSET_POT6 0x06
#define POKEY_OFFSET_POT7 0x07
#define POKEY_OFFSET_ALLPOT 0x08
#define POKEY_OFFSET_KBCODE 0x09
#define POKEY_OFFSET_RANDOM 0x0a
#define POKEY_OFFSET_SERIN 0x0d
#define POKEY_OFFSET_IRQST 0x0e
#define POKEY_OFFSET_SKSTAT 0x0f
/* CONSTANT DEFINITIONS */
/* definitions for AUDCx (D201, D203, D205, D207) */
#define POKEY_NOTPOLY5 0x80 /* selects POLY5 or direct CLOCK */
#define POKEY_POLY4 0x40 /* selects POLY4 or POLY17 */
#define POKEY_PURETONE 0x20 /* selects POLY4/17 or PURE tone */
#define POKEY_VOL_ONLY 0x10 /* selects VOLUME OUTPUT ONLY */
#define POKEY_VOLUME_MASK 0x0f /* volume mask */
/* definitions for AUDCTL (D208) */
#define POKEY_POLY9 0x80 /* selects POLY9 or POLY17 */
#define POKEY_CH1_179 0x40 /* selects 1.78979 MHz for Ch 1 */
#define POKEY_CH3_179 0x20 /* selects 1.78979 MHz for Ch 3 */
#define POKEY_CH1_CH2 0x10 /* clocks channel 1 w/channel 2 */
#define POKEY_CH3_CH4 0x08 /* clocks channel 3 w/channel 4 */
#define POKEY_CH1_FILTER 0x04 /* selects channel 1 high pass filter */
#define POKEY_CH2_FILTER 0x02 /* selects channel 2 high pass filter */
#define POKEY_CLOCK_15 0x01 /* selects 15.6999kHz or 63.9210kHz */
/* for accuracy, the 64kHz and 15kHz clocks are exact divisions of
the 1.79MHz clock */
#define POKEY_DIV_64 28 /* divisor for 1.79MHz clock to 64 kHz */
#define POKEY_DIV_15 114 /* divisor for 1.79MHz clock to 15 kHz */
/* the size (in entries) of the 4 polynomial tables */
#define POKEY_POLY4_SIZE 0x000f
#define POKEY_POLY5_SIZE 0x001f
#define POKEY_POLY9_SIZE 0x01ff
#define POKEY_POLY17_SIZE 0x0001ffff
#define POKEY_MAXPOKEYS 2 /* max number of emulated chips */
/* channel/chip definitions */
#define POKEY_CHAN1 0
#define POKEY_CHAN2 1
#define POKEY_CHAN3 2
#define POKEY_CHAN4 3
#define POKEY_CHIP1 0
#define POKEY_CHIP2 4
#define POKEY_CHIP3 8
#define POKEY_CHIP4 12
#define POKEY_SAMPLE 127
#endif /* POKEY_H_ */

View file

@ -306,9 +306,9 @@ void DivPlatformTX81Z::tick(bool sysTick) {
chan[i].freq=chan[i].baseFreq+(chan[i].pitch>>1)-64+chan[i].pitch2;
if (!parent->song.oldArpStrategy) {
if (chan[i].fixedArp) {
chan[i].freq=(chan[i].baseNoteOverride<<7)+(chan[i].pitch>>1)-64+chan[i].pitch2;
chan[i].freq=(chan[i].baseNoteOverride<<6)+(chan[i].pitch>>1)-64+chan[i].pitch2;
} else {
chan[i].freq+=chan[i].arpOff<<7;
chan[i].freq+=chan[i].arpOff<<6;
}
}
if (chan[i].freq<0) chan[i].freq=0;

View file

@ -41,15 +41,30 @@ int DivPlatformYM2203Ext::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
if (opChan[ch].insChanged) {
chan[2].state.alg=ins->fm.alg;
chan[2].state.fb=ins->fm.fb;
chan[2].state.op[ordch]=ins->fm.op[ordch];
}
if (noExtMacros) {
opChan[ch].macroInit(NULL);
} else {
opChan[ch].macroInit(ins);
}
if (!opChan[ch].std.vol.will) {
opChan[ch].outVol=opChan[ch].vol;
}
unsigned short baseAddr=chanOffs[2]|opOffs[ordch];
DivInstrumentFM::Operator op=ins->fm.op[ordch];
DivInstrumentFM::Operator& op=chan[2].state.op[ordch];
// TODO: how does this work?!
if (isOpMuted[ch]) {
rWrite(baseAddr+0x40,127);
} else {
if (opChan[ch].insChanged) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].vol&0x7f,127));
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].outVol&0x7f,127));
}
}
if (opChan[ch].insChanged) {
@ -62,13 +77,14 @@ int DivPlatformYM2203Ext::dispatch(DivCommand c) {
opChan[ch].mask=op.enable;
}
if (opChan[ch].insChanged) { // TODO how does this work?
rWrite(chanOffs[2]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3));
rWrite(chanOffs[2]+0xb0,(chan[2].state.alg&7)|(chan[2].state.fb<<3));
}
opChan[ch].insChanged=false;
if (c.value!=DIV_NOTE_NULL) {
opChan[ch].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
opChan[ch].portaPause=false;
opChan[ch].note=c.value;
opChan[ch].freqChanged=true;
}
opChan[ch].keyOn=true;
@ -80,15 +96,28 @@ int DivPlatformYM2203Ext::dispatch(DivCommand c) {
opChan[ch].keyOn=false;
opChan[ch].active=false;
break;
case DIV_CMD_NOTE_OFF_ENV:
if (noExtMacros) break;
opChan[ch].keyOff=true;
opChan[ch].keyOn=false;
opChan[ch].active=false;
opChan[ch].std.release();
break;
case DIV_CMD_ENV_RELEASE:
if (noExtMacros) break;
opChan[ch].std.release();
break;
case DIV_CMD_VOLUME: {
opChan[ch].vol=c.value;
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
if (!opChan[ch].std.vol.has) {
opChan[ch].outVol=c.value;
}
unsigned short baseAddr=chanOffs[2]|opOffs[ordch];
DivInstrumentFM::Operator op=ins->fm.op[ordch];
DivInstrumentFM::Operator& op=chan[2].state.op[ordch];
if (isOpMuted[ch]) {
rWrite(baseAddr+0x40,127);
} else {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].vol&0x7f,127));
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].outVol&0x7f,127));
}
break;
}
@ -144,29 +173,28 @@ int DivPlatformYM2203Ext::dispatch(DivCommand c) {
immWrite(0x27,extMode?0x40:0);
break;
}
case DIV_CMD_FM_LFO: {
rWrite(0x22,(c.value&7)|((c.value>>4)<<3));
break;
}
case DIV_CMD_FM_FB: {
chan[2].state.fb=c.value&7;
rWrite(chanOffs[2]+ADDR_FB_ALG,(chan[2].state.alg&7)|(chan[2].state.fb<<3));
break;
}
case DIV_CMD_FM_MULT: { // TODO
case DIV_CMD_FM_MULT: {
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
DivInstrumentFM::Operator op=ins->fm.op[orderedOps[c.value]];
rWrite(baseAddr+0x30,(c.value2&15)|(dtTable[op.dt&7]<<4));
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
op.mult=c.value2&15;
rWrite(baseAddr+0x30,(op.mult&15)|(dtTable[op.dt&7]<<4));
break;
}
case DIV_CMD_FM_TL: { // TODO
case DIV_CMD_FM_TL: {
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
if (isOutput[ins->fm.alg][c.value]) {
rWrite(baseAddr+0x40,127-(((127-c.value2)*(opChan[ch].vol&0x7f))/127));
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
op.tl=c.value2;
if (isOpMuted[ch]) {
rWrite(baseAddr+0x40,127);
} else if (KVS(2,c.value)) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].outVol&0x7f,127));
} else {
rWrite(baseAddr+0x40,c.value2);
rWrite(baseAddr+0x40,op.tl);
}
break;
}
@ -360,6 +388,92 @@ void DivPlatformYM2203Ext::tick(bool sysTick) {
}
}
if (extMode && !noExtMacros) for (int i=0; i<4; i++) {
opChan[i].std.next();
if (opChan[i].std.vol.had) {
opChan[i].outVol=VOL_SCALE_LOG_BROKEN(opChan[i].vol,MIN(127,opChan[i].std.vol.val),127);
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[i]];
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[i]];
if (isOpMuted[i]) {
rWrite(baseAddr+ADDR_TL,127);
} else {
rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[i].outVol&0x7f,127));
}
}
if (opChan[i].std.arp.had) {
if (!opChan[i].inPorta) {
opChan[i].baseFreq=NOTE_FNUM_BLOCK(parent->calcArp(opChan[i].note,opChan[i].std.arp.val),11);
}
opChan[i].freqChanged=true;
}
if (opChan[i].std.pitch.had) {
if (opChan[i].std.pitch.mode) {
opChan[i].pitch2+=opChan[i].std.pitch.val;
CLAMP_VAR(opChan[i].pitch2,-32768,32767);
} else {
opChan[i].pitch2=opChan[i].std.pitch.val;
}
opChan[i].freqChanged=true;
}
// param macros
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[i]];
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[i]];
DivMacroInt::IntOp& m=opChan[i].std.op[orderedOps[i]];
if (m.am.had) {
op.am=m.am.val;
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
}
if (m.ar.had) {
op.ar=m.ar.val;
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
}
if (m.dr.had) {
op.dr=m.dr.val;
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
}
if (m.mult.had) {
op.mult=m.mult.val;
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
}
if (m.rr.had) {
op.rr=m.rr.val;
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
}
if (m.sl.had) {
op.sl=m.sl.val;
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
}
if (m.tl.had) {
op.tl=127-m.tl.val;
if (isOpMuted[i]) {
rWrite(baseAddr+ADDR_TL,127);
} else {
rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[i].outVol&0x7f,127));
}
}
if (m.rs.had) {
op.rs=m.rs.val;
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
}
if (m.dt.had) {
op.dt=m.dt.val;
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
}
if (m.d2r.had) {
op.d2r=m.d2r.val;
rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31);
}
if (m.ssg.had) {
op.ssgEnv=m.ssg.val;
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
}
}
DivPlatformYM2203::tick(sysTick);
bool writeNoteOn=false;
@ -411,15 +525,17 @@ void DivPlatformYM2203Ext::muteChannel(int ch, bool mute) {
isOpMuted[ch-2]=mute;
int ordch=orderedOps[ch-2];
DivInstrument* ins=parent->getIns(opChan[ch-2].ins,DIV_INS_FM);
unsigned short baseAddr=chanOffs[2]|opOffs[ordch];
DivInstrumentFM::Operator op=ins->fm.op[ordch];
DivInstrumentFM::Operator op=chan[2].state.op[ordch];
if (isOpMuted[ch-2]) {
rWrite(baseAddr+0x40,127);
} else if (isOutput[ins->fm.alg][ordch]) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].vol&0x7f,127));
immWrite(baseAddr+0x40,127);
} else if (KVS(2,ordch)) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127));
immWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127));
} else {
rWrite(baseAddr+0x40,op.tl);
immWrite(baseAddr+0x40,op.tl);
}
}
@ -428,13 +544,23 @@ void DivPlatformYM2203Ext::forceIns() {
for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
DivInstrumentFM::Operator& op=chan[i].state.op[j];
if (isMuted[i]) {
rWrite(baseAddr+ADDR_TL,127);
} else {
if (KVS(i,j)) {
rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG_BROKEN(127-op.tl,chan[i].outVol&0x7f,127));
if (i==2 && extMode) { // extended channel
if (isOpMuted[j]) {
rWrite(baseAddr+0x40,127);
} else if (KVS(i,j)) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[j].outVol&0x7f,127));
} else {
rWrite(baseAddr+ADDR_TL,op.tl);
rWrite(baseAddr+0x40,op.tl);
}
} else {
if (isMuted[i]) {
rWrite(baseAddr+ADDR_TL,127);
} else {
if (KVS(i,j)) {
rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG_BROKEN(127-op.tl,chan[i].outVol&0x7f,127));
} else {
rWrite(baseAddr+ADDR_TL,op.tl);
}
}
}
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
@ -475,7 +601,7 @@ void* DivPlatformYM2203Ext::getChanState(int ch) {
DivMacroInt* DivPlatformYM2203Ext::getChanMacroInt(int ch) {
if (ch>=6) return ay->getChanMacroInt(ch-6);
if (ch>=2) return NULL; // currently not implemented
if (ch>=2) return &opChan[ch-2].std;
return &chan[ch].std;
}
@ -490,7 +616,9 @@ void DivPlatformYM2203Ext::reset() {
for (int i=0; i<4; i++) {
opChan[i]=DivPlatformOPN::OPNOpChannel();
opChan[i].std.setEngine(parent);
opChan[i].vol=127;
opChan[i].outVol=127;
}
// channel 2 mode

View file

@ -950,7 +950,9 @@ int DivPlatformYM2608::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_LFO: {
rWrite(0x22,(c.value&7)|((c.value>>4)<<3));
if (c.chan>=6) break;
lfoValue=(c.value&7)|((c.value>>4)<<3);
rWrite(0x22,lfoValue);
break;
}
case DIV_CMD_FM_FB: {
@ -1200,6 +1202,7 @@ void DivPlatformYM2608::forceIns() {
chan[i].freqChanged=true;
}
}
immWrite(0x22,lfoValue);
for (int i=9; i<16; i++) {
chan[i].insChanged=true;
if (i>14) { // ADPCM-B
@ -1276,6 +1279,7 @@ void DivPlatformYM2608::reset() {
}
lastBusy=60;
lfoValue=8;
sampleBank=0;
writeRSSOff=0;
writeRSSOn=0;
@ -1286,7 +1290,7 @@ void DivPlatformYM2608::reset() {
extMode=false;
// LFO
immWrite(0x22,0x08);
immWrite(0x22,lfoValue);
// PCM volume
immWrite(0x11,globalRSSVolume); // A

View file

@ -41,15 +41,32 @@ int DivPlatformYM2608Ext::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
if (opChan[ch].insChanged) {
chan[2].state.alg=ins->fm.alg;
chan[2].state.fb=ins->fm.fb;
chan[2].state.fms=ins->fm.fms;
chan[2].state.ams=ins->fm.ams;
chan[2].state.op[ordch]=ins->fm.op[ordch];
}
if (noExtMacros) {
opChan[ch].macroInit(NULL);
} else {
opChan[ch].macroInit(ins);
}
if (!opChan[ch].std.vol.will) {
opChan[ch].outVol=opChan[ch].vol;
}
unsigned short baseAddr=chanOffs[2]|opOffs[ordch];
DivInstrumentFM::Operator op=ins->fm.op[ordch];
DivInstrumentFM::Operator& op=chan[2].state.op[ordch];
// TODO: how does this work?!
if (isOpMuted[ch]) {
rWrite(baseAddr+0x40,127);
} else {
if (opChan[ch].insChanged) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].vol&0x7f,127));
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].outVol&0x7f,127));
}
}
if (opChan[ch].insChanged) {
@ -62,14 +79,15 @@ int DivPlatformYM2608Ext::dispatch(DivCommand c) {
opChan[ch].mask=op.enable;
}
if (opChan[ch].insChanged) { // TODO how does this work?
rWrite(chanOffs[2]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3));
rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4));
rWrite(chanOffs[2]+0xb0,(chan[2].state.alg&7)|(chan[2].state.fb<<3));
rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));
}
opChan[ch].insChanged=false;
if (c.value!=DIV_NOTE_NULL) {
opChan[ch].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
opChan[ch].portaPause=false;
opChan[ch].note=c.value;
opChan[ch].freqChanged=true;
}
opChan[ch].keyOn=true;
@ -81,15 +99,28 @@ int DivPlatformYM2608Ext::dispatch(DivCommand c) {
opChan[ch].keyOn=false;
opChan[ch].active=false;
break;
case DIV_CMD_NOTE_OFF_ENV:
if (noExtMacros) break;
opChan[ch].keyOff=true;
opChan[ch].keyOn=false;
opChan[ch].active=false;
opChan[ch].std.release();
break;
case DIV_CMD_ENV_RELEASE:
if (noExtMacros) break;
opChan[ch].std.release();
break;
case DIV_CMD_VOLUME: {
opChan[ch].vol=c.value;
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
if (!opChan[ch].std.vol.has) {
opChan[ch].outVol=c.value;
}
unsigned short baseAddr=chanOffs[2]|opOffs[ordch];
DivInstrumentFM::Operator op=ins->fm.op[ordch];
DivInstrumentFM::Operator& op=chan[2].state.op[ordch];
if (isOpMuted[ch]) {
rWrite(baseAddr+0x40,127);
} else {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].vol&0x7f,127));
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].outVol&0x7f,127));
}
break;
}
@ -109,14 +140,13 @@ int DivPlatformYM2608Ext::dispatch(DivCommand c) {
} else {
opChan[ch].pan=(c.value2>0)|((c.value>0)<<1);
}
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
if (parent->song.sharedExtStat) {
for (int i=0; i<4; i++) {
if (ch==i) continue;
opChan[i].pan=opChan[ch].pan;
}
}
rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4));
rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));
break;
}
case DIV_CMD_PITCH: {
@ -162,7 +192,8 @@ int DivPlatformYM2608Ext::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_LFO: {
rWrite(0x22,(c.value&7)|((c.value>>4)<<3));
lfoValue=(c.value&7)|((c.value>>4)<<3);
rWrite(0x22,lfoValue);
break;
}
case DIV_CMD_FM_FB: {
@ -170,20 +201,23 @@ int DivPlatformYM2608Ext::dispatch(DivCommand c) {
rWrite(chanOffs[2]+ADDR_FB_ALG,(chan[2].state.alg&7)|(chan[2].state.fb<<3));
break;
}
case DIV_CMD_FM_MULT: { // TODO
case DIV_CMD_FM_MULT: {
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
DivInstrumentFM::Operator op=ins->fm.op[orderedOps[c.value]];
rWrite(baseAddr+0x30,(c.value2&15)|(dtTable[op.dt&7]<<4));
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
op.mult=c.value2&15;
rWrite(baseAddr+0x30,(op.mult&15)|(dtTable[op.dt&7]<<4));
break;
}
case DIV_CMD_FM_TL: { // TODO
case DIV_CMD_FM_TL: {
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]];
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
if (isOutput[ins->fm.alg][c.value]) {
rWrite(baseAddr+0x40,127-(((127-c.value2)*(opChan[ch].vol&0x7f))/127));
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]];
op.tl=c.value2;
if (isOpMuted[ch]) {
rWrite(baseAddr+0x40,127);
} else if (KVS(2,c.value)) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].outVol&0x7f,127));
} else {
rWrite(baseAddr+0x40,c.value2);
rWrite(baseAddr+0x40,op.tl);
}
break;
}
@ -377,6 +411,91 @@ void DivPlatformYM2608Ext::tick(bool sysTick) {
}
}
if (extMode && !noExtMacros) for (int i=0; i<4; i++) {
opChan[i].std.next();
if (opChan[i].std.vol.had) {
opChan[i].outVol=VOL_SCALE_LOG_BROKEN(opChan[i].vol,MIN(127,opChan[i].std.vol.val),127);
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[i]];
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[i]];
if (isOpMuted[i]) {
rWrite(baseAddr+ADDR_TL,127);
} else {
rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[i].outVol&0x7f,127));
}
}
if (opChan[i].std.arp.had) {
if (!opChan[i].inPorta) {
opChan[i].baseFreq=NOTE_FNUM_BLOCK(parent->calcArp(opChan[i].note,opChan[i].std.arp.val),11);
}
opChan[i].freqChanged=true;
}
if (opChan[i].std.pitch.had) {
if (opChan[i].std.pitch.mode) {
opChan[i].pitch2+=opChan[i].std.pitch.val;
CLAMP_VAR(opChan[i].pitch2,-32768,32767);
} else {
opChan[i].pitch2=opChan[i].std.pitch.val;
}
opChan[i].freqChanged=true;
}
// param macros
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[i]];
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[i]];
DivMacroInt::IntOp& m=opChan[i].std.op[orderedOps[i]];
if (m.am.had) {
op.am=m.am.val;
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
}
if (m.ar.had) {
op.ar=m.ar.val;
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
}
if (m.dr.had) {
op.dr=m.dr.val;
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
}
if (m.mult.had) {
op.mult=m.mult.val;
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
}
if (m.rr.had) {
op.rr=m.rr.val;
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
}
if (m.sl.had) {
op.sl=m.sl.val;
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
}
if (m.tl.had) {
op.tl=127-m.tl.val;
if (isOpMuted[i]) {
rWrite(baseAddr+ADDR_TL,127);
} else {
rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[i].outVol&0x7f,127));
}
}
if (m.rs.had) {
op.rs=m.rs.val;
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
}
if (m.dt.had) {
op.dt=m.dt.val;
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
}
if (m.d2r.had) {
op.d2r=m.d2r.val;
rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31);
}
if (m.ssg.had) {
op.ssgEnv=m.ssg.val;
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
}
}
DivPlatformYM2608::tick(sysTick);
bool writeNoteOn=false;
@ -428,16 +547,20 @@ void DivPlatformYM2608Ext::muteChannel(int ch, bool mute) {
isOpMuted[ch-2]=mute;
int ordch=orderedOps[ch-2];
DivInstrument* ins=parent->getIns(opChan[ch-2].ins,DIV_INS_FM);
unsigned short baseAddr=chanOffs[2]|opOffs[ordch];
DivInstrumentFM::Operator op=ins->fm.op[ordch];
DivInstrumentFM::Operator op=chan[2].state.op[ordch];
if (isOpMuted[ch-2]) {
rWrite(baseAddr+0x40,127);
} else if (isOutput[ins->fm.alg][ordch]) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].vol&0x7f,127));
immWrite(baseAddr+0x40,127);
} else if (KVS(2,ordch)) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127));
immWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127));
} else {
rWrite(baseAddr+0x40,op.tl);
immWrite(baseAddr+0x40,op.tl);
}
rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch-2].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));
}
void DivPlatformYM2608Ext::forceIns() {
@ -445,11 +568,11 @@ void DivPlatformYM2608Ext::forceIns() {
for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
DivInstrumentFM::Operator& op=chan[i].state.op[j];
if (i==2) { // extended channel
if (i==2 && extMode) { // extended channel
if (isOpMuted[j]) {
rWrite(baseAddr+0x40,127);
} else if (KVS(i,j)) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[j].vol&0x7f,127));
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[j].outVol&0x7f,127));
} else {
rWrite(baseAddr+0x40,op.tl);
}
@ -472,7 +595,11 @@ void DivPlatformYM2608Ext::forceIns() {
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
}
rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3));
rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
if (i==2) {
rWrite(chanOffs[i]+ADDR_LRAF,(IS_EXTCH_MUTED?0:(opChan[0].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
} else {
rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
}
if (chan[i].active) {
chan[i].keyOn=true;
chan[i].freqChanged=true;
@ -493,6 +620,7 @@ void DivPlatformYM2608Ext::forceIns() {
immWrite(i.addr&15,i.val);
}
ay->getRegisterWrites().clear();
immWrite(0x22,lfoValue);
for (int i=0; i<4; i++) {
opChan[i].insChanged=true;
if (opChan[i].active) {
@ -511,7 +639,7 @@ void* DivPlatformYM2608Ext::getChanState(int ch) {
DivMacroInt* DivPlatformYM2608Ext::getChanMacroInt(int ch) {
if (ch>=9 && ch<12) return ay->getChanMacroInt(ch-9);
if (ch>=6) return &chan[ch-3].std;
if (ch>=2) return NULL; // currently not implemented
if (ch>=2) return &opChan[ch-2].std;
return &chan[ch].std;
}
@ -526,7 +654,9 @@ void DivPlatformYM2608Ext::reset() {
for (int i=0; i<4; i++) {
opChan[i]=DivPlatformOPN::OPNOpChannelStereo();
opChan[i].std.setEngine(parent);
opChan[i].vol=127;
opChan[i].outVol=127;
}
// channel 2 mode

View file

@ -930,7 +930,9 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_LFO: {
rWrite(0x22,(c.value&7)|((c.value>>4)<<3));
if (c.chan>=psgChanOffs) break;
lfoValue=(c.value&7)|((c.value>>4)<<3);
rWrite(0x22,lfoValue);
break;
}
case DIV_CMD_FM_FB: {
@ -1173,6 +1175,7 @@ void DivPlatformYM2610::forceIns() {
chan[i].freqChanged=true;
}
}
immWrite(0x22,lfoValue);
for (int i=adpcmAChanOffs; i<=adpcmBChanOffs; i++) {
chan[i].insChanged=true;
}
@ -1247,6 +1250,7 @@ void DivPlatformYM2610::reset() {
}
lastBusy=60;
lfoValue=8;
sampleBank=0;
DivPlatformYM2610Base::reset();
@ -1255,7 +1259,7 @@ void DivPlatformYM2610::reset() {
extMode=false;
// LFO
immWrite(0x22,0x08);
immWrite(0x22,lfoValue);
// PCM volume
immWrite(0x101,globalADPCMAVolume); // A

View file

@ -993,7 +993,9 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_LFO: {
rWrite(0x22,(c.value&7)|((c.value>>4)<<3));
if (c.chan>=psgChanOffs) break;
lfoValue=(c.value&7)|((c.value>>4)<<3);
rWrite(0x22,lfoValue);
break;
}
case DIV_CMD_FM_FB: {
@ -1236,6 +1238,7 @@ void DivPlatformYM2610B::forceIns() {
chan[i].freqChanged=true;
}
}
immWrite(0x22,lfoValue);
for (int i=adpcmAChanOffs; i<=adpcmBChanOffs; i++) {
chan[i].insChanged=true;
}
@ -1310,6 +1313,7 @@ void DivPlatformYM2610B::reset() {
}
lastBusy=60;
lfoValue=8;
sampleBank=0;
DivPlatformYM2610Base::reset();
@ -1318,7 +1322,7 @@ void DivPlatformYM2610B::reset() {
extMode=false;
// LFO
immWrite(0x22,0x08);
immWrite(0x22,lfoValue);
// PCM volume
immWrite(0x101,0x3f); // A

View file

@ -37,15 +37,32 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
if (opChan[ch].insChanged) {
chan[extChanOffs].state.alg=ins->fm.alg;
chan[extChanOffs].state.fb=ins->fm.fb;
chan[extChanOffs].state.fms=ins->fm.fms;
chan[extChanOffs].state.ams=ins->fm.ams;
chan[extChanOffs].state.op[ordch]=ins->fm.op[ordch];
}
if (noExtMacros) {
opChan[ch].macroInit(NULL);
} else {
opChan[ch].macroInit(ins);
}
if (!opChan[ch].std.vol.will) {
opChan[ch].outVol=opChan[ch].vol;
}
unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[ordch];
DivInstrumentFM::Operator op=ins->fm.op[ordch];
DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[ordch];
// TODO: how does this work?!
if (isOpMuted[ch]) {
rWrite(baseAddr+0x40,127);
} else {
if (opChan[ch].insChanged) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].vol&0x7f,127));
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].outVol&0x7f,127));
}
}
if (opChan[ch].insChanged) {
@ -58,14 +75,15 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) {
opChan[ch].mask=op.enable;
}
if (opChan[ch].insChanged) { // TODO how does this work?
rWrite(chanOffs[extChanOffs]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3));
rWrite(chanOffs[extChanOffs]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4));
rWrite(chanOffs[extChanOffs]+0xb0,(chan[extChanOffs].state.alg&7)|(chan[extChanOffs].state.fb<<3));
rWrite(chanOffs[extChanOffs]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch].pan<<6))|(chan[extChanOffs].state.fms&7)|((chan[extChanOffs].state.ams&3)<<4));
}
opChan[ch].insChanged=false;
if (c.value!=DIV_NOTE_NULL) {
opChan[ch].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
opChan[ch].portaPause=false;
opChan[ch].note=c.value;
opChan[ch].freqChanged=true;
}
opChan[ch].keyOn=true;
@ -77,15 +95,28 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) {
opChan[ch].keyOn=false;
opChan[ch].active=false;
break;
case DIV_CMD_NOTE_OFF_ENV:
if (noExtMacros) break;
opChan[ch].keyOff=true;
opChan[ch].keyOn=false;
opChan[ch].active=false;
opChan[ch].std.release();
break;
case DIV_CMD_ENV_RELEASE:
if (noExtMacros) break;
opChan[ch].std.release();
break;
case DIV_CMD_VOLUME: {
opChan[ch].vol=c.value;
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
if (!opChan[ch].std.vol.has) {
opChan[ch].outVol=c.value;
}
unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[ordch];
DivInstrumentFM::Operator op=ins->fm.op[ordch];
DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[ordch];
if (isOpMuted[ch]) {
rWrite(baseAddr+0x40,127);
} else {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].vol&0x7f,127));
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].outVol&0x7f,127));
}
break;
}
@ -105,14 +136,13 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) {
} else {
opChan[ch].pan=(c.value2>0)|((c.value>0)<<1);
}
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
if (parent->song.sharedExtStat) {
for (int i=0; i<4; i++) {
if (ch==i) continue;
opChan[i].pan=opChan[ch].pan;
}
}
rWrite(chanOffs[extChanOffs]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4));
rWrite(chanOffs[extChanOffs]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch].pan<<6))|(chan[extChanOffs].state.fms&7)|((chan[extChanOffs].state.ams&3)<<4));
break;
}
case DIV_CMD_PITCH: {
@ -158,7 +188,8 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_LFO: {
rWrite(0x22,(c.value&7)|((c.value>>4)<<3));
lfoValue=(c.value&7)|((c.value>>4)<<3);
rWrite(0x22,lfoValue);
break;
}
case DIV_CMD_FM_FB: {
@ -166,20 +197,23 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) {
rWrite(chanOffs[extChanOffs]+ADDR_FB_ALG,(chan[extChanOffs].state.alg&7)|(chan[extChanOffs].state.fb<<3));
break;
}
case DIV_CMD_FM_MULT: { // TODO
case DIV_CMD_FM_MULT: {
unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]];
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
DivInstrumentFM::Operator op=ins->fm.op[orderedOps[c.value]];
rWrite(baseAddr+0x30,(c.value2&15)|(dtTable[op.dt&7]<<4));
DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[c.value]];
op.mult=c.value2&15;
rWrite(baseAddr+0x30,(op.mult&15)|(dtTable[op.dt&7]<<4));
break;
}
case DIV_CMD_FM_TL: { // TODO
case DIV_CMD_FM_TL: {
unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]];
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
if (isOutput[ins->fm.alg][c.value]) {
rWrite(baseAddr+0x40,127-(((127-c.value2)*(opChan[ch].vol&0x7f))/127));
DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[c.value]];
op.tl=c.value2;
if (isOpMuted[ch]) {
rWrite(baseAddr+0x40,127);
} else if (KVS(2,c.value)) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].outVol&0x7f,127));
} else {
rWrite(baseAddr+0x40,c.value2);
rWrite(baseAddr+0x40,op.tl);
}
break;
}
@ -373,6 +407,91 @@ void DivPlatformYM2610BExt::tick(bool sysTick) {
}
}
if (extMode && !noExtMacros) for (int i=0; i<4; i++) {
opChan[i].std.next();
if (opChan[i].std.vol.had) {
opChan[i].outVol=VOL_SCALE_LOG_BROKEN(opChan[i].vol,MIN(127,opChan[i].std.vol.val),127);
unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[i]];
DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[i]];
if (isOpMuted[i]) {
rWrite(baseAddr+ADDR_TL,127);
} else {
rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[i].outVol&0x7f,127));
}
}
if (opChan[i].std.arp.had) {
if (!opChan[i].inPorta) {
opChan[i].baseFreq=NOTE_FNUM_BLOCK(parent->calcArp(opChan[i].note,opChan[i].std.arp.val),11);
}
opChan[i].freqChanged=true;
}
if (opChan[i].std.pitch.had) {
if (opChan[i].std.pitch.mode) {
opChan[i].pitch2+=opChan[i].std.pitch.val;
CLAMP_VAR(opChan[i].pitch2,-32768,32767);
} else {
opChan[i].pitch2=opChan[i].std.pitch.val;
}
opChan[i].freqChanged=true;
}
// param macros
unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[i]];
DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[i]];
DivMacroInt::IntOp& m=opChan[i].std.op[orderedOps[i]];
if (m.am.had) {
op.am=m.am.val;
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
}
if (m.ar.had) {
op.ar=m.ar.val;
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
}
if (m.dr.had) {
op.dr=m.dr.val;
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
}
if (m.mult.had) {
op.mult=m.mult.val;
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
}
if (m.rr.had) {
op.rr=m.rr.val;
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
}
if (m.sl.had) {
op.sl=m.sl.val;
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
}
if (m.tl.had) {
op.tl=127-m.tl.val;
if (isOpMuted[i]) {
rWrite(baseAddr+ADDR_TL,127);
} else {
rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[i].outVol&0x7f,127));
}
}
if (m.rs.had) {
op.rs=m.rs.val;
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
}
if (m.dt.had) {
op.dt=m.dt.val;
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
}
if (m.d2r.had) {
op.d2r=m.d2r.val;
rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31);
}
if (m.ssg.had) {
op.ssgEnv=m.ssg.val;
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
}
}
DivPlatformYM2610B::tick(sysTick);
bool writeNoteOn=false;
@ -424,16 +543,20 @@ void DivPlatformYM2610BExt::muteChannel(int ch, bool mute) {
isOpMuted[ch-extChanOffs]=mute;
int ordch=orderedOps[ch-extChanOffs];
DivInstrument* ins=parent->getIns(opChan[ch-extChanOffs].ins,DIV_INS_FM);
unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[ordch];
DivInstrumentFM::Operator op=ins->fm.op[ordch];
DivInstrumentFM::Operator op=chan[extChanOffs].state.op[ordch];
if (isOpMuted[ch-extChanOffs]) {
rWrite(baseAddr+0x40,127);
} else if (isOutput[ins->fm.alg][ordch]) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-extChanOffs].vol&0x7f,127));
immWrite(baseAddr+0x40,127);
} else if (KVS(2,ordch)) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-extChanOffs].outVol&0x7f,127));
immWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-extChanOffs].outVol&0x7f,127));
} else {
rWrite(baseAddr+0x40,op.tl);
immWrite(baseAddr+0x40,op.tl);
}
rWrite(chanOffs[extChanOffs]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch-extChanOffs].pan<<6))|(chan[extChanOffs].state.fms&7)|((chan[extChanOffs].state.ams&3)<<4));
}
void DivPlatformYM2610BExt::forceIns() {
@ -441,11 +564,11 @@ void DivPlatformYM2610BExt::forceIns() {
for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
DivInstrumentFM::Operator& op=chan[i].state.op[j];
if (i==2 && extMode) { // extended channel
if (i==extChanOffs && extMode) { // extended channel
if (isOpMuted[j]) {
rWrite(baseAddr+0x40,127);
} else if (KVS(i,j)) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[j].vol&0x7f,127));
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[j].outVol&0x7f,127));
} else {
rWrite(baseAddr+0x40,op.tl);
}
@ -468,7 +591,11 @@ void DivPlatformYM2610BExt::forceIns() {
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
}
rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3));
rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
if (i==extChanOffs) {
rWrite(chanOffs[i]+ADDR_LRAF,(IS_EXTCH_MUTED?0:(opChan[0].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
} else {
rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
}
if (chan[i].active) {
chan[i].keyOn=true;
chan[i].freqChanged=true;
@ -484,6 +611,7 @@ void DivPlatformYM2610BExt::forceIns() {
immWrite(i.addr&15,i.val);
}
ay->getRegisterWrites().clear();
immWrite(0x22,lfoValue);
for (int i=0; i<4; i++) {
opChan[i].insChanged=true;
if (opChan[i].active) {
@ -502,7 +630,7 @@ void* DivPlatformYM2610BExt::getChanState(int ch) {
DivMacroInt* DivPlatformYM2610BExt::getChanMacroInt(int ch) {
if (ch>=(psgChanOffs+3) && ch<(adpcmAChanOffs+3)) return ay->getChanMacroInt(ch-psgChanOffs-3);
if (ch>=(extChanOffs+4)) return &chan[ch-3].std;
if (ch>=extChanOffs) return NULL; // currently not implemented
if (ch>=extChanOffs) return &opChan[ch-extChanOffs].std;
return &chan[ch].std;
}
@ -517,7 +645,9 @@ void DivPlatformYM2610BExt::reset() {
for (int i=0; i<4; i++) {
opChan[i]=DivPlatformOPN::OPNOpChannelStereo();
opChan[i].std.setEngine(parent);
opChan[i].vol=127;
opChan[i].outVol=127;
}
// channel 2 mode

View file

@ -37,15 +37,32 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
if (opChan[ch].insChanged) {
chan[extChanOffs].state.alg=ins->fm.alg;
chan[extChanOffs].state.fb=ins->fm.fb;
chan[extChanOffs].state.fms=ins->fm.fms;
chan[extChanOffs].state.ams=ins->fm.ams;
chan[extChanOffs].state.op[ordch]=ins->fm.op[ordch];
}
if (noExtMacros) {
opChan[ch].macroInit(NULL);
} else {
opChan[ch].macroInit(ins);
}
if (!opChan[ch].std.vol.will) {
opChan[ch].outVol=opChan[ch].vol;
}
unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[ordch];
DivInstrumentFM::Operator op=ins->fm.op[ordch];
DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[ordch];
// TODO: how does this work?!
if (isOpMuted[ch]) {
rWrite(baseAddr+0x40,127);
} else {
if (opChan[ch].insChanged) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].vol&0x7f,127));
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].outVol&0x7f,127));
}
}
if (opChan[ch].insChanged) {
@ -58,14 +75,15 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) {
opChan[ch].mask=op.enable;
}
if (opChan[ch].insChanged) { // TODO how does this work?
rWrite(chanOffs[extChanOffs]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3));
rWrite(chanOffs[extChanOffs]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4));
rWrite(chanOffs[extChanOffs]+0xb0,(chan[extChanOffs].state.alg&7)|(chan[extChanOffs].state.fb<<3));
rWrite(chanOffs[extChanOffs]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch].pan<<6))|(chan[extChanOffs].state.fms&7)|((chan[extChanOffs].state.ams&3)<<4));
}
opChan[ch].insChanged=false;
if (c.value!=DIV_NOTE_NULL) {
opChan[ch].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
opChan[ch].portaPause=false;
opChan[ch].note=c.value;
opChan[ch].freqChanged=true;
}
opChan[ch].keyOn=true;
@ -77,15 +95,28 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) {
opChan[ch].keyOn=false;
opChan[ch].active=false;
break;
case DIV_CMD_NOTE_OFF_ENV:
if (noExtMacros) break;
opChan[ch].keyOff=true;
opChan[ch].keyOn=false;
opChan[ch].active=false;
opChan[ch].std.release();
break;
case DIV_CMD_ENV_RELEASE:
if (noExtMacros) break;
opChan[ch].std.release();
break;
case DIV_CMD_VOLUME: {
opChan[ch].vol=c.value;
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
if (!opChan[ch].std.vol.has) {
opChan[ch].outVol=c.value;
}
unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[ordch];
DivInstrumentFM::Operator op=ins->fm.op[ordch];
DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[ordch];
if (isOpMuted[ch]) {
rWrite(baseAddr+0x40,127);
} else {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].vol&0x7f,127));
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].outVol&0x7f,127));
}
break;
}
@ -105,14 +136,13 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) {
} else {
opChan[ch].pan=(c.value2>0)|((c.value>0)<<1);
}
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
if (parent->song.sharedExtStat) {
for (int i=0; i<4; i++) {
if (ch==i) continue;
opChan[i].pan=opChan[ch].pan;
}
}
rWrite(chanOffs[extChanOffs]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4));
rWrite(chanOffs[extChanOffs]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch].pan<<6))|(chan[extChanOffs].state.fms&7)|((chan[extChanOffs].state.ams&3)<<4));
break;
}
case DIV_CMD_PITCH: {
@ -158,7 +188,8 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_LFO: {
rWrite(0x22,(c.value&7)|((c.value>>4)<<3));
lfoValue=(c.value&7)|((c.value>>4)<<3);
rWrite(0x22,lfoValue);
break;
}
case DIV_CMD_FM_FB: {
@ -166,20 +197,23 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) {
rWrite(chanOffs[extChanOffs]+ADDR_FB_ALG,(chan[extChanOffs].state.alg&7)|(chan[extChanOffs].state.fb<<3));
break;
}
case DIV_CMD_FM_MULT: { // TODO
case DIV_CMD_FM_MULT: {
unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]];
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
DivInstrumentFM::Operator op=ins->fm.op[orderedOps[c.value]];
rWrite(baseAddr+0x30,(c.value2&15)|(dtTable[op.dt&7]<<4));
DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[c.value]];
op.mult=c.value2&15;
rWrite(baseAddr+0x30,(op.mult&15)|(dtTable[op.dt&7]<<4));
break;
}
case DIV_CMD_FM_TL: { // TODO
case DIV_CMD_FM_TL: {
unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[c.value]];
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
if (isOutput[ins->fm.alg][c.value]) {
rWrite(baseAddr+0x40,127-(((127-c.value2)*(opChan[ch].vol&0x7f))/127));
DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[c.value]];
op.tl=c.value2;
if (isOpMuted[ch]) {
rWrite(baseAddr+0x40,127);
} else if (KVS(2,c.value)) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].outVol&0x7f,127));
} else {
rWrite(baseAddr+0x40,c.value2);
rWrite(baseAddr+0x40,op.tl);
}
break;
}
@ -373,6 +407,91 @@ void DivPlatformYM2610Ext::tick(bool sysTick) {
}
}
if (extMode && !noExtMacros) for (int i=0; i<4; i++) {
opChan[i].std.next();
if (opChan[i].std.vol.had) {
opChan[i].outVol=VOL_SCALE_LOG_BROKEN(opChan[i].vol,MIN(127,opChan[i].std.vol.val),127);
unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[i]];
DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[i]];
if (isOpMuted[i]) {
rWrite(baseAddr+ADDR_TL,127);
} else {
rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[i].outVol&0x7f,127));
}
}
if (opChan[i].std.arp.had) {
if (!opChan[i].inPorta) {
opChan[i].baseFreq=NOTE_FNUM_BLOCK(parent->calcArp(opChan[i].note,opChan[i].std.arp.val),11);
}
opChan[i].freqChanged=true;
}
if (opChan[i].std.pitch.had) {
if (opChan[i].std.pitch.mode) {
opChan[i].pitch2+=opChan[i].std.pitch.val;
CLAMP_VAR(opChan[i].pitch2,-32768,32767);
} else {
opChan[i].pitch2=opChan[i].std.pitch.val;
}
opChan[i].freqChanged=true;
}
// param macros
unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[orderedOps[i]];
DivInstrumentFM::Operator& op=chan[extChanOffs].state.op[orderedOps[i]];
DivMacroInt::IntOp& m=opChan[i].std.op[orderedOps[i]];
if (m.am.had) {
op.am=m.am.val;
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
}
if (m.ar.had) {
op.ar=m.ar.val;
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
}
if (m.dr.had) {
op.dr=m.dr.val;
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
}
if (m.mult.had) {
op.mult=m.mult.val;
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
}
if (m.rr.had) {
op.rr=m.rr.val;
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
}
if (m.sl.had) {
op.sl=m.sl.val;
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
}
if (m.tl.had) {
op.tl=127-m.tl.val;
if (isOpMuted[i]) {
rWrite(baseAddr+ADDR_TL,127);
} else {
rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[i].outVol&0x7f,127));
}
}
if (m.rs.had) {
op.rs=m.rs.val;
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
}
if (m.dt.had) {
op.dt=m.dt.val;
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
}
if (m.d2r.had) {
op.d2r=m.d2r.val;
rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31);
}
if (m.ssg.had) {
op.ssgEnv=m.ssg.val;
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
}
}
DivPlatformYM2610::tick(sysTick);
bool writeNoteOn=false;
@ -424,16 +543,20 @@ void DivPlatformYM2610Ext::muteChannel(int ch, bool mute) {
isOpMuted[ch-extChanOffs]=mute;
int ordch=orderedOps[ch-extChanOffs];
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
unsigned short baseAddr=chanOffs[extChanOffs]|opOffs[ordch];
DivInstrumentFM::Operator op=ins->fm.op[ordch];
if (isOpMuted[ch]) {
DivInstrumentFM::Operator op=chan[extChanOffs].state.op[ordch];
if (isOpMuted[ch-extChanOffs]) {
rWrite(baseAddr+0x40,127);
} else if (isOutput[ins->fm.alg][ordch]) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-extChanOffs].vol&0x7f,127));
immWrite(baseAddr+0x40,127);
} else if (KVS(2,ordch)) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-extChanOffs].outVol&0x7f,127));
immWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-extChanOffs].outVol&0x7f,127));
} else {
rWrite(baseAddr+0x40,op.tl);
immWrite(baseAddr+0x40,op.tl);
}
rWrite(chanOffs[extChanOffs]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch-extChanOffs].pan<<6))|(chan[extChanOffs].state.fms&7)|((chan[extChanOffs].state.ams&3)<<4));
}
void DivPlatformYM2610Ext::forceIns() {
@ -441,11 +564,11 @@ void DivPlatformYM2610Ext::forceIns() {
for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
DivInstrumentFM::Operator& op=chan[i].state.op[j];
if (i==1 && extMode) { // extended channel
if (i==extChanOffs && extMode) { // extended channel
if (isOpMuted[j]) {
rWrite(baseAddr+0x40,127);
} else if (KVS(i,j)) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[j].vol&0x7f,127));
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[j].outVol&0x7f,127));
} else {
rWrite(baseAddr+0x40,op.tl);
}
@ -468,7 +591,11 @@ void DivPlatformYM2610Ext::forceIns() {
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
}
rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3));
rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
if (i==extChanOffs) {
rWrite(chanOffs[i]+ADDR_LRAF,(IS_EXTCH_MUTED?0:(opChan[0].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
} else {
rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
}
if (chan[i].active) {
chan[i].keyOn=true;
chan[i].freqChanged=true;
@ -484,6 +611,7 @@ void DivPlatformYM2610Ext::forceIns() {
immWrite(i.addr&15,i.val);
}
ay->getRegisterWrites().clear();
immWrite(0x22,lfoValue);
for (int i=0; i<4; i++) {
opChan[i].insChanged=true;
if (opChan[i].active) {
@ -502,7 +630,7 @@ void* DivPlatformYM2610Ext::getChanState(int ch) {
DivMacroInt* DivPlatformYM2610Ext::getChanMacroInt(int ch) {
if (ch>=(psgChanOffs+3) && ch<(adpcmAChanOffs+3)) return ay->getChanMacroInt(ch-psgChanOffs-3);
if (ch>=(extChanOffs+4)) return &chan[ch-3].std;
if (ch>=extChanOffs) return NULL; // currently not implemented
if (ch>=extChanOffs) return &opChan[ch-extChanOffs].std;
return &chan[ch].std;
}
@ -517,7 +645,9 @@ void DivPlatformYM2610Ext::reset() {
for (int i=0; i<4; i++) {
opChan[i]=DivPlatformOPN::OPNOpChannelStereo();
opChan[i].std.setEngine(parent);
opChan[i].vol=127;
opChan[i].outVol=127;
}
// channel 2 mode

View file

@ -64,10 +64,10 @@ enum DivSystem {
DIV_SYSTEM_FDS,
DIV_SYSTEM_MMC5,
DIV_SYSTEM_N163,
DIV_SYSTEM_OPN,
DIV_SYSTEM_OPN_EXT,
DIV_SYSTEM_PC98,
DIV_SYSTEM_PC98_EXT,
DIV_SYSTEM_YM2203,
DIV_SYSTEM_YM2203_EXT,
DIV_SYSTEM_YM2608,
DIV_SYSTEM_YM2608_EXT,
DIV_SYSTEM_OPL,
DIV_SYSTEM_OPL2,
DIV_SYSTEM_OPL3,
@ -111,8 +111,8 @@ enum DivSystem {
DIV_SYSTEM_NAMCO,
DIV_SYSTEM_NAMCO_15XX,
DIV_SYSTEM_NAMCO_CUS30,
DIV_SYSTEM_YM2612_FRAC,
DIV_SYSTEM_YM2612_FRAC_EXT,
DIV_SYSTEM_YM2612_DUALPCM,
DIV_SYSTEM_YM2612_DUALPCM_EXT,
DIV_SYSTEM_MSM5232,
DIV_SYSTEM_T6W28,
DIV_SYSTEM_K007232,
@ -120,6 +120,11 @@ enum DivSystem {
DIV_SYSTEM_PCM_DAC,
DIV_SYSTEM_PONG,
DIV_SYSTEM_DUMMY,
DIV_SYSTEM_YM2612_CSM,
DIV_SYSTEM_YM2610_CSM,
DIV_SYSTEM_YM2610B_CSM,
DIV_SYSTEM_YM2203_CSM,
DIV_SYSTEM_YM2608_CSM,
DIV_SYSTEM_MAX // boundary for max system number
};

View file

@ -1014,7 +1014,7 @@ void DivEngine::registerSystems() {
}
);
sysDefs[DIV_SYSTEM_OPN]=new DivSysDef(
sysDefs[DIV_SYSTEM_YM2203]=new DivSysDef(
"Yamaha YM2203 (OPN)", NULL, 0x8d, 0, 6, true, true, 0x151, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"cost-reduced version of the OPM with a different register layout and no stereo...\n...but it has a built-in AY-3-8910! (actually an YM2149)",
{"FM 1", "FM 2", "FM 3", "PSG 1", "PSG 2", "PSG 3"},
@ -1026,7 +1026,7 @@ void DivEngine::registerSystems() {
fmOPNPostEffectHandlerMap
);
sysDefs[DIV_SYSTEM_OPN_EXT]=new DivSysDef(
sysDefs[DIV_SYSTEM_YM2203_EXT]=new DivSysDef(
"Yamaha YM2203 (OPN) Extended Channel 3", NULL, 0xb6, 0, 9, true, true, 0x151, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"cost-reduced version of the OPM with a different register layout and no stereo...\n...but it has a built-in AY-3-8910! (actually an YM2149)\nthis one is in Extended Channel mode, which turns the third FM channel into four operators with independent notes/frequencies",
{"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "PSG 1", "PSG 2", "PSG 3"},
@ -1038,7 +1038,19 @@ void DivEngine::registerSystems() {
fmOPNPostEffectHandlerMap
);
sysDefs[DIV_SYSTEM_PC98]=new DivSysDef(
sysDefs[DIV_SYSTEM_YM2203_CSM]=new DivSysDef(
"Yamaha YM2203 (OPN) CSM", NULL, 0xc3, 0, 10, true, true, 0x151, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"cost-reduced version of the OPM with a different register layout and no stereo...\n...but it has a built-in AY-3-8910! (actually an YM2149)\nCSM blah blah",
{"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "CSM Timer", "PSG 1", "PSG 2", "PSG 3"},
{"F1", "F2", "O1", "O2", "O3", "O4", "CSM", "S1", "S2", "S3"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_NOISE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE},
{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},
{},
fmEffectHandlerMap,
fmOPNPostEffectHandlerMap
);
sysDefs[DIV_SYSTEM_YM2608]=new DivSysDef(
"Yamaha YM2608 (OPNA)", NULL, 0x8e, 0, 16, true, true, 0x151, false, (1U<<DIV_SAMPLE_DEPTH_ADPCM_B)|(1U<<DIV_SAMPLE_DEPTH_8BIT),
"OPN but twice the FM channels, stereo makes a come-back and has rhythm and ADPCM channels.",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Kick", "Snare", "Top", "HiHat", "Tom", "Rim", "ADPCM"},
@ -1050,7 +1062,7 @@ void DivEngine::registerSystems() {
fmOPNAPostEffectHandlerMap
);
sysDefs[DIV_SYSTEM_PC98_EXT]=new DivSysDef(
sysDefs[DIV_SYSTEM_YM2608_EXT]=new DivSysDef(
"Yamaha YM2608 (OPNA) Extended Channel 3", NULL, 0xb7, 0, 19, true, true, 0x151, false, (1U<<DIV_SAMPLE_DEPTH_ADPCM_B)|(1U<<DIV_SAMPLE_DEPTH_8BIT),
"OPN but twice the FM channels, stereo makes a come-back and has rhythm and ADPCM channels.\nthis one is in Extended Channel mode, which turns the third FM channel into four operators with independent notes/frequencies",
{"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", "Kick", "Snare", "Top", "HiHat", "Tom", "Rim", "ADPCM"},
@ -1062,6 +1074,18 @@ void DivEngine::registerSystems() {
fmOPNAPostEffectHandlerMap
);
sysDefs[DIV_SYSTEM_YM2608_CSM]=new DivSysDef(
"Yamaha YM2608 (OPNA) CSM", NULL, 0xc4, 0, 20, true, true, 0x151, false, (1U<<DIV_SAMPLE_DEPTH_ADPCM_B)|(1U<<DIV_SAMPLE_DEPTH_8BIT),
"OPN but twice the FM channels, stereo makes a come-back and has rhythm and ADPCM channels.\nCSM blah blah",
{"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "CSM Timer", "Square 1", "Square 2", "Square 3", "Kick", "Snare", "Top", "HiHat", "Tom", "Rim", "ADPCM"},
{"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "CSM", "S1", "S2", "S3", "BD", "SD", "TP", "HH", "TM", "RM", "P"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_PCM},
{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_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMB},
{DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_AMIGA},
fmEffectHandlerMap,
fmOPNAPostEffectHandlerMap
);
sysDefs[DIV_SYSTEM_OPL]=new DivSysDef(
"Yamaha YM3526 (OPL)", NULL, 0x8f, 0, 9, true, false, 0x151, false, 0,
"OPN, but what if you only had two operators, no stereo, no detune and a lower ADSR parameter range?",
@ -1127,12 +1151,17 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_POKEY]=new DivSysDef(
"POKEY", NULL, 0x94, 0, 4, false, true, 0, false, 0,
"POKEY", NULL, 0x94, 0, 4, false, true, 0x161, false, 0,
"TIA, but better and more flexible.\nused in the Atari 8-bit family of computers (400/800/XL/XE).",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4"},
{"CH1", "CH2", "CH3", "CH4"},
{DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE},
{DIV_INS_POKEY, DIV_INS_POKEY, DIV_INS_POKEY, DIV_INS_POKEY}
{DIV_INS_POKEY, DIV_INS_POKEY, DIV_INS_POKEY, DIV_INS_POKEY},
{},
{
{0x10, {DIV_CMD_WAVE, "10xx: Set waveform (0 to 7)"}},
{0x11, {DIV_CMD_STD_NOISE_MODE, "11xx: Set AUDCTL"}},
}
);
sysDefs[DIV_SYSTEM_RF5C68]=new DivSysDef(
@ -1265,6 +1294,18 @@ void DivEngine::registerSystems() {
fmOPN2PostEffectHandlerMap
);
sysDefs[DIV_SYSTEM_YM2612_CSM]=new DivSysDef(
"Yamaha YM2612 (OPN2) CSM", NULL, 0xc1, 0, 10, true, false, 0x150, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"this chip is mostly known for being in the Sega Genesis (but it also was on the FM Towns computer).\nthis one includes CSM mode control for special effects on Channel 3.",
{"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "CSM Timer"},
{"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "CSM"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE},
{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_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_AMIGA, DIV_INS_NULL},
fmOPN2EffectHandlerMap,
fmOPN2PostEffectHandlerMap
);
sysDefs[DIV_SYSTEM_SCC]=new DivSysDef(
"Konami SCC", NULL, 0xa1, 0, 5, false, true, 0x161, false, 0,
"a wavetable chip made by Konami for use with the MSX.\nthe last channel shares its wavetable with the previous one though.",
@ -1336,6 +1377,18 @@ void DivEngine::registerSystems() {
fmOPNAPostEffectHandlerMap
);
sysDefs[DIV_SYSTEM_YM2610_CSM]=new DivSysDef(
"Yamaha YM2610 (OPNB) CSM", NULL, 0xc2, 0, 18, true, false, 0x151, false, (1U<<DIV_SAMPLE_DEPTH_ADPCM_A)|(1U<<DIV_SAMPLE_DEPTH_ADPCM_B)|(1U<<DIV_SAMPLE_DEPTH_8BIT),
"this chip was used in SNK's Neo Geo arcade board and video game console.\nit's like OPNA but the rhythm channels are ADPCM channels and two FM channels went missing.\nthis one includes CSM mode control for special effects on Channel 2.",
{"FM 1", "FM 2 OP1", "FM 2 OP2", "FM 2 OP3", "FM 2 OP4", "FM 3", "FM 4", "CSM Timer", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"},
{"F1", "O1", "O2", "O3", "O4", "F3", "F4", "CSM", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"},
{DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
{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_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMB},
{DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA},
fmEffectHandlerMap,
fmOPNAPostEffectHandlerMap
);
sysDefs[DIV_SYSTEM_OPLL_DRUMS]=new DivSysDef(
"Yamaha YM2413 (OPLL) with drums", NULL, 0xa7, 0, 11, true, false, 0x150, false, 0,
"the OPLL chips but with drums mode turned on.",
@ -1413,6 +1466,18 @@ void DivEngine::registerSystems() {
fmOPNAPostEffectHandlerMap
);
sysDefs[DIV_SYSTEM_YM2610B_CSM]=new DivSysDef(
"Yamaha YM2610B (OPNB2) CSM", NULL, 0xc5, 0, 20, true, false, 0x151, false, (1U<<DIV_SAMPLE_DEPTH_ADPCM_A)|(1U<<DIV_SAMPLE_DEPTH_ADPCM_B)|(1U<<DIV_SAMPLE_DEPTH_8BIT),
"so Taito asked Yamaha if they could get the two missing FM channels back, and Yamaha gladly provided them with this chip.\nCSM blah blah",
{"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "CSM Timer", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"},
{"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "CSM", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
{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_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMA, DIV_INS_ADPCMB},
{DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA},
fmEffectHandlerMap,
fmOPNAPostEffectHandlerMap
);
sysDefs[DIV_SYSTEM_SEGAPCM_COMPAT]=new DivSysDef(
"SegaPCM (compatible 5-channel mode)", NULL, 0xa9, 0, 5, false, true, 0x151, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"this is the same thing as SegaPCM, but only exposes 5 of the channels for compatibility with DefleMask.",
@ -1687,7 +1752,7 @@ void DivEngine::registerSystems() {
}
);
sysDefs[DIV_SYSTEM_YM2612_FRAC]=new DivSysDef(
sysDefs[DIV_SYSTEM_YM2612_DUALPCM]=new DivSysDef(
"Yamaha YM2612 (OPN2) with DualPCM", NULL, 0xbe, 0, 7, true, false, 0x150, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"this chip is mostly known for being in the Sega Genesis (but it also was on the FM Towns computer).\nthis system uses software mixing to provide two sample channels.",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6/PCM 1", "PCM 2"},
@ -1699,7 +1764,7 @@ void DivEngine::registerSystems() {
fmOPN2PostEffectHandlerMap
);
sysDefs[DIV_SYSTEM_YM2612_FRAC_EXT]=new DivSysDef(
sysDefs[DIV_SYSTEM_YM2612_DUALPCM_EXT]=new DivSysDef(
"Yamaha YM2612 (OPN2) Extended Channel 3 with DualPCM and CSM", NULL, 0xbd, 0, 11, true, false, 0x150, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"this chip is mostly known for being in the Sega Genesis (but it also was on the FM Towns computer).\nthis system uses software mixing to provide two sample channels.\nthis one is in Extended Channel mode, which turns the third FM channel into four operators with independent notes/frequencies.",
{"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6/PCM 1", "PCM 2", "CSM Timer"},

View file

@ -34,8 +34,8 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
switch (sys) {
case DIV_SYSTEM_YM2612:
case DIV_SYSTEM_YM2612_EXT:
case DIV_SYSTEM_YM2612_FRAC:
case DIV_SYSTEM_YM2612_FRAC_EXT:
case DIV_SYSTEM_YM2612_DUALPCM:
case DIV_SYSTEM_YM2612_DUALPCM_EXT:
for (int i=0; i<3; i++) { // set SL and RR to highest
w->writeC(2|baseAddr1);
w->writeC(0x80+i);
@ -252,8 +252,8 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
w->writeC(0);
}
break;
case DIV_SYSTEM_OPN:
case DIV_SYSTEM_OPN_EXT:
case DIV_SYSTEM_YM2203:
case DIV_SYSTEM_YM2203_EXT:
for (int i=0; i<3; i++) { // set SL and RR to highest
w->writeC(5|baseAddr1);
w->writeC(0x80+i);
@ -333,6 +333,13 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
w->writeC(0);
}
break;
case DIV_SYSTEM_POKEY:
for (int i=0; i<9; i++) {
w->writeC(0xbb);
w->writeC(i|baseAddr2);
w->writeC(0);
}
break;
case DIV_SYSTEM_LYNX:
w->writeC(0x4e);
w->writeC(0x44);
@ -600,8 +607,8 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
switch (sys) {
case DIV_SYSTEM_YM2612:
case DIV_SYSTEM_YM2612_EXT:
case DIV_SYSTEM_YM2612_FRAC:
case DIV_SYSTEM_YM2612_FRAC_EXT:
case DIV_SYSTEM_YM2612_DUALPCM:
case DIV_SYSTEM_YM2612_DUALPCM_EXT:
switch (write.addr>>8) {
case 0: // port 0
w->writeC(2|baseAddr1);
@ -693,14 +700,14 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
break;
}
break;
case DIV_SYSTEM_OPN:
case DIV_SYSTEM_OPN_EXT:
case DIV_SYSTEM_YM2203:
case DIV_SYSTEM_YM2203_EXT:
w->writeC(5|baseAddr1);
w->writeC(write.addr&0xff);
w->writeC(write.val);
break;
case DIV_SYSTEM_PC98:
case DIV_SYSTEM_PC98_EXT:
case DIV_SYSTEM_YM2608:
case DIV_SYSTEM_YM2608_EXT:
switch (write.addr>>8) {
case 0: // port 0
w->writeC(6|baseAddr1);
@ -732,6 +739,11 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
w->writeC(baseAddr2|(write.addr&0xff));
w->writeC(write.val);
break;
case DIV_SYSTEM_POKEY:
w->writeC(0xbb);
w->writeC(baseAddr2|(write.addr&0x0f));
w->writeC(write.val&0xff);
break;
case DIV_SYSTEM_LYNX:
w->writeC(0x4e);
w->writeC(write.addr&0xff);
@ -1208,8 +1220,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
break;
case DIV_SYSTEM_YM2612:
case DIV_SYSTEM_YM2612_EXT:
case DIV_SYSTEM_YM2612_FRAC:
case DIV_SYSTEM_YM2612_FRAC_EXT:
case DIV_SYSTEM_YM2612_DUALPCM:
case DIV_SYSTEM_YM2612_DUALPCM_EXT:
if (!hasOPN2) {
hasOPN2=disCont[i].dispatch->chipClock;
willExport[i]=true;
@ -1232,8 +1244,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
howManyChips++;
}
break;
case DIV_SYSTEM_OPN:
case DIV_SYSTEM_OPN_EXT:
case DIV_SYSTEM_YM2203:
case DIV_SYSTEM_YM2203_EXT:
if (!hasOPN) {
hasOPN=disCont[i].dispatch->chipClock;
willExport[i]=true;
@ -1245,8 +1257,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
howManyChips++;
}
break;
case DIV_SYSTEM_PC98:
case DIV_SYSTEM_PC98_EXT:
case DIV_SYSTEM_YM2608:
case DIV_SYSTEM_YM2608_EXT:
if (!hasOPNA) {
hasOPNA=disCont[i].dispatch->chipClock;
willExport[i]=true;
@ -1286,6 +1298,17 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
howManyChips++;
}
break;
case DIV_SYSTEM_POKEY:
if (!hasPOKEY) {
hasPOKEY=disCont[i].dispatch->chipClock;
willExport[i]=true;
} else if (!(hasPOKEY&0x40000000)) {
isSecond[i]=true;
willExport[i]=true;
hasPOKEY|=0x40000000;
howManyChips++;
}
break;
case DIV_SYSTEM_LYNX:
if (!hasLynx) {
hasLynx=disCont[i].dispatch->chipClock;
@ -1892,8 +1915,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
switch (song.system[i]) {
case DIV_SYSTEM_YM2612:
case DIV_SYSTEM_YM2612_EXT:
case DIV_SYSTEM_YM2612_FRAC:
case DIV_SYSTEM_YM2612_FRAC_EXT:
case DIV_SYSTEM_YM2612_DUALPCM:
case DIV_SYSTEM_YM2612_DUALPCM_EXT:
w->writeC(0x90);
w->writeC(streamID);
w->writeC(0x02);

View file

@ -71,6 +71,7 @@ const char* aboutLine[]={
"DeMOSic",
"DevEd",
"Dippy",
"dumbut",
"FΛDE",
"Forte",
"Fragmare",
@ -78,6 +79,7 @@ const char* aboutLine[]={
"iyatemu",
"JayBOB18",
"Jimmy-DS",
"Kagamiin~",
"kleeder",
"jaezu",
"Laggy",
@ -152,6 +154,9 @@ const char* aboutLine[]={
"QSound emulator by superctr and Valley Bell",
"VICE VIC-20 sound core by Rami Rasanen and viznut",
"VERA sound core by Frank van den Hoef",
"mzpokeysnd POKEY emulator by Michael Borisov",
"ASAP POKEY emulator by Piotr Fusik",
"ported by laoo to C++",
"K005289 emulator by cam900",
"Namco 163 emulator by cam900",
"Seta X1-010 emulator by cam900",

View file

@ -242,8 +242,8 @@ void putDispatchChip(void* data, int type) {
switch (type) {
case DIV_SYSTEM_YM2612:
case DIV_SYSTEM_YM2612_EXT:
case DIV_SYSTEM_YM2612_FRAC:
case DIV_SYSTEM_YM2612_FRAC_EXT: {
case DIV_SYSTEM_YM2612_DUALPCM:
case DIV_SYSTEM_YM2612_DUALPCM_EXT: {
GENESIS_CHIP_DEBUG;
break;
}
@ -257,8 +257,8 @@ void putDispatchChip(void* data, int type) {
SMS_CHIP_DEBUG;
break;
}
case DIV_SYSTEM_OPN:
case DIV_SYSTEM_OPN_EXT: {
case DIV_SYSTEM_YM2203:
case DIV_SYSTEM_YM2203_EXT: {
DivPlatformYM2203* ch=(DivPlatformYM2203*)data;
ImGui::Text("> YM2203");
FM_OPN_CHIP_DEBUG;
@ -268,8 +268,8 @@ void putDispatchChip(void* data, int type) {
ImGui::TextColored(ch->extMode?colorOn:colorOff,">> ExtMode");
break;
}
case DIV_SYSTEM_PC98:
case DIV_SYSTEM_PC98_EXT: {
case DIV_SYSTEM_YM2608:
case DIV_SYSTEM_YM2608_EXT: {
DivPlatformYM2608* ch=(DivPlatformYM2608*)data;
ImGui::Text("> YM2608");
FM_OPN_CHIP_DEBUG;
@ -560,12 +560,12 @@ void putDispatchChan(void* data, int chanNum, int type) {
break;
}
case DIV_SYSTEM_YM2612:
case DIV_SYSTEM_YM2612_FRAC: {
case DIV_SYSTEM_YM2612_DUALPCM: {
GENESIS_CHAN_DEBUG;
break;
}
case DIV_SYSTEM_YM2612_EXT:
case DIV_SYSTEM_YM2612_FRAC_EXT: {
case DIV_SYSTEM_YM2612_DUALPCM_EXT: {
if (chanNum>=2 && chanNum<=5) {
DivPlatformOPN::OPNOpChannelStereo* ch=(DivPlatformOPN::OPNOpChannelStereo*)data;
ImGui::Text("> YM2612 (per operator)");
@ -579,11 +579,11 @@ void putDispatchChan(void* data, int chanNum, int type) {
SMS_CHAN_DEBUG;
break;
}
case DIV_SYSTEM_OPN: {
case DIV_SYSTEM_YM2203: {
OPN_CHAN_DEBUG;
break;
}
case DIV_SYSTEM_OPN_EXT: {
case DIV_SYSTEM_YM2203_EXT: {
if (chanNum>=2 && chanNum<=5) {
OPN_OPCHAN_DEBUG;
} else {
@ -591,13 +591,13 @@ void putDispatchChan(void* data, int chanNum, int type) {
}
break;
}
case DIV_SYSTEM_PC98: {
case DIV_SYSTEM_YM2608: {
DivPlatformOPN::OPNChannelStereo* ch=(DivPlatformOPN::OPNChannelStereo*)data;
ImGui::Text("> YM2608");
OPNB_CHAN_DEBUG;
break;
}
case DIV_SYSTEM_PC98_EXT: {
case DIV_SYSTEM_YM2608_EXT: {
if (chanNum>=2 && chanNum<=5) {
DivPlatformOPN::OPNOpChannelStereo* ch=(DivPlatformOPN::OPNOpChannelStereo*)data;
ImGui::Text("> YM2608 (per operator)");

View file

@ -592,12 +592,14 @@ void FurnaceGUI::updateWindowTitle() {
void FurnaceGUI::autoDetectSystem() {
std::map<DivSystem,int> sysCountMap;
std::map<DivSystem,DivConfig> sysConfMap;
for (int i=0; i<e->song.systemLen; i++) {
try {
sysCountMap.at(e->song.system[i])++;
} catch (std::exception& ex) {
sysCountMap[e->song.system[i]]=1;
}
sysConfMap[e->song.system[i]]=e->song.systemFlags[i];
}
logV("sysCountMap:");
@ -607,16 +609,20 @@ void FurnaceGUI::autoDetectSystem() {
bool isMatch=false;
std::map<DivSystem,int> defCountMap;
std::map<DivSystem,DivConfig> defConfMap;
for (FurnaceGUISysCategory& i: sysCategories) {
for (FurnaceGUISysDef& j: i.systems) {
defCountMap.clear();
for (size_t k=0; k<j.definition.size(); k+=4) {
if (j.definition[k]==0) break;
defConfMap.clear();
for (FurnaceGUISysDefChip& k: j.orig) {
try {
defCountMap.at((DivSystem)j.definition[k])++;
defCountMap.at(k.sys)++;
} catch (std::exception& ex) {
defCountMap[(DivSystem)j.definition[k]]=1;
defCountMap[k.sys]=1;
}
DivConfig dc;
dc.loadFromMemory(k.flags);
defConfMap[k.sys]=dc;
}
if (defCountMap.size()!=sysCountMap.size()) continue;
isMatch=true;
@ -630,6 +636,18 @@ void FurnaceGUI::autoDetectSystem() {
isMatch=false;
break;
}
DivConfig& sysDC=sysConfMap.at(k.first);
for (std::pair<String,String> l: defConfMap.at(k.first).configMap()) {
if (!sysDC.has(l.first)) {
isMatch=false;
break;
}
if (sysDC.getString(l.first,"")!=l.second) {
isMatch=false;
break;
}
}
if (!isMatch) break;
} catch (std::exception& ex) {
isMatch=false;
break;
@ -1825,6 +1843,7 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
int FurnaceGUI::save(String path, int dmfVersion) {
SafeWriter* w;
logD("saving file...");
if (dmfVersion) {
if (dmfVersion<24) dmfVersion=24;
w=e->saveDMF(dmfVersion);
@ -1833,11 +1852,14 @@ int FurnaceGUI::save(String path, int dmfVersion) {
}
if (w==NULL) {
lastError=e->getLastError();
logE("couldn't save! %s",lastError);
return 3;
}
logV("opening file for writing...");
FILE* outFile=ps_fopen(path.c_str(),"wb");
if (outFile==NULL) {
lastError=strerror(errno);
logE("couldn't save! %s",lastError);
w->finish();
return 1;
}
@ -1918,6 +1940,7 @@ int FurnaceGUI::save(String path, int dmfVersion) {
showWarning(e->getWarnings(),GUI_WARN_GENERIC);
}
pushRecentFile(path);
logD("save complete.");
return 0;
}
@ -4070,7 +4093,6 @@ bool FurnaceGUI::loop() {
}
break;
case GUI_FILE_SAVE: {
logD("saving: %s",copyOfName.c_str());
bool saveWasSuccessful=true;
if (save(copyOfName,0)>0) {
showError(fmt::sprintf("Error while saving file! (%s)",lastError));
@ -5019,8 +5041,12 @@ bool FurnaceGUI::loop() {
ImGui::Checkbox("Big endian",&pendingRawSampleBigEndian);
ImGui::EndDisabled();
ImGui::BeginDisabled(pendingRawSampleDepth==DIV_SAMPLE_DEPTH_16BIT);
ImGui::Checkbox("Swap nibbles",&pendingRawSampleSwapNibbles);
ImGui::EndDisabled();
if (ImGui::Button("OK")) {
DivSample* s=e->sampleFromFileRaw(pendingRawSample.c_str(),(DivSampleDepth)pendingRawSampleDepth,pendingRawSampleChannels,pendingRawSampleBigEndian,pendingRawSampleUnsigned);
DivSample* s=e->sampleFromFileRaw(pendingRawSample.c_str(),(DivSampleDepth)pendingRawSampleDepth,pendingRawSampleChannels,pendingRawSampleBigEndian,pendingRawSampleUnsigned,pendingRawSampleSwapNibbles);
if (s==NULL) {
showError(e->getLastError());
} else {
@ -5053,6 +5079,7 @@ bool FurnaceGUI::loop() {
}
logD("saving backup...");
SafeWriter* w=e->saveFur(true);
logV("writing file...");
if (w!=NULL) {
FILE* outFile=ps_fopen(backupPath.c_str(),"wb");
@ -5067,6 +5094,7 @@ bool FurnaceGUI::loop() {
w->finish();
}
}
logD("backup saved.");
backupTimer=30.0;
return true;
});
@ -5684,6 +5712,7 @@ FurnaceGUI::FurnaceGUI():
pendingRawSampleChannels(1),
pendingRawSampleUnsigned(false),
pendingRawSampleBigEndian(false),
pendingRawSampleSwapNibbles(false),
globalWinFlags(0),
curFileDialog(GUI_FILE_OPEN),
warnAction(GUI_WARN_OPEN),

View file

@ -948,6 +948,7 @@ struct FurnaceGUISysDef {
const char* name;
const char* extra;
String definition;
std::vector<FurnaceGUISysDefChip> orig;
FurnaceGUISysDef(const char* n, std::initializer_list<FurnaceGUISysDefChip> def, const char* e=NULL);
};
@ -1116,7 +1117,7 @@ class FurnaceGUI {
String pendingRawSample;
int pendingRawSampleDepth, pendingRawSampleChannels;
bool pendingRawSampleUnsigned, pendingRawSampleBigEndian;
bool pendingRawSampleUnsigned, pendingRawSampleBigEndian, pendingRawSampleSwapNibbles;
ImGuiWindowFlags globalWinFlags;
@ -1177,6 +1178,7 @@ class FurnaceGUI {
int nesCore;
int fdsCore;
int c64Core;
int pokeyCore;
int pcSpeakerOutMethod;
String yrw801Path;
String tg100Path;
@ -1309,6 +1311,7 @@ class FurnaceGUI {
nesCore(0),
fdsCore(0),
c64Core(1),
pokeyCore(1),
pcSpeakerOutMethod(0),
yrw801Path(""),
tg100Path(""),

View file

@ -913,8 +913,9 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={
const int availableSystems[]={
DIV_SYSTEM_YM2612,
DIV_SYSTEM_YM2612_EXT,
DIV_SYSTEM_YM2612_FRAC,
DIV_SYSTEM_YM2612_FRAC_EXT,
DIV_SYSTEM_YM2612_CSM,
DIV_SYSTEM_YM2612_DUALPCM,
DIV_SYSTEM_YM2612_DUALPCM_EXT,
DIV_SYSTEM_SMS,
DIV_SYSTEM_GB,
DIV_SYSTEM_PCE,
@ -939,10 +940,10 @@ const int availableSystems[]={
DIV_SYSTEM_YMU759,
DIV_SYSTEM_DUMMY,
DIV_SYSTEM_SOUND_UNIT,
DIV_SYSTEM_OPN,
DIV_SYSTEM_OPN_EXT,
DIV_SYSTEM_PC98,
DIV_SYSTEM_PC98_EXT,
DIV_SYSTEM_YM2203,
DIV_SYSTEM_YM2203_EXT,
DIV_SYSTEM_YM2608,
DIV_SYSTEM_YM2608_EXT,
DIV_SYSTEM_OPLL,
DIV_SYSTEM_OPLL_DRUMS,
DIV_SYSTEM_VRC7,
@ -958,6 +959,7 @@ const int availableSystems[]={
DIV_SYSTEM_TIA,
DIV_SYSTEM_SAA1099,
DIV_SYSTEM_AY8930,
DIV_SYSTEM_POKEY,
DIV_SYSTEM_LYNX,
DIV_SYSTEM_QSOUND,
DIV_SYSTEM_X1_010,
@ -994,8 +996,9 @@ const int availableSystems[]={
const int chipsFM[]={
DIV_SYSTEM_YM2612,
DIV_SYSTEM_YM2612_EXT,
DIV_SYSTEM_YM2612_FRAC,
DIV_SYSTEM_YM2612_FRAC_EXT,
DIV_SYSTEM_YM2612_CSM,
DIV_SYSTEM_YM2612_DUALPCM,
DIV_SYSTEM_YM2612_DUALPCM_EXT,
DIV_SYSTEM_YM2151,
DIV_SYSTEM_YM2610,
DIV_SYSTEM_YM2610_EXT,
@ -1004,10 +1007,10 @@ const int chipsFM[]={
DIV_SYSTEM_YM2610B,
DIV_SYSTEM_YM2610B_EXT,
DIV_SYSTEM_YMU759,
DIV_SYSTEM_OPN,
DIV_SYSTEM_OPN_EXT,
DIV_SYSTEM_PC98,
DIV_SYSTEM_PC98_EXT,
DIV_SYSTEM_YM2203,
DIV_SYSTEM_YM2203_EXT,
DIV_SYSTEM_YM2608,
DIV_SYSTEM_YM2608_EXT,
DIV_SYSTEM_OPLL,
DIV_SYSTEM_OPLL_DRUMS,
DIV_SYSTEM_VRC7,
@ -1064,6 +1067,7 @@ const int chipsSpecial[]={
DIV_SYSTEM_SOUND_UNIT,
DIV_SYSTEM_TIA,
DIV_SYSTEM_AY8930,
DIV_SYSTEM_POKEY,
DIV_SYSTEM_LYNX,
DIV_SYSTEM_VERA,
DIV_SYSTEM_PET,

View file

@ -255,6 +255,10 @@ const char* c64SpecialBits[3]={
"sync", "ring", NULL
};
const char* pokeyCtlBits[9]={
"15KHz", "filter 2+4", "filter 1+3", "16-bit 3+4", "16-bit 1+2", "high3", "high1", "poly9", NULL
};
const char* mikeyFeedbackBits[11] = {
"0", "1", "2", "3", "4", "5", "7", "10", "11", "int", NULL
};
@ -5003,6 +5007,10 @@ void FurnaceGUI::drawInsEdit() {
if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_FM || ins->type==DIV_INS_OPM) {
dutyLabel="Noise Freq";
}
if (ins->type==DIV_INS_POKEY) {
dutyLabel="AUDCTL";
dutyMax=8;
}
if (ins->type==DIV_INS_MIKEY) {
dutyLabel="Duty/Int";
dutyMax=ins->amiga.useSample?0:10;
@ -5131,7 +5139,7 @@ void FurnaceGUI::drawInsEdit() {
if (ins->type==DIV_INS_K007232) waveMax=0;
if (ins->type==DIV_INS_GA20) waveMax=0;
if (ins->type==DIV_INS_POKEMINI) waveMax=0;
if (ins->type==DIV_INS_SU) waveMax=7;
if (ins->type==DIV_INS_SU || ins->type==DIV_INS_POKEY) waveMax=7;
if (ins->type==DIV_INS_PET) {
waveMax=8;
waveBitMode=true;
@ -5265,6 +5273,8 @@ void FurnaceGUI::drawInsEdit() {
if (dutyMax>0) {
if (ins->type==DIV_INS_MIKEY) {
macroList.push_back(FurnaceGUIMacroDesc(dutyLabel,&ins->std.dutyMacro,0,dutyMax,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,mikeyFeedbackBits));
} else if (ins->type==DIV_INS_POKEY) {
macroList.push_back(FurnaceGUIMacroDesc(dutyLabel,&ins->std.dutyMacro,0,dutyMax,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,pokeyCtlBits));
} else if (ins->type==DIV_INS_MSM5232) {
macroList.push_back(FurnaceGUIMacroDesc(dutyLabel,&ins->std.dutyMacro,0,dutyMax,160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,msm5232ControlBits));
} else if (ins->type==DIV_INS_ES5506) {

View file

@ -55,15 +55,21 @@ void FurnaceGUI::initSystemPresets() {
CH(DIV_SYSTEM_SMS, 32, 0, "")
}
);
ENTRY(
"Sega Genesis (CSM)", {
CH(DIV_SYSTEM_YM2612_CSM, 64, 0, ""),
CH(DIV_SYSTEM_SMS, 32, 0, "")
}
);
ENTRY(
"Sega Genesis (DualPCM)", {
CH(DIV_SYSTEM_YM2612_FRAC, 64, 0, ""),
CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, ""),
CH(DIV_SYSTEM_SMS, 32, 0, "")
}
);
ENTRY(
"Sega Genesis (DualPCM, extended channel 3)", {
CH(DIV_SYSTEM_YM2612_FRAC_EXT, 64, 0, ""),
CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, ""),
CH(DIV_SYSTEM_SMS, 32, 0, "")
}
);
@ -87,6 +93,16 @@ void FurnaceGUI::initSystemPresets() {
)
}
);
ENTRY(
"Sega Genesis (CSM with Sega CD)", {
CH(DIV_SYSTEM_YM2612_CSM, 64, 0, ""),
CH(DIV_SYSTEM_SMS, 32, 0, ""),
CH(DIV_SYSTEM_RF5C68, 64, 0,
"clockSel=2\n"
"chipType=1\n"
)
}
);
ENTRY(
"Sega Master System", {
CH(DIV_SYSTEM_SMS, 64, 0, "")
@ -114,6 +130,19 @@ void FurnaceGUI::initSystemPresets() {
CH(DIV_SYSTEM_GB, 64, 0, "")
}
);
ENTRY(
"Neo Geo Pocket", {
CH(DIV_SYSTEM_T6W28, 64, 0, ""),
CH(DIV_SYSTEM_PCM_DAC, 64, -127,
"rate=11025\n"
"outDepth=5\n"
),
CH(DIV_SYSTEM_PCM_DAC, 64, 127,
"rate=11025\n"
"outDepth=5\n"
) // don't know what the actual sample rate is
}
);
ENTRY(
"NEC PC Engine/TurboGrafx-16", {
CH(DIV_SYSTEM_PCE, 64, 0, "")
@ -190,6 +219,12 @@ void FurnaceGUI::initSystemPresets() {
CH(DIV_SYSTEM_TIA, 64, 0, "")
}
);
ENTRY(
"Atari 7800 + Ballblazer/Commando", {
CH(DIV_SYSTEM_TIA, 64, 0, ""),
CH(DIV_SYSTEM_POKEY, 64, 0, "")
}
);
ENTRY(
"Atari Lynx", {
CH(DIV_SYSTEM_LYNX, 64, 0, "")
@ -311,7 +346,7 @@ void FurnaceGUI::initSystemPresets() {
);
ENTRY(
"MSX", {
CH(DIV_SYSTEM_AY8910, 64, 0, "chipType=1")
CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=0\nchipType=1")
}
);
ENTRY(
@ -411,77 +446,286 @@ void FurnaceGUI::initSystemPresets() {
) // variable rate, Mono DAC
}
);
ENTRY(
"NEC PC-88 (with PC-8801-10)", {
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=15"), // external
CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=15") // ""
}
);
ENTRY(
"NEC PC-88 (with PC-8801-11)", {
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=4") // external
}
);
ENTRY(
"NEC PC-88 (with PC-8801-11; extended channel 3)", {
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=4") // external
}
);
ENTRY(
"NEC PC-88 (with PC-8801-23)", {
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_YM2608, 64, 0, "clockSel=1") // external
}
);
ENTRY(
"NEC PC-88 (with PC-8801-23; extended channel 3)", {
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_YM2608_EXT, 64, 0, "clockSel=1") // external
}
);
ENTRY(
"NEC PC-88 (with HMB-20 HIBIKI-8800)", {
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_YM2151, 64, 0, "clockSel=2") // external; 4.0000MHz
}
);
ENTRY(
"NEC PC-8801mk2SR (with PC-8801-10)", {
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=4"), // internal
CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=15"), // external
CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=15") // ""
}
);
ENTRY(
"NEC PC-8801mk2SR (with PC-8801-10; extended channel 3)", {
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=4"), // internal
CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=15"), // external
CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=15") // ""
}
);
ENTRY(
"NEC PC-8801mk2SR (with PC-8801-11)", {
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=4"), // internal
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=4") // external
}
);
ENTRY(
"NEC PC-8801mk2SR (with PC-8801-11; extended channel 3 on internal OPN)", {
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=4"), // internal
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=4") // external
}
);
ENTRY(
"NEC PC-8801mk2SR (with PC-8801-11; extended channel 3 on external OPN)", {
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=4"), // internal
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=4") // external
}
);
ENTRY(
"NEC PC-8801mk2SR (with PC-8801-11; extended channel 3 on both OPNs)", {
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=4"), // internal
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=4") // external
}
);
ENTRY(
"NEC PC-8801mk2SR (with PC-8801-23)", {
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=4"), // internal
CH(DIV_SYSTEM_YM2608, 64, 0, "clockSel=1") // external
}
);
ENTRY(
"NEC PC-8801mk2SR (with PC-8801-23; extended channel 3 on internal OPN)", {
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=4"), // internal
CH(DIV_SYSTEM_YM2608, 64, 0, "clockSel=1") // external
}
);
ENTRY(
"NEC PC-8801mk2SR (with PC-8801-23; extended channel 3 on external OPN)", {
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=4"), // internal
CH(DIV_SYSTEM_YM2608_EXT, 64, 0, "clockSel=1") // external
}
);
ENTRY(
"NEC PC-8801mk2SR (with PC-8801-23; extended channel 3 on both OPNs)", {
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=4"), // internal
CH(DIV_SYSTEM_YM2608_EXT, 64, 0, "clockSel=1") // external
}
);
ENTRY(
"NEC PC-8801mk2SR (with HMB-20 HIBIKI-8800)", {
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=4"), // internal
CH(DIV_SYSTEM_YM2151, 64, 0, "clockSel=2") // external; 4.0000MHz
}
);
ENTRY(
"NEC PC-8801mk2SR (with HMB-20 HIBIKI-8800; extended channel 3)", {
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=4"), // internal
CH(DIV_SYSTEM_YM2151, 64, 0, "clockSel=2") // external; 4.0000MHz
}
);
ENTRY(
"NEC PC-8801FA (with PC-8801-10)", {
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_YM2608, 64, 0, "clockSel=4"), // internal
CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=15"), // external
CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=15") // ""
}
);
ENTRY(
"NEC PC-8801FA (with PC-8801-10; extended channel 3)", {
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_YM2608_EXT, 64, 0, "clockSel=4"), // internal
CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=15"), // external
CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=15") // ""
}
);
ENTRY(
"NEC PC-8801FA (with PC-8801-11)", {
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_YM2608, 64, 0, "clockSel=1"), // internal
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=4") // external
}
);
ENTRY(
"NEC PC-8801FA (with PC-8801-11; extended channel 3 on internal OPN)", {
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_YM2608_EXT, 64, 0, "clockSel=1"), // internal
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=4") // external
}
);
ENTRY(
"NEC PC-8801FA (with PC-8801-11; extended channel 3 on external OPN)", {
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_YM2608, 64, 0, "clockSel=1"), // internal
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=4") // external
}
);
ENTRY(
"NEC PC-8801FA (with PC-8801-11; extended channel 3 on both OPNs)", {
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_YM2608_EXT, 64, 0, "clockSel=1"), // internal
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=4") // external
}
);
ENTRY(
"NEC PC-8801FA (with PC-8801-23)", {
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_YM2608, 64, 0, "clockSel=1"), // internal
CH(DIV_SYSTEM_YM2608, 64, 0, "clockSel=1") // external
}
);
ENTRY(
"NEC PC-8801FA (with PC-8801-23; extended channel 3 on internal OPN)", {
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_YM2608_EXT, 64, 0, "clockSel=1"), // internal
CH(DIV_SYSTEM_YM2608, 64, 0, "clockSel=1") // external
}
);
ENTRY(
"NEC PC-8801FA (with PC-8801-23; extended channel 3 on external OPN)", {
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_YM2608, 64, 0, "clockSel=1"), // internal
CH(DIV_SYSTEM_YM2608_EXT, 64, 0, "clockSel=1") // external
}
);
ENTRY(
"NEC PC-8801FA (with PC-8801-23; extended channel 3 on both OPNs)", {
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_YM2608_EXT, 64, 0, "clockSel=1"), // internal
CH(DIV_SYSTEM_YM2608_EXT, 64, 0, "clockSel=1") // external
}
);
ENTRY(
"NEC PC-8801FA (with HMB-20 HIBIKI-8800)", {
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_YM2608, 64, 0, "clockSel=1"), // internal
CH(DIV_SYSTEM_YM2151, 64, 0, "clockSel=2") // external; 4.0000MHz
}
);
ENTRY(
"NEC PC-8801FA (with HMB-20 HIBIKI-8800; extended channel 3)", {
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_YM2608_EXT, 64, 0, "clockSel=1"), // internal
CH(DIV_SYSTEM_YM2151, 64, 0, "clockSel=2") // external; 4.0000MHz
}
);
ENTRY(
"NEC PC-98 (with PC-9801-26/K)", {
CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=4"), // 3.9936MHz but some compatible card has 4MHz
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=4"), // 3.9936MHz but some compatible card has 4MHz
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1")
}
);
ENTRY(
"NEC PC-98 (with PC-9801-26/K; extended channel 3)", {
CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=4"), // 3.9936MHz but some compatible card has 4MHz
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=4"), // 3.9936MHz but some compatible card has 4MHz
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1")
}
);
ENTRY(
"NEC PC-98 (with Sound Orchestra)", {
CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=4"),
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=4"),
CH(DIV_SYSTEM_OPL2, 64, 0, "clockSel=4"),
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1")
}
);
ENTRY(
"NEC PC-98 (with Sound Orchestra; extended channel 3)", {
CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=4"),
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=4"),
CH(DIV_SYSTEM_OPL2, 64, 0, "clockSel=4"),
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1")
}
);
ENTRY(
"NEC PC-98 (with Sound Orchestra in drums mode)", {
CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=4"),
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=4"),
CH(DIV_SYSTEM_OPL2_DRUMS, 64, 0, "clockSel=4"),
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1")
}
);
ENTRY(
"NEC PC-98 (with Sound Orchestra in drums mode; extended channel 3)", {
CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=4"),
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=4"),
CH(DIV_SYSTEM_OPL2_DRUMS, 64, 0, "clockSel=4"),
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1")
}
);
ENTRY(
"NEC PC-98 (with Sound Orchestra V)", {
CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=4"),
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=4"),
CH(DIV_SYSTEM_Y8950, 64, 0, "clockSel=4"),
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1")
}
);
ENTRY(
"NEC PC-98 (with Sound Orchestra V; extended channel 3)", {
CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=4"),
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=4"),
CH(DIV_SYSTEM_Y8950, 64, 0, "clockSel=4"),
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1")
}
);
ENTRY(
"NEC PC-98 (with Sound Orchestra V in drums mode)", {
CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=4"),
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=4"),
CH(DIV_SYSTEM_Y8950_DRUMS, 64, 0, "clockSel=4"),
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1")
}
);
ENTRY(
"NEC PC-98 (with Sound Orchestra V in drums mode; extended channel 3)", {
CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=4"),
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=4"),
CH(DIV_SYSTEM_Y8950_DRUMS, 64, 0, "clockSel=4"),
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1")
}
);
ENTRY(
"NEC PC-98 (with PC-9801-86)", { // -73 also has OPNA
CH(DIV_SYSTEM_PC98, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_YM2608, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_PCM_DAC, 64, 0, // 2x 16-bit Burr Brown DAC
"rate=44100\n"
"outDepth=15\n"
@ -495,7 +739,7 @@ void FurnaceGUI::initSystemPresets() {
);
ENTRY(
"NEC PC-98 (with PC-9801-86; extended channel 3)", { // -73 also has OPNA
CH(DIV_SYSTEM_PC98_EXT, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_YM2608_EXT, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_PCM_DAC, 64, 0,
"rate=44100\n"
"outDepth=15\n"
@ -509,19 +753,19 @@ void FurnaceGUI::initSystemPresets() {
);
ENTRY(
"NEC PC-98 (with PC-9801-73)", {
CH(DIV_SYSTEM_PC98, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_YM2608, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1")
}
);
ENTRY(
"NEC PC-98 (with PC-9801-73; extended channel 3)", {
CH(DIV_SYSTEM_PC98_EXT, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_YM2608_EXT, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_PCSPKR, 64, 0, "clockSel=1")
}
);
ENTRY(
"NEC PC-98 (with Sound Blaster 16 for PC-9800 w/PC-9801-26/K compatible)", {
CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=2"), // 4MHz
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=2"), // 4MHz
CH(DIV_SYSTEM_PCM_DAC, 64, 0,
"rate=44100\n"
"outDepth=15\n"
@ -533,7 +777,7 @@ void FurnaceGUI::initSystemPresets() {
);
ENTRY(
"NEC PC-98 (with Sound Blaster 16 for PC-9800 w/PC-9801-26/K compatible; extended channel 3)", {
CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=2"), // 4MHz
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=2"), // 4MHz
CH(DIV_SYSTEM_PCM_DAC, 64, 0,
"rate=44100\n"
"outDepth=15\n"
@ -545,7 +789,7 @@ void FurnaceGUI::initSystemPresets() {
);
ENTRY(
"NEC PC-98 (with Sound Blaster 16 for PC-9800 w/PC-9801-26/K compatible in drums mode)", {
CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=2"), // 4MHz
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=2"), // 4MHz
CH(DIV_SYSTEM_PCM_DAC, 64, 0,
"rate=44100\n"
"outDepth=15\n"
@ -557,7 +801,7 @@ void FurnaceGUI::initSystemPresets() {
);
ENTRY(
"NEC PC-98 (with Sound Blaster 16 for PC-9800 w/PC-9801-26/K compatible in drums mode; extended channel 3)", {
CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=2"), // 4MHz
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=2"), // 4MHz
CH(DIV_SYSTEM_PCM_DAC, 64, 0,
"rate=44100\n"
"outDepth=15\n"
@ -580,29 +824,29 @@ void FurnaceGUI::initSystemPresets() {
ENTRY(
"ZX Spectrum (128K) with TurboSound FM", {
CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=1")
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=1")
}
);
ENTRY(
"ZX Spectrum (128K) with TurboSound FM (extended channel 3 on first OPN)", {
CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=1")
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=1")
}
);
ENTRY(
"ZX Spectrum (128K) with TurboSound FM (extended channel 3 on second OPN)", {
CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=1")
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=1")
}
);
ENTRY(
"ZX Spectrum (128K) with TurboSound FM (extended channel 3 on both OPNs)", {
CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=1")
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=1"),
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=1")
}
);
ENTRY(
@ -617,6 +861,19 @@ void FurnaceGUI::initSystemPresets() {
CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=5")
}
);
ENTRY(
"Atari 800", {
CH(DIV_SYSTEM_POKEY, 64, 0, "clockSel=1")
},
"tickRate=50"
);
ENTRY(
"Atari 800 (stereo)", {
CH(DIV_SYSTEM_POKEY, 64, -127, "clockSel=1"),
CH(DIV_SYSTEM_POKEY, 64, 127, "clockSel=1"),
},
"tickRate=50"
);
ENTRY(
"Atari ST", {
CH(DIV_SYSTEM_AY8910, 64, 0,
@ -835,6 +1092,12 @@ void FurnaceGUI::initSystemPresets() {
CH(DIV_SYSTEM_RF5C68, 64, 0, "")
}
);
ENTRY(
"FM Towns (CSM)", {
CH(DIV_SYSTEM_YM2612_CSM, 64, 0, "clockSel=2"), // YM3438
CH(DIV_SYSTEM_RF5C68, 64, 0, "")
}
);
ENTRY(
"Commander X16", {
CH(DIV_SYSTEM_VERA, 64, 0, ""),
@ -859,22 +1122,22 @@ void FurnaceGUI::initSystemPresets() {
);
ENTRY(
"Yamaha YM2203 (OPN)", {
CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=3")
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=3")
}
);
ENTRY(
"Yamaha YM2203 (extended channel 3)", {
CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=3")
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=3")
}
);
ENTRY(
"Yamaha YM2608 (OPNA)", {
CH(DIV_SYSTEM_PC98, 64, 0, "")
CH(DIV_SYSTEM_YM2608, 64, 0, "")
}
);
ENTRY(
"Yamaha YM2608 (extended channel 3)", {
CH(DIV_SYSTEM_PC98_EXT, 64, 0, "")
CH(DIV_SYSTEM_YM2608_EXT, 64, 0, "")
}
);
ENTRY(
@ -907,14 +1170,19 @@ void FurnaceGUI::initSystemPresets() {
CH(DIV_SYSTEM_YM2612_EXT, 64, 0, "ladderEffect=true")
}
);
ENTRY(
"Yamaha YM2612 (OPN2) CSM", {
CH(DIV_SYSTEM_YM2612_CSM, 64, 0, "ladderEffect=true")
}
);
ENTRY(
"Yamaha YM2612 (OPN2) with DualPCM", {
CH(DIV_SYSTEM_YM2612_FRAC, 64, 0, "ladderEffect=true")
CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, "ladderEffect=true")
}
);
ENTRY(
"Yamaha YM2612 (extended channel 3) with DualPCM", {
CH(DIV_SYSTEM_YM2612_FRAC_EXT, 64, 0, "ladderEffect=true")
CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, "ladderEffect=true")
}
);
ENTRY(
@ -942,14 +1210,19 @@ void FurnaceGUI::initSystemPresets() {
CH(DIV_SYSTEM_YM2612_EXT, 64, 0, "")
}
);
ENTRY(
"Yamaha YM3438 (OPN2C) CSM", {
CH(DIV_SYSTEM_YM2612_CSM, 64, 0, "")
}
);
ENTRY(
"Yamaha YM3438 (OPN2C) with DualPCM", {
CH(DIV_SYSTEM_YM2612_FRAC, 64, 0, "")
CH(DIV_SYSTEM_YM2612_DUALPCM, 64, 0, "")
}
);
ENTRY(
"Yamaha YM3438 (extended channel 3) with DualPCM", {
CH(DIV_SYSTEM_YM2612_FRAC_EXT, 64, 0, "")
CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 64, 0, "")
}
);
ENTRY(
@ -1475,13 +1748,13 @@ void FurnaceGUI::initSystemPresets() {
);
ENTRY(
"Sega Hang-On", {
CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=2"), // 4MHz
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=2"), // 4MHz
CH(DIV_SYSTEM_SEGAPCM, 64, 0, "") // discrete logics, 62.5KHz output rate
}
);
ENTRY(
"Sega Hang-On (extended channel 3)", {
CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=2"), // 4MHz
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=2"), // 4MHz
CH(DIV_SYSTEM_SEGAPCM, 64, 0, "") // discrete logics, 62.5KHz output rate
}
);
@ -1571,26 +1844,26 @@ void FurnaceGUI::initSystemPresets() {
);
ENTRY(
"Capcom Arcade", { // 1943, Side arms, etc
CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=5"), // 4 or 1.5MHz; various per games
CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=5")
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=5"), // 4 or 1.5MHz; various per games
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=5")
}
);
ENTRY(
"Capcom Arcade (extended channel 3 on first OPN)", {
CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=5"),
CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=5")
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=5"),
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=5")
}
);
ENTRY(
"Capcom Arcade (extended channel 3 on second OPN)", {
CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=5"),
CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=5")
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=5"),
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=5")
}
);
ENTRY(
"Capcom Arcade (extended channel 3 on both OPNs)", {
CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=5"),
CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=5")
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=5"),
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=5")
}
);
ENTRY(
@ -1625,7 +1898,7 @@ void FurnaceGUI::initSystemPresets() {
);
ENTRY(
"NMK 16-bit Arcade", {
CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=5"), // 1.5MHz; optional
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=5"), // 1.5MHz; optional
CH(DIV_SYSTEM_MSM6295, 64, 0,
"clockSel=2\n"
"rateSel=true\n"
@ -1638,7 +1911,7 @@ void FurnaceGUI::initSystemPresets() {
);
ENTRY(
"NMK 16-bit Arcade (extended channel 3)", {
CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=5"), // 1.5MHz; optional
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=5"), // 1.5MHz; optional
CH(DIV_SYSTEM_MSM6295, 64, 0,
"clockSel=2\n"
"rateSel=true\n"
@ -1651,21 +1924,21 @@ void FurnaceGUI::initSystemPresets() {
);
ENTRY(
"Kaneko DJ Boy", {
CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=3"), // 3MHz
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=3"), // 3MHz
CH(DIV_SYSTEM_MSM6295, 64, -127, "clockSel=12"), // 1.5MHz, Left output
CH(DIV_SYSTEM_MSM6295, 64, 127, "clockSel=12"), // 1.5MHz, Right output
}
);
ENTRY(
"Kaneko DJ Boy (extended channel 3)", {
CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=3"), // 3MHz
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=3"), // 3MHz
CH(DIV_SYSTEM_MSM6295, 64, -127, "clockSel=12"), // 1.5MHz, Left output
CH(DIV_SYSTEM_MSM6295, 64, 127, "clockSel=12") // 1.5MHz, Right output
}
);
ENTRY(
"Kaneko Air Buster", {
CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=3"), // 3MHz
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=3"), // 3MHz
CH(DIV_SYSTEM_MSM6295, 64, 0,
"clockSel=13\n"
"rateSel=true\n"
@ -1674,7 +1947,7 @@ void FurnaceGUI::initSystemPresets() {
);
ENTRY(
"Kaneko Air Buster (extended channel 3)", {
CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=3"), // 3MHz
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=3"), // 3MHz
CH(DIV_SYSTEM_MSM6295, 64, 0,
"clockSel=13\n"
"rateSel=true\n"
@ -1706,29 +1979,29 @@ void FurnaceGUI::initSystemPresets() {
);
ENTRY(
"Tecmo Ninja Gaiden", { // Ninja Gaiden, Raiga, etc
CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=2"), // 4MHz
CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=2"), // 4MHz
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=2"), // 4MHz
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=2"), // 4MHz
CH(DIV_SYSTEM_MSM6295, 64, 0, "") // 1MHz
}
);
ENTRY(
"Tecmo Ninja Gaiden (extended channel 3 on first OPN)", { // Ninja Gaiden, Raiga, etc
CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=2"), // 4MHz
CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=2"), // 4MHz
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=2"), // 4MHz
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=2"), // 4MHz
CH(DIV_SYSTEM_MSM6295, 64, 0, "") // 1MHz
}
);
ENTRY(
"Tecmo Ninja Gaiden (extended channel 3 on second OPN)", { // Ninja Gaiden, Raiga, etc
CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=2"), // 4MHz
CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=2"), // 4MHz
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=2"), // 4MHz
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=2"), // 4MHz
CH(DIV_SYSTEM_MSM6295, 64, 0, "") // 1MHz
}
);
ENTRY(
"Tecmo Ninja Gaiden (extended channel 3 on both OPNs)", { // Ninja Gaiden, Raiga, etc
CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=2"), // 4MHz
CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=2"), // 4MHz
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=2"), // 4MHz
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=2"), // 4MHz
CH(DIV_SYSTEM_MSM6295, 64, 0, "") // 1MHz
}
);
@ -1803,68 +2076,95 @@ void FurnaceGUI::initSystemPresets() {
CH(DIV_SYSTEM_MSM6295, 64, 127, "clockSel=14") // 1.193MHz (3.579545MHz / 3), Right output
}
);
ENTRY(
"Atari Marble Madness", {
CH(DIV_SYSTEM_YM2151, 64, 0, ""),
CH(DIV_SYSTEM_POKEY, 64, 0, "")
}
);
ENTRY(
"Atari Championship Sprint", {
CH(DIV_SYSTEM_YM2151, 64, 0, ""),
CH(DIV_SYSTEM_POKEY, 64, 0, ""),
CH(DIV_SYSTEM_POKEY, 64, 0, "")
}
);
ENTRY(
"Atari Tetris", {
CH(DIV_SYSTEM_POKEY, 64, 0, ""),
CH(DIV_SYSTEM_POKEY, 64, 0, "")
}
);
ENTRY(
"Atari I, Robot", {
CH(DIV_SYSTEM_POKEY, 64, 0, "customClock=1512000"),
CH(DIV_SYSTEM_POKEY, 64, 0, "customClock=1512000"),
CH(DIV_SYSTEM_POKEY, 64, 0, "customClock=1512000"),
CH(DIV_SYSTEM_POKEY, 64, 0, "customClock=1512000")
}
);
ENTRY(
"Data East Karnov", {
CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=5"), // 1.5MHz
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=5"), // 1.5MHz
CH(DIV_SYSTEM_OPL, 64, 0, "clockSel=3") // 3MHz
}
);
ENTRY(
"Data East Karnov (extended channel 3)", {
CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=5"), // 1.5MHz
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=5"), // 1.5MHz
CH(DIV_SYSTEM_OPL, 64, 0, "clockSel=3") // 3MHz
}
);
ENTRY(
"Data East Karnov (drums mode)", {
CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=5"), // 1.5MHz
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=5"), // 1.5MHz
CH(DIV_SYSTEM_OPL_DRUMS, 64, 0, "clockSel=3") // 3MHz
}
);
ENTRY(
"Data East Karnov (extended channel 3; drums mode)", {
CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=5"), // 1.5MHz
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=5"), // 1.5MHz
CH(DIV_SYSTEM_OPL_DRUMS, 64, 0, "clockSel=3") // 3MHz
}
);
ENTRY(
"Data East Arcade", { // Bad dudes, Robocop, etc
CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=5"), // 1.5MHz
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=5"), // 1.5MHz
CH(DIV_SYSTEM_OPL2, 64, 0, "clockSel=3"), // 3MHz
CH(DIV_SYSTEM_MSM6295, 64, 0, "") // 1 to 1.056MHz; various per games or optional
}
);
ENTRY(
"Data East Arcade (extended channel 3)", { // Bad dudes, Robocop, etc
CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=5"), // 1.5MHz
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=5"), // 1.5MHz
CH(DIV_SYSTEM_OPL2, 64, 0, "clockSel=3"), // 3MHz
CH(DIV_SYSTEM_MSM6295, 64, 0, "") // 1 to 1.056MHz; various per games or optional
}
);
ENTRY(
"Data East Arcade (drums mode)", { // Bad dudes, Robocop, etc
CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=5"), // 1.5MHz
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=5"), // 1.5MHz
CH(DIV_SYSTEM_OPL2_DRUMS, 64, 0, "clockSel=3"), // 3MHz
CH(DIV_SYSTEM_MSM6295, 64, 0, "") // 1 to 1.056MHz; various per games or optional
}
);
ENTRY(
"Data East Arcade (extended channel 3; drums mode)", { // Bad dudes, Robocop, etc
CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=5"), // 1.5MHz
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=5"), // 1.5MHz
CH(DIV_SYSTEM_OPL2_DRUMS, 64, 0, "clockSel=3"), // 3MHz
CH(DIV_SYSTEM_MSM6295, 64, 0, "") // 1 to 1.056MHz; various per games or optional
}
);
ENTRY(
"Data East PCX", {
CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=5"), // 1.5MHz
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=5"), // 1.5MHz
CH(DIV_SYSTEM_PCE, 64, 0, "")
// software controlled MSM5205
}
);
ENTRY(
"Data East PCX (extended channel 3)", {
CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=5"), // 1.5MHz
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=5"), // 1.5MHz
CH(DIV_SYSTEM_PCE, 64, 0, "")
// software controlled MSM5205
}
@ -1872,7 +2172,7 @@ void FurnaceGUI::initSystemPresets() {
ENTRY(
"Data East Dark Seal", { // Dark Seal, Crude Buster, Vapor Trail, etc
CH(DIV_SYSTEM_YM2151, 64, 0, ""), // 3.580MHz (32.22MHz / 9)
CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=2"), // 4.0275MHz (32.22MHz / 8); optional
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=2"), // 4.0275MHz (32.22MHz / 8); optional
CH(DIV_SYSTEM_MSM6295, 64, 0, ""), // 1.007MHz (32.22MHz / 32)
CH(DIV_SYSTEM_MSM6295, 64, 0, "clockSel=8") // 2.014MHz (32.22MHz / 16); optional
// HuC6280 is for control them, internal sound isn't used
@ -1881,7 +2181,7 @@ void FurnaceGUI::initSystemPresets() {
ENTRY(
"Data East Dark Seal (extended channel 3)", { // Dark Seal, Crude Buster, Vapor Trail, etc
CH(DIV_SYSTEM_YM2151, 64, 0, ""), // 3.580MHz (32.22MHz / 9)
CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=2"), // 4.0275MHz (32.22MHz / 8); optional
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=2"), // 4.0275MHz (32.22MHz / 8); optional
CH(DIV_SYSTEM_MSM6295, 64, 0, ""), // 1.007MHz (32.22MHz / 32)
CH(DIV_SYSTEM_MSM6295, 64, 0, "clockSel=8") // 2.014MHz (32.22MHz / 16); optional
// HuC6280 is for control them, internal sound isn't used
@ -1996,7 +2296,7 @@ void FurnaceGUI::initSystemPresets() {
);
ENTRY(
"Alpha denshi Alpha-68K", {
CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=3"), // 3MHz
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=3"), // 3MHz
CH(DIV_SYSTEM_OPLL, 64, 0, "clockSel=0"), // 3.58MHz
CH(DIV_SYSTEM_PCM_DAC, 64, 0,
"rate=7614\n"
@ -2006,7 +2306,7 @@ void FurnaceGUI::initSystemPresets() {
);
ENTRY(
"Alpha denshi Alpha-68K (extended channel 3)", {
CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=3"), // 3MHz
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=3"), // 3MHz
CH(DIV_SYSTEM_OPLL, 64, 0, "clockSel=0"), // 3.58MHz
CH(DIV_SYSTEM_PCM_DAC, 64, 0,
"rate=7614\n"
@ -2016,7 +2316,7 @@ void FurnaceGUI::initSystemPresets() {
);
ENTRY(
"Alpha denshi Alpha-68K (drums mode)", {
CH(DIV_SYSTEM_OPN, 64, 0, "clockSel=3"), // 3MHz
CH(DIV_SYSTEM_YM2203, 64, 0, "clockSel=3"), // 3MHz
CH(DIV_SYSTEM_OPLL_DRUMS, 64, 0, "clockSel=0"), // 3.58MHz
CH(DIV_SYSTEM_PCM_DAC, 64, 0,
"rate=7614\n"
@ -2026,7 +2326,7 @@ void FurnaceGUI::initSystemPresets() {
);
ENTRY(
"Alpha denshi Alpha-68K (extended channel 3; drums mode)", {
CH(DIV_SYSTEM_OPN_EXT, 64, 0, "clockSel=3"), // 3MHz
CH(DIV_SYSTEM_YM2203_EXT, 64, 0, "clockSel=3"), // 3MHz
CH(DIV_SYSTEM_OPLL_DRUMS, 64, 0, "clockSel=0"), // 3.58MHz
CH(DIV_SYSTEM_PCM_DAC, 64, 0,
"rate=7614\n"
@ -2034,6 +2334,20 @@ void FurnaceGUI::initSystemPresets() {
) // software controlled 8 bit DAC
}
);
ENTRY(
"Alpha denshi Equites", {
CH(DIV_SYSTEM_MSM5232, 64, 0, "customClock=6144000"),
CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=14"),
CH(DIV_SYSTEM_PCM_DAC, 64, 0,
"rate=11025\n"
"outDepth=5\n"
),
CH(DIV_SYSTEM_PCM_DAC, 64, 0,
"rate=11025\n"
"outDepth=5\n"
) // don't know what the actual sample rate is
}
);
ENTRY(
"Neo Geo MVS", {
CH(DIV_SYSTEM_YM2610_FULL, 64, 0, "")
@ -2106,6 +2420,43 @@ void FurnaceGUI::initSystemPresets() {
CH(DIV_SYSTEM_YM2610B_EXT, 64, 0, "")
}
);
ENTRY(
"Taito Metal Soldier Isaac II", {
CH(DIV_SYSTEM_MSM5232, 64, 0, ""),
CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=3"),
CH(DIV_SYSTEM_AY8910, 64, 0, "clockSel=3")
}
);
ENTRY(
"Taito The Fairyland Story", {
CH(DIV_SYSTEM_MSM5232, 64, 0, ""),
CH(DIV_SYSTEM_AY8910, 64, 0,
"clockSel=3\n"
"chipType=1\n"
),
CH(DIV_SYSTEM_PCM_DAC, 64, 0,
"rate=11025\n"
"outDepth=7\n"
) // don't know what the actual sample rate is
}
);
ENTRY(
"Taito Wyvern F-0", {
CH(DIV_SYSTEM_MSM5232, 64, 0, ""),
CH(DIV_SYSTEM_AY8910, 64, 0,
"clockSel=3\n"
"chipType=1\n"
),
CH(DIV_SYSTEM_AY8910, 64, 0,
"clockSel=3\n"
"chipType=1\n"
),
CH(DIV_SYSTEM_PCM_DAC, 64, 0,
"rate=11025\n"
"outDepth=7\n"
) // don't know what the actual sample rate is
}
);
ENTRY(
"Seta 1", {
CH(DIV_SYSTEM_X1_010, 64, 0, "")
@ -2302,9 +2653,9 @@ void FurnaceGUI::initSystemPresets() {
FurnaceGUISysDef::FurnaceGUISysDef(const char* n, std::initializer_list<FurnaceGUISysDefChip> def, const char* e):
name(n),
extra(e) {
std::vector<FurnaceGUISysDefChip> uncompiled=def;
orig=def;
int index=0;
for (FurnaceGUISysDefChip& i: uncompiled) {
for (FurnaceGUISysDefChip& i: orig) {
definition+=fmt::sprintf(
"id%d=%d\nvol%d=%d\npan%d=%d\nflags%d=%s\n",
index,

View file

@ -227,11 +227,11 @@ void FurnaceGUI::drawSampleEdit() {
if (sampleDepths[i]==NULL) continue;
if (ImGui::Selectable(sampleDepths[i])) {
sample->prepareUndo(true);
e->lockEngine([sample]() {
e->lockEngine([this,sample,i]() {
sample->render();
sample->depth=(DivSampleDepth)i;
e->renderSamples();
});
sample->depth=(DivSampleDepth)i;
e->renderSamplesP();
updateSampleTex=true;
MARK_MODIFIED;
}

View file

@ -102,6 +102,11 @@ const char* c64Cores[]={
"reSIDfp"
};
const char* pokeyCores[]={
"Atari800 (mzpokeysnd)",
"ASAP (C++ port)"
};
const char* pcspkrOutMethods[]={
"evdev SND_TONE",
"KIOCSOUND on /dev/tty1",
@ -1069,6 +1074,10 @@ void FurnaceGUI::drawSettings() {
ImGui::SameLine();
ImGui::Combo("##C64Core",&settings.c64Core,c64Cores,2);
ImGui::Text("POKEY core");
ImGui::SameLine();
ImGui::Combo("##POKEYCore",&settings.pokeyCore,pokeyCores,2);
ImGui::Separator();
ImGui::Text("PC Speaker strategy");
@ -2336,6 +2345,7 @@ void FurnaceGUI::syncSettings() {
settings.nesCore=e->getConfInt("nesCore",0);
settings.fdsCore=e->getConfInt("fdsCore",0);
settings.c64Core=e->getConfInt("c64Core",1);
settings.pokeyCore=e->getConfInt("pokeyCore",1);
settings.pcSpeakerOutMethod=e->getConfInt("pcSpeakerOutMethod",0);
settings.yrw801Path=e->getConfString("yrw801Path","");
settings.tg100Path=e->getConfString("tg100Path","");
@ -2461,6 +2471,7 @@ void FurnaceGUI::syncSettings() {
clampSetting(settings.nesCore,0,1);
clampSetting(settings.fdsCore,0,1);
clampSetting(settings.c64Core,0,1);
clampSetting(settings.pokeyCore,0,1);
clampSetting(settings.pcSpeakerOutMethod,0,4);
clampSetting(settings.mainFont,0,6);
clampSetting(settings.patFont,0,6);
@ -2604,7 +2615,8 @@ void FurnaceGUI::commitSettings() {
settings.snCore!=e->getConfInt("snCore",0) ||
settings.nesCore!=e->getConfInt("nesCore",0) ||
settings.fdsCore!=e->getConfInt("fdsCore",0) ||
settings.c64Core!=e->getConfInt("c64Core",1)
settings.c64Core!=e->getConfInt("c64Core",1) ||
settings.pokeyCore!=e->getConfInt("pokeyCore",1)
);
e->setConf("mainFontSize",settings.mainFontSize);
@ -2624,6 +2636,7 @@ void FurnaceGUI::commitSettings() {
e->setConf("nesCore",settings.nesCore);
e->setConf("fdsCore",settings.fdsCore);
e->setConf("c64Core",settings.c64Core);
e->setConf("pokeyCore",settings.pokeyCore);
e->setConf("pcSpeakerOutMethod",settings.pcSpeakerOutMethod);
e->setConf("yrw801Path",settings.yrw801Path);
e->setConf("tg100Path",settings.tg100Path);

View file

@ -30,8 +30,8 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
switch (type) {
case DIV_SYSTEM_YM2612:
case DIV_SYSTEM_YM2612_EXT:
case DIV_SYSTEM_YM2612_FRAC:
case DIV_SYSTEM_YM2612_FRAC_EXT: {
case DIV_SYSTEM_YM2612_DUALPCM:
case DIV_SYSTEM_YM2612_DUALPCM_EXT: {
int clockSel=flags.getInt("clockSel",0);
bool ladder=flags.getBool("ladderEffect",0);
bool noExtMacros=flags.getBool("noExtMacros",false);
@ -59,7 +59,7 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
if (ImGui::Checkbox("Enable DAC distortion",&ladder)) {
altered=true;
}
if (type==DIV_SYSTEM_YM2612_EXT || type==DIV_SYSTEM_YM2612_FRAC_EXT) {
if (type==DIV_SYSTEM_YM2612_EXT || type==DIV_SYSTEM_YM2612_DUALPCM_EXT) {
if (ImGui::Checkbox("Disable ExtCh FM macros (compatibility)",&noExtMacros)) {
altered=true;
}
@ -850,8 +850,8 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
}
break;
}
case DIV_SYSTEM_OPN:
case DIV_SYSTEM_OPN_EXT: {
case DIV_SYSTEM_YM2203:
case DIV_SYSTEM_YM2203_EXT: {
int clockSel=flags.getInt("clockSel",0);
int prescale=flags.getInt("prescale",0);
bool noExtMacros=flags.getBool("noExtMacros",false);
@ -895,7 +895,7 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
altered=true;
}
if (type==DIV_SYSTEM_OPN_EXT) {
if (type==DIV_SYSTEM_YM2203_EXT) {
if (ImGui::Checkbox("Disable ExtCh FM macros (compatibility)",&noExtMacros)) {
altered=true;
}
@ -910,8 +910,8 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
}
break;
}
case DIV_SYSTEM_PC98:
case DIV_SYSTEM_PC98_EXT: {
case DIV_SYSTEM_YM2608:
case DIV_SYSTEM_YM2608_EXT: {
int clockSel=flags.getInt("clockSel",0);
int prescale=flags.getInt("prescale",0);
bool noExtMacros=flags.getBool("noExtMacros",false);
@ -939,7 +939,7 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
altered=true;
}
if (type==DIV_SYSTEM_PC98_EXT) {
if (type==DIV_SYSTEM_YM2608_EXT) {
if (ImGui::Checkbox("Disable ExtCh FM macros (compatibility)",&noExtMacros)) {
altered=true;
}
@ -1243,6 +1243,7 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
// default to 44100Hz 16-bit stereo
int sampRate=flags.getInt("rate",44100);
int bitDepth=flags.getInt("outDepth",15)+1;
int interpolation=flags.getInt("interpolation",0);
bool stereo=flags.getBool("stereo",false);
ImGui::Text("Output rate:");
@ -1261,11 +1262,30 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
altered=true;
}
ImGui::Text("Interpolation:");
if (ImGui::RadioButton("None",interpolation==0)) {
interpolation=0;
altered=true;
}
if (ImGui::RadioButton("Linear",interpolation==1)) {
interpolation=1;
altered=true;
}
if (ImGui::RadioButton("Cubic",interpolation==2)) {
interpolation=2;
altered=true;
}
if (ImGui::RadioButton("Sinc",interpolation==3)) {
interpolation=3;
altered=true;
}
if (altered) {
e->lockSave([&]() {
flags.set("rate",sampRate);
flags.set("outDepth",bitDepth-1);
flags.set("stereo",stereo);
flags.set("interpolation",interpolation);
});
}
break;

View file

@ -18,6 +18,8 @@
*/
#include "ta-log.h"
#include <thread>
#include <condition_variable>
#ifdef IS_MOBILE
int logLevel=LOGLEVEL_TRACE;
@ -25,18 +27,71 @@ int logLevel=LOGLEVEL_TRACE;
int logLevel=LOGLEVEL_INFO;
#endif
FILE* logFile;
char* logFileBuf;
unsigned int logFilePosI;
unsigned int logFilePosO;
std::thread* logFileThread;
std::mutex logFileLock;
std::mutex logFileLockI;
std::condition_variable logFileNotify;
bool logFileAvail=false;
std::atomic<unsigned short> logPosition;
LogEntry logEntries[TA_LOG_SIZE];
static constexpr unsigned int TA_LOG_MASK=TA_LOG_SIZE-1;
static constexpr unsigned int TA_LOGFILE_BUF_MASK=TA_LOGFILE_BUF_SIZE-1;
const char* logTypes[5]={
"ERROR",
"warning",
"info",
"debug",
"trace"
};
void appendLogBuf(const LogEntry& entry) {
logFileLockI.lock();
std::string toWrite=fmt::sprintf(
"%02d:%02d:%02d [%s] %s\n",
entry.time.tm_hour,
entry.time.tm_min,
entry.time.tm_sec,
logTypes[entry.loglevel],
entry.text
);
const char* msg=toWrite.c_str();
size_t len=toWrite.size();
int remaining=(logFilePosO-logFilePosI-1)&TA_LOGFILE_BUF_SIZE;
if (len>=(unsigned int)remaining) {
printf("line too long to fit in log buffer!\n");
logFileLockI.unlock();
return;
}
if ((logFilePosI+len)>=TA_LOGFILE_BUF_SIZE) {
size_t firstWrite=TA_LOGFILE_BUF_SIZE-logFilePosI;
memcpy(logFileBuf+logFilePosI,msg,firstWrite);
memcpy(logFileBuf,msg+firstWrite,len-firstWrite);
} else {
memcpy(logFileBuf+logFilePosI,msg,len);
}
logFilePosI=(logFilePosI+len)&TA_LOGFILE_BUF_MASK;
logFileLockI.unlock();
}
int writeLog(int level, const char* msg, fmt::printf_args args) {
time_t thisMakesNoSense=time(NULL);
int pos=logPosition;
logPosition=(logPosition+1)&TA_LOG_MASK;
int pos=(logPosition.fetch_add(1))&TA_LOG_MASK;
logEntries[pos].text=fmt::vsprintf(msg,args);
logEntries[pos].text.assign(fmt::vsprintf(msg,args));
// why do I have to pass a pointer
// can't I just pass the time_t directly?!
#ifdef _WIN32
@ -54,6 +109,12 @@ int writeLog(int level, const char* msg, fmt::printf_args args) {
logEntries[pos].loglevel=level;
logEntries[pos].ready=true;
// write to log file
if (logFileAvail) {
appendLogBuf(logEntries[pos]);
logFileNotify.notify_one();
}
if (logLevel<level) return 0;
switch (level) {
case LOGLEVEL_ERROR:
@ -71,8 +132,70 @@ int writeLog(int level, const char* msg, fmt::printf_args args) {
}
void initLog() {
// initalize log buffer
logPosition=0;
for (int i=0; i<TA_LOG_SIZE; i++) {
logEntries[i].text.reserve(128);
}
// initialize log to file thread
logFileAvail=false;
}
void _logFileThread() {
std::unique_lock<std::mutex> lock(logFileLock);
while (true) {
unsigned int logFilePosICopy=logFilePosI;
if (logFilePosICopy!=logFilePosO) {
// write
if (logFilePosO>logFilePosICopy) {
fwrite(logFileBuf+logFilePosO,1,TA_LOGFILE_BUF_SIZE-logFilePosO,logFile);
logFilePosO=0;
} else {
fwrite(logFileBuf+logFilePosO,1,logFilePosICopy-logFilePosO,logFile);
logFilePosO=logFilePosICopy&TA_LOGFILE_BUF_MASK;
}
} else {
// wait
if (!logFileAvail) break;
fflush(logFile);
logFileNotify.wait(lock);
}
}
}
bool startLogFile(const char* path) {
if (logFileAvail) return true;
// rotate log file if possible
// open log file
if ((logFile=fopen(path,"w+"))==NULL) {
logFileAvail=false;
logW("could not open log file! (%s)",strerror(errno));
return false;
}
logFileBuf=new char[TA_LOGFILE_BUF_SIZE];
logFilePosI=0;
logFilePosO=0;
logFileAvail=true;
logFileThread=new std::thread(_logFileThread);
return true;
}
bool finishLogFile() {
if (!logFileAvail) return false;
logFileAvail=false;
// flush
logFileLockI.lock();
logFileNotify.notify_one();
logFileThread->join();
logFileLockI.unlock();
fclose(logFile);
return true;
}

View file

@ -195,6 +195,8 @@ TAParamResult pVersion(String) {
printf("- Stella by Stella Team (GPLv2)\n");
printf("- vgsound_emu (second version, modified version) by cam900 (zlib license)\n");
printf("- MAME GA20 core by Acho A. Tang, R. Belmont, Valley Bell (BSD 3-clause)\n");
printf("- Atari800 mzpokeysnd POKEY emulator by Michael Borisov (GPLv2)\n");
printf("- ASAP POKEY emulator by Piotr Fusik ported to C++ by laoo (GPLv2)\n");
return TA_PARAM_QUIT;
}
@ -416,23 +418,30 @@ int main(int argc, char** argv) {
logI("usage: %s file",argv[0]);
return 1;
}
logI("Furnace version " DIV_VERSION ".");
e.preInit();
if (!fileName.empty()) {
logI("loading module...");
FILE* f=ps_fopen(fileName.c_str(),"rb");
if (f==NULL) {
reportError(fmt::sprintf("couldn't open file! (%s)",strerror(errno)));
finishLogFile();
return 1;
}
if (fseek(f,0,SEEK_END)<0) {
reportError(fmt::sprintf("couldn't open file! (couldn't get file size: %s)",strerror(errno)));
fclose(f);
finishLogFile();
return 1;
}
ssize_t len=ftell(f);
if (len==(SIZE_MAX>>1)) {
reportError(fmt::sprintf("couldn't open file! (couldn't get file length: %s)",strerror(errno)));
fclose(f);
finishLogFile();
return 1;
}
if (len<1) {
@ -442,6 +451,7 @@ int main(int argc, char** argv) {
reportError(fmt::sprintf("couldn't open file! (tell error: %s)",strerror(errno)));
}
fclose(f);
finishLogFile();
return 1;
}
unsigned char* file=new unsigned char[len];
@ -449,23 +459,27 @@ int main(int argc, char** argv) {
reportError(fmt::sprintf("couldn't open file! (size error: %s)",strerror(errno)));
fclose(f);
delete[] file;
finishLogFile();
return 1;
}
if (fread(file,1,(size_t)len,f)!=(size_t)len) {
reportError(fmt::sprintf("couldn't open file! (read error: %s)",strerror(errno)));
fclose(f);
delete[] file;
finishLogFile();
return 1;
}
fclose(f);
if (!e.load(file,(size_t)len)) {
reportError(fmt::sprintf("could not open file! (%s)",e.getLastError()));
finishLogFile();
return 1;
}
}
if (!e.init()) {
if (consoleMode) {
reportError("could not initialize engine!");
finishLogFile();
return 1;
} else {
logE("could not initialize engine!");
@ -479,6 +493,7 @@ int main(int argc, char** argv) {
} else {
e.benchmarkPlayback();
}
finishLogFile();
return 0;
}
if (outName!="" || vgmOutName!="" || cmdOutName!="") {
@ -519,6 +534,7 @@ int main(int argc, char** argv) {
e.saveAudio(outName.c_str(),loops,outMode);
e.waitAudioFile();
}
finishLogFile();
return 0;
}
@ -536,6 +552,7 @@ int main(int argc, char** argv) {
cli.loop();
cli.finish();
e.quit();
finishLogFile();
return 0;
} else {
#ifdef HAVE_SDL2
@ -545,6 +562,7 @@ int main(int argc, char** argv) {
if (ev.type==SDL_QUIT) break;
}
e.quit();
finishLogFile();
return 0;
#else
while (true) {
@ -562,6 +580,7 @@ int main(int argc, char** argv) {
g.bindEngine(&e);
if (!g.init()) {
reportError(g.getLastError());
finishLogFile();
return 1;
}
@ -584,6 +603,8 @@ int main(int argc, char** argv) {
logI("stopping engine.");
e.quit();
finishLogFile();
#ifdef _WIN32
if (coResult==S_OK || coResult==S_FALSE) {
CoUninitialize();

View file

@ -35,6 +35,9 @@
// this has to be a power of 2
#define TA_LOG_SIZE 2048
// this as well
#define TA_LOGFILE_BUF_SIZE 65536
extern int logLevel;
extern std::atomic<unsigned short> logPosition;
@ -76,4 +79,6 @@ template<typename... T> int logE(const char* msg, const T&... args) {
}
void initLog();
bool startLogFile(const char* path);
bool finishLogFile();
#endif