#include <string.h>
#include "mdi.h"
CPlayer *CmdiPlayer::factory(Copl *newopl)
{
return new CmdiPlayer(newopl);
}
bool CmdiPlayer::load(const std::string &filename, const CFileProvider &fp)
{
binistream *f = fp.open(filename); if(!f) return false;
if (!fp.extension(filename, ".mdi"))
{
fp.close(f);
return false;
}
if (fp.filesize(f) < MIDI_MIN_SIZE)
{
fp.close(f);
return false;
}
char chunk[MIDI_CHUNK_SIZE + 1];
chunk[MIDI_CHUNK_SIZE] = 0;
f->readString(chunk, MIDI_CHUNK_SIZE);
if (strcmp(chunk, "MThd"))
{
fp.close(f);
return false;
}
f->setFlag(binio::BigEndian, true);
if (f->readInt(4) != MIDI_HEAD_SIZE || f->readInt(2) != 0 || f->readInt(2) != 1) {
fp.close(f);
return false;
}
division = f->readInt(2);
f->readString(chunk, MIDI_CHUNK_SIZE);
if (strcmp(chunk, "MTrk"))
{
fp.close(f);
return false;
}
size = f->readInt(4);
if (fp.filesize(f) < MIDI_MIN_SIZE + size)
{
fp.close(f);
return false;
}
data = new uint8_t[size];
f->readString((char *)data, size);
fp.close(f);
drv = new CadlibDriver(opl);
rewind(0);
return true;
}
void CmdiPlayer::rewind(int subsong)
{
SetTempo(MIDI_DEF_TEMPO);
pos = 0; songend = false;
for (int i = 0; i < MAX_VOICES; i++)
volume[i] = 0;
counter = 0;
ticks = 0;
opl->init();
if (drv) drv->SoundWarmInit();
}
void CmdiPlayer::SetTempo(uint32_t tempo)
{
if (!tempo) tempo = MIDI_DEF_TEMPO;
timer = division * 1000000 / (float)tempo;
}
uint32_t CmdiPlayer::GetVarVal()
{
uint32_t result = 0;
do
{
result <<= 7;
result |= data[pos] & 0x7F;
} while (data[pos++] & 0x80 && pos < size);
return result;
}
void CmdiPlayer::executeCommand()
{
uint32_t len, tempo;
uint8_t new_status = 0, meta, voice, note, vol;
uint16_t code, pitch;
if (data[pos] < 0x80)
{
new_status = status;
}
else
new_status = data[pos++];
if (new_status == STOP_FC)
{
pos = size;
}
else if (new_status == SYSEX_F0 || new_status == SYSEX_F7)
{
len = GetVarVal();
pos += len;
}
else if (new_status == META)
{
meta = data[pos++];
len = GetVarVal();
switch (meta)
{
case END_OF_TRACK:
pos = size - len; break;
case TEMPO:
if (len >= 3)
{
tempo = data[pos] << 16 | data[pos + 1] << 8 | data[pos + 2];
SetTempo(tempo);
}
break;
case SEQ_SPECIFIC:
if (len >= META_MIN_SIZE)
{
if (data[pos] == 0 &&
data[pos + 1] == 0 &&
data[pos + 2] == 0x3f)
{
code = data[pos + 3] << 8 | data[pos + 4];
if (code == ADLIB_TIMBRE && len >= META_MIN_SIZE + ADLIB_INST_LEN)
{
voice = data[pos + 5];
int16_t params[ADLIB_INST_LEN];
for (int n = 0; n < ADLIB_INST_LEN; n++)
params[n] = (int8_t)data[pos + META_MIN_SIZE + n];
if (drv) drv->SetVoiceTimbre(voice, ¶ms[0]);
}
else if (code == ADLIB_RHYTHM) {
if (drv) drv->SetMode((int)data[pos + 5]);
}
else if (code == ADLIB_PITCH) {
if (drv) drv->SetPitchRange((int)data[pos + 5]);
}
}
}
break;
}
pos += len;
}
else
{
status = new_status;
voice = status & 0xF;
switch (status & 0xF0)
{
case NOTE_OFF:
pos += 2;
if (voice > MAX_VOICES - 1)
break;
if (drv) drv->NoteOff(voice);
break;
case NOTE_ON:
note = data[pos++];
vol = data[pos++];
if (voice > MAX_VOICES - 1)
break;
if (!vol)
{
if (drv) drv->NoteOff(voice);
volume[voice] = vol;
}
else
{
if (vol != volume[voice])
{
if (drv) drv->SetVoiceVolume(voice, vol);
volume[voice] = vol;
}
if (drv) drv->NoteOn(voice, note);
}
break;
case AFTER_TOUCH:
pos++; vol = data[pos++];
if (voice > MAX_VOICES - 1)
break;
if (vol != volume[voice])
{
if (drv) drv->SetVoiceVolume(voice, vol);
volume[voice] = vol;
}
break;
case CONTROL_CHANGE:
pos += 2;
break;
case PROG_CHANGE:
pos += 1;
break;
case CHANNEL_PRESSURE:
vol = data[pos++];
if (voice > MAX_VOICES - 1)
break;
if (vol != volume[voice])
{
if (drv) drv->SetVoiceVolume(voice, vol);
volume[voice] = vol;
}
break;
case PITCH_BEND:
pitch = data[pos++];
pitch |= data[pos++] << 7;
if (voice > MAX_VOICES - 1)
break;
if (drv) drv->SetVoicePitch(voice, pitch);
break;
default:
while (data[pos++] < NOTE_OFF && pos < size);
if (pos >= size)
break;
break;
}
}
}
bool CmdiPlayer::update()
{
if (!counter)
{
ticks = GetVarVal();
}
if (++counter >= ticks)
{
counter = 0;
while (pos < size)
{
executeCommand();
if (pos >= size) {
pos = 0;
songend = true;
break;
}
else if (!data[pos]) {
pos++;
}
else break;
}
}
return !songend;
}