Merge branch 'master' of https://github.com/tildearrow/furnace into mod-import
This commit is contained in:
commit
c7fb5df206
83 changed files with 7113 additions and 372 deletions
|
|
@ -24,7 +24,7 @@
|
|||
#include <math.h>
|
||||
|
||||
#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;}
|
||||
#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} }
|
||||
#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(regRemap(a),v); if (dumpWrites) {addWrite(regRemap(a),v);} }
|
||||
|
||||
#define CHIP_DIVIDER 8
|
||||
|
||||
|
|
@ -48,8 +48,28 @@ const char* regCheatSheetAY[]={
|
|||
NULL
|
||||
};
|
||||
|
||||
const char* regCheatSheetAY8914[]={
|
||||
"FreqL_A", "0",
|
||||
"FreqL_B", "1",
|
||||
"FreqL_C", "2",
|
||||
"FreqL_Env", "3",
|
||||
"FreqH_A", "4",
|
||||
"FreqH_B", "5",
|
||||
"FreqH_C", "6",
|
||||
"FreqH_Env", "7",
|
||||
"Enable", "8",
|
||||
"FreqNoise", "9",
|
||||
"Control_Env", "A",
|
||||
"Volume_A", "B",
|
||||
"Volume_B", "C",
|
||||
"Volume_C", "D",
|
||||
"PortA", "E",
|
||||
"PortB", "F",
|
||||
NULL
|
||||
};
|
||||
|
||||
const char** DivPlatformAY8910::getRegisterSheet() {
|
||||
return regCheatSheetAY;
|
||||
return intellivision?regCheatSheetAY8914:regCheatSheetAY;
|
||||
}
|
||||
|
||||
const char* DivPlatformAY8910::getEffectName(unsigned char effect) {
|
||||
|
|
@ -92,8 +112,13 @@ void DivPlatformAY8910::acquire(short* bufL, short* bufR, size_t start, size_t l
|
|||
}
|
||||
while (!writes.empty()) {
|
||||
QueuedWrite w=writes.front();
|
||||
ay->address_w(w.addr);
|
||||
ay->data_w(w.val);
|
||||
if (intellivision) {
|
||||
ay8914_device* ay8914=(ay8914_device*)ay;
|
||||
ay8914->write(w.addr,w.val);
|
||||
} else {
|
||||
ay->address_w(w.addr);
|
||||
ay->data_w(w.val);
|
||||
}
|
||||
regPool[w.addr&0x0f]=w.val;
|
||||
writes.pop();
|
||||
}
|
||||
|
|
@ -125,6 +150,8 @@ void DivPlatformAY8910::tick() {
|
|||
if (chan[i].outVol<0) chan[i].outVol=0;
|
||||
if (isMuted[i]) {
|
||||
rWrite(0x08+i,0);
|
||||
} else if (intellivision && (chan[i].psgMode&4)) {
|
||||
rWrite(0x08+i,(chan[i].outVol&0xc)<<2);
|
||||
} else {
|
||||
rWrite(0x08+i,(chan[i].outVol&15)|((chan[i].psgMode&4)<<2));
|
||||
}
|
||||
|
|
@ -151,6 +178,8 @@ void DivPlatformAY8910::tick() {
|
|||
chan[i].psgMode=(chan[i].std.wave+1)&7;
|
||||
if (isMuted[i]) {
|
||||
rWrite(0x08+i,0);
|
||||
} else if (intellivision && (chan[i].psgMode&4)) {
|
||||
rWrite(0x08+i,(chan[i].outVol&0xc)<<2);
|
||||
} else {
|
||||
rWrite(0x08+i,(chan[i].outVol&15)|((chan[i].psgMode&4)<<2));
|
||||
}
|
||||
|
|
@ -242,6 +271,8 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
|
|||
chan[c.chan].std.init(ins);
|
||||
if (isMuted[c.chan]) {
|
||||
rWrite(0x08+c.chan,0);
|
||||
} else if (intellivision && (chan[c.chan].psgMode&4)) {
|
||||
rWrite(0x08+c.chan,(chan[c.chan].vol&0xc)<<2);
|
||||
} else {
|
||||
rWrite(0x08+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2));
|
||||
}
|
||||
|
|
@ -264,7 +295,13 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
|
|||
if (isMuted[c.chan]) {
|
||||
rWrite(0x08+c.chan,0);
|
||||
} else {
|
||||
if (chan[c.chan].active) rWrite(0x08+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2));
|
||||
if (chan[c.chan].active) {
|
||||
if (intellivision && (chan[c.chan].psgMode&4)) {
|
||||
rWrite(0x08+c.chan,(chan[c.chan].vol&0xc)<<2);
|
||||
} else {
|
||||
rWrite(0x08+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2));
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -317,7 +354,11 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
|
|||
if (isMuted[c.chan]) {
|
||||
rWrite(0x08+c.chan,0);
|
||||
} else if (chan[c.chan].active) {
|
||||
rWrite(0x08+c.chan,(chan[c.chan].outVol&15)|((chan[c.chan].psgMode&4)<<2));
|
||||
if (intellivision && (chan[c.chan].psgMode&4)) {
|
||||
rWrite(0x08+c.chan,(chan[c.chan].outVol&0xc)<<2);
|
||||
} else {
|
||||
rWrite(0x08+c.chan,(chan[c.chan].outVol&15)|((chan[c.chan].psgMode&4)<<2));
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
@ -334,6 +375,8 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
|
|||
}
|
||||
if (isMuted[c.chan]) {
|
||||
rWrite(0x08+c.chan,0);
|
||||
} else if (intellivision && (chan[c.chan].psgMode&4)) {
|
||||
rWrite(0x08+c.chan,(chan[c.chan].vol&0xc)<<2);
|
||||
} else {
|
||||
rWrite(0x08+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2));
|
||||
}
|
||||
|
|
@ -383,6 +426,8 @@ void DivPlatformAY8910::muteChannel(int ch, bool mute) {
|
|||
isMuted[ch]=mute;
|
||||
if (isMuted[ch]) {
|
||||
rWrite(0x08+ch,0);
|
||||
} else if (intellivision && (chan[ch].psgMode&4)) {
|
||||
rWrite(0x08+ch,(chan[ch].vol&0xc)<<2);
|
||||
} else {
|
||||
rWrite(0x08+ch,(chan[ch].outVol&15)|((chan[ch].psgMode&4)<<2));
|
||||
}
|
||||
|
|
@ -508,14 +553,22 @@ void DivPlatformAY8910::setFlags(unsigned int flags) {
|
|||
case 1:
|
||||
ay=new ym2149_device(rate);
|
||||
sunsoft=false;
|
||||
intellivision=false;
|
||||
break;
|
||||
case 2:
|
||||
ay=new sunsoft_5b_sound_device(rate);
|
||||
sunsoft=true;
|
||||
intellivision=false;
|
||||
break;
|
||||
case 3:
|
||||
ay=new ay8914_device(rate);
|
||||
sunsoft=false;
|
||||
intellivision=true;
|
||||
break;
|
||||
default:
|
||||
ay=new ay8910_device(rate);
|
||||
sunsoft=false;
|
||||
intellivision=false;
|
||||
break;
|
||||
}
|
||||
ay->device_start();
|
||||
|
|
|
|||
|
|
@ -26,6 +26,10 @@
|
|||
|
||||
class DivPlatformAY8910: public DivDispatch {
|
||||
protected:
|
||||
const unsigned char AY8914RegRemap[16]={
|
||||
0,4,1,5,2,6,9,8,11,12,13,3,7,10,14,15
|
||||
};
|
||||
inline unsigned char regRemap(unsigned char reg) { return intellivision?AY8914RegRemap[reg&0x0f]:reg&0x0f; }
|
||||
struct Channel {
|
||||
unsigned char freqH, freqL;
|
||||
int freq, baseFreq, note, pitch;
|
||||
|
|
@ -60,7 +64,7 @@ class DivPlatformAY8910: public DivDispatch {
|
|||
int delay;
|
||||
|
||||
bool extMode;
|
||||
bool stereo, sunsoft;
|
||||
bool stereo, sunsoft, intellivision;
|
||||
|
||||
short oldWrites[16];
|
||||
short pendingWrites[16];
|
||||
|
|
|
|||
|
|
@ -509,10 +509,17 @@ void DivPlatformC64::setChipModel(bool is6581) {
|
|||
}
|
||||
|
||||
void DivPlatformC64::setFlags(unsigned int flags) {
|
||||
if (flags&1) {
|
||||
rate=COLOR_PAL*2.0/9.0;
|
||||
} else {
|
||||
rate=COLOR_NTSC*2.0/7.0;
|
||||
switch (flags&0xf) {
|
||||
case 0x0: // NTSC C64
|
||||
rate=COLOR_NTSC*2.0/7.0;
|
||||
break;
|
||||
case 0x1: // PAL C64
|
||||
rate=COLOR_PAL*2.0/9.0;
|
||||
break;
|
||||
case 0x2: // SSI 2001
|
||||
default:
|
||||
rate=14318180.0/16.0;
|
||||
break;
|
||||
}
|
||||
chipClock=rate;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,8 +97,12 @@ void DivPlatformGB::updateWave() {
|
|||
if (wt->max<1 || wt->len<1) {
|
||||
rWrite(0x30+i,0);
|
||||
} else {
|
||||
unsigned char nibble1=15-((wt->data[(i*2)*wt->len/32]*15)/wt->max);
|
||||
unsigned char nibble2=15-((wt->data[(1+i*2)*wt->len/32]*15)/wt->max);
|
||||
int nibble1=15-((wt->data[(i*2)*wt->len/32]*15)/wt->max);
|
||||
int nibble2=15-((wt->data[(1+i*2)*wt->len/32]*15)/wt->max);
|
||||
if (nibble1<0) nibble1=0;
|
||||
if (nibble1>15) nibble1=15;
|
||||
if (nibble2<0) nibble2=0;
|
||||
if (nibble2>15) nibble2=15;
|
||||
rWrite(0x30+i,(nibble1<<4)|nibble2);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,13 +92,16 @@ void DivPlatformGenesis::acquire_nuked(short* bufL, short* bufR, size_t start, s
|
|||
DivSample* s=parent->getSample(dacSample);
|
||||
if (s->samples>0) {
|
||||
if (!isMuted[5]) {
|
||||
immWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80);
|
||||
urgentWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80);
|
||||
}
|
||||
if (++dacPos>=s->samples) {
|
||||
if (s->loopStart>=0 && s->loopStart<=(int)s->samples) {
|
||||
dacPos=s->loopStart;
|
||||
} else {
|
||||
dacSample=-1;
|
||||
if (parent->song.brokenDACMode) {
|
||||
rWrite(0x2b,0);
|
||||
}
|
||||
}
|
||||
}
|
||||
dacPeriod+=MAX(40,dacRate);
|
||||
|
|
@ -118,7 +121,7 @@ void DivPlatformGenesis::acquire_nuked(short* bufL, short* bufR, size_t start, s
|
|||
//printf("write: %x = %.2x\n",w.addr,w.val);
|
||||
lastBusy=0;
|
||||
regPool[w.addr&0x1ff]=w.val;
|
||||
writes.pop();
|
||||
writes.pop_front();
|
||||
} else {
|
||||
lastBusy++;
|
||||
if (fm.write_busy==0) {
|
||||
|
|
@ -156,13 +159,16 @@ void DivPlatformGenesis::acquire_ymfm(short* bufL, short* bufR, size_t start, si
|
|||
DivSample* s=parent->getSample(dacSample);
|
||||
if (s->samples>0) {
|
||||
if (!isMuted[5]) {
|
||||
immWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80);
|
||||
urgentWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80);
|
||||
}
|
||||
if (++dacPos>=s->samples) {
|
||||
if (s->loopStart>=0 && s->loopStart<=(int)s->samples) {
|
||||
dacPos=s->loopStart;
|
||||
} else {
|
||||
dacSample=-1;
|
||||
if (parent->song.brokenDACMode) {
|
||||
rWrite(0x2b,0);
|
||||
}
|
||||
}
|
||||
}
|
||||
dacPeriod+=MAX(40,dacRate);
|
||||
|
|
@ -178,7 +184,7 @@ void DivPlatformGenesis::acquire_ymfm(short* bufL, short* bufR, size_t start, si
|
|||
fm_ymfm->write(0x0+((w.addr>>8)<<1),w.addr);
|
||||
fm_ymfm->write(0x1+((w.addr>>8)<<1),w.val);
|
||||
regPool[w.addr&0x1ff]=w.val;
|
||||
writes.pop();
|
||||
writes.pop_front();
|
||||
lastBusy=1;
|
||||
}
|
||||
|
||||
|
|
@ -460,6 +466,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
|
|||
if (dumpWrites) addWrite(0xffff0002,0);
|
||||
break;
|
||||
} else {
|
||||
rWrite(0x2b,1<<7);
|
||||
if (dumpWrites) addWrite(0xffff0000,dacSample);
|
||||
}
|
||||
dacPos=0;
|
||||
|
|
@ -477,6 +484,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
|
|||
if (dumpWrites) addWrite(0xffff0002,0);
|
||||
break;
|
||||
} else {
|
||||
rWrite(0x2b,1<<7);
|
||||
if (dumpWrites) addWrite(0xffff0000,dacSample);
|
||||
}
|
||||
dacPos=0;
|
||||
|
|
@ -541,6 +549,10 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
|
|||
if (c.chan==5) {
|
||||
dacSample=-1;
|
||||
if (dumpWrites) addWrite(0xffff0002,0);
|
||||
if (parent->song.brokenDACMode) {
|
||||
rWrite(0x2b,0);
|
||||
if (dacMode) break;
|
||||
}
|
||||
}
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].keyOn=false;
|
||||
|
|
@ -770,7 +782,7 @@ int DivPlatformGenesis::getRegisterPoolSize() {
|
|||
}
|
||||
|
||||
void DivPlatformGenesis::reset() {
|
||||
while (!writes.empty()) writes.pop();
|
||||
while (!writes.empty()) writes.pop_front();
|
||||
memset(regPool,0,512);
|
||||
if (useYMFM) {
|
||||
fm_ymfm->reset();
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
#ifndef _GENESIS_H
|
||||
#define _GENESIS_H
|
||||
#include "../dispatch.h"
|
||||
#include <queue>
|
||||
#include <deque>
|
||||
#include "../../../extern/Nuked-OPN2/ym3438.h"
|
||||
#include "sound/ymfm/ymfm_opn.h"
|
||||
|
||||
|
|
@ -68,7 +68,7 @@ class DivPlatformGenesis: public DivDispatch {
|
|||
bool addrOrVal;
|
||||
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {}
|
||||
};
|
||||
std::queue<QueuedWrite> writes;
|
||||
std::deque<QueuedWrite> writes;
|
||||
ym3438_t fm;
|
||||
int delay;
|
||||
unsigned char lastBusy;
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ static int orderedOps[4]={
|
|||
};
|
||||
|
||||
#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;}
|
||||
#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} }
|
||||
#define immWrite(a,v) if (!skipRegisterWrites) {writes.push_back(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);} }
|
||||
#define urgentWrite(a,v) if (!skipRegisterWrites) {if (writes.front().addrOrVal) {writes.push_back(QueuedWrite(a,v));} else {writes.push_front(QueuedWrite(a,v));}; if (dumpWrites) {addWrite(a,v);} }
|
||||
|
||||
#include "fmshared_OPN.h"
|
||||
|
|
|
|||
|
|
@ -82,7 +82,44 @@ static int32_t clamp(int32_t v, int32_t lo, int32_t hi)
|
|||
}
|
||||
|
||||
const char* regCheatSheetLynx[]={
|
||||
"DATA", "0",
|
||||
"AUDIO0_VOLCNTRL", "20",
|
||||
"AUDIO0_FEEDBACK", "21",
|
||||
"AUDIO0_OUTPUT", "22",
|
||||
"AUDIO0_SHIFT", "23",
|
||||
"AUDIO0_BACKUP", "24",
|
||||
"AUDIO0_CONTROL", "25",
|
||||
"AUDIO0_COUNTER", "26",
|
||||
"AUDIO0_OTHER", "27",
|
||||
"AUDIO1_VOLCNTRL", "28",
|
||||
"AUDIO1_FEEDBACK", "29",
|
||||
"AUDIO1_OUTPUT", "2a",
|
||||
"AUDIO1_SHIFT", "2b",
|
||||
"AUDIO1_BACKUP", "2c",
|
||||
"AUDIO1_CONTROL", "2d",
|
||||
"AUDIO1_COUNTER", "2e",
|
||||
"AUDIO1_OTHER", "2f",
|
||||
"AUDIO2_VOLCNTRL", "30",
|
||||
"AUDIO2_FEEDBACK", "31",
|
||||
"AUDIO2_OUTPUT", "32",
|
||||
"AUDIO2_SHIFT", "33",
|
||||
"AUDIO2_BACKUP", "34",
|
||||
"AUDIO2_CONTROL", "35",
|
||||
"AUDIO2_COUNTER", "36",
|
||||
"AUDIO2_OTHER", "37",
|
||||
"AUDIO3_VOLCNTRL", "38",
|
||||
"AUDIO3_FEEDBACK", "39",
|
||||
"AUDIO3_OUTPUT", "3a",
|
||||
"AUDIO3_SHIFT", "3b",
|
||||
"AUDIO3_BACKUP", "3c",
|
||||
"AUDIO3_CONTROL", "3d",
|
||||
"AUDIO3_COUNTER", "3e",
|
||||
"AUDIO3_OTHER", "3f",
|
||||
"ATTENREG0", "40",
|
||||
"ATTENREG1", "41",
|
||||
"ATTENREG2", "42",
|
||||
"ATTENREG3", "43",
|
||||
"MPAN", "44",
|
||||
"MSTEREO", "50",
|
||||
NULL
|
||||
};
|
||||
|
||||
|
|
@ -198,7 +235,7 @@ int DivPlatformLynx::dispatch(DivCommand c) {
|
|||
}
|
||||
break;
|
||||
case DIV_CMD_PANNING:
|
||||
chan[c.chan].pan=((c.value&0x0f)<<4)|((c.value&0xf0)>>4);
|
||||
chan[c.chan].pan=c.value;
|
||||
WRITE_ATTEN(c.chan,chan[c.chan].pan);
|
||||
break;
|
||||
case DIV_CMD_GET_VOLUME:
|
||||
|
|
|
|||
|
|
@ -201,6 +201,7 @@ void DivPlatformNES::tick() {
|
|||
} else {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)-1;
|
||||
if (chan[i].freq>2047) chan[i].freq=2047;
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
}
|
||||
if (chan[i].keyOn) {
|
||||
//rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63)));
|
||||
|
|
|
|||
|
|
@ -222,7 +222,7 @@ void DivPlatformOPL::acquire(short* bufL, short* bufR, size_t start, size_t len)
|
|||
}
|
||||
|
||||
void DivPlatformOPL::tick() {
|
||||
for (int i=0; i<20; i++) {
|
||||
for (int i=0; i<melodicChans; i++) {
|
||||
chan[i].std.next();
|
||||
|
||||
/*
|
||||
|
|
@ -349,10 +349,22 @@ void DivPlatformOPL::tick() {
|
|||
|
||||
if (chan[i].keyOn || chan[i].keyOff) {
|
||||
immWrite(chanMap[i]+ADDR_FREQH,0x00|(chan[i].freqH&31));
|
||||
if (chan[i].state.ops==4 && i<6) {
|
||||
immWrite(chanMap[i+1]+ADDR_FREQH,0x00|(chan[i].freqH&31));
|
||||
}
|
||||
chan[i].keyOff=false;
|
||||
}
|
||||
}
|
||||
|
||||
if (update4OpMask) {
|
||||
update4OpMask=false;
|
||||
if (oplType==3) {
|
||||
unsigned char opMask=chan[0].fourOp|(chan[2].fourOp<<1)|(chan[4].fourOp<<2)|(chan[6].fourOp<<3)|(chan[8].fourOp<<4)|(chan[10].fourOp<<5);
|
||||
immWrite(0x104,opMask);
|
||||
//printf("updating opMask to %.2x\n",opMask);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i=0; i<512; i++) {
|
||||
if (pendingWrites[i]!=oldWrites[i]) {
|
||||
immWrite(i,pendingWrites[i]&0xff);
|
||||
|
|
@ -360,7 +372,7 @@ void DivPlatformOPL::tick() {
|
|||
}
|
||||
}
|
||||
|
||||
for (int i=0; i<20; i++) {
|
||||
for (int i=0; i<melodicChans; i++) {
|
||||
if (chan[i].freqChanged) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq));
|
||||
if (chan[i].freq>131071) chan[i].freq=131071;
|
||||
|
|
@ -368,12 +380,21 @@ void DivPlatformOPL::tick() {
|
|||
chan[i].freqH=freqt>>8;
|
||||
chan[i].freqL=freqt&0xff;
|
||||
immWrite(chanMap[i]+ADDR_FREQ,chan[i].freqL);
|
||||
if (chan[i].state.ops==4 && i<6) {
|
||||
immWrite(chanMap[i+1]+ADDR_FREQ,chan[i].freqL);
|
||||
}
|
||||
}
|
||||
if (chan[i].keyOn) {
|
||||
immWrite(chanMap[i]+ADDR_FREQH,chan[i].freqH|(0x20));
|
||||
if (chan[i].state.ops==4 && i<6) {
|
||||
immWrite(chanMap[i+1]+ADDR_FREQH,chan[i].freqH|(0x20));
|
||||
}
|
||||
chan[i].keyOn=false;
|
||||
} else if (chan[i].freqChanged) {
|
||||
immWrite(chanMap[i]+ADDR_FREQH,chan[i].freqH|(chan[i].active<<5));
|
||||
if (chan[i].state.ops==4 && i<6) {
|
||||
immWrite(chanMap[i+1]+ADDR_FREQH,chan[i].freqH|(chan[i].active<<5));
|
||||
}
|
||||
}
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
|
|
@ -424,25 +445,42 @@ int DivPlatformOPL::toFreq(int freq) {
|
|||
|
||||
void DivPlatformOPL::muteChannel(int ch, bool mute) {
|
||||
isMuted[ch]=mute;
|
||||
/*
|
||||
for (int j=0; j<4; j++) {
|
||||
unsigned short baseAddr=chanOffs[ch]|opOffs[j];
|
||||
DivInstrumentFM::Operator& op=chan[ch].state.op[j];
|
||||
int ops=(slots[3][ch]!=255 && chan[ch].state.ops==4 && oplType==3)?4:2;
|
||||
chan[ch].fourOp=(ops==4);
|
||||
update4OpMask=true;
|
||||
for (int i=0; i<ops; i++) {
|
||||
unsigned char slot=slots[i][ch];
|
||||
if (slot==255) continue;
|
||||
unsigned short baseAddr=slotMap[slot];
|
||||
DivInstrumentFM::Operator& op=chan[ch].state.op[(ops==4)?orderedOpsL[i]:i];
|
||||
|
||||
if (isMuted[ch]) {
|
||||
rWrite(baseAddr+ADDR_TL,127);
|
||||
rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6));
|
||||
} else {
|
||||
if (isOutput[chan[ch].state.alg][j]) {
|
||||
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[ch].outVol&0x7f))/127));
|
||||
if (isOutputL[ops==4][chan[ch].state.alg][i]) {
|
||||
rWrite(baseAddr+ADDR_KSL_TL,(63-(((63-op.tl)*(chan[ch].outVol&0x3f))/63))|(op.ksl<<6));
|
||||
} else {
|
||||
rWrite(baseAddr+ADDR_TL,op.tl);
|
||||
rWrite(baseAddr+ADDR_KSL_TL,op.tl|(op.ksl<<6));
|
||||
}
|
||||
}
|
||||
}
|
||||
rWrite(chanOffs[ch]+ADDR_LRAF,(isMuted[ch]?0:(chan[ch].pan<<6))|(chan[ch].state.fms&7)|((chan[ch].state.ams&3)<<4));
|
||||
*/
|
||||
|
||||
if (isMuted[ch]) {
|
||||
rWrite(chanMap[ch]+ADDR_LR_FB_ALG,(chan[ch].state.alg&1)|(chan[ch].state.fb<<1));
|
||||
if (ops==4) {
|
||||
rWrite(chanMap[ch+1]+ADDR_LR_FB_ALG,((chan[ch].state.alg>>1)&1)|(chan[ch].state.fb<<1));
|
||||
}
|
||||
} else {
|
||||
rWrite(chanMap[ch]+ADDR_LR_FB_ALG,(chan[ch].state.alg&1)|(chan[ch].state.fb<<1)|((chan[ch].pan&3)<<4));
|
||||
if (ops==4) {
|
||||
rWrite(chanMap[ch+1]+ADDR_LR_FB_ALG,((chan[ch].state.alg>>1)&1)|(chan[ch].state.fb<<1)|((chan[ch].pan&3)<<4));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformOPL::dispatch(DivCommand c) {
|
||||
// TODO: drums mode!
|
||||
if (c.chan>=melodicChans) return 0;
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
|
||||
|
|
@ -456,7 +494,9 @@ int DivPlatformOPL::dispatch(DivCommand c) {
|
|||
chan[c.chan].outVol=chan[c.chan].vol;
|
||||
}
|
||||
if (chan[c.chan].insChanged) {
|
||||
int ops=(slots[3][c.chan]!=255 && ins->fm.ops==4 && oplType==3)?4:2;
|
||||
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
|
||||
chan[c.chan].fourOp=(ops==4);
|
||||
update4OpMask=true;
|
||||
for (int i=0; i<ops; i++) {
|
||||
unsigned char slot=slots[i][c.chan];
|
||||
if (slot==255) continue;
|
||||
|
|
@ -524,21 +564,23 @@ int DivPlatformOPL::dispatch(DivCommand c) {
|
|||
if (!chan[c.chan].std.hasVol) {
|
||||
chan[c.chan].outVol=c.value;
|
||||
}
|
||||
/*
|
||||
for (int i=0; i<4; i++) {
|
||||
unsigned short baseAddr=chanOffs[c.chan]|opOffs[i];
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[i];
|
||||
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
|
||||
for (int i=0; i<ops; i++) {
|
||||
unsigned char slot=slots[i][c.chan];
|
||||
if (slot==255) continue;
|
||||
unsigned short baseAddr=slotMap[slot];
|
||||
DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[i]:i];
|
||||
|
||||
if (isMuted[c.chan]) {
|
||||
rWrite(baseAddr+ADDR_TL,127);
|
||||
rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6));
|
||||
} else {
|
||||
if (isOutput[chan[c.chan].state.alg][i]) {
|
||||
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127));
|
||||
if (isOutputL[ops==4][chan[c.chan].state.alg][i]) {
|
||||
rWrite(baseAddr+ADDR_KSL_TL,(63-(((63-op.tl)*(chan[c.chan].outVol&0x3f))/63))|(op.ksl<<6));
|
||||
} else {
|
||||
rWrite(baseAddr+ADDR_TL,op.tl);
|
||||
rWrite(baseAddr+ADDR_KSL_TL,op.tl|(op.ksl<<6));
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_GET_VOLUME: {
|
||||
|
|
@ -552,16 +594,22 @@ int DivPlatformOPL::dispatch(DivCommand c) {
|
|||
chan[c.chan].ins=c.value;
|
||||
break;
|
||||
case DIV_CMD_PANNING: {
|
||||
switch (c.value) {
|
||||
case 0x01:
|
||||
chan[c.chan].pan=1;
|
||||
break;
|
||||
case 0x10:
|
||||
chan[c.chan].pan=2;
|
||||
break;
|
||||
default:
|
||||
chan[c.chan].pan=3;
|
||||
break;
|
||||
if (c.value==0) {
|
||||
chan[c.chan].pan=3;
|
||||
} else {
|
||||
chan[c.chan].pan=(((c.value&15)>0)<<1)|((c.value>>4)>0);
|
||||
}
|
||||
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
|
||||
if (isMuted[c.chan]) {
|
||||
rWrite(chanMap[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&1)|(chan[c.chan].state.fb<<1));
|
||||
if (ops==4) {
|
||||
rWrite(chanMap[c.chan+1]+ADDR_LR_FB_ALG,((chan[c.chan].state.alg>>1)&1)|(chan[c.chan].state.fb<<1));
|
||||
}
|
||||
} else {
|
||||
rWrite(chanMap[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&1)|(chan[c.chan].state.fb<<1)|((chan[c.chan].pan&3)<<4));
|
||||
if (ops==4) {
|
||||
rWrite(chanMap[c.chan+1]+ADDR_LR_FB_ALG,((chan[c.chan].state.alg>>1)&1)|(chan[c.chan].state.fb<<1)|((chan[c.chan].pan&3)<<4));
|
||||
}
|
||||
}
|
||||
//rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4));
|
||||
break;
|
||||
|
|
@ -683,39 +731,46 @@ int DivPlatformOPL::dispatch(DivCommand c) {
|
|||
}
|
||||
|
||||
void DivPlatformOPL::forceIns() {
|
||||
/*
|
||||
for (int i=0; i<20; i++) {
|
||||
for (int j=0; j<4; j++) {
|
||||
unsigned short baseAddr=chanOffs[i]|opOffs[j];
|
||||
DivInstrumentFM::Operator& op=chan[i].state.op[j];
|
||||
for (int i=0; i<melodicChans; i++) {
|
||||
int ops=(slots[3][i]!=255 && chan[i].state.ops==4 && oplType==3)?4:2;
|
||||
chan[i].fourOp=(ops==4);
|
||||
for (int j=0; j<ops; j++) {
|
||||
unsigned char slot=slots[j][i];
|
||||
if (slot==255) continue;
|
||||
unsigned short baseAddr=slotMap[slot];
|
||||
DivInstrumentFM::Operator& op=chan[i].state.op[(ops==4)?orderedOpsL[j]:j];
|
||||
|
||||
if (isMuted[i]) {
|
||||
rWrite(baseAddr+ADDR_TL,127);
|
||||
rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6));
|
||||
} else {
|
||||
if (isOutput[chan[i].state.alg][j]) {
|
||||
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127));
|
||||
if (isOutputL[ops==4][chan[i].state.alg][j]) {
|
||||
rWrite(baseAddr+ADDR_KSL_TL,(63-(((63-op.tl)*(chan[i].outVol&0x3f))/63))|(op.ksl<<6));
|
||||
} else {
|
||||
rWrite(baseAddr+ADDR_TL,op.tl);
|
||||
rWrite(baseAddr+ADDR_KSL_TL,op.tl|(op.ksl<<6));
|
||||
}
|
||||
}
|
||||
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
|
||||
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
|
||||
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
|
||||
rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31);
|
||||
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
|
||||
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
|
||||
|
||||
rWrite(baseAddr+ADDR_AM_VIB_SUS_KSR_MULT,(op.am<<7)|(op.vib<<6)|(op.sus<<5)|(op.ksr<<4)|op.mult);
|
||||
rWrite(baseAddr+ADDR_AR_DR,(op.ar<<4)|op.dr);
|
||||
rWrite(baseAddr+ADDR_SL_RR,(op.sl<<4)|op.rr);
|
||||
if (oplType>1) {
|
||||
rWrite(baseAddr+ADDR_WS,op.ws&((oplType==3)?7:3));
|
||||
}
|
||||
}
|
||||
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 (chan[i].active) {
|
||||
chan[i].keyOn=true;
|
||||
chan[i].freqChanged=true;
|
||||
|
||||
if (isMuted[i]) {
|
||||
rWrite(chanMap[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&1)|(chan[i].state.fb<<1));
|
||||
if (ops==4) {
|
||||
rWrite(chanMap[i+1]+ADDR_LR_FB_ALG,((chan[i].state.alg>>1)&1)|(chan[i].state.fb<<1));
|
||||
}
|
||||
} else {
|
||||
rWrite(chanMap[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&1)|(chan[i].state.fb<<1)|((chan[i].pan&3)<<4));
|
||||
if (ops==4) {
|
||||
rWrite(chanMap[i+1]+ADDR_LR_FB_ALG,((chan[i].state.alg>>1)&1)|(chan[i].state.fb<<1)|((chan[i].pan&3)<<4));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dacMode) {
|
||||
rWrite(0x2b,0x80);
|
||||
}
|
||||
immWrite(0x22,lfoValue);
|
||||
*/
|
||||
update4OpMask=true;
|
||||
}
|
||||
|
||||
void DivPlatformOPL::toggleRegisterDump(bool enable) {
|
||||
|
|
@ -746,7 +801,7 @@ void DivPlatformOPL::reset() {
|
|||
if (dumpWrites) {
|
||||
addWrite(0xffffffff,0);
|
||||
}
|
||||
for (int i=0; i<20; i++) {
|
||||
for (int i=0; i<totalChans; i++) {
|
||||
chan[i]=DivPlatformOPL::Channel();
|
||||
chan[i].vol=0x3f;
|
||||
chan[i].outVol=0x3f;
|
||||
|
|
@ -761,10 +816,15 @@ void DivPlatformOPL::reset() {
|
|||
lfoValue=8;
|
||||
properDrums=properDrumsSys;
|
||||
|
||||
if (oplType==1) { // disable waveforms
|
||||
immWrite(0x01,0x20);
|
||||
}
|
||||
|
||||
if (oplType==3) { // enable OPL3 features
|
||||
immWrite(0x105,1);
|
||||
}
|
||||
|
||||
|
||||
update4OpMask=true;
|
||||
delay=0;
|
||||
}
|
||||
|
||||
|
|
@ -781,7 +841,7 @@ bool DivPlatformOPL::keyOffAffectsPorta(int ch) {
|
|||
}
|
||||
|
||||
void DivPlatformOPL::notifyInsChange(int ins) {
|
||||
for (int i=0; i<20; i++) {
|
||||
for (int i=0; i<totalChans; i++) {
|
||||
if (chan[i].ins==ins) {
|
||||
chan[i].insChanged=true;
|
||||
}
|
||||
|
|
@ -815,6 +875,9 @@ void DivPlatformOPL::setOPLType(int type, bool drums) {
|
|||
slots=drums?slotsDrums:slotsNonDrums;
|
||||
chanMap=chanMapOPL2;
|
||||
chipFreqBase=9440540*0.25;
|
||||
chans=9;
|
||||
melodicChans=drums?6:9;
|
||||
totalChans=drums?11:9;
|
||||
break;
|
||||
case 3:
|
||||
slotsNonDrums=slotsOPL3;
|
||||
|
|
@ -822,6 +885,9 @@ void DivPlatformOPL::setOPLType(int type, bool drums) {
|
|||
slots=drums?slotsDrums:slotsNonDrums;
|
||||
chanMap=chanMapOPL3;
|
||||
chipFreqBase=9440540;
|
||||
chans=18;
|
||||
melodicChans=drums?15:18;
|
||||
totalChans=drums?20:18;
|
||||
break;
|
||||
}
|
||||
oplType=type;
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ class DivPlatformOPL: public DivDispatch {
|
|||
unsigned char freqH, freqL;
|
||||
int freq, baseFreq, pitch, note;
|
||||
unsigned char ins;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta, fourOp;
|
||||
int vol, outVol;
|
||||
unsigned char pan;
|
||||
Channel():
|
||||
|
|
@ -51,6 +51,7 @@ class DivPlatformOPL: public DivDispatch {
|
|||
portaPause(false),
|
||||
furnaceDac(false),
|
||||
inPorta(false),
|
||||
fourOp(false),
|
||||
vol(0),
|
||||
pan(3) {}
|
||||
};
|
||||
|
|
@ -69,7 +70,7 @@ class DivPlatformOPL: public DivDispatch {
|
|||
const unsigned char** slots;
|
||||
const unsigned short* chanMap;
|
||||
double chipFreqBase;
|
||||
int delay, oplType;
|
||||
int delay, oplType, chans, melodicChans, totalChans;
|
||||
unsigned char lastBusy;
|
||||
|
||||
unsigned char regPool[512];
|
||||
|
|
@ -78,7 +79,7 @@ class DivPlatformOPL: public DivDispatch {
|
|||
|
||||
unsigned char lfoValue;
|
||||
|
||||
bool useYMFM;
|
||||
bool useYMFM, update4OpMask;
|
||||
|
||||
short oldWrites[512];
|
||||
short pendingWrites[512];
|
||||
|
|
|
|||
|
|
@ -138,7 +138,10 @@ void DivPlatformPCE::updateWave(int ch) {
|
|||
if (wt->max<1 || wt->len<1) {
|
||||
chWrite(ch,0x06,0);
|
||||
} else {
|
||||
chWrite(ch,0x06,wt->data[i*wt->len/32]*31/wt->max);
|
||||
int data=wt->data[i*wt->len/32]*31/wt->max;
|
||||
if (data<0) data=0;
|
||||
if (data>31) data=31;
|
||||
chWrite(ch,0x06,data);
|
||||
}
|
||||
}
|
||||
if (chan[ch].active) {
|
||||
|
|
|
|||
|
|
@ -435,8 +435,8 @@ public:
|
|||
case ATTENREG2:
|
||||
case ATTENREG3:
|
||||
mRegisterPool[8*4+idx] = value;
|
||||
mAttenuationLeft[idx] = ( value & 0x0f ) << 2;
|
||||
mAttenuationRight[idx] = ( value & 0xf0 ) >> 2;
|
||||
mAttenuationRight[idx] = ( value & 0x0f ) << 2;
|
||||
mAttenuationLeft[idx] = ( value & 0xf0 ) >> 2;
|
||||
break;
|
||||
case MPAN:
|
||||
mPan = value;
|
||||
|
|
|
|||
135
src/engine/platform/sound/vera_pcm.c
Normal file
135
src/engine/platform/sound/vera_pcm.c
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
// Commander X16 Emulator
|
||||
// Copyright (c) 2020 Frank van den Hoef
|
||||
// All rights reserved. License: 2-clause BSD
|
||||
|
||||
#include "vera_pcm.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static uint8_t volume_lut[16] = {0, 1, 2, 3, 4, 5, 6, 8, 11, 14, 18, 23, 30, 38, 49, 64};
|
||||
|
||||
static void
|
||||
fifo_reset(struct VERA_PCM* pcm)
|
||||
{
|
||||
pcm->fifo_wridx = 0;
|
||||
pcm->fifo_rdidx = 0;
|
||||
pcm->fifo_cnt = 0;
|
||||
memset(pcm->fifo,0,sizeof(pcm->fifo));
|
||||
}
|
||||
|
||||
void
|
||||
pcm_reset(struct VERA_PCM* pcm)
|
||||
{
|
||||
fifo_reset(pcm);
|
||||
pcm->ctrl = 0;
|
||||
pcm->rate = 0;
|
||||
pcm->cur_l = 0;
|
||||
pcm->cur_r = 0;
|
||||
pcm->phase = 0;
|
||||
}
|
||||
|
||||
void
|
||||
pcm_write_ctrl(struct VERA_PCM* pcm, uint8_t val)
|
||||
{
|
||||
if (val & 0x80) {
|
||||
fifo_reset(pcm);
|
||||
}
|
||||
|
||||
pcm->ctrl = val & 0x3F;
|
||||
}
|
||||
|
||||
uint8_t
|
||||
pcm_read_ctrl(struct VERA_PCM* pcm)
|
||||
{
|
||||
uint8_t result = pcm->ctrl;
|
||||
if (pcm->fifo_cnt == sizeof(pcm->fifo)) {
|
||||
result |= 0x80;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void
|
||||
pcm_write_rate(struct VERA_PCM* pcm, uint8_t val)
|
||||
{
|
||||
pcm->rate = val;
|
||||
}
|
||||
|
||||
uint8_t
|
||||
pcm_read_rate(struct VERA_PCM* pcm)
|
||||
{
|
||||
return pcm->rate;
|
||||
}
|
||||
|
||||
void
|
||||
pcm_write_fifo(struct VERA_PCM* pcm, uint8_t val)
|
||||
{
|
||||
if (pcm->fifo_cnt < sizeof(pcm->fifo)) {
|
||||
pcm->fifo[pcm->fifo_wridx++] = val;
|
||||
if (pcm->fifo_wridx == sizeof(pcm->fifo)) {
|
||||
pcm->fifo_wridx = 0;
|
||||
}
|
||||
pcm->fifo_cnt++;
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t
|
||||
read_fifo(struct VERA_PCM* pcm)
|
||||
{
|
||||
if (pcm->fifo_cnt == 0) {
|
||||
return 0;
|
||||
}
|
||||
uint8_t result = pcm->fifo[pcm->fifo_rdidx++];
|
||||
if (pcm->fifo_rdidx == sizeof(pcm->fifo)) {
|
||||
pcm->fifo_rdidx = 0;
|
||||
}
|
||||
pcm->fifo_cnt--;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool
|
||||
pcm_is_fifo_almost_empty(struct VERA_PCM* pcm)
|
||||
{
|
||||
return pcm->fifo_cnt < 1024;
|
||||
}
|
||||
|
||||
void
|
||||
pcm_render(struct VERA_PCM* pcm, int16_t* buf_l, int16_t* buf_r, unsigned num_samples)
|
||||
{
|
||||
while (num_samples--) {
|
||||
uint8_t old_phase = pcm->phase;
|
||||
pcm->phase += pcm->rate;
|
||||
if ((old_phase & 0x80) != (pcm->phase & 0x80)) {
|
||||
switch ((pcm->ctrl >> 4) & 3) {
|
||||
case 0: { // mono 8-bit
|
||||
pcm->cur_l = (int16_t)read_fifo(pcm) << 8;
|
||||
pcm->cur_r = pcm->cur_l;
|
||||
break;
|
||||
}
|
||||
case 1: { // stereo 8-bit
|
||||
pcm->cur_l = read_fifo(pcm) << 8;
|
||||
pcm->cur_r = read_fifo(pcm) << 8;
|
||||
break;
|
||||
}
|
||||
case 2: { // mono 16-bit
|
||||
pcm->cur_l = read_fifo(pcm);
|
||||
pcm->cur_l |= read_fifo(pcm) << 8;
|
||||
pcm->cur_r = pcm->cur_l;
|
||||
break;
|
||||
}
|
||||
case 3: { // stereo 16-bit
|
||||
pcm->cur_l = read_fifo(pcm);
|
||||
pcm->cur_l |= read_fifo(pcm) << 8;
|
||||
pcm->cur_r = read_fifo(pcm);
|
||||
pcm->cur_r |= read_fifo(pcm) << 8;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*(buf_l) += ((int)pcm->cur_l * (int)volume_lut[pcm->ctrl & 0xF]) >> 6;
|
||||
*(buf_r) += ((int)pcm->cur_r * (int)volume_lut[pcm->ctrl & 0xF]) >> 6;
|
||||
|
||||
buf_l++;
|
||||
buf_r++;
|
||||
}
|
||||
}
|
||||
31
src/engine/platform/sound/vera_pcm.h
Normal file
31
src/engine/platform/sound/vera_pcm.h
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
// Commander X16 Emulator
|
||||
// Copyright (c) 2020 Frank van den Hoef
|
||||
// All rights reserved. License: 2-clause BSD
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
struct VERA_PCM {
|
||||
uint8_t fifo[4096 - 1]; // Actual hardware FIFO is 4kB, but you can only use 4095 bytes.
|
||||
unsigned fifo_wridx;
|
||||
unsigned fifo_rdidx;
|
||||
unsigned fifo_cnt;
|
||||
|
||||
uint8_t ctrl;
|
||||
uint8_t rate;
|
||||
|
||||
int16_t cur_l, cur_r;
|
||||
uint8_t phase;
|
||||
};
|
||||
|
||||
|
||||
void pcm_reset(struct VERA_PCM* pcm);
|
||||
void pcm_write_ctrl(struct VERA_PCM* pcm, uint8_t val);
|
||||
uint8_t pcm_read_ctrl(struct VERA_PCM* pcm);
|
||||
void pcm_write_rate(struct VERA_PCM* pcm, uint8_t val);
|
||||
uint8_t pcm_read_rate(struct VERA_PCM* pcm);
|
||||
void pcm_write_fifo(struct VERA_PCM* pcm, uint8_t val);
|
||||
void pcm_render(struct VERA_PCM* pcm, int16_t* buf_l, int16_t* buf_r, unsigned num_samples);
|
||||
bool pcm_is_fifo_almost_empty(struct VERA_PCM* pcm);
|
||||
106
src/engine/platform/sound/vera_psg.c
Normal file
106
src/engine/platform/sound/vera_psg.c
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
// Commander X16 Emulator
|
||||
// Copyright (c) 2020 Frank van den Hoef
|
||||
// All rights reserved. License: 2-clause BSD
|
||||
|
||||
#include "vera_psg.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
enum waveform {
|
||||
WF_PULSE = 0,
|
||||
WF_SAWTOOTH,
|
||||
WF_TRIANGLE,
|
||||
WF_NOISE,
|
||||
};
|
||||
|
||||
static uint8_t volume_lut[64] = {0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 7, 7, 7, 8, 8, 9, 9, 10, 11, 11, 12, 13, 14, 14, 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 28, 29, 31, 33, 35, 37, 39, 42, 44, 47, 50, 52, 56, 59, 63};
|
||||
|
||||
void
|
||||
psg_reset(struct VERA_PSG* psg)
|
||||
{
|
||||
memset(psg->channels, 0, sizeof(psg->channels));
|
||||
psg->noiseState=1;
|
||||
psg->noiseOut=0;
|
||||
}
|
||||
|
||||
void
|
||||
psg_writereg(struct VERA_PSG* psg, uint8_t reg, uint8_t val)
|
||||
{
|
||||
reg &= 0x3f;
|
||||
|
||||
int ch = reg / 4;
|
||||
int idx = reg & 3;
|
||||
|
||||
switch (idx) {
|
||||
case 0: psg->channels[ch].freq = (psg->channels[ch].freq & 0xFF00) | val; break;
|
||||
case 1: psg->channels[ch].freq = (psg->channels[ch].freq & 0x00FF) | (val << 8); break;
|
||||
case 2: {
|
||||
psg->channels[ch].right = (val & 0x80) != 0;
|
||||
psg->channels[ch].left = (val & 0x40) != 0;
|
||||
psg->channels[ch].volume = volume_lut[val & 0x3F];
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
psg->channels[ch].pw = val & 0x3F;
|
||||
psg->channels[ch].waveform = val >> 6;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
render(struct VERA_PSG* psg, int16_t *left, int16_t *right)
|
||||
{
|
||||
int l = 0;
|
||||
int r = 0;
|
||||
// TODO this is a currently speculated noise generation
|
||||
// as the hardware and sources for it are not out in the public
|
||||
// and the official emulator just uses rand()
|
||||
psg->noiseOut=((psg->noiseOut<<1)|(psg->noiseState&1))&63;
|
||||
psg->noiseState=(psg->noiseState<<1)|(((psg->noiseState>>1)^(psg->noiseState>>2)^(psg->noiseState>>4)^(psg->noiseState>>15))&1);
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
struct VERAChannel *ch = &psg->channels[i];
|
||||
|
||||
unsigned new_phase = (ch->phase + ch->freq) & 0x1FFFF;
|
||||
if ((ch->phase & 0x10000) != (new_phase & 0x10000)) {
|
||||
ch->noiseval = psg->noiseOut;
|
||||
}
|
||||
ch->phase = new_phase;
|
||||
|
||||
uint8_t v = 0;
|
||||
switch (ch->waveform) {
|
||||
case WF_PULSE: v = (ch->phase >> 10) > ch->pw ? 0 : 63; break;
|
||||
case WF_SAWTOOTH: v = ch->phase >> 11; break;
|
||||
case WF_TRIANGLE: v = (ch->phase & 0x10000) ? (~(ch->phase >> 10) & 0x3F) : ((ch->phase >> 10) & 0x3F); break;
|
||||
case WF_NOISE: v = ch->noiseval; break;
|
||||
}
|
||||
int8_t sv = (v ^ 0x20);
|
||||
if (sv & 0x20) {
|
||||
sv |= 0xC0;
|
||||
}
|
||||
|
||||
int val = (int)sv * (int)ch->volume;
|
||||
|
||||
if (ch->left) {
|
||||
l += val;
|
||||
}
|
||||
if (ch->right) {
|
||||
r += val;
|
||||
}
|
||||
}
|
||||
|
||||
*left = l;
|
||||
*right = r;
|
||||
}
|
||||
|
||||
void
|
||||
psg_render(struct VERA_PSG* psg, int16_t *bufL, int16_t *bufR, unsigned num_samples)
|
||||
{
|
||||
while (num_samples--) {
|
||||
render(psg, bufL, bufR);
|
||||
bufL++;
|
||||
bufR++;
|
||||
}
|
||||
}
|
||||
28
src/engine/platform/sound/vera_psg.h
Normal file
28
src/engine/platform/sound/vera_psg.h
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
// Commander X16 Emulator
|
||||
// Copyright (c) 2020 Frank van den Hoef
|
||||
// All rights reserved. License: 2-clause BSD
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
struct VERAChannel {
|
||||
uint16_t freq;
|
||||
uint8_t volume;
|
||||
bool left, right;
|
||||
uint8_t pw;
|
||||
uint8_t waveform;
|
||||
|
||||
unsigned phase;
|
||||
uint8_t noiseval;
|
||||
};
|
||||
|
||||
struct VERA_PSG {
|
||||
unsigned int noiseState, noiseOut;
|
||||
struct VERAChannel channels[16];
|
||||
};
|
||||
|
||||
void psg_reset(struct VERA_PSG* psg);
|
||||
void psg_writereg(struct VERA_PSG* psg, uint8_t reg, uint8_t val);
|
||||
void psg_render(struct VERA_PSG* psg, int16_t *bufL, int16_t *bufR, unsigned num_samples);
|
||||
224
src/engine/platform/sound/x1_010/x1_010.cpp
Normal file
224
src/engine/platform/sound/x1_010/x1_010.cpp
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/LICENSE for more details
|
||||
|
||||
Copyright holders: cam900
|
||||
Seta/Allumer X1-010 Emulation core
|
||||
|
||||
the chip has 16 voices, all voices can be switchable to Wavetable or PCM sample playback mode.
|
||||
It has also 2 output channels, but no known hardware using this feature for stereo sound.
|
||||
|
||||
Wavetable needs to paired with envelope, it's always enabled and similar as AY PSG's one
|
||||
but its shape is stored at RAM.
|
||||
|
||||
PCM volume is stored by each register.
|
||||
|
||||
Both volume is 4bit per output.
|
||||
|
||||
Everything except PCM sample is stored at paired 8 bit RAM.
|
||||
|
||||
RAM layout (common case: Address bit 12 is swapped when RAM is shared with CPU)
|
||||
|
||||
-----------------------------
|
||||
0000...007f Voice Registers
|
||||
|
||||
0000...0007 Voice 0 Register
|
||||
|
||||
Address Bits Description
|
||||
7654 3210
|
||||
0 x--- ---- Frequency divider*
|
||||
---- -x-- Envelope one-shot mode
|
||||
---- --x- Sound format
|
||||
---- --0- PCM
|
||||
---- --1- Wavetable
|
||||
---- ---x Keyon/off
|
||||
PCM case:
|
||||
1 xxxx xxxx Volume (Each nibble is for each output)
|
||||
|
||||
2 xxxx xxxx Frequency*
|
||||
|
||||
4 xxxx xxxx Start address / 4096
|
||||
|
||||
5 xxxx xxxx 0x100 - (End address / 4096)
|
||||
Wavetable case:
|
||||
1 ---x xxxx Wavetable data select
|
||||
|
||||
2 xxxx xxxx Frequency LSB*
|
||||
3 xxxx xxxx "" MSB
|
||||
|
||||
4 xxxx xxxx Envelope period (.10 fixed point, Low 8 bit)
|
||||
|
||||
5 ---x xxxx Envelope shape select (!= 0 : Reserved for Voice registers)
|
||||
|
||||
0008...000f Voice 1 Register
|
||||
...
|
||||
0078...007f Voice 15 Register
|
||||
-----------------------------
|
||||
0080...0fff Envelope shape data (Same as volume; Each nibble is for each output)
|
||||
|
||||
0080...00ff Envelope shape data 1
|
||||
0100...017f Envelope shape data 2
|
||||
...
|
||||
0f80...0fff Envelope shape data 31
|
||||
-----------------------------
|
||||
1000...1fff Wavetable data
|
||||
|
||||
1000...107f Wavetable data 0
|
||||
1080...10ff Wavetable data 1
|
||||
...
|
||||
1f80...1fff Wavetable data 31
|
||||
-----------------------------
|
||||
|
||||
* Frequency is 4.4 fixed point for PCM,
|
||||
6.10 for Wavetable.
|
||||
Frequency divider is higher precision or just right shift?
|
||||
needs verification.
|
||||
*/
|
||||
|
||||
#include "x1_010.hpp"
|
||||
|
||||
void x1_010_core::tick()
|
||||
{
|
||||
// reset output
|
||||
m_out[0] = m_out[1] = 0;
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
voice_t &v = m_voice[i];
|
||||
v.tick();
|
||||
m_out[0] += v.data * v.vol_out[0];
|
||||
m_out[1] += v.data * v.vol_out[1];
|
||||
}
|
||||
}
|
||||
|
||||
void x1_010_core::voice_t::tick()
|
||||
{
|
||||
data = vol_out[0] = vol_out[1] = 0;
|
||||
if (flag.keyon)
|
||||
{
|
||||
if (flag.wavetable) // Wavetable
|
||||
{
|
||||
// envelope, each nibble is for each output
|
||||
u8 vol = m_host.m_envelope[(bitfield(end_envshape, 0, 5) << 7) | bitfield(env_acc, 10, 7)];
|
||||
vol_out[0] = bitfield(vol, 4, 4);
|
||||
vol_out[1] = bitfield(vol, 0, 4);
|
||||
env_acc += start_envfreq;
|
||||
if (flag.env_oneshot && bitfield(env_acc, 17))
|
||||
flag.keyon = false;
|
||||
else
|
||||
env_acc = bitfield(env_acc, 0, 17);
|
||||
// get wavetable data
|
||||
data = m_host.m_wave[(bitfield(vol_wave, 0, 5) << 7) | bitfield(acc, 10, 7)];
|
||||
acc = bitfield(acc + (freq >> flag.div), 0, 17);
|
||||
}
|
||||
else // PCM sample
|
||||
{
|
||||
// volume register, each nibble is for each output
|
||||
vol_out[0] = bitfield(vol_wave, 4, 4);
|
||||
vol_out[1] = bitfield(vol_wave, 0, 4);
|
||||
// get PCM sample
|
||||
data = m_host.m_intf.read_byte(bitfield(acc, 4, 20));
|
||||
acc += bitfield(freq, 0, 8) >> flag.div;
|
||||
if ((acc >> 16) > (0xff ^ end_envshape))
|
||||
flag.keyon = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u8 x1_010_core::ram_r(u16 offset)
|
||||
{
|
||||
if (offset & 0x1000) // wavetable data
|
||||
return m_wave[offset & 0xfff];
|
||||
else if (offset & 0xf80) // envelope shape data
|
||||
return m_envelope[offset & 0xfff];
|
||||
else // channel register
|
||||
return m_voice[bitfield(offset, 3, 4)].reg_r(offset & 0x7);
|
||||
}
|
||||
|
||||
void x1_010_core::ram_w(u16 offset, u8 data)
|
||||
{
|
||||
if (offset & 0x1000) // wavetable data
|
||||
m_wave[offset & 0xfff] = data;
|
||||
else if (offset & 0xf80) // envelope shape data
|
||||
m_envelope[offset & 0xfff] = data;
|
||||
else // channel register
|
||||
m_voice[bitfield(offset, 3, 4)].reg_w(offset & 0x7, data);
|
||||
}
|
||||
|
||||
u8 x1_010_core::voice_t::reg_r(u8 offset)
|
||||
{
|
||||
switch (offset & 0x7)
|
||||
{
|
||||
case 0x00: return (flag.div << 7)
|
||||
| (flag.env_oneshot << 2)
|
||||
| (flag.wavetable << 1)
|
||||
| (flag.keyon << 0);
|
||||
case 0x01: return vol_wave;
|
||||
case 0x02: return bitfield(freq, 0, 8);
|
||||
case 0x03: return bitfield(freq, 8, 8);
|
||||
case 0x04: return start_envfreq;
|
||||
case 0x05: return end_envshape;
|
||||
default: break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void x1_010_core::voice_t::reg_w(u8 offset, u8 data)
|
||||
{
|
||||
switch (offset & 0x7)
|
||||
{
|
||||
case 0x00:
|
||||
{
|
||||
const bool prev_keyon = flag.keyon;
|
||||
flag.div = bitfield(data, 7);
|
||||
flag.env_oneshot = bitfield(data, 2);
|
||||
flag.wavetable = bitfield(data, 1);
|
||||
flag.keyon = bitfield(data, 0);
|
||||
if (!prev_keyon && flag.keyon) // Key on
|
||||
{
|
||||
acc = flag.wavetable ? 0 : (u32(start_envfreq) << 16);
|
||||
env_acc = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x01:
|
||||
vol_wave = data;
|
||||
break;
|
||||
case 0x02:
|
||||
freq = (freq & 0xff00) | data;
|
||||
break;
|
||||
case 0x03:
|
||||
freq = (freq & 0x00ff) | (u16(data) << 8);
|
||||
break;
|
||||
case 0x04:
|
||||
start_envfreq = data;
|
||||
break;
|
||||
case 0x05:
|
||||
end_envshape = data;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void x1_010_core::voice_t::reset()
|
||||
{
|
||||
flag.reset();
|
||||
vol_wave = 0;
|
||||
freq = 0;
|
||||
start_envfreq = 0;
|
||||
end_envshape = 0;
|
||||
acc = 0;
|
||||
env_acc = 0;
|
||||
data = 0;
|
||||
vol_out[0] = vol_out[1] = 0;
|
||||
}
|
||||
|
||||
void x1_010_core::reset()
|
||||
{
|
||||
for (auto & elem : m_voice)
|
||||
elem.reset();
|
||||
|
||||
std::fill_n(&m_envelope[0], 0x1000, 0);
|
||||
std::fill_n(&m_wave[0], 0x1000, 0);
|
||||
m_out[0] = m_out[1] = 0;
|
||||
}
|
||||
127
src/engine/platform/sound/x1_010/x1_010.hpp
Normal file
127
src/engine/platform/sound/x1_010/x1_010.hpp
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/LICENSE for more details
|
||||
|
||||
Copyright holders: cam900
|
||||
Seta/Allumer X1-010 Emulation core
|
||||
|
||||
See x1_010.cpp for more info.
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
#ifndef _VGSOUND_EMU_X1_010_HPP
|
||||
#define _VGSOUND_EMU_X1_010_HPP
|
||||
|
||||
#pragma once
|
||||
|
||||
typedef unsigned char u8;
|
||||
typedef unsigned short u16;
|
||||
typedef unsigned int u32;
|
||||
typedef signed char s8;
|
||||
typedef signed int s32;
|
||||
|
||||
template<typename T> T bitfield(T in, u8 pos, u8 len = 1)
|
||||
{
|
||||
return (in >> pos) & (len ? (T(1 << len) - 1) : 1);
|
||||
}
|
||||
|
||||
class x1_010_mem_intf
|
||||
{
|
||||
public:
|
||||
virtual u8 read_byte(u32 address) { return 0; }
|
||||
};
|
||||
|
||||
class x1_010_core
|
||||
{
|
||||
friend class x1_010_mem_intf;
|
||||
public:
|
||||
// constructor
|
||||
x1_010_core(x1_010_mem_intf &intf)
|
||||
: m_voice{*this,*this,*this,*this,
|
||||
*this,*this,*this,*this,
|
||||
*this,*this,*this,*this,
|
||||
*this,*this,*this,*this}
|
||||
, m_intf(intf)
|
||||
{
|
||||
m_envelope = std::make_unique<u8[]>(0x1000);
|
||||
m_wave = std::make_unique<u8[]>(0x1000);
|
||||
|
||||
std::fill_n(&m_envelope[0], 0x1000, 0);
|
||||
std::fill_n(&m_wave[0], 0x1000, 0);
|
||||
}
|
||||
|
||||
// register accessor
|
||||
u8 ram_r(u16 offset);
|
||||
void ram_w(u16 offset, u8 data);
|
||||
|
||||
// getters
|
||||
s32 output(u8 channel) { return m_out[channel & 1]; }
|
||||
|
||||
// internal state
|
||||
void reset();
|
||||
void tick();
|
||||
|
||||
private:
|
||||
// 16 voices in chip
|
||||
struct voice_t
|
||||
{
|
||||
// constructor
|
||||
voice_t(x1_010_core &host) : m_host(host) {}
|
||||
|
||||
// internal state
|
||||
void reset();
|
||||
void tick();
|
||||
|
||||
// register accessor
|
||||
u8 reg_r(u8 offset);
|
||||
void reg_w(u8 offset, u8 data);
|
||||
|
||||
// registers
|
||||
x1_010_core &m_host;
|
||||
struct flag_t
|
||||
{
|
||||
u8 div : 1;
|
||||
u8 env_oneshot : 1;
|
||||
u8 wavetable : 1;
|
||||
u8 keyon : 1;
|
||||
void reset()
|
||||
{
|
||||
div = 0;
|
||||
env_oneshot = 0;
|
||||
wavetable = 0;
|
||||
keyon = 0;
|
||||
}
|
||||
flag_t()
|
||||
: div(0)
|
||||
, env_oneshot(0)
|
||||
, wavetable(0)
|
||||
, keyon(0)
|
||||
{ }
|
||||
};
|
||||
flag_t flag;
|
||||
u8 vol_wave = 0;
|
||||
u16 freq = 0;
|
||||
u8 start_envfreq = 0;
|
||||
u8 end_envshape = 0;
|
||||
|
||||
// internal registers
|
||||
u32 acc = 0;
|
||||
u32 env_acc = 0;
|
||||
s8 data = 0;
|
||||
u8 vol_out[2] = {0};
|
||||
};
|
||||
voice_t m_voice[16];
|
||||
|
||||
// RAM
|
||||
std::unique_ptr<u8[]> m_envelope = nullptr;
|
||||
std::unique_ptr<u8[]> m_wave = nullptr;
|
||||
|
||||
// output data
|
||||
s32 m_out[2] = {0};
|
||||
|
||||
x1_010_mem_intf &m_intf;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -119,8 +119,12 @@ void DivPlatformSwan::updateWave(int ch) {
|
|||
}
|
||||
} else {
|
||||
for (int i=0; i<16; i++) {
|
||||
unsigned char nibble1=(wt->data[(i*2)*wt->len/32]*15)/wt->max;
|
||||
unsigned char nibble2=(wt->data[(1+i*2)*wt->len/32]*15)/wt->max;
|
||||
int nibble1=(wt->data[(i*2)*wt->len/32]*15)/wt->max;
|
||||
int nibble2=(wt->data[(1+i*2)*wt->len/32]*15)/wt->max;
|
||||
if (nibble1<0) nibble1=0;
|
||||
if (nibble1>15) nibble1=15;
|
||||
if (nibble2<0) nibble2=0;
|
||||
if (nibble2>15) nibble2=15;
|
||||
rWrite(addr+i,nibble1|(nibble2<<4));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
347
src/engine/platform/vera.cpp
Normal file
347
src/engine/platform/vera.cpp
Normal file
|
|
@ -0,0 +1,347 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2022 tildearrow and contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "vera.h"
|
||||
#include "../engine.h"
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
extern "C" {
|
||||
#include "sound/vera_psg.h"
|
||||
#include "sound/vera_pcm.h"
|
||||
}
|
||||
|
||||
#define rWrite(c,a,d) {regPool[(c)*4+(a)]=(d); psg_writereg(psg,((c)*4+(a)),(d));}
|
||||
#define rWriteLo(c,a,d) rWrite(c,a,(regPool[(c)*4+(a)]&(~0x3f))|((d)&0x3f))
|
||||
#define rWriteHi(c,a,d) rWrite(c,a,(regPool[(c)*4+(a)]&(~0xc0))|(((d)<<6)&0xc0))
|
||||
#define rWriteFIFOVol(d) rWrite(16,0,(regPool[64]&(~0x3f))|((d)&0x3f))
|
||||
|
||||
const char* regCheatSheetVERA[]={
|
||||
"CHxFreq", "00+x*4",
|
||||
"CHxVol", "02+x*4",
|
||||
"CHxWave", "03+x*4",
|
||||
|
||||
"AUDIO_CTRL", "40",
|
||||
"AUDIO_RATE", "41",
|
||||
|
||||
NULL
|
||||
};
|
||||
|
||||
const char** DivPlatformVERA::getRegisterSheet() {
|
||||
return regCheatSheetVERA;
|
||||
}
|
||||
|
||||
const char* DivPlatformVERA::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x20:
|
||||
return "20xx: Change waveform";
|
||||
break;
|
||||
case 0x22:
|
||||
return "22xx: Set duty cycle (0 to 63)";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// TODO: wire up PCM.
|
||||
void DivPlatformVERA::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
psg_render(psg,bufL+start,bufR+start,len);
|
||||
pcm_render(pcm,bufL+start,bufR+start,len);
|
||||
}
|
||||
|
||||
void DivPlatformVERA::reset() {
|
||||
for (int i=0; i<17; i++) {
|
||||
chan[i]=Channel();
|
||||
}
|
||||
psg_reset(psg);
|
||||
pcm_reset(pcm);
|
||||
memset(regPool,0,66);
|
||||
for (int i=0; i<16; i++) {
|
||||
chan[i].vol=63;
|
||||
chan[i].pan=3;
|
||||
rWriteHi(i,2,isMuted[i]?0:3);
|
||||
}
|
||||
chan[16].vol=15;
|
||||
chan[16].pan=3;
|
||||
}
|
||||
|
||||
int DivPlatformVERA::calcNoteFreq(int ch, int note) {
|
||||
if (ch<16) {
|
||||
return parent->calcBaseFreq(chipClock,2097152,note,false);
|
||||
} else {
|
||||
double off=1.0;
|
||||
if (chan[ch].pcm.sample>=0 && chan[ch].pcm.sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(chan[ch].pcm.sample);
|
||||
if (s->centerRate<1) {
|
||||
off=1.0;
|
||||
} else {
|
||||
off=s->centerRate/8363.0;
|
||||
}
|
||||
}
|
||||
return (int)(off*parent->calcBaseFreq(chipClock,65536,note,false));
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformVERA::tick() {
|
||||
for (int i=0; i<16; i++) {
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.hadVol) {
|
||||
chan[i].outVol=MAX(chan[i].vol+chan[i].std.vol-63,0);
|
||||
rWriteLo(i,2,chan[i].outVol);
|
||||
}
|
||||
if (chan[i].std.hadArp) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arpMode) {
|
||||
chan[i].baseFreq=calcNoteFreq(0,chan[i].std.arp);
|
||||
} else {
|
||||
chan[i].baseFreq=calcNoteFreq(0,chan[i].note+chan[i].std.arp);
|
||||
}
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arpMode && chan[i].std.finishedArp) {
|
||||
chan[i].baseFreq=calcNoteFreq(0,chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.hadDuty) {
|
||||
rWriteLo(i,3,chan[i].std.duty);
|
||||
}
|
||||
if (chan[i].std.hadWave) {
|
||||
rWriteHi(i,3,chan[i].std.wave);
|
||||
}
|
||||
if (chan[i].freqChanged) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8);
|
||||
if (chan[i].freq>65535) chan[i].freq=65535;
|
||||
rWrite(i,0,chan[i].freq&0xff);
|
||||
rWrite(i,1,(chan[i].freq>>8)&0xff);
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
}
|
||||
// PCM
|
||||
chan[16].std.next();
|
||||
if (chan[16].std.hadVol) {
|
||||
chan[16].outVol=MAX(chan[16].vol+MIN(chan[16].std.vol/4,15)-15,0);
|
||||
rWriteFIFOVol(chan[16].outVol&15);
|
||||
}
|
||||
if (chan[16].std.hadArp) {
|
||||
if (!chan[16].inPorta) {
|
||||
if (chan[16].std.arpMode) {
|
||||
chan[16].baseFreq=calcNoteFreq(16,chan[16].std.arp);
|
||||
} else {
|
||||
chan[16].baseFreq=calcNoteFreq(16,chan[16].note+chan[16].std.arp);
|
||||
}
|
||||
}
|
||||
chan[16].freqChanged=true;
|
||||
} else {
|
||||
if (chan[16].std.arpMode && chan[16].std.finishedArp) {
|
||||
chan[16].baseFreq=calcNoteFreq(16,chan[16].note);
|
||||
chan[16].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[16].freqChanged) {
|
||||
chan[16].freq=parent->calcFreq(chan[16].baseFreq,chan[16].pitch,false,8);
|
||||
if (chan[16].freq>128) chan[16].freq=128;
|
||||
rWrite(16,1,chan[16].freq&0xff);
|
||||
chan[16].freqChanged=false;
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformVERA::dispatch(DivCommand c) {
|
||||
int tmp;
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON:
|
||||
if(c.chan<16) {
|
||||
rWriteLo(c.chan,2,chan[c.chan].vol)
|
||||
} else {
|
||||
chan[c.chan].pcm.sample=parent->getIns(chan[16].ins)->amiga.initSample;
|
||||
if (chan[c.chan].pcm.sample<0 || chan[c.chan].pcm.sample>=parent->song.sampleLen) {
|
||||
chan[c.chan].pcm.sample=-1;
|
||||
}
|
||||
chan[16].pcm.pos=0;
|
||||
rWriteFIFOVol(chan[c.chan].vol);
|
||||
}
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=calcNoteFreq(c.chan,c.value);
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
}
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
chan[c.chan].active=false;
|
||||
if(c.chan<16) {
|
||||
rWriteLo(c.chan,2,0)
|
||||
} else {
|
||||
chan[16].pcm.sample=-1;
|
||||
rWriteFIFOVol(0);
|
||||
rWrite(16,1,0);
|
||||
}
|
||||
chan[c.chan].std.init(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
case DIV_CMD_ENV_RELEASE:
|
||||
chan[c.chan].std.release();
|
||||
break;
|
||||
case DIV_CMD_INSTRUMENT:
|
||||
chan[c.chan].ins=(unsigned char)c.value;
|
||||
break;
|
||||
case DIV_CMD_VOLUME:
|
||||
if (c.chan<16) {
|
||||
tmp=c.value&0x3f;
|
||||
chan[c.chan].vol=tmp;
|
||||
rWriteLo(c.chan,2,tmp);
|
||||
} else {
|
||||
tmp=c.value&0x0f;
|
||||
chan[c.chan].vol=tmp;
|
||||
rWriteFIFOVol(tmp);
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_GET_VOLUME:
|
||||
return chan[c.chan].vol;
|
||||
break;
|
||||
case DIV_CMD_PITCH:
|
||||
chan[c.chan].pitch=c.value;
|
||||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
int destFreq=calcNoteFreq(c.chan,c.value2);
|
||||
bool return2=false;
|
||||
if (destFreq>chan[c.chan].baseFreq) {
|
||||
chan[c.chan].baseFreq+=c.value;
|
||||
if (chan[c.chan].baseFreq>=destFreq) {
|
||||
chan[c.chan].baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
} else {
|
||||
chan[c.chan].baseFreq-=c.value;
|
||||
if (chan[c.chan].baseFreq<=destFreq) {
|
||||
chan[c.chan].baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
}
|
||||
chan[c.chan].freqChanged=true;
|
||||
if (return2) {
|
||||
chan[c.chan].inPorta=false;
|
||||
return 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_LEGATO:
|
||||
chan[c.chan].baseFreq=calcNoteFreq(c.chan,c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0)));
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
|
||||
}
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_STD_NOISE_MODE:
|
||||
if (c.chan<16) rWriteLo(c.chan,3,c.value);
|
||||
break;
|
||||
case DIV_CMD_WAVE:
|
||||
if (c.chan<16) rWriteHi(c.chan,3,c.value);
|
||||
break;
|
||||
case DIV_CMD_PANNING: {
|
||||
tmp=0;
|
||||
tmp|=(c.value&0x10)?1:0;
|
||||
tmp|=(c.value&0x01)?2:0;
|
||||
chan[c.chan].pan=tmp&3;
|
||||
if (c.chan<16) {
|
||||
rWriteHi(c.chan,2,isMuted[c.chan]?0:chan[c.chan].pan);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
if(c.chan<16) {
|
||||
return 63;
|
||||
} else {
|
||||
return 15;
|
||||
}
|
||||
break;
|
||||
case DIV_ALWAYS_SET_VOLUME:
|
||||
return 0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void* DivPlatformVERA::getChanState(int ch) {
|
||||
return &chan[ch];
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformVERA::getRegisterPool() {
|
||||
return regPool;
|
||||
}
|
||||
|
||||
int DivPlatformVERA::getRegisterPoolSize() {
|
||||
return 66;
|
||||
}
|
||||
|
||||
void DivPlatformVERA::muteChannel(int ch, bool mute) {
|
||||
isMuted[ch]=mute;
|
||||
if (ch<16) {
|
||||
rWriteHi(ch,2,mute?0:chan[ch].pan);
|
||||
}
|
||||
}
|
||||
|
||||
bool DivPlatformVERA::isStereo() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void DivPlatformVERA::notifyInsDeletion(void* ins) {
|
||||
for (int i=0; i<2; i++) {
|
||||
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformVERA::poke(unsigned int addr, unsigned short val) {
|
||||
regPool[addr] = (unsigned char)val;
|
||||
}
|
||||
|
||||
void DivPlatformVERA::poke(std::vector<DivRegWrite>& wlist) {
|
||||
for (auto &i: wlist) regPool[i.addr] = (unsigned char)i.val;
|
||||
}
|
||||
|
||||
int DivPlatformVERA::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||
for (int i=0; i<17; i++) {
|
||||
isMuted[i]=false;
|
||||
}
|
||||
parent=p;
|
||||
psg=new struct VERA_PSG;
|
||||
pcm=new struct VERA_PCM;
|
||||
dumpWrites=false;
|
||||
skipRegisterWrites=false;
|
||||
chipClock=25000000;
|
||||
rate=chipClock/512;
|
||||
reset();
|
||||
return 17;
|
||||
}
|
||||
|
||||
void DivPlatformVERA::quit() {
|
||||
delete psg;
|
||||
delete pcm;
|
||||
}
|
||||
DivPlatformVERA::~DivPlatformVERA() {
|
||||
}
|
||||
78
src/engine/platform/vera.h
Normal file
78
src/engine/platform/vera.h
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2022 tildearrow and contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef _VERA_H
|
||||
#define _VERA_H
|
||||
#include "../dispatch.h"
|
||||
#include "../instrument.h"
|
||||
#include "../macroInt.h"
|
||||
|
||||
struct VERA_PSG;
|
||||
struct VERA_PCM;
|
||||
|
||||
class DivPlatformVERA: public DivDispatch {
|
||||
protected:
|
||||
struct Channel {
|
||||
int freq, baseFreq, pitch, note;
|
||||
unsigned char ins, pan;
|
||||
bool active, freqChanged, inPorta;
|
||||
int vol, outVol;
|
||||
unsigned accum;
|
||||
int noiseval;
|
||||
DivMacroInt std;
|
||||
|
||||
struct PCMChannel {
|
||||
int sample;
|
||||
int out_l, out_r;
|
||||
unsigned pos;
|
||||
unsigned len;
|
||||
unsigned char freq;
|
||||
PCMChannel(): sample(-1), out_l(0), out_r(0), pos(0), len(0), freq(0) {}
|
||||
} pcm;
|
||||
Channel(): freq(0), baseFreq(0), pitch(0), note(0), ins(-1), pan(0), active(false), freqChanged(false), inPorta(false), vol(0), outVol(0), accum(0), noiseval(0) {}
|
||||
};
|
||||
Channel chan[17];
|
||||
bool isMuted[17];
|
||||
unsigned char regPool[66];
|
||||
struct VERA_PSG* psg;
|
||||
struct VERA_PCM* pcm;
|
||||
|
||||
int calcNoteFreq(int ch, int note);
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
|
||||
public:
|
||||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void reset();
|
||||
void tick();
|
||||
void muteChannel(int ch, bool mute);
|
||||
void notifyInsDeletion(void* ins);
|
||||
bool isStereo();
|
||||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformVERA();
|
||||
};
|
||||
#endif
|
||||
893
src/engine/platform/x1_010.cpp
Normal file
893
src/engine/platform/x1_010.cpp
Normal file
|
|
@ -0,0 +1,893 @@
|
|||
/**
|
||||
* 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 "x1_010.h"
|
||||
#include "../engine.h"
|
||||
#include <math.h>
|
||||
|
||||
//#define rWrite(a,v) pendingWrites[a]=v;
|
||||
#define rWrite(a,v) if (!skipRegisterWrites) { x1_010->ram_w(a,v); if (dumpWrites) { addWrite(a,v); } }
|
||||
|
||||
#define chRead(c,a) x1_010->ram_r((c<<3)|(a&7))
|
||||
#define chWrite(c,a,v) rWrite((c<<3)|(a&7),v)
|
||||
#define waveWrite(c,a,v) rWrite(0x1000|(chan[c].waveBank<<11)|(c<<7)|(a&0x7f),(v-128)&0xff)
|
||||
#define envFill(c,a) rWrite(0x800|(c<<7)|(a&0x7f),(chan[c].lvol<<4)|chan[c].rvol)
|
||||
#define envWrite(c,a,l,r) rWrite(0x800|(c<<7)|(a&0x7f),(((chan[c].lvol*(l))/15)<<4)|((chan[c].rvol*(r))/15))
|
||||
|
||||
#define refreshControl(c) chWrite(c,0,chan[c].active?(chan[c].pcm?1:((chan[c].env.flag.envEnable && chan[c].env.flag.envOneshot)?7:3)):0);
|
||||
|
||||
#define CHIP_FREQBASE 4194304
|
||||
|
||||
const char* regCheatSheetX1_010[]={
|
||||
// Channel registers
|
||||
"Ch00_Control", "0000",
|
||||
"Ch00_PCMVol_WavSel", "0001",
|
||||
"Ch00_FreqL", "0002",
|
||||
"Ch00_FreqH", "0003",
|
||||
"Ch00_Start_EnvFrq", "0004",
|
||||
"Ch00_End_EnvSel", "0005",
|
||||
"Ch01_Control", "0008",
|
||||
"Ch01_PCMVol_WavSel", "0009",
|
||||
"Ch01_FreqL", "000A",
|
||||
"Ch01_FreqH", "000B",
|
||||
"Ch01_Start_EnvFrq", "000C",
|
||||
"Ch01_End_EnvSel", "000D",
|
||||
"Ch02_Control", "0010",
|
||||
"Ch02_PCMVol_WavSel", "0011",
|
||||
"Ch02_FreqL", "0012",
|
||||
"Ch02_FreqH", "0013",
|
||||
"Ch02_Start_EnvFrq", "0014",
|
||||
"Ch02_End_EnvSel", "0015",
|
||||
"Ch03_Control", "0018",
|
||||
"Ch03_PCMVol_WavSel", "0019",
|
||||
"Ch03_FreqL", "001A",
|
||||
"Ch03_FreqH", "001B",
|
||||
"Ch03_Start_EnvFrq", "001C",
|
||||
"Ch03_End_EnvSel", "001D",
|
||||
"Ch04_Control", "0020",
|
||||
"Ch04_PCMVol_WavSel", "0021",
|
||||
"Ch04_FreqL", "0022",
|
||||
"Ch04_FreqH", "0023",
|
||||
"Ch04_Start_EnvFrq", "0024",
|
||||
"Ch04_End_EnvSel", "0025",
|
||||
"Ch05_Control", "0028",
|
||||
"Ch05_PCMVol_WavSel", "0029",
|
||||
"Ch05_FreqL", "002A",
|
||||
"Ch05_FreqH", "002B",
|
||||
"Ch05_Start_EnvFrq", "002C",
|
||||
"Ch05_End_EnvSel", "002D",
|
||||
"Ch06_Control", "0030",
|
||||
"Ch06_PCMVol_WavSel", "0031",
|
||||
"Ch06_FreqL", "0032",
|
||||
"Ch06_FreqH", "0033",
|
||||
"Ch06_Start_EnvFrq", "0034",
|
||||
"Ch06_End_EnvSel", "0035",
|
||||
"Ch07_Control", "0038",
|
||||
"Ch07_PCMVol_WavSel", "0039",
|
||||
"Ch07_FreqL", "003A",
|
||||
"Ch07_FreqH", "003B",
|
||||
"Ch07_Start_EnvFrq", "003C",
|
||||
"Ch07_End_EnvSel", "003D",
|
||||
"Ch08_Control", "0040",
|
||||
"Ch08_PCMVol_WavSel", "0041",
|
||||
"Ch08_FreqL", "0042",
|
||||
"Ch08_FreqH", "0043",
|
||||
"Ch08_Start_EnvFrq", "0044",
|
||||
"Ch08_End_EnvSel", "0045",
|
||||
"Ch09_Control", "0048",
|
||||
"Ch09_PCMVol_WavSel", "0049",
|
||||
"Ch09_FreqL", "004A",
|
||||
"Ch09_FreqH", "004B",
|
||||
"Ch09_Start_EnvFrq", "004C",
|
||||
"Ch09_End_EnvSel", "004D",
|
||||
"Ch10_Control", "0050",
|
||||
"Ch10_PCMVol_WavSel", "0051",
|
||||
"Ch10_FreqL", "0052",
|
||||
"Ch10_FreqH", "0053",
|
||||
"Ch10_Start_EnvFrq", "0054",
|
||||
"Ch10_End_EnvSel", "0055",
|
||||
"Ch11_Control", "0058",
|
||||
"Ch11_PCMVol_WavSel", "0059",
|
||||
"Ch11_FreqL", "005A",
|
||||
"Ch11_FreqH", "005B",
|
||||
"Ch11_Start_EnvFrq", "005C",
|
||||
"Ch11_End_EnvSel", "005D",
|
||||
"Ch12_Control", "0060",
|
||||
"Ch12_PCMVol_WavSel", "0061",
|
||||
"Ch12_FreqL", "0062",
|
||||
"Ch12_FreqH", "0063",
|
||||
"Ch12_Start_EnvFrq", "0064",
|
||||
"Ch12_End_EnvSel", "0065",
|
||||
"Ch13_Control", "0068",
|
||||
"Ch13_PCMVol_WavSel", "0069",
|
||||
"Ch13_FreqL", "006A",
|
||||
"Ch13_FreqH", "006B",
|
||||
"Ch13_Start_EnvFrq", "006C",
|
||||
"Ch13_End_EnvSel", "006D",
|
||||
"Ch14_Control", "0070",
|
||||
"Ch14_PCMVol_WavSel", "0071",
|
||||
"Ch14_FreqL", "0072",
|
||||
"Ch14_FreqH", "0073",
|
||||
"Ch14_Start_EnvFrq", "0074",
|
||||
"Ch14_End_EnvSel", "0075",
|
||||
"Ch15_Control", "0078",
|
||||
"Ch15_PCMVol_WavSel", "0079",
|
||||
"Ch15_FreqL", "007A",
|
||||
"Ch15_FreqH", "007B",
|
||||
"Ch15_Start_EnvFrq", "007C",
|
||||
"Ch15_End_EnvSel", "007D",
|
||||
// Envelope data
|
||||
"Env01Data", "0080",
|
||||
"Env02Data", "0100",
|
||||
"Env03Data", "0180",
|
||||
"Env04Data", "0200",
|
||||
"Env05Data", "0280",
|
||||
"Env06Data", "0300",
|
||||
"Env07Data", "0380",
|
||||
"Env08Data", "0400",
|
||||
"Env09Data", "0480",
|
||||
"Env10Data", "0500",
|
||||
"Env11Data", "0580",
|
||||
"Env12Data", "0600",
|
||||
"Env13Data", "0680",
|
||||
"Env14Data", "0700",
|
||||
"Env15Data", "0780",
|
||||
"Env16Data", "0800",
|
||||
"Env17Data", "0880",
|
||||
"Env18Data", "0900",
|
||||
"Env19Data", "0980",
|
||||
"Env20Data", "0A00",
|
||||
"Env21Data", "0A80",
|
||||
"Env22Data", "0B00",
|
||||
"Env23Data", "0B80",
|
||||
"Env24Data", "0C00",
|
||||
"Env25Data", "0C80",
|
||||
"Env26Data", "0D00",
|
||||
"Env27Data", "0D80",
|
||||
"Env28Data", "0E00",
|
||||
"Env29Data", "0E80",
|
||||
"Env30Data", "0F00",
|
||||
"Env31Data", "0F80",
|
||||
// Wavetable data
|
||||
"Wave00Data", "1000",
|
||||
"Wave01Data", "1080",
|
||||
"Wave02Data", "1100",
|
||||
"Wave03Data", "1180",
|
||||
"Wave04Data", "1200",
|
||||
"Wave05Data", "1280",
|
||||
"Wave06Data", "1300",
|
||||
"Wave07Data", "1380",
|
||||
"Wave08Data", "1400",
|
||||
"Wave09Data", "1480",
|
||||
"Wave10Data", "1500",
|
||||
"Wave11Data", "1580",
|
||||
"Wave12Data", "1600",
|
||||
"Wave13Data", "1680",
|
||||
"Wave14Data", "1700",
|
||||
"Wave15Data", "1780",
|
||||
"Wave16Data", "1800",
|
||||
"Wave17Data", "1880",
|
||||
"Wave18Data", "1900",
|
||||
"Wave19Data", "1980",
|
||||
"Wave20Data", "1A00",
|
||||
"Wave21Data", "1A80",
|
||||
"Wave22Data", "1B00",
|
||||
"Wave23Data", "1B80",
|
||||
"Wave24Data", "1C00",
|
||||
"Wave25Data", "1C80",
|
||||
"Wave26Data", "1D00",
|
||||
"Wave27Data", "1D80",
|
||||
"Wave28Data", "1E00",
|
||||
"Wave29Data", "1E80",
|
||||
"Wave30Data", "1F00",
|
||||
"Wave31Data", "1F80",
|
||||
NULL
|
||||
};
|
||||
|
||||
const char** DivPlatformX1_010::getRegisterSheet() {
|
||||
return regCheatSheetX1_010;
|
||||
}
|
||||
|
||||
const char* DivPlatformX1_010::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xx: Change waveform";
|
||||
break;
|
||||
case 0x11:
|
||||
return "11xx: Change envelope shape";
|
||||
break;
|
||||
case 0x17:
|
||||
return "17xx: Toggle PCM mode";
|
||||
break;
|
||||
case 0x20:
|
||||
return "20xx: Set PCM frequency (1 to FF)";
|
||||
break;
|
||||
case 0x22:
|
||||
return "22xx: Set envelope mode (bit 0: enable, bit 1: one-shot, bit 2: split shape to L/R, bit 3/5: H.invert right/left, bit 4/6: V.invert right/left)";
|
||||
break;
|
||||
case 0x23:
|
||||
return "23xx: Set envelope period";
|
||||
break;
|
||||
case 0x25:
|
||||
return "25xx: Envelope slide up";
|
||||
break;
|
||||
case 0x26:
|
||||
return "26xx: Envelope slide down";
|
||||
break;
|
||||
case 0x29:
|
||||
return "29xy: Set auto-envelope (x: numerator; y: denominator)";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
x1_010->tick();
|
||||
|
||||
signed int tempL=x1_010->output(0);
|
||||
signed int tempR=x1_010->output(1);
|
||||
|
||||
if (tempL<-32768) tempL=-32768;
|
||||
if (tempL>32767) tempL=32767;
|
||||
if (tempR<-32768) tempR=-32768;
|
||||
if (tempR>32767) tempR=32767;
|
||||
|
||||
//printf("tempL: %d tempR: %d\n",tempL,tempR);
|
||||
bufL[h]=stereo?tempL:((tempL+tempR)>>1);
|
||||
bufR[h]=stereo?tempR:bufL[h];
|
||||
}
|
||||
}
|
||||
|
||||
double DivPlatformX1_010::NoteX1_010(int ch, int note) {
|
||||
if (chan[ch].pcm) { // PCM note
|
||||
double off=1.0;
|
||||
int sample=chan[ch].sample;
|
||||
if (sample>=0 && sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(sample);
|
||||
if (s->centerRate<1) {
|
||||
off=1.0;
|
||||
} else {
|
||||
off=s->centerRate/8363.0;
|
||||
}
|
||||
}
|
||||
return off*parent->calcBaseFreq(chipClock,8192,note,false);
|
||||
}
|
||||
// Wavetable note
|
||||
return NOTE_FREQUENCY(note);
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::updateWave(int ch) {
|
||||
DivWavetable* wt=parent->getWave(chan[ch].wave);
|
||||
if (chan[ch].active) {
|
||||
chan[ch].waveBank ^= 1;
|
||||
}
|
||||
for (int i=0; i<128; i++) {
|
||||
if (wt->max<1 || wt->len<1) {
|
||||
waveWrite(ch,i,0);
|
||||
} else {
|
||||
int data=wt->data[i*wt->len/128]*255/wt->max;
|
||||
if (data<0) data=0;
|
||||
if (data>255) data=255;
|
||||
waveWrite(ch,i,data);
|
||||
}
|
||||
}
|
||||
if (!chan[ch].pcm) {
|
||||
chWrite(ch,1,(chan[ch].waveBank<<4)|(ch&0xf));
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::updateEnvelope(int ch) {
|
||||
if (!chan[ch].pcm) {
|
||||
if (isMuted[ch]) {
|
||||
for (int i=0; i<128; i++) {
|
||||
rWrite(0x800|(ch<<7)|(i&0x7f),0);
|
||||
}
|
||||
} else {
|
||||
if (!chan[ch].env.flag.envEnable) {
|
||||
for (int i=0; i<128; i++) {
|
||||
envFill(ch,i);
|
||||
}
|
||||
} else {
|
||||
DivWavetable* wt=parent->getWave(chan[ch].env.shape);
|
||||
for (int i=0; i<128; i++) {
|
||||
if (wt->max<1 || wt->len<1) {
|
||||
envFill(ch,i);
|
||||
} else if (chan[ch].env.flag.envSplit || chan[ch].env.flag.envHinvR || chan[ch].env.flag.envVinvR || chan[ch].env.flag.envHinvL || chan[ch].env.flag.envVinvL) { // Stereo config
|
||||
int la=i,ra=i;
|
||||
int lo,ro;
|
||||
if (chan[ch].env.flag.envHinvR) { ra=127-i; } // horizontal invert right envelope
|
||||
if (chan[ch].env.flag.envHinvL) { la=127-i; } // horizontal invert left envelope
|
||||
if (chan[ch].env.flag.envSplit) { // Split shape to left and right half
|
||||
lo=wt->data[la*(wt->len/128/2)]*15/wt->max;
|
||||
ro=wt->data[(ra+128)*(wt->len/128/2)]*15/wt->max;
|
||||
} else {
|
||||
lo=wt->data[la*wt->len/128]*15/wt->max;
|
||||
ro=wt->data[ra*wt->len/128]*15/wt->max;
|
||||
}
|
||||
if (chan[ch].env.flag.envVinvR) { ro=15-ro; } // vertical invert right envelope
|
||||
if (chan[ch].env.flag.envVinvL) { lo=15-lo; } // vertical invert left envelope
|
||||
if (lo<0) lo=0;
|
||||
if (lo>15) lo=15;
|
||||
if (ro<0) ro=0;
|
||||
if (ro>15) ro=15;
|
||||
envWrite(ch,i,lo,ro);
|
||||
} else {
|
||||
int out=wt->data[i*wt->len/128]*15/wt->max;
|
||||
if (out<0) out=0;
|
||||
if (out>15) out=15;
|
||||
envWrite(ch,i,out,out);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
chWrite(ch,5,0x10|(ch&0xf));
|
||||
} else {
|
||||
chWrite(ch,1,(chan[ch].lvol<<4)|chan[ch].rvol);
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::tick() {
|
||||
for (int i=0; i<16; i++) {
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.hadVol) {
|
||||
signed char macroVol=((chan[i].vol&15)*MIN(chan[i].furnacePCM?64:15,chan[i].std.vol))/(chan[i].furnacePCM?64:15);
|
||||
if ((!isMuted[i]) && (macroVol!=chan[i].outVol)) {
|
||||
chan[i].outVol=macroVol;
|
||||
chan[i].envChanged=true;
|
||||
}
|
||||
}
|
||||
if ((!chan[i].pcm) || chan[i].furnacePCM) {
|
||||
if (chan[i].std.hadArp) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arpMode) {
|
||||
chan[i].baseFreq=NoteX1_010(i,chan[i].std.arp);
|
||||
} else {
|
||||
chan[i].baseFreq=NoteX1_010(i,chan[i].note+chan[i].std.arp);
|
||||
}
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arpMode && chan[i].std.finishedArp) {
|
||||
chan[i].baseFreq=NoteX1_010(i,chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chan[i].std.hadWave && !chan[i].pcm) {
|
||||
if (chan[i].wave!=chan[i].std.wave) {
|
||||
chan[i].wave=chan[i].std.wave;
|
||||
if (!chan[i].pcm) {
|
||||
updateWave(i);
|
||||
if (!chan[i].keyOff) chan[i].keyOn=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chan[i].std.hadEx1) {
|
||||
bool nextEnable=(chan[i].std.ex1&1);
|
||||
if (nextEnable!=(chan[i].env.flag.envEnable)) {
|
||||
chan[i].env.flag.envEnable=nextEnable;
|
||||
if (!chan[i].pcm) {
|
||||
if (!isMuted[i]) {
|
||||
chan[i].envChanged=true;
|
||||
}
|
||||
refreshControl(i);
|
||||
}
|
||||
}
|
||||
bool nextOneshot=(chan[i].std.ex1&2);
|
||||
if (nextOneshot!=(chan[i].env.flag.envOneshot)) {
|
||||
chan[i].env.flag.envOneshot=nextOneshot;
|
||||
if (!chan[i].pcm) {
|
||||
refreshControl(i);
|
||||
}
|
||||
}
|
||||
bool nextSplit=(chan[i].std.ex1&4);
|
||||
if (nextSplit!=(chan[i].env.flag.envSplit)) {
|
||||
chan[i].env.flag.envSplit=nextSplit;
|
||||
if (!isMuted[i] && !chan[i].pcm) {
|
||||
chan[i].envChanged=true;
|
||||
}
|
||||
}
|
||||
bool nextHinvR=(chan[i].std.ex1&8);
|
||||
if (nextHinvR!=(chan[i].env.flag.envHinvR)) {
|
||||
chan[i].env.flag.envHinvR=nextHinvR;
|
||||
if (!isMuted[i] && !chan[i].pcm) {
|
||||
chan[i].envChanged=true;
|
||||
}
|
||||
}
|
||||
bool nextVinvR=(chan[i].std.ex1&16);
|
||||
if (nextVinvR!=(chan[i].env.flag.envVinvR)) {
|
||||
chan[i].env.flag.envVinvR=nextVinvR;
|
||||
if (!isMuted[i] && !chan[i].pcm) {
|
||||
chan[i].envChanged=true;
|
||||
}
|
||||
}
|
||||
bool nextHinvL=(chan[i].std.ex1&32);
|
||||
if (nextHinvL!=(chan[i].env.flag.envHinvL)) {
|
||||
chan[i].env.flag.envHinvL=nextHinvL;
|
||||
if (!isMuted[i] && !chan[i].pcm) {
|
||||
chan[i].envChanged=true;
|
||||
}
|
||||
}
|
||||
bool nextVinvL=(chan[i].std.ex1&64);
|
||||
if (nextVinvL!=(chan[i].env.flag.envVinvL)) {
|
||||
chan[i].env.flag.envVinvL=nextVinvL;
|
||||
if (!isMuted[i] && !chan[i].pcm) {
|
||||
chan[i].envChanged=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chan[i].std.hadEx2) {
|
||||
if (chan[i].env.shape!=chan[i].std.ex2) {
|
||||
chan[i].env.shape=chan[i].std.ex2;
|
||||
if (!chan[i].pcm) {
|
||||
if (chan[i].env.flag.envEnable && (!isMuted[i])) {
|
||||
chan[i].envChanged=true;
|
||||
}
|
||||
if (!chan[i].keyOff) chan[i].keyOn=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chan[i].std.hadEx3) {
|
||||
chan[i].autoEnvNum=chan[i].std.ex3;
|
||||
if (!chan[i].pcm) {
|
||||
chan[i].freqChanged=true;
|
||||
if (!chan[i].std.willAlg) chan[i].autoEnvDen=1;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.hadAlg) {
|
||||
chan[i].autoEnvDen=chan[i].std.alg;
|
||||
if (!chan[i].pcm) {
|
||||
chan[i].freqChanged=true;
|
||||
if (!chan[i].std.willEx3) chan[i].autoEnvNum=1;
|
||||
}
|
||||
}
|
||||
if (chan[i].envChanged) {
|
||||
if (!isMuted[i]) {
|
||||
chan[i].lvol=((chan[i].outVol&0xf)*((chan[i].pan>>4)&0xf))/15;
|
||||
chan[i].rvol=((chan[i].outVol&0xf)*((chan[i].pan>>0)&0xf))/15;
|
||||
}
|
||||
updateEnvelope(i);
|
||||
chan[i].envChanged=false;
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false);
|
||||
if (chan[i].pcm) {
|
||||
if (chan[i].freq<1) chan[i].freq=1;
|
||||
if (chan[i].freq>255) chan[i].freq=255;
|
||||
chWrite(i,2,chan[i].freq&0xff);
|
||||
} else {
|
||||
if (chan[i].freq>65535) chan[i].freq=65535;
|
||||
chWrite(i,2,chan[i].freq&0xff);
|
||||
chWrite(i,3,(chan[i].freq>>8)&0xff);
|
||||
if (chan[i].freqChanged && chan[i].autoEnvNum>0 && chan[i].autoEnvDen>0) {
|
||||
chan[i].env.period=(chan[i].freq*chan[i].autoEnvDen/chan[i].autoEnvNum)>>12;
|
||||
chWrite(i,4,chan[i].env.period);
|
||||
}
|
||||
}
|
||||
if (chan[i].keyOn || chan[i].keyOff || (chRead(i,0)&1)) {
|
||||
refreshControl(i);
|
||||
}
|
||||
if (chan[i].keyOn) chan[i].keyOn=false;
|
||||
if (chan[i].keyOff) chan[i].keyOff=false;
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
if (chan[i].env.slide!=0) {
|
||||
chan[i].env.slidefrac+=chan[i].env.slide;
|
||||
while (chan[i].env.slidefrac>0xf) {
|
||||
chan[i].env.slidefrac-=0x10;
|
||||
if (chan[i].env.period<0xff) {
|
||||
chan[i].env.period++;
|
||||
if (!chan[i].pcm) {
|
||||
chWrite(i,4,chan[i].env.period);
|
||||
}
|
||||
}
|
||||
}
|
||||
while (chan[i].env.slidefrac<-0xf) {
|
||||
chan[i].env.slidefrac+=0x10;
|
||||
if (chan[i].env.period>0) {
|
||||
chan[i].env.period--;
|
||||
if (!chan[i].pcm) {
|
||||
chWrite(i,4,chan[i].env.period);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformX1_010::dispatch(DivCommand c) {
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
chWrite(c.chan,0,0); // reset previous note
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
|
||||
if ((ins->type==DIV_INS_AMIGA) || chan[c.chan].pcm) {
|
||||
if (ins->type==DIV_INS_AMIGA) {
|
||||
chan[c.chan].furnacePCM=true;
|
||||
} else {
|
||||
chan[c.chan].furnacePCM=false;
|
||||
}
|
||||
if (skipRegisterWrites) break;
|
||||
if (chan[c.chan].furnacePCM) {
|
||||
chan[c.chan].pcm=true;
|
||||
chan[c.chan].std.init(ins);
|
||||
chan[c.chan].sample=ins->amiga.initSample;
|
||||
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(chan[c.chan].sample);
|
||||
chWrite(c.chan,4,(s->offX1_010>>12)&0xff);
|
||||
int end=(s->offX1_010+s->length8+0xfff)&~0xfff; // padded
|
||||
chWrite(c.chan,5,(0x100-(end>>12))&0xff);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].note=c.value;
|
||||
chan[c.chan].baseFreq=NoteX1_010(c.chan,chan[c.chan].note);
|
||||
chan[c.chan].freqChanged=true;
|
||||
}
|
||||
} else {
|
||||
chan[c.chan].std.init(NULL);
|
||||
chan[c.chan].outVol=chan[c.chan].vol;
|
||||
if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) {
|
||||
chWrite(c.chan,0,0); // reset
|
||||
chWrite(c.chan,1,0);
|
||||
chWrite(c.chan,2,0);
|
||||
chWrite(c.chan,4,0);
|
||||
chWrite(c.chan,5,0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
chan[c.chan].std.init(NULL);
|
||||
chan[c.chan].outVol=chan[c.chan].vol;
|
||||
if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) {
|
||||
chWrite(c.chan,0,0); // reset
|
||||
chWrite(c.chan,1,0);
|
||||
chWrite(c.chan,2,0);
|
||||
chWrite(c.chan,4,0);
|
||||
chWrite(c.chan,5,0);
|
||||
break;
|
||||
}
|
||||
DivSample* s=parent->getSample(12*sampleBank+c.value%12);
|
||||
chWrite(c.chan,4,(s->offX1_010>>12)&0xff);
|
||||
int end=(s->offX1_010+s->length8+0xfff)&~0xfff; // padded
|
||||
chWrite(c.chan,5,(0x100-(end>>12))&0xff);
|
||||
chan[c.chan].baseFreq=(((unsigned int)s->rate)<<4)/(chipClock/512);
|
||||
chan[c.chan].freqChanged=true;
|
||||
}
|
||||
} else if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].note=c.value;
|
||||
chan[c.chan].baseFreq=NoteX1_010(c.chan,chan[c.chan].note);
|
||||
chan[c.chan].freqChanged=true;
|
||||
}
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
chan[c.chan].envChanged=true;
|
||||
chan[c.chan].std.init(ins);
|
||||
refreshControl(c.chan);
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
chan[c.chan].pcm=false;
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].std.init(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
case DIV_CMD_ENV_RELEASE:
|
||||
chan[c.chan].std.release();
|
||||
break;
|
||||
case DIV_CMD_INSTRUMENT:
|
||||
if (chan[c.chan].ins!=c.value || c.value2==1) {
|
||||
chan[c.chan].ins=c.value;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_VOLUME:
|
||||
if (chan[c.chan].vol!=c.value) {
|
||||
chan[c.chan].vol=c.value;
|
||||
if (!chan[c.chan].std.hasVol) {
|
||||
if (chan[c.chan].outVol!=c.value) {
|
||||
chan[c.chan].outVol=c.value;
|
||||
if (!isMuted[c.chan]) {
|
||||
chan[c.chan].envChanged=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_GET_VOLUME:
|
||||
if (chan[c.chan].std.hasVol) {
|
||||
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;
|
||||
updateWave(c.chan);
|
||||
chan[c.chan].keyOn=true;
|
||||
break;
|
||||
case DIV_CMD_X1_010_ENVELOPE_SHAPE:
|
||||
if (chan[c.chan].env.shape!=c.value) {
|
||||
chan[c.chan].env.shape=c.value;
|
||||
if (!chan[c.chan].pcm) {
|
||||
if (chan[c.chan].env.flag.envEnable && (!isMuted[c.chan])) {
|
||||
chan[c.chan].envChanged=true;
|
||||
}
|
||||
chan[c.chan].keyOn=true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
int destFreq=NoteX1_010(c.chan,c.value2);
|
||||
bool return2=false;
|
||||
if (destFreq>chan[c.chan].baseFreq) {
|
||||
chan[c.chan].baseFreq+=c.value;
|
||||
if (chan[c.chan].baseFreq>=destFreq) {
|
||||
chan[c.chan].baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
} else {
|
||||
chan[c.chan].baseFreq-=c.value;
|
||||
if (chan[c.chan].baseFreq<=destFreq) {
|
||||
chan[c.chan].baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
}
|
||||
chan[c.chan].freqChanged=true;
|
||||
if (return2) {
|
||||
chan[c.chan].inPorta=false;
|
||||
return 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_SAMPLE_MODE:
|
||||
if (chan[c.chan].pcm!=(c.value&1)) {
|
||||
chan[c.chan].pcm=c.value&1;
|
||||
chan[c.chan].freqChanged=true;
|
||||
if (!isMuted[c.chan]) {
|
||||
chan[c.chan].envChanged=true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_BANK:
|
||||
sampleBank=c.value;
|
||||
if (sampleBank>(parent->song.sample.size()/12)) {
|
||||
sampleBank=parent->song.sample.size()/12;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_PANNING: {
|
||||
if (chan[c.chan].pan!=c.value) {
|
||||
chan[c.chan].pan=c.value;
|
||||
if (!isMuted[c.chan]) {
|
||||
chan[c.chan].envChanged=true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_LEGATO:
|
||||
chan[c.chan].note=c.value;
|
||||
chan[c.chan].baseFreq=NoteX1_010(c.chan,chan[c.chan].note+((chan[c.chan].std.willArp&&!chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0)));
|
||||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
|
||||
}
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_FREQ:
|
||||
if (chan[c.chan].pcm) {
|
||||
chan[c.chan].freq=MAX(1,c.value&0xff);
|
||||
chWrite(c.chan,2,chan[c.chan].freq&0xff);
|
||||
if (chRead(c.chan,0)&1) {
|
||||
refreshControl(c.chan);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_X1_010_ENVELOPE_MODE: {
|
||||
bool nextEnable=c.value&1;
|
||||
if (nextEnable!=(chan[c.chan].env.flag.envEnable)) {
|
||||
chan[c.chan].env.flag.envEnable=nextEnable;
|
||||
if (!chan[c.chan].pcm) {
|
||||
if (!isMuted[c.chan]) {
|
||||
chan[c.chan].envChanged=true;
|
||||
}
|
||||
refreshControl(c.chan);
|
||||
}
|
||||
}
|
||||
bool nextOneshot=c.value&2;
|
||||
if (nextOneshot!=(chan[c.chan].env.flag.envOneshot)) {
|
||||
chan[c.chan].env.flag.envOneshot=nextOneshot;
|
||||
if (!chan[c.chan].pcm) {
|
||||
refreshControl(c.chan);
|
||||
}
|
||||
}
|
||||
bool nextSplit=c.value&4;
|
||||
if (nextSplit!=(chan[c.chan].env.flag.envSplit)) {
|
||||
chan[c.chan].env.flag.envSplit=nextSplit;
|
||||
if (!isMuted[c.chan] && !chan[c.chan].pcm) {
|
||||
chan[c.chan].envChanged=true;
|
||||
}
|
||||
}
|
||||
bool nextHinvR=c.value&8;
|
||||
if (nextHinvR!=(chan[c.chan].env.flag.envHinvR)) {
|
||||
chan[c.chan].env.flag.envHinvR=nextHinvR;
|
||||
if (!isMuted[c.chan] && !chan[c.chan].pcm) {
|
||||
chan[c.chan].envChanged=true;
|
||||
}
|
||||
}
|
||||
bool nextVinvR=c.value&16;
|
||||
if (nextVinvR!=(chan[c.chan].env.flag.envVinvR)) {
|
||||
chan[c.chan].env.flag.envVinvR=nextVinvR;
|
||||
if (!isMuted[c.chan] && !chan[c.chan].pcm) {
|
||||
chan[c.chan].envChanged=true;
|
||||
}
|
||||
}
|
||||
bool nextHinvL=c.value&32;
|
||||
if (nextHinvL!=(chan[c.chan].env.flag.envHinvL)) {
|
||||
chan[c.chan].env.flag.envHinvL=nextHinvL;
|
||||
if (!isMuted[c.chan] && !chan[c.chan].pcm) {
|
||||
chan[c.chan].envChanged=true;
|
||||
}
|
||||
}
|
||||
bool nextVinvL=c.value&64;
|
||||
if (nextVinvL!=(chan[c.chan].env.flag.envVinvL)) {
|
||||
chan[c.chan].env.flag.envVinvL=nextVinvL;
|
||||
if (!isMuted[c.chan] && !chan[c.chan].pcm) {
|
||||
chan[c.chan].envChanged=true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_X1_010_ENVELOPE_PERIOD:
|
||||
chan[c.chan].env.period=c.value;
|
||||
if (!chan[c.chan].pcm) {
|
||||
chWrite(c.chan,4,chan[c.chan].env.period);
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_X1_010_ENVELOPE_SLIDE:
|
||||
chan[c.chan].env.slide=c.value;
|
||||
break;
|
||||
case DIV_CMD_X1_010_AUTO_ENVELOPE:
|
||||
chan[c.chan].autoEnvNum=c.value>>4;
|
||||
chan[c.chan].autoEnvDen=c.value&15;
|
||||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
return 15;
|
||||
break;
|
||||
case DIV_ALWAYS_SET_VOLUME:
|
||||
return 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::muteChannel(int ch, bool mute) {
|
||||
isMuted[ch]=mute;
|
||||
chan[ch].envChanged=true;
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::forceIns() {
|
||||
for (int i=0; i<16; i++) {
|
||||
chan[i].insChanged=true;
|
||||
chan[i].envChanged=true;
|
||||
chan[i].freqChanged=true;
|
||||
updateWave(i);
|
||||
}
|
||||
}
|
||||
|
||||
void* DivPlatformX1_010::getChanState(int ch) {
|
||||
return &chan[ch];
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformX1_010::getRegisterPool() {
|
||||
for (int i=0; i<0x2000; i++) {
|
||||
regPool[i]=x1_010->ram_r(i);
|
||||
}
|
||||
return regPool;
|
||||
}
|
||||
|
||||
int DivPlatformX1_010::getRegisterPoolSize() {
|
||||
return 0x2000;
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::reset() {
|
||||
memset(regPool,0,0x2000);
|
||||
for (int i=0; i<16; i++) {
|
||||
chan[i]=DivPlatformX1_010::Channel();
|
||||
chan[i].reset();
|
||||
}
|
||||
x1_010->reset();
|
||||
sampleBank=0;
|
||||
// set per-channel initial panning
|
||||
for (int i=0; i<16; i++) {
|
||||
chWrite(i,0,0);
|
||||
}
|
||||
}
|
||||
|
||||
bool DivPlatformX1_010::isStereo() {
|
||||
return stereo;
|
||||
}
|
||||
|
||||
bool DivPlatformX1_010::keyOffAffectsArp(int ch) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::notifyWaveChange(int wave) {
|
||||
for (int i=0; i<16; i++) {
|
||||
if (chan[i].wave==wave) {
|
||||
updateWave(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::notifyInsDeletion(void* ins) {
|
||||
for (int i=0; i<16; i++) {
|
||||
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::setFlags(unsigned int flags) {
|
||||
switch (flags&15) {
|
||||
case 0: // 16MHz (earlier hardwares)
|
||||
chipClock=16000000;
|
||||
break;
|
||||
case 1: // 16.67MHz (later hardwares)
|
||||
chipClock=50000000.0/3.0;
|
||||
break;
|
||||
// Other clock is used?
|
||||
}
|
||||
rate=chipClock/512;
|
||||
stereo=flags&16;
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::poke(unsigned int addr, unsigned short val) {
|
||||
rWrite(addr,val);
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::poke(std::vector<DivRegWrite>& wlist) {
|
||||
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
|
||||
}
|
||||
|
||||
int DivPlatformX1_010::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||
parent=p;
|
||||
dumpWrites=false;
|
||||
skipRegisterWrites=false;
|
||||
stereo=false;
|
||||
for (int i=0; i<16; i++) {
|
||||
isMuted[i]=false;
|
||||
}
|
||||
setFlags(flags);
|
||||
intf.parent=parent;
|
||||
x1_010=new x1_010_core(intf);
|
||||
x1_010->reset();
|
||||
reset();
|
||||
return 16;
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::quit() {
|
||||
delete x1_010;
|
||||
}
|
||||
|
||||
DivPlatformX1_010::~DivPlatformX1_010() {
|
||||
}
|
||||
144
src/engine/platform/x1_010.h
Normal file
144
src/engine/platform/x1_010.h
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
/**
|
||||
* 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 _X1_010_H
|
||||
#define _X1_010_H
|
||||
|
||||
#include <queue>
|
||||
#include "../dispatch.h"
|
||||
#include "../engine.h"
|
||||
#include "../macroInt.h"
|
||||
#include "sound/x1_010/x1_010.hpp"
|
||||
|
||||
class DivX1_010Interface: public x1_010_mem_intf {
|
||||
public:
|
||||
DivEngine* parent;
|
||||
int sampleBank;
|
||||
virtual u8 read_byte(u32 address) override {
|
||||
if (parent->x1_010Mem==NULL) return 0;
|
||||
return parent->x1_010Mem[address & 0xfffff];
|
||||
}
|
||||
DivX1_010Interface(): parent(NULL), sampleBank(0) {}
|
||||
};
|
||||
|
||||
class DivPlatformX1_010: public DivDispatch {
|
||||
struct Channel {
|
||||
struct Envelope {
|
||||
struct EnvFlag {
|
||||
unsigned char envEnable : 1;
|
||||
unsigned char envOneshot : 1;
|
||||
unsigned char envSplit : 1;
|
||||
unsigned char envHinvR : 1;
|
||||
unsigned char envVinvR : 1;
|
||||
unsigned char envHinvL : 1;
|
||||
unsigned char envVinvL : 1;
|
||||
void reset() {
|
||||
envEnable=0;
|
||||
envOneshot=0;
|
||||
envSplit=0;
|
||||
envHinvR=0;
|
||||
envVinvR=0;
|
||||
envHinvL=0;
|
||||
envVinvL=0;
|
||||
}
|
||||
EnvFlag():
|
||||
envEnable(0),
|
||||
envOneshot(0),
|
||||
envSplit(0),
|
||||
envHinvR(0),
|
||||
envVinvR(0),
|
||||
envHinvL(0),
|
||||
envVinvL(0) {}
|
||||
};
|
||||
int shape, period, slide, slidefrac;
|
||||
EnvFlag flag;
|
||||
void reset() {
|
||||
shape=-1;
|
||||
period=0;
|
||||
flag.reset();
|
||||
}
|
||||
Envelope():
|
||||
shape(-1),
|
||||
period(0),
|
||||
slide(0),
|
||||
slidefrac(0) {}
|
||||
};
|
||||
int freq, baseFreq, pitch, note;
|
||||
int wave, sample, ins;
|
||||
unsigned char pan, autoEnvNum, autoEnvDen;
|
||||
bool active, insChanged, envChanged, freqChanged, keyOn, keyOff, inPorta, furnacePCM, pcm;
|
||||
int vol, outVol, lvol, rvol;
|
||||
unsigned char waveBank;
|
||||
Envelope env;
|
||||
DivMacroInt std;
|
||||
void reset() {
|
||||
freq = baseFreq = pitch = note = 0;
|
||||
wave = sample = ins = -1;
|
||||
pan = 255;
|
||||
autoEnvNum = autoEnvDen = 0;
|
||||
active = false;
|
||||
insChanged = envChanged = freqChanged = true;
|
||||
keyOn = keyOff = inPorta = furnacePCM = pcm = false;
|
||||
vol = outVol = lvol = rvol = 15;
|
||||
waveBank = 0;
|
||||
}
|
||||
Channel():
|
||||
freq(0), baseFreq(0), pitch(0), note(0),
|
||||
wave(-1), sample(-1), ins(-1),
|
||||
pan(255), autoEnvNum(0), autoEnvDen(0),
|
||||
active(false), insChanged(true), envChanged(true), freqChanged(false), keyOn(false), keyOff(false), inPorta(false), furnacePCM(false), pcm(false),
|
||||
vol(15), outVol(15), lvol(15), rvol(15),
|
||||
waveBank(0) {}
|
||||
};
|
||||
Channel chan[16];
|
||||
bool isMuted[16];
|
||||
bool stereo=false;
|
||||
unsigned char sampleBank;
|
||||
DivX1_010Interface intf;
|
||||
x1_010_core* x1_010;
|
||||
unsigned char regPool[0x2000];
|
||||
double NoteX1_010(int ch, int note);
|
||||
void updateWave(int ch);
|
||||
void updateEnvelope(int ch);
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
public:
|
||||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick();
|
||||
void muteChannel(int ch, bool mute);
|
||||
bool isStereo();
|
||||
bool keyOffAffectsArp(int ch);
|
||||
void setFlags(unsigned int flags);
|
||||
void notifyWaveChange(int wave);
|
||||
void notifyInsDeletion(void* ins);
|
||||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformX1_010();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -313,11 +313,22 @@ const char* DivPlatformYM2610::getEffectName(unsigned char effect) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
double DivPlatformYM2610::NOTE_OPNB(int ch, int note) {
|
||||
if (ch>6) { // ADPCM
|
||||
return NOTE_ADPCMB(note);
|
||||
} else if (ch>3) { // PSG
|
||||
return NOTE_PERIODIC(note);
|
||||
}
|
||||
// FM
|
||||
return NOTE_FREQUENCY(note);
|
||||
}
|
||||
|
||||
double DivPlatformYM2610::NOTE_ADPCMB(int note) {
|
||||
DivInstrument* ins=parent->getIns(chan[13].ins);
|
||||
if (ins->type!=DIV_INS_AMIGA) return 0;
|
||||
double off=(double)(parent->getSample(ins->amiga.initSample)->centerRate)/8363.0;
|
||||
return off*parent->calcBaseFreq((double)chipClock/144,65535,note,false);
|
||||
if (chan[13].sample>=0 && chan[13].sample<parent->song.sampleLen) {
|
||||
double off=(double)(parent->getSample(chan[13].sample)->centerRate)/8363.0;
|
||||
return off*parent->calcBaseFreq((double)chipClock/144,65535,note,false);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void DivPlatformYM2610::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
|
|
@ -691,22 +702,33 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
|
|||
chan[c.chan].outVol=chan[c.chan].vol;
|
||||
immWrite(0x1b,chan[c.chan].outVol);
|
||||
}
|
||||
DivSample* s=parent->getSample(ins->amiga.initSample);
|
||||
immWrite(0x12,(s->offB>>8)&0xff);
|
||||
immWrite(0x13,s->offB>>16);
|
||||
int end=s->offB+s->lengthB-1;
|
||||
immWrite(0x14,(end>>8)&0xff);
|
||||
immWrite(0x15,end>>16);
|
||||
immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6));
|
||||
immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].note=c.value;
|
||||
chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note);
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].sample=ins->amiga.initSample;
|
||||
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(chan[c.chan].sample);
|
||||
immWrite(0x12,(s->offB>>8)&0xff);
|
||||
immWrite(0x13,s->offB>>16);
|
||||
int end=s->offB+s->lengthB-1;
|
||||
immWrite(0x14,(end>>8)&0xff);
|
||||
immWrite(0x15,end>>16);
|
||||
immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6));
|
||||
immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].note=c.value;
|
||||
chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note);
|
||||
chan[c.chan].freqChanged=true;
|
||||
}
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
} else {
|
||||
immWrite(0x10,0x01); // reset
|
||||
immWrite(0x12,0);
|
||||
immWrite(0x13,0);
|
||||
immWrite(0x14,0);
|
||||
immWrite(0x15,0);
|
||||
break;
|
||||
}
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
} else {
|
||||
chan[c.chan].sample=-1;
|
||||
chan[c.chan].std.init(NULL);
|
||||
chan[c.chan].outVol=chan[c.chan].vol;
|
||||
if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) {
|
||||
|
|
@ -915,8 +937,8 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
|
|||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
if (c.chan>3) { // PSG
|
||||
int destFreq=NOTE_PERIODIC(c.value2);
|
||||
if (c.chan>3) { // PSG, ADPCM-B
|
||||
int destFreq=NOTE_OPNB(c.chan,c.value2);
|
||||
bool return2=false;
|
||||
if (destFreq>chan[c.chan].baseFreq) {
|
||||
chan[c.chan].baseFreq+=c.value;
|
||||
|
|
@ -975,11 +997,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
|
|||
iface.sampleBank=sampleBank;
|
||||
break;
|
||||
case DIV_CMD_LEGATO: {
|
||||
if (c.chan>3) { // PSG
|
||||
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
|
||||
} else {
|
||||
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
|
||||
}
|
||||
chan[c.chan].baseFreq=NOTE_OPNB(c.chan,c.value);
|
||||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
}
|
||||
|
|
@ -1210,11 +1228,6 @@ void DivPlatformYM2610::reset() {
|
|||
}
|
||||
|
||||
lastBusy=60;
|
||||
dacMode=0;
|
||||
dacPeriod=0;
|
||||
dacPos=0;
|
||||
dacRate=0;
|
||||
dacSample=-1;
|
||||
sampleBank=0;
|
||||
ayEnvPeriod=0;
|
||||
ayEnvMode=0;
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ class DivPlatformYM2610: public DivDispatch {
|
|||
signed char konCycles;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM;
|
||||
int vol, outVol;
|
||||
int sample;
|
||||
unsigned char pan;
|
||||
DivMacroInt std;
|
||||
Channel():
|
||||
|
|
@ -70,6 +71,7 @@ class DivPlatformYM2610: public DivDispatch {
|
|||
furnacePCM(false),
|
||||
vol(0),
|
||||
outVol(15),
|
||||
sample(-1),
|
||||
pan(3) {}
|
||||
};
|
||||
Channel chan[14];
|
||||
|
|
@ -87,11 +89,6 @@ class DivPlatformYM2610: public DivDispatch {
|
|||
unsigned char regPool[512];
|
||||
unsigned char lastBusy;
|
||||
|
||||
bool dacMode;
|
||||
int dacPeriod;
|
||||
int dacRate;
|
||||
int dacPos;
|
||||
int dacSample;
|
||||
int ayNoiseFreq;
|
||||
unsigned char sampleBank;
|
||||
|
||||
|
|
@ -108,6 +105,7 @@ class DivPlatformYM2610: public DivDispatch {
|
|||
|
||||
int octave(int freq);
|
||||
int toFreq(int freq);
|
||||
double NOTE_OPNB(int ch, int note);
|
||||
double NOTE_ADPCMB(int note);
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
|
||||
|
|
|
|||
|
|
@ -377,11 +377,22 @@ const char* DivPlatformYM2610B::getEffectName(unsigned char effect) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
double DivPlatformYM2610B::NOTE_OPNB(int ch, int note) {
|
||||
if (ch>8) { // ADPCM-B
|
||||
return NOTE_ADPCMB(note);
|
||||
} else if (ch>5) { // PSG
|
||||
return NOTE_PERIODIC(note);
|
||||
}
|
||||
// FM
|
||||
return NOTE_FREQUENCY(note);
|
||||
}
|
||||
|
||||
double DivPlatformYM2610B::NOTE_ADPCMB(int note) {
|
||||
DivInstrument* ins=parent->getIns(chan[15].ins);
|
||||
if (ins->type!=DIV_INS_AMIGA) return 0;
|
||||
double off=(double)(parent->getSample(ins->amiga.initSample)->centerRate)/8363.0;
|
||||
return off*parent->calcBaseFreq((double)chipClock/144,65535,note,false);
|
||||
if (chan[15].sample>=0 && chan[15].sample<parent->song.sampleLen) {
|
||||
double off=(double)(parent->getSample(chan[15].sample)->centerRate)/8363.0;
|
||||
return off*parent->calcBaseFreq((double)chipClock/144,65535,note,false);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void DivPlatformYM2610B::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
|
|
@ -752,24 +763,35 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
|
|||
chan[c.chan].std.init(ins);
|
||||
if (!chan[c.chan].std.willVol) {
|
||||
chan[c.chan].outVol=chan[c.chan].vol;
|
||||
immWrite(0x1b,chan[c.chan].outVol);
|
||||
immWrite(0x1b,chan[c.chan].outVol);
|
||||
}
|
||||
DivSample* s=parent->getSample(ins->amiga.initSample);
|
||||
immWrite(0x12,(s->offB>>8)&0xff);
|
||||
immWrite(0x13,s->offB>>16);
|
||||
int end=s->offB+s->lengthB-1;
|
||||
immWrite(0x14,(end>>8)&0xff);
|
||||
immWrite(0x15,end>>16);
|
||||
immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6));
|
||||
immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].note=c.value;
|
||||
chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note);
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].sample=ins->amiga.initSample;
|
||||
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(chan[c.chan].sample);
|
||||
immWrite(0x12,(s->offB>>8)&0xff);
|
||||
immWrite(0x13,s->offB>>16);
|
||||
int end=s->offB+s->lengthB-1;
|
||||
immWrite(0x14,(end>>8)&0xff);
|
||||
immWrite(0x15,end>>16);
|
||||
immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6));
|
||||
immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].note=c.value;
|
||||
chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note);
|
||||
chan[c.chan].freqChanged=true;
|
||||
}
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
} else {
|
||||
immWrite(0x10,0x01); // reset
|
||||
immWrite(0x12,0);
|
||||
immWrite(0x13,0);
|
||||
immWrite(0x14,0);
|
||||
immWrite(0x15,0);
|
||||
break;
|
||||
}
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
} else {
|
||||
chan[c.chan].sample=-1;
|
||||
chan[c.chan].std.init(NULL);
|
||||
chan[c.chan].outVol=chan[c.chan].vol;
|
||||
if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) {
|
||||
|
|
@ -978,8 +1000,8 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
|
|||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
if (c.chan>5) { // PSG
|
||||
int destFreq=NOTE_PERIODIC(c.value2);
|
||||
if (c.chan>5) { // PSG, ADPCM-B
|
||||
int destFreq=NOTE_OPNB(c.chan,c.value2);
|
||||
bool return2=false;
|
||||
if (destFreq>chan[c.chan].baseFreq) {
|
||||
chan[c.chan].baseFreq+=c.value;
|
||||
|
|
@ -1038,11 +1060,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
|
|||
iface.sampleBank=sampleBank;
|
||||
break;
|
||||
case DIV_CMD_LEGATO: {
|
||||
if (c.chan>5) { // PSG
|
||||
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
|
||||
} else {
|
||||
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
|
||||
}
|
||||
chan[c.chan].baseFreq=NOTE_OPNB(c.chan,c.value);
|
||||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
}
|
||||
|
|
@ -1273,11 +1291,6 @@ void DivPlatformYM2610B::reset() {
|
|||
}
|
||||
|
||||
lastBusy=60;
|
||||
dacMode=0;
|
||||
dacPeriod=0;
|
||||
dacPos=0;
|
||||
dacRate=0;
|
||||
dacSample=-1;
|
||||
sampleBank=0;
|
||||
ayEnvPeriod=0;
|
||||
ayEnvMode=0;
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ class DivPlatformYM2610B: public DivDispatch {
|
|||
signed char konCycles;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM;
|
||||
int vol, outVol;
|
||||
int sample;
|
||||
unsigned char pan;
|
||||
DivMacroInt std;
|
||||
Channel():
|
||||
|
|
@ -63,6 +64,7 @@ class DivPlatformYM2610B: public DivDispatch {
|
|||
furnacePCM(false),
|
||||
vol(0),
|
||||
outVol(15),
|
||||
sample(-1),
|
||||
pan(3) {}
|
||||
};
|
||||
Channel chan[16];
|
||||
|
|
@ -80,11 +82,6 @@ class DivPlatformYM2610B: public DivDispatch {
|
|||
unsigned char regPool[512];
|
||||
unsigned char lastBusy;
|
||||
|
||||
bool dacMode;
|
||||
int dacPeriod;
|
||||
int dacRate;
|
||||
int dacPos;
|
||||
int dacSample;
|
||||
int ayNoiseFreq;
|
||||
unsigned char sampleBank;
|
||||
|
||||
|
|
@ -101,6 +98,7 @@ class DivPlatformYM2610B: public DivDispatch {
|
|||
|
||||
int octave(int freq);
|
||||
int toFreq(int freq);
|
||||
double NOTE_OPNB(int ch, int note);
|
||||
double NOTE_ADPCMB(int note);
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue