#include <string.h>
#include <stddef.h>
#include "cmfmcsop.h"
#include "debug.h"
CPlayer* CcmfmacsoperaPlayer::factory(Copl* newopl)
{
return new CcmfmacsoperaPlayer(newopl);
}
static const int16_t fNumbers[] = { 0x157, 0x16b, 0x181, 0x198, 0x1b0, 0x1ca, 0x1e5, 0x202, 0x221, 0x242, 0x264, 0x288 };
static const int8_t slotRegisterOffsets[] = {
0, 1, 2, 3, 4, 5,
8, 9, 10, 11, 12, 13,
16, 17, 18, 19, 20, 21
};
static const struct {
int8_t slotOp1;
int8_t slotOp2;
}
channelSlots[] = { {0, 3}, {1, 4}, {2, 5}, {6, 9}, {7, 10}, {8, 11}, {12, 15}, {13, 16}, {14, 17}};
static const int8_t channelSlotsRhythm[] = { -1, -1, -1, -1, -1, -1, 12, 16, 14, 17, 13};
enum {
CHANNEL_RHY_BD = 6,
CHANNEL_RHY_SN = 7,
CHANNEL_RHY_TOM = 8,
CHANNEL_RHY_TC = 9,
CHANNEL_RHY_HH = 10
};
CcmfmacsoperaPlayer::CcmfmacsoperaPlayer(Copl* newopl): CPlayer(newopl)
{
}
std::string CcmfmacsoperaPlayer::gettype() { return std::string("SoundFX Macs Opera CMF"); }
class binistream_closer {
public:
binistream_closer(const CFileProvider& fp, binistream* strm): fp(fp), strm(strm) {}
~binistream_closer() { if(strm) fp.close(strm); }
const CFileProvider& fp;
binistream* strm;
};
bool CcmfmacsoperaPlayer::load(const std::string& filename, const CFileProvider& fp)
{
if (!fp.extension(filename, ".cmf"))
return false;
binistream* f = fp.open(filename);
if (!f)
return false;
binistream_closer _closer(fp, f);
std::string signature = f->readString();
if (signature != "A.H.")
return false;
nrOfOrders = -1;
for (int i = 0; i < 99; ++i) {
patternOrder[i] = f->readInt(2);
if (patternOrder[i] == 99 && nrOfOrders < 0)
nrOfOrders = i;
}
if (nrOfOrders == -1) nrOfOrders = 99;
nrOfPatterns = f->readInt(2);
int speed = f->readInt(2);
if (speed < 1 || speed > 3)
return false;
speedRowsPerSec = 18.2f / (1 << (speed - 1));
rhythmMode = f->readInt(2) == 1;
int nrOfInstruments = f->readInt(2);
if (!loadInstruments(f, nrOfInstruments))
return false;
if (!loadPatterns(f))
return false;
rewind(0);
return true;
}
bool CcmfmacsoperaPlayer::loadInstruments(binistream* f, int nrOfInstruments)
{
if (nrOfInstruments > 0xff)
return false;
instruments.resize(nrOfInstruments);
static const intptr_t loadOffsets[] = {
offsetof(Instrument, op[0].ksl),
offsetof(Instrument, op[0].multiple),
offsetof(Instrument, feedback),
offsetof(Instrument, op[0].attackRate),
offsetof(Instrument, op[0].sustainLevel),
offsetof(Instrument, op[0].egType),
offsetof(Instrument, op[0].decayRate),
offsetof(Instrument, op[0].releaseRate),
offsetof(Instrument, op[0].totalLevel),
offsetof(Instrument, op[0].ampMod),
offsetof(Instrument, op[0].vib),
offsetof(Instrument, op[0].ksr),
offsetof(Instrument, connection),
offsetof(Instrument, op[1].ksl),
offsetof(Instrument, op[1].multiple),
-1,
offsetof(Instrument, op[1].attackRate),
offsetof(Instrument, op[1].sustainLevel),
offsetof(Instrument, op[1].egType),
offsetof(Instrument, op[1].decayRate),
offsetof(Instrument, op[1].releaseRate),
offsetof(Instrument, op[1].totalLevel),
offsetof(Instrument, op[1].ampMod),
offsetof(Instrument, op[1].vib),
offsetof(Instrument, op[1].ksr),
-1,
offsetof(Instrument, op[0].waveSelect),
offsetof(Instrument, op[1].waveSelect)
};
for (int i = 0; i < nrOfInstruments; ++i) {
for (int j = 0; j < sizeof(loadOffsets)/sizeof(loadOffsets[0]); ++j) {
int v = f->readInt(2);
if (loadOffsets[j] >= 0 ) {
*(int16_t*)((char*)&instruments[i] + loadOffsets[j]) = v;
}
}
f->readString(instruments[i].name, 13);
instruments[i].name[13] = 0;
}
return !f->ateof();
}
bool CcmfmacsoperaPlayer::loadPatterns(binistream* f)
{
if (nrOfPatterns > 0xff)
return false;
patterns.resize(nrOfPatterns);
for (int i = 0; i < nrOfPatterns; ++i) {
while (!f->eof()) {
NoteEvent n;
n.row = f->readInt(1);
if (n.row == 0xff)
break;
uint8_t* d = &n.col;
for (int j = 0; j < 5; ++j)
*d++ = f->readInt(1);
n.instrument--;
patterns[i].push_back(n);
}
}
return true;
}
bool CcmfmacsoperaPlayer::isValidChannel(int channelNr) const
{
return channelNr >= 0 && ((rhythmMode && channelNr < 11) || (!rhythmMode && channelNr < 9));
}
bool CcmfmacsoperaPlayer::isRhythmChannel(int channelNr) const
{
return rhythmMode && channelNr >= 6;
}
static inline int getSlotRegister(int base, int slotNr)
{
return base + slotRegisterOffsets[slotNr];
}
void CcmfmacsoperaPlayer::setSlot(int slotNr, const SlotSettings& settings)
{
opl->write(getSlotRegister(0x20, slotNr), (settings.multiple & 0xf) | ((settings.ksr & 1) << 4) | ((settings.egType & 1) << 5) | ((settings.vib & 1) << 6) | ((settings.ampMod & 1) << 7));
opl->write(getSlotRegister(0x60, slotNr), ((settings.attackRate & 0xf) << 4) | (settings.decayRate & 0xf));
opl->write(getSlotRegister(0x80, slotNr), ((settings.sustainLevel & 0xf) << 4) | (settings.releaseRate & 0xf));
opl->write(getSlotRegister(0xe0, slotNr), settings.waveSelect & 3);
}
bool CcmfmacsoperaPlayer::setInstrument(int channelNr, const Instrument& inst)
{
if (!isValidChannel(channelNr))
return false;
if (channelCurrentInstrument[channelNr] != &inst) {
if (!isRhythmChannel(channelNr) || channelNr == CHANNEL_RHY_BD) {
opl->write(0xc0 + channelNr, ((inst.feedback & 7) << 1) | (1 - (inst.connection & 1)));
setSlot(channelSlots[channelNr].slotOp1, inst.op[0]);
setSlot(channelSlots[channelNr].slotOp2, inst.op[1]);
}
else {
setSlot(channelSlotsRhythm[channelNr], inst.op[0]);
}
channelCurrentInstrument[channelNr] = &inst;
}
return true;
}
static int calculateAttenuation(int attenuation, int volumeColumn)
{
if (attenuation < 0 ) attenuation = 0;
if (attenuation > 63 ) attenuation = 63;
if (volumeColumn < 0 ) volumeColumn = 0;
if (volumeColumn > 127) volumeColumn = 127;
return attenuation + ((63 - attenuation) * (127 - volumeColumn)) / 127;
}
void CcmfmacsoperaPlayer::keyOn(int channelNr)
{
if (!isValidChannel(channelNr))
return;
if (!isRhythmChannel(channelNr)) {
current0xBx[channelNr] |= (1 << 5);
opl->write(0xb0 + channelNr, current0xBx[channelNr]);
}
else {
current0xBD |= 1 << (10 - channelNr);
opl->write(0xbd, current0xBD);
}
}
void CcmfmacsoperaPlayer::keyOff(int channelNr)
{
if (!isValidChannel(channelNr))
return;
if (!isRhythmChannel(channelNr)) {
current0xBx[channelNr] &= ~(1 << 5);
opl->write(0xb0 + channelNr, current0xBx[channelNr]);
}
else {
current0xBD &= ~(1 << (10 - channelNr));
opl->write(0xbd, current0xBD);
}
}
void CcmfmacsoperaPlayer::setAxBx(int channelNr, int Ax, int Bx)
{
if (channelNr < 0 || channelNr >= 8)
return;
opl->write(0xa0 + channelNr, Ax);
current0xBx[channelNr] = Bx;
opl->write(0xb0 + channelNr, current0xBx[channelNr]);
}
bool CcmfmacsoperaPlayer::setNote(int channelNr, int note)
{
if (!isValidChannel(channelNr))
return false;
if (note < 23 || note >= 120)
return false;
int fNumber = fNumbers[note % 12];
int Ax = fNumber & 0xff;
int Bx = ((fNumber >> 8) & 3) | ((note / 12 - 2) << 2);
if (!isRhythmChannel(channelNr)) {
setAxBx(channelNr, Ax, Bx);
}
else {
if (channelNr == CHANNEL_RHY_BD)
setAxBx(6, Ax, Bx);
setAxBx(7, Ax, Bx);
if (channelNr == CHANNEL_RHY_SN || channelNr == CHANNEL_RHY_TOM)
setAxBx(8, Ax, Bx);
}
return true;
}
void CcmfmacsoperaPlayer::setVolume(int channelNr, int vol)
{
if (!isValidChannel(channelNr) || !channelCurrentInstrument[channelNr])
return;
const Instrument& inst = *channelCurrentInstrument[channelNr];
if (!isRhythmChannel(channelNr) || channelNr == CHANNEL_RHY_BD) {
opl->write(
getSlotRegister(0x40, channelSlots[channelNr].slotOp1),
((inst.op[0].ksl & 3) << 6) |
(inst.connection == 0 ? calculateAttenuation(inst.op[0].totalLevel, vol) : (inst.op[0].totalLevel & 0x3f))
);
opl->write(
getSlotRegister(0x40, channelSlots[channelNr].slotOp2),
((inst.op[1].ksl & 3) << 6) | calculateAttenuation(inst.op[1].totalLevel, vol)
);
}
else {
opl->write(
getSlotRegister(0x40, channelSlotsRhythm[channelNr]),
((inst.op[1].ksl & 3) << 6) | calculateAttenuation(inst.op[0].totalLevel, vol)
);
}
}
bool CcmfmacsoperaPlayer::advanceRow()
{
for (;;) {
if (currentRow < 0 || ++currentRow >= 64) {
currentRow = 0;
currentPatternIndex = 0;
do {
currentOrderIndex++;
if (currentOrderIndex < 0 || currentOrderIndex >= sizeof(patternOrder) / sizeof(patternOrder[0]))
return false;
if (patternOrder[currentOrderIndex] == 99)
return false;
} while (patternOrder[currentOrderIndex] >= patterns.size());
AdPlug_LogWrite("order %d, pattern %d\n", currentOrderIndex, patternOrder[currentOrderIndex]);
}
const Pattern &p = patterns[patternOrder[currentOrderIndex]];
if (currentPatternIndex < p.size() && p[currentPatternIndex].row == currentRow && p[currentPatternIndex].note == 1) {
currentRow = -1;
}
else break;
}
return true;
}
void CcmfmacsoperaPlayer::processNoteEvent(const CcmfmacsoperaPlayer::NoteEvent &n)
{
int channelNr = n.col;
if (!isValidChannel(channelNr))
return;
keyOff(channelNr);
if (n.note == 4) return;
if (n.instrument >= 0 && n.instrument < instruments.size())
setInstrument(channelNr, instruments[n.instrument]);
setVolume(channelNr, n.volume);
if (setNote(channelNr, n.note))
keyOn(channelNr);
}
bool CcmfmacsoperaPlayer::update()
{
AdPlug_LogWrite( "%2d: ", currentRow);
const Pattern& p = patterns[patternOrder[currentOrderIndex]];
int currentCol = 0;
for (; currentPatternIndex < p.size() && p[currentPatternIndex].row == currentRow; ++currentPatternIndex) {
const NoteEvent& n = p[currentPatternIndex];
for (; currentCol < n.col; ++currentCol)
AdPlug_LogWrite(" ");
AdPlug_LogWrite( "%2d %2d %2x %2d ", n.note, n.instrument, n.volume, n.pitch);
currentCol++;
processNoteEvent(n);
}
AdPlug_LogWrite("\n");
bool stillPlaying = advanceRow();
if (!stillPlaying) {
resetPlayer();
songDone = true;
}
return !songDone;
}
void CcmfmacsoperaPlayer::resetPlayer()
{
currentRow = -1;
currentOrderIndex = -1;
advanceRow();
}
void CcmfmacsoperaPlayer::rewind(int subsong)
{
opl->init();
opl->write(1, 1 << 5);
current0xBD = rhythmMode ? (1 << 5) : 0;
opl->write(0xbd, current0xBD);
memset(current0xBx, 0, sizeof(current0xBx));
memset(channelCurrentInstrument, 0, sizeof(channelCurrentInstrument));
static const Instrument defaultInstrument = {
{{0, 1, 15, 5, 0, 1, 0, 0, 0, 0, 0, 0},
{0, 1, 15, 7, 0, 2, 0, 0, 0, 0, 1, 0}},
3, 0, {'D', 'e', 'f', 'a', 'u', 'l', 't', 0, 0, 0, 0, 0, 0, 0 }
};
for (int i = 0; i < 11; ++i)
setInstrument(i, defaultInstrument);
songDone = false;
resetPlayer();
}