#include "jbm.h"
static const unsigned short notetable[96] = {
0x0158, 0x016d, 0x0183, 0x019a, 0x01b2, 0x01cc, 0x01e7, 0x0204,
0x0223, 0x0244, 0x0266, 0x028b, 0x0558, 0x056d, 0x0583, 0x059a,
0x05b2, 0x05cc, 0x05e7, 0x0604, 0x0623, 0x0644, 0x0666, 0x068b,
0x0958, 0x096d, 0x0983, 0x099a, 0x09b2, 0x09cc, 0x09e7, 0x0a04,
0x0a23, 0x0a44, 0x0a66, 0x0a8b, 0x0d58, 0x0d6d, 0x0d83, 0x0d9a,
0x0db2, 0x0dcc, 0x0de7, 0x0e04, 0x0e23, 0x0e44, 0x0e66, 0x0e8b,
0x1158, 0x116d, 0x1183, 0x119a, 0x11b2, 0x11cc, 0x11e7, 0x1204,
0x1223, 0x1244, 0x1266, 0x128b, 0x1558, 0x156d, 0x1583, 0x159a,
0x15b2, 0x15cc, 0x15e7, 0x1604, 0x1623, 0x1644, 0x1666, 0x168b,
0x1958, 0x196d, 0x1983, 0x199a, 0x19b2, 0x19cc, 0x19e7, 0x1a04,
0x1a23, 0x1a44, 0x1a66, 0x1a8b, 0x1d58, 0x1d6d, 0x1d83, 0x1d9a,
0x1db2, 0x1dcc, 0x1de7, 0x1e04, 0x1e23, 0x1e44, 0x1e66, 0x1e8b
};
static const unsigned char percmx_tab[4] = { 0x14, 0x12, 0x15, 0x11 };
static const unsigned char perchn_tab[5] = { 6, 7, 8, 8, 7 };
static unsigned char percmaskoff[5] = { 0xef, 0xf7, 0xfb, 0xfd, 0xfe };
static unsigned char percmaskon[5] = { 0x10, 0x08, 0x04, 0x02, 0x01 };
static inline unsigned short GET_WORD(unsigned char *b, int x)
{
return ((unsigned short)(b[x+1] << 8) | b[x]);
}
CPlayer *CjbmPlayer::factory(Copl *newopl)
{
return new CjbmPlayer(newopl);
}
bool CjbmPlayer::load(const std::string &filename, const CFileProvider &fp)
{
binistream *f = fp.open(filename); if(!f) return false;
int filelen = fp.filesize(f);
int i;
if (!filelen || !fp.extension(filename, ".jbm")) goto loaderr;
m = new unsigned char[filelen];
if (f->readString((char *)m, filelen) != filelen) goto loaderr;
fp.close(f);
if (GET_WORD(m, 0) != 0x0002)
return false;
i = GET_WORD(m, 2);
timer = 1193810.0 / (i ? i : 0xffff);
seqtable = GET_WORD(m, 4);
instable = GET_WORD(m, 6);
flags = GET_WORD(m, 8);
inscount = (filelen - instable) >> 4;
seqcount = 0xffff;
for (i = 0; i < 11; i++) {
voice[i].trkpos = voice[i].trkstart = GET_WORD(m, 10 + (i<<1));
if (voice[i].trkpos && voice[i].trkpos < seqcount)
seqcount = voice[i].trkpos;
}
seqcount = (seqcount - seqtable) >> 1;
sequences = new unsigned short[seqcount];
for (i = 0; i < seqcount; i++)
sequences[i] = GET_WORD(m, seqtable + (i<<1));
rewind(0);
return true;
loaderr:
fp.close(f);
return false;
}
bool CjbmPlayer::update()
{
short c, spos, frq;
for (c = 0; c < 11; c++) {
if (!voice[c].trkpos) continue;
if (--voice[c].delay)
continue;
if (voice[c].note&0x7f)
opl_noteonoff(c, &voice[c], 0);
spos = voice[c].seqpos;
while(!voice[c].delay) {
switch(m[spos]) {
case 0xFD: voice[c].instr = m[spos+1];
set_opl_instrument(c, &voice[c]);
spos+=2;
break;
case 0xFF: voice[c].seqno = m[++voice[c].trkpos];
if (voice[c].seqno == 0xff) {
voice[c].trkpos = voice[c].trkstart;
voice[c].seqno = m[voice[c].trkpos];
voicemask &= ~(1<<c);
}
spos = voice[c].seqpos = sequences[voice[c].seqno];
break;
default: if ((m[spos] & 127) > 95)
return 0;
voice[c].note = m[spos];
voice[c].vol = m[spos+1];
voice[c].delay =
(m[spos+2] + (m[spos+3]<<8)) + 1;
frq = notetable[voice[c].note&127];
voice[c].frq[0] = (unsigned char)frq;
voice[c].frq[1] = frq >> 8;
spos+=4;
}
}
voice[c].seqpos = spos;
if (flags&1 && c > 6)
opl->write(0x40 + percmx_tab[c-7], voice[c].vol ^ 0x3f);
else
opl->write(0x43 + op_table[c], voice[c].vol ^ 0x3f);
opl_noteonoff(c, &voice[c], !(voice[c].note & 0x80));
}
return (voicemask);
}
void CjbmPlayer::rewind(int subsong)
{
int c;
voicemask = 0;
for (c = 0; c < 11; c++) {
voice[c].trkpos = voice[c].trkstart;
if (!voice[c].trkpos) continue;
voicemask |= (1<<c);
voice[c].seqno = m[voice[c].trkpos];
voice[c].seqpos = sequences[voice[c].seqno];
voice[c].note = 0;
voice[c].delay = 1;
}
opl->init();
opl->write(0x01, 32);
bdreg = 0xC0 | (flags&1)<<5;
opl->write(0xbd, bdreg);
#if 0#endif
return;
}
void CjbmPlayer::opl_noteonoff(int channel, JBMVoice *v, bool state)
{
if (flags&1 && channel > 5) {
opl->write(0xa0 + perchn_tab[channel-6], voice[channel].frq[0]);
opl->write(0xb0 + perchn_tab[channel-6], voice[channel].frq[1]);
opl->write(0xbd,
state ? bdreg | percmaskon[channel-6] :
bdreg & percmaskoff[channel-6]);
} else {
opl->write(0xa0 + channel, voice[channel].frq[0]);
opl->write(0xb0 + channel,
state ? voice[channel].frq[1] | 0x20 :
voice[channel].frq[1] & 0x1f);
}
return;
}
void CjbmPlayer::set_opl_instrument(int channel, JBMVoice *v)
{
short i = instable + (v->instr << 4);
if (v->instr >= inscount)
return;
if ((flags&1) & (channel > 6)) {
opl->write(0x20 + percmx_tab[channel-7], m[i+0]);
opl->write(0x40 + percmx_tab[channel-7], m[i+1] ^ 0x3f);
opl->write(0x60 + percmx_tab[channel-7], m[i+2]);
opl->write(0x80 + percmx_tab[channel-7], m[i+3]);
opl->write(0xc0 + perchn_tab[channel-6], m[i+8]&15);
return;
}
opl->write(0x20 + op_table[channel], m[i+0]);
opl->write(0x40 + op_table[channel], m[i+1] ^ 0x3f);
opl->write(0x60 + op_table[channel], m[i+2]);
opl->write(0x80 + op_table[channel], m[i+3]);
opl->write(0x23 + op_table[channel], m[i+4]);
opl->write(0x43 + op_table[channel], m[i+5] ^ 0x3f);
opl->write(0x63 + op_table[channel], m[i+6]);
opl->write(0x83 + op_table[channel], m[i+7]);
opl->write(0xe0 + op_table[channel], (m[i+8]>>4)&3);
opl->write(0xe3 + op_table[channel], (m[i+8]>>6)&3);
opl->write(0xc0 + channel, m[i+8]&15);
return;
}