// This file is taken from the openMSX project.
// The file has been modified to be built in the blueMSX environment.
// $Id: OpenMsxYMF278.cpp,v 1.3 2005/09/24 00:09:50 dvik Exp $
#include "../std.h"
#include "ymf278.h"
#include <cmath>
const int EG_SH = 16; // 16.16 fixed point (EG timing)
const unsigned int EG_TIMER_OVERFLOW = 1 << EG_SH;
// envelope output entries
const int ENV_BITS = 10;
const int ENV_LEN = 1 << ENV_BITS;
const double ENV_STEP = 128.0 / ENV_LEN;
const int MAX_ATT_INDEX = (1 << (ENV_BITS - 1)) - 1; //511
const int MIN_ATT_INDEX = 0;
// Envelope Generator phases
const int EG_ATT = 4;
const int EG_DEC = 3;
const int EG_SUS = 2;
const int EG_REL = 1;
const int EG_OFF = 0;
const int EG_REV = 5; //pseudo reverb
const int EG_DMP = 6; //damp
// Pan values, units are -3dB, i.e. 8.
const int pan_left[16] = {
0, 8, 16, 24, 32, 40, 48, 256, 256, 0, 0, 0, 0, 0, 0, 0
};
const int pan_right[16] = {
0, 0, 0, 0, 0, 0, 0, 0, 256, 256, 48, 40, 32, 24, 16, 8
};
// Mixing levels, units are -3dB, and add some marging to avoid clipping
const int mix_level[8] = {
8, 16, 24, 32, 40, 48, 56, 256
};
// decay level table (3dB per step)
// 0 - 15: 0, 3, 6, 9,12,15,18,21,24,27,30,33,36,39,42,93 (dB)
#define SC(db) (unsigned int)(db * (2.0 / ENV_STEP))
const unsigned int dl_tab[16] = {
SC( 0), SC( 1), SC( 2), SC(3 ), SC(4 ), SC(5 ), SC(6 ), SC( 7),
SC( 8), SC( 9), SC(10), SC(11), SC(12), SC(13), SC(14), SC(31)
};
#undef SC
const u8 RATE_STEPS = 8;
const u8 eg_inc[15 * RATE_STEPS] = {
//cycle:0 1 2 3 4 5 6 7
0, 1, 0, 1, 0, 1, 0, 1, // 0 rates 00..12 0 (increment by 0 or 1)
0, 1, 0, 1, 1, 1, 0, 1, // 1 rates 00..12 1
0, 1, 1, 1, 0, 1, 1, 1, // 2 rates 00..12 2
0, 1, 1, 1, 1, 1, 1, 1, // 3 rates 00..12 3
1, 1, 1, 1, 1, 1, 1, 1, // 4 rate 13 0 (increment by 1)
1, 1, 1, 2, 1, 1, 1, 2, // 5 rate 13 1
1, 2, 1, 2, 1, 2, 1, 2, // 6 rate 13 2
1, 2, 2, 2, 1, 2, 2, 2, // 7 rate 13 3
2, 2, 2, 2, 2, 2, 2, 2, // 8 rate 14 0 (increment by 2)
2, 2, 2, 4, 2, 2, 2, 4, // 9 rate 14 1
2, 4, 2, 4, 2, 4, 2, 4, // 10 rate 14 2
2, 4, 4, 4, 2, 4, 4, 4, // 11 rate 14 3
4, 4, 4, 4, 4, 4, 4, 4, // 12 rates 15 0, 15 1, 15 2, 15 3 for decay
8, 8, 8, 8, 8, 8, 8, 8, // 13 rates 15 0, 15 1, 15 2, 15 3 for attack (zero time)
0, 0, 0, 0, 0, 0, 0, 0, // 14 infinity rates for attack and decay(s)
};
#define O(a) (a * RATE_STEPS)
const u8 eg_rate_select[64] = {
O( 0),O( 1),O( 2),O( 3),
O( 0),O( 1),O( 2),O( 3),
O( 0),O( 1),O( 2),O( 3),
O( 0),O( 1),O( 2),O( 3),
O( 0),O( 1),O( 2),O( 3),
O( 0),O( 1),O( 2),O( 3),
O( 0),O( 1),O( 2),O( 3),
O( 0),O( 1),O( 2),O( 3),
O( 0),O( 1),O( 2),O( 3),
O( 0),O( 1),O( 2),O( 3),
O( 0),O( 1),O( 2),O( 3),
O( 0),O( 1),O( 2),O( 3),
O( 0),O( 1),O( 2),O( 3),
O( 4),O( 5),O( 6),O( 7),
O( 8),O( 9),O(10),O(11),
O(12),O(12),O(12),O(12),
};
#undef O
//rate 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
//shift 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0
//mask 4095, 2047, 1023, 511, 255, 127, 63, 31, 15, 7, 3, 1, 0, 0, 0, 0
#define O(a) (a)
const u8 eg_rate_shift[64] = {
O(12),O(12),O(12),O(12),
O(11),O(11),O(11),O(11),
O(10),O(10),O(10),O(10),
O( 9),O( 9),O( 9),O( 9),
O( 8),O( 8),O( 8),O( 8),
O( 7),O( 7),O( 7),O( 7),
O( 6),O( 6),O( 6),O( 6),
O( 5),O( 5),O( 5),O( 5),
O( 4),O( 4),O( 4),O( 4),
O( 3),O( 3),O( 3),O( 3),
O( 2),O( 2),O( 2),O( 2),
O( 1),O( 1),O( 1),O( 1),
O( 0),O( 0),O( 0),O( 0),
O( 0),O( 0),O( 0),O( 0),
O( 0),O( 0),O( 0),O( 0),
O( 0),O( 0),O( 0),O( 0),
};
#undef O
//number of steps to take in quarter of lfo frequency
//TODO check if frequency matches real chip
#define O(a) ((int)((EG_TIMER_OVERFLOW / a) / 6))
const int lfo_period[8] = {
O(0.168), O(2.019), O(3.196), O(4.206),
O(5.215), O(5.888), O(6.224), O(7.066)
};
#undef O
#define O(a) ((int)(a * 65536))
const int vib_depth[8] = {
O(0), O(3.378), O(5.065), O(6.750),
O(10.114), O(20.170), O(40.106), O(79.307)
};
#undef O
#define SC(db) (unsigned int) (db * (2.0 / ENV_STEP))
const int am_depth[8] = {
SC(0), SC(1.781), SC(2.906), SC(3.656),
SC(4.406), SC(5.906), SC(7.406), SC(11.91)
};
#undef SC
YMF278Slot::YMF278Slot()
{
reset();
}
void YMF278Slot::reset()
{
wave = FN = OCT = PRVB = LD = TL = pan = lfo = vib = AM = 0;
AR = D1R = DL = D2R = RC = RR = 0;
step = stepptr = 0;
bits = startaddr = loopaddr = endaddr = 0;
env_vol = MAX_ATT_INDEX;
//env_vol_step = env_vol_lim = 0;
lfo_active = false;
lfo_cnt = lfo_step = 0;
lfo_max = lfo_period[0];
state = EG_OFF;
active = false;
}
int YMF278Slot::compute_rate(int val)
{
if (val == 0) {
return 0;
} else if (val == 15) {
return 63;
}
int res;
if (RC != 15) {
int oct = OCT;
if (oct & 8) {
oct |= -8;
}
res = (oct + RC) * 2 + (FN & 0x200 ? 1 : 0) + val * 4;
} else {
res = val * 4;
}
if (res < 0) {
res = 0;
} else if (res > 63) {
res = 63;
}
return res;
}
int YMF278Slot::compute_vib()
{
return (((lfo_step << 8) / lfo_max) * vib_depth[(int)vib]) >> 24;
}
int YMF278Slot::compute_am()
{
if (lfo_active && AM) {
return (((lfo_step << 8) / lfo_max) * am_depth[(int)AM]) >> 12;
} else {
return 0;
}
}
void YMF278Slot::set_lfo(int newlfo)
{
lfo_step = (((lfo_step << 8) / lfo_max) * newlfo) >> 8;
lfo_cnt = (((lfo_cnt << 8) / lfo_max) * newlfo) >> 8;
lfo = newlfo;
lfo_max = lfo_period[(int)lfo];
}
void YMF278::advance()
{
eg_timer += eg_timer_add;
if (eg_timer > 4 * EG_TIMER_OVERFLOW) {
eg_timer = EG_TIMER_OVERFLOW;
}
while (eg_timer >= EG_TIMER_OVERFLOW) {
eg_timer -= EG_TIMER_OVERFLOW;
eg_cnt++;
for (int i = 0; i < 24; i++) {
YMF278Slot &op = slots[i];
if (op.lfo_active) {
op.lfo_cnt++;
if (op.lfo_cnt < op.lfo_max) {
op.lfo_step++;
} else if (op.lfo_cnt < (op.lfo_max * 3)) {
op.lfo_step--;
} else {
op.lfo_step++;
if (op.lfo_cnt == (op.lfo_max * 4)) {
op.lfo_cnt = 0;
}
}
}
// Envelope Generator
switch(op.state) {
case EG_ATT: { // attack phase
u8 rate = op.compute_rate(op.AR);
if (rate < 4) {
break;
}
u8 shift = eg_rate_shift[rate];
if (!(eg_cnt & ((1 << shift) -1))) {
u8 select = eg_rate_select[rate];
op.env_vol += (~op.env_vol * eg_inc[select + ((eg_cnt >> shift) & 7)]) >> 3;
if (op.env_vol <= MIN_ATT_INDEX) {
op.env_vol = MIN_ATT_INDEX;
if (op.DL == 0) {
op.state = EG_SUS;
}
else {
op.state = EG_DEC;
}
}
}
break;
}
case EG_DEC: { // decay phase
u8 rate = op.compute_rate(op.D1R);
if (rate < 4) {
break;
}
u8 shift = eg_rate_shift[rate];
if (!(eg_cnt & ((1 << shift) -1))) {
u8 select = eg_rate_select[rate];
op.env_vol += eg_inc[select + ((eg_cnt >> shift) & 7)];
if (((unsigned int)op.env_vol > dl_tab[6]) && op.PRVB) {
op.state = EG_REV;
} else {
if (op.env_vol >= op.DL) {
op.state = EG_SUS;
}
}
}
break;
}
case EG_SUS: { // sustain phase
u8 rate = op.compute_rate(op.D2R);
if (rate < 4) {
break;
}
u8 shift = eg_rate_shift[rate];
if (!(eg_cnt & ((1 << shift) -1))) {
u8 select = eg_rate_select[rate];
op.env_vol += eg_inc[select + ((eg_cnt >> shift) & 7)];
if (((unsigned int)op.env_vol > dl_tab[6]) && op.PRVB) {
op.state = EG_REV;
} else {
if (op.env_vol >= MAX_ATT_INDEX) {
op.env_vol = MAX_ATT_INDEX;
op.active = false;
checkMute();
}
}
}
break;
}
case EG_REL: { // release phase
u8 rate = op.compute_rate(op.RR);
if (rate < 4) {
break;
}
u8 shift = eg_rate_shift[rate];
if (!(eg_cnt & ((1 << shift) -1))) {
u8 select = eg_rate_select[rate];
op.env_vol += eg_inc[select + ((eg_cnt >> shift) & 7)];
if (((unsigned int)op.env_vol > dl_tab[6]) && op.PRVB) {
op.state = EG_REV;
} else {
if (op.env_vol >= MAX_ATT_INDEX) {
op.env_vol = MAX_ATT_INDEX;
op.active = false;
checkMute();
}
}
}
break;
}
case EG_REV: { //pseudo reverb
//TODO improve env_vol update
u8 rate = op.compute_rate(5);
//if (rate < 4) {
// break;
//}
u8 shift = eg_rate_shift[rate];
if (!(eg_cnt & ((1 << shift) - 1))) {
u8 select = eg_rate_select[rate];
op.env_vol += eg_inc[select + ((eg_cnt >> shift) & 7)];
if (op.env_vol >= MAX_ATT_INDEX) {
op.env_vol = MAX_ATT_INDEX;
op.active = false;
checkMute();
}
}
break;
}
case EG_DMP: { //damping
//TODO improve env_vol update, damp is just fastest decay now
u8 rate = 56;
u8 shift = eg_rate_shift[rate];
if (!(eg_cnt & ((1 << shift) - 1))) {
u8 select = eg_rate_select[rate];
op.env_vol += eg_inc[select + ((eg_cnt >> shift) & 7)];
if (op.env_vol >= MAX_ATT_INDEX) {
op.env_vol = MAX_ATT_INDEX;
op.active = false;
checkMute();
}
}
break;
}
case EG_OFF:
// nothing
break;
default:
break;
}
}
}
}
short YMF278::getSample(YMF278Slot &op)
{
short sample;
switch (op.bits) {
case 0: {
// 8 bit
sample = readMem(op.startaddr + op.pos) << 8;
break;
}
case 1: {
// 12 bit
int addr = op.startaddr + ((op.pos / 2) * 3);
if (op.pos & 1) {
sample = readMem(addr + 2) << 8 |
((readMem(addr + 1) << 4) & 0xF0);
} else {
sample = readMem(addr + 0) << 8 |
(readMem(addr + 1) & 0xF0);
}
break;
}
case 2: {
// 16 bit
int addr = op.startaddr + (op.pos * 2);
sample = (readMem(addr + 0) << 8) |
(readMem(addr + 1));
break;
}
default:
// TODO unspecified
sample = 0;
}
return sample;
}
void YMF278::checkMute()
{
setInternalMute(!anyActive());
}
bool YMF278::anyActive()
{
for (int i = 0; i < 24; i++) {
if (slots[i].active) {
return true;
}
}
return false;
}
int* YMF278::updateBuffer(int length)
{
if (isInternalMuted()) {
return NULL;
}
int vl = mix_level[pcm_l];
int vr = mix_level[pcm_r];
int *buf = buffer;
while (length--) {
int left = 0;
int right = 0;
int cnt = oplOversampling;
while (cnt--) {
for (int i = 0; i < 24; i++) {
YMF278Slot &sl = slots[i];
if (!sl.active) {
continue;
}
short sample = (sl.sample1 * (0x10000 - sl.stepptr) +
sl.sample2 * sl.stepptr) >> 16;
int vol = sl.TL + (sl.env_vol >> 2) + sl.compute_am();
int volLeft = vol + pan_left [(int)sl.pan] + vl;
int volRight = vol + pan_right[(int)sl.pan] + vr;
// TODO prob doesn't happen in real chip
if (volLeft < 0) {
volLeft = 0;
}
if (volRight < 0) {
volRight = 0;
}
left += (sample * volume[volLeft] ) >> 10;
right += (sample * volume[volRight]) >> 10;
if (sl.lfo_active && sl.vib) {
int oct = sl.OCT;
if (oct & 8) {
oct |= -8;
}
oct += 5;
sl.stepptr += (oct >= 0 ? ((sl.FN | 1024) + sl.compute_vib()) << oct
: ((sl.FN | 1024) + sl.compute_vib()) >> -oct) / oplOversampling;
} else {
sl.stepptr += sl.step / oplOversampling;
}
int count = (sl.stepptr >> 16) & 0x0f;
sl.stepptr &= 0xffff;
while (count--) {
sl.sample1 = sl.sample2;
sl.pos++;
if (sl.pos >= sl.endaddr) {
sl.pos = sl.loopaddr;
}
sl.sample2 = getSample(sl);
}
}
advance();
}
*buf++ = left / oplOversampling;
*buf++ = right / oplOversampling;
}
return buffer;
}
void YMF278::keyOnHelper(YMF278Slot& slot)
{
slot.active = true;
setInternalMute(false);
int oct = slot.OCT;
if (oct & 8) {
oct |= -8;
}
oct += 5;
slot.step = oct >= 0 ? (slot.FN | 1024) << oct : (slot.FN | 1024) >> -oct;
slot.state = EG_ATT;
slot.stepptr = 0;
slot.pos = 0;
slot.sample1 = getSample(slot);
slot.pos = 1;
slot.sample2 = getSample(slot);
}
void YMF278::writeRegOPL4(u8 reg, u8 data, const EmuTime &time)
{
BUSY_Time = time + 88 * 6 / 9;
// Handle slot registers specifically
if (reg >= 0x08 && reg <= 0xF7) {
int snum = (reg - 8) % 24;
YMF278Slot& slot = slots[snum];
switch ((reg - 8) / 24) {
case 0: {
LD_Time = time;
slot.wave = (slot.wave & 0x100) | data;
int base = (slot.wave < 384 || !wavetblhdr) ?
(slot.wave * 12) :
(wavetblhdr * 0x80000 + ((slot.wave - 384) * 12));
u8 buf[12];
for (int i = 0; i < 12; i++) {
buf[i] = readMem(base + i);
}
slot.bits = (buf[0] & 0xC0) >> 6;
slot.set_lfo((buf[7] >> 3) & 7);
slot.vib = buf[7] & 7;
slot.AR = buf[8] >> 4;
slot.D1R = buf[8] & 0xF;
slot.DL = dl_tab[buf[9] >> 4];
slot.D2R = buf[9] & 0xF;
slot.RC = buf[10] >> 4;
slot.RR = buf[10] & 0xF;
slot.AM = buf[11] & 7;
slot.startaddr = buf[2] | (buf[1] << 8) |
((buf[0] & 0x3F) << 16);
slot.loopaddr = buf[4] + (buf[3] << 8);
slot.endaddr = (((buf[6] + (buf[5] << 8)) ^ 0xFFFF) + 1);
if ((regs[reg + 4] & 0x080)) {
keyOnHelper(slot);
}
break;
}
case 1: {
slot.wave = (slot.wave & 0xFF) | ((data & 0x1) << 8);
slot.FN = (slot.FN & 0x380) | (data >> 1);
int oct = slot.OCT;
if (oct & 8) {
oct |= -8;
}
oct += 5;
slot.step = oct >= 0 ? (slot.FN | 1024) << oct : (slot.FN | 1024) >> -oct;
break;
}
case 2: {
slot.FN = (slot.FN & 0x07F) | ((data & 0x07) << 7);
slot.PRVB = ((data & 0x08) >> 3);
slot.OCT = ((data & 0xF0) >> 4);
int oct = slot.OCT;
if (oct & 8) {
oct |= -8;
}
oct += 5;
slot.step = oct >= 0 ? (slot.FN | 1024) << oct : (slot.FN | 1024) >> -oct;
break;
}
case 3:
slot.TL = data >> 1;
slot.LD = data & 0x1;
// TODO
if (slot.LD) {
// directly change volume
} else {
// interpolate volume
}
break;
case 4:
slot.pan = data & 0x0F;
if (data & 0x020) {
// LFO reset
slot.lfo_active = false;
slot.lfo_cnt = 0;
slot.lfo_max = lfo_period[(int)slot.vib];
slot.lfo_step = 0;
} else {
// LFO activate
slot.lfo_active = true;
}
switch (data >> 6) {
case 0: //tone off, no damp
if (slot.active && (slot.state != EG_REV) ) {
slot.state = EG_REL;
}
break;
case 1: //tone off, damp
slot.state = EG_DMP;
break;
case 2: //tone on, no damp
if (!(regs[reg] & 0x080)) {
keyOnHelper(slot);
}
break;
case 3: //tone on, damp
slot.state = EG_DMP;
break;
}
break;
case 5:
slot.vib = data & 0x7;
slot.set_lfo((data >> 3) & 0x7);
break;
case 6:
slot.AR = data >> 4;
slot.D1R = data & 0xF;
break;
case 7:
slot.DL = dl_tab[data >> 4];
slot.D2R = data & 0xF;
break;
case 8:
slot.RC = data >> 4;
slot.RR = data & 0xF;
break;
case 9:
slot.AM = data & 0x7;
break;
}
} else {
// All non-slot registers
switch (reg) {
case 0x00: // TEST
case 0x01:
break;
case 0x02:
wavetblhdr = (data >> 2) & 0x7;
memmode = data & 1;
break;
case 0x03:
memadr = (memadr & 0x00FFFF) | (data << 16);
break;
case 0x04:
memadr = (memadr & 0xFF00FF) | (data << 8);
break;
case 0x05:
memadr = (memadr & 0xFFFF00) | data;
break;
case 0x06: // memory data
BUSY_Time += 28 * 6 / 9;
writeMem(memadr, data);
memadr = (memadr + 1) & 0xFFFFFF;
break;
case 0xF8:
// TODO use these
fm_l = data & 0x7;
fm_r = (data >> 3) & 0x7;
break;
case 0xF9:
pcm_l = data & 0x7;
pcm_r = (data >> 3) & 0x7;
break;
}
}
regs[reg] = data;
}
u8 YMF278::peekRegOPL4(u8 reg, const EmuTime &time)
{
BUSY_Time = time;
u8 result;
switch(reg) {
case 2: // 3 upper bits are device ID
result = (regs[2] & 0x1F) | 0x20;
break;
case 6: // Memory Data Register
result = readMem(memadr);
break;
default:
result = regs[reg];
break;
}
return result;
}
u8 YMF278::readRegOPL4(u8 reg, const EmuTime &time)
{
BUSY_Time = time;
u8 result;
switch(reg) {
case 2: // 3 upper bits are device ID
result = (regs[2] & 0x1F) | 0x20;
break;
case 6: // Memory Data Register
BUSY_Time += 38 * 6 / 9;
result = readMem(memadr);
memadr = (memadr + 1) & 0xFFFFFF;
break;
default:
result = regs[reg];
break;
}
return result;
}
u8 YMF278::peekStatus(const EmuTime &time)
{
u8 result = 0;
if (time - BUSY_Time < 88 * 6 / 9) {
result |= 0x01;
}
if (time - LD_Time < 10000 * 6 / 9) {
result |= 0x02;
}
return result;
}
u8 YMF278::readStatus(const EmuTime &time)
{
u8 result = 0;
if (time - BUSY_Time < 88 * 6 / 9) {
result |= 0x01;
}
if (time - LD_Time < 10000 * 6 / 9) {
result |= 0x02;
}
return result;
}
YMF278::YMF278(short volume, int ramSize, int romSize,
const EmuTime &time)
{
LD_Time = 0;
BUSY_Time = 0;
memadr = 0; // avoid UMR
endRom = romSize;
ramSize *= 1024; // in kb
this->ramSize = ramSize;
// rom = new u8[romSize];
// ram = new u8[4096 * 1024];
rom = (u8 *)malloc(romSize);
ram = (u8 *)malloc(4096*1024); // NULL-checking is elsewhere
oplOversampling = 1;
endRam = endRom + ramSize;
reset(time);
}
YMF278::~YMF278()
{
// delete[] ram;
// delete[] rom;
if(ram)
free(ram);
if(rom)
free(rom);
}
void YMF278::reset(const EmuTime &time)
{
eg_timer = 0;
eg_cnt = 0;
int i;
for (i = 0; i < 24; i++) {
slots[i].reset();
}
for (i = 255; i >= 0; i--) { // reverse order to avoid UMR
writeRegOPL4(i, 0, time);
}
setInternalMute(true);
wavetblhdr = memmode = memadr = 0;
fm_l = fm_r = pcm_l = pcm_r = 0;
BUSY_Time = time;
LD_Time = time;
}
void YMF278::setSampleRate(int sampleRate, int Oversampling)
{
oplOversampling = Oversampling;
eg_timer_add = (unsigned int)((1 << EG_SH) / oplOversampling);
}
void YMF278::setInternalVolume(short newVolume)
{
newVolume /= 32;
// Volume table, 1 = -0.375dB, 8 = -3dB, 256 = -96dB
int i;
for (i = 0; i < 256; i++) {
volume[i] = (int)(4.0 * (double)newVolume * pow(2.0, (-0.375 / 6) * i));
}
for (i = 256; i < 256 * 4; i++) {
volume[i] = 0;
}
}
u8 YMF278::readMem(unsigned int address)
{
if (rom && address < endRom) {
return rom[address];
} else if (ram && address < endRam) {
return ram[address - endRom];
} else {
return 255; // TODO check
}
}
void YMF278::writeMem(unsigned int address, u8 value)
{
if (address < endRom) {
// can't write to ROM
} else if (ram && address < endRam) {
ram[address - endRom] = value;
} else {
// can't write to unmapped memory
}
}