Skip to content
Permalink
eb2a752bb2
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
1280 lines (1202 sloc) 27.7 KB
/*
* Adplug - Replayer for many OPL2/OPL3 audio file formats.
* Copyright (C) 1999 - 2008 Simon Peter <dn.tlp@gmx.net>, et al.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* herad.cpp - Herbulot AdLib Player by Stas'M <binarymaster@mail.ru>
*
* Thanks goes to co-workers:
* -=CHE@TER=- (SQX decompression)
* SynaMax (general documentation, reverse-engineering, testing)
* Jepael (timer code sample, DOS driver shell)
* opl2 (pitch slides code sample)
*
* REFERENCES:
* http://www.vgmpf.com/Wiki/index.php/HERAD
*
* TODO:
* - Fix strange AGD sound
* - Fix splash sound in Gorbi (at 0:23)
* - Fix hiss sound in NewSan (at beginning)
*/
#include <cstring>
#include <stdio.h>
#include "herad.h"
#ifdef DEBUG
#include "debug.h"
#endif
const uint8_t CheradPlayer::slot_offset[HERAD_NUM_VOICES] = {
0, 1, 2, 8, 9, 10, 16, 17, 18
};
const uint16_t CheradPlayer::FNum[HERAD_NUM_NOTES] = {
343, 364, 385, 408, 433, 459, 486, 515, 546, 579, 614, 650
};
const uint8_t CheradPlayer::fine_bend[HERAD_NUM_NOTES + 1] = {
19, 21, 21, 23, 25, 26, 27, 29, 31, 33, 35, 36, 37
};
const uint8_t CheradPlayer::coarse_bend[10] = {
0, 5, 10, 15, 20,
0, 6, 12, 18, 24
};
CPlayer *CheradPlayer::factory(Copl *newopl)
{
return new CheradPlayer(newopl);
}
std::string CheradPlayer::gettype()
{
char scomp[12 + 1] = "";
if (comp > HERAD_COMP_NONE)
sprintf(scomp, ", %s packed", (comp == HERAD_COMP_HSQ ? "HSQ" : "SQX"));
char type[40 + 1];
sprintf(type, "HERAD System %s (version %d%s)", (AGD ? "AGD" : "SDB"), (v2 ? 2 : 1), scomp);
return std::string(type);
}
bool isHSQ(uint8_t * data, int size)
{
// data[0] - word DecompSize
// data[1]
// data[2] - byte Null = 0
// data[3] - word CompSize
// data[4]
// data[5] - byte Checksum
if ( data[2] != 0 )
{
#ifdef DEBUG
AdPlug_LogWrite("HERAD: Is not HSQ, wrong check byte.\n");
#endif
return false;
}
if ( *(uint16_t *)(data + 3) != size )
{
#ifdef DEBUG
AdPlug_LogWrite("HERAD: Is not HSQ, wrong compressed size.\n");
#endif
return false;
}
uint8_t checksum = 0;
for (int i = 0; i < HERAD_MIN_SIZE; i++)
{
checksum += data[i];
}
if ( checksum != 0xAB )
{
#ifdef DEBUG
AdPlug_LogWrite("HERAD: Is not HSQ, wrong checksum.\n");
#endif
return false;
}
return true;
}
bool isSQX(uint8_t * data)
{
// data[0] - word OutbufInit
// data[1]
// data[2] - byte SQX flag #1
// data[3] - byte SQX flag #2
// data[4] - byte SQX flag #3
// data[5] - byte CntOffPart
if ( data[2] > 2 || data[3] > 2 || data[4] > 2 )
{
#ifdef DEBUG
AdPlug_LogWrite("HERAD: Is not SQX, wrong flags.\n");
#endif
return false;
}
if ( data[5] == 0 || data[5] > 15 )
{
#ifdef DEBUG
AdPlug_LogWrite("HERAD: Is not SQX, wrong bit count.\n");
#endif
return false;
}
return true;
}
uint16_t HSQ_decompress(uint8_t * data, int size, uint8_t * out)
{
uint32_t queue = 1;
int8_t bit;
int16_t offset;
uint16_t count, out_size = *(uint16_t *)data;
uint8_t * src = data;
uint8_t * dst = out;
src += 6;
while (true)
{
// get next bit of the queue
if (queue == 1)
{
queue = *(uint16_t *)src | 0x10000;
src += 2;
}
bit = queue & 1;
queue >>= 1;
// if bit is non-zero
if (bit)
{
// copy next byte of the input to the output
*dst++ = *src++;
}
else
{
// get next bit of the queue
if (queue == 1)
{
queue = *(uint16_t *)src | 0x10000;
src += 2;
}
bit = queue & 1;
queue >>= 1;
// if bit is non-zero
if (bit)
{
// count = next 3 bits of the input
// offset = next 13 bits of the input minus 8192
count = *(uint16_t *)src;
offset = (count >> 3) - 8192;
count &= 7;
src += 2;
// if count is zero
if (!count)
{
// count = next 8 bits of the input
count = *(uint8_t *)src;
src++;
}
// if count is zero
if (!count)
break; // finish the unpacking
}
else
{
// count = next bit of the queue * 2 + next bit of the queue
if (queue == 1)
{
queue = *(uint16_t *)src | 0x10000;
src += 2;
}
bit = queue & 1;
queue >>= 1;
count = bit << 1;
if (queue == 1)
{
queue = *(uint16_t *)src | 0x10000;
src += 2;
}
bit = queue & 1;
queue >>= 1;
count += bit;
// offset = next 8 bits of the input minus 256
offset = *(uint8_t *)src;
offset -= 256;
src++;
}
count += 2;
// copy count bytes at (output + offset) to the output
while (count--)
{
*dst = *(dst + offset);
dst++;
}
}
}
return out_size;
}
uint16_t SQX_decompress(uint8_t * data, int size, uint8_t * out)
{
int16_t offset;
uint16_t count;
uint8_t * src = data;
uint8_t * dst = out;
bool done = false;
*(uint16_t *)dst = *(uint16_t *)src;
src += 6;
uint16_t queue = 1;
uint8_t bit, bit_p;
while (true)
{
bit = queue & 1;
queue >>= 1;
if (queue == 0)
{
queue = *(uint16_t *)src;
src += 2;
bit_p = bit;
bit = queue & 1;
queue >>= 1;
if (bit_p)
queue |= 0x8000;
}
if (bit == 0)
{
switch (data[2])
{
case 0:
*dst++ = *src++;
break;
case 1:
count = 0;
bit = queue & 1;
queue >>= 1;
if (queue == 0)
{
queue = *(uint16_t *)src;
src += 2;
bit_p = bit;
bit = queue & 1;
queue >>= 1;
if (bit_p)
queue |= 0x8000;
count = bit;
bit = queue & 1;
queue >>= 1;
}
else
{
count = bit;
bit = queue & 1;
queue >>= 1;
if (queue == 0)
{
queue = *(uint16_t *)src;
src += 2;
bit_p = bit;
bit = queue & 1;
queue >>= 1;
if (bit_p)
queue |= 0x8000;
}
}
count = (count << 1) | bit;
offset = *(uint8_t *)src;
offset -= 256;
src++;
count += 2;
while (count--)
{
*dst = *(dst + offset);
dst++;
}
break;
case 2:
count = *(uint16_t *)src;
offset = (count >> data[5]) - (1 << (16 - data[5]));
count &= (1 << data[5]) - 1;
src += 2;
if (!count)
{
count = *(uint8_t *)src;
src++;
}
if (!count)
{
done = true;
break;
}
count += 2;
while (count--)
{
*dst = *(dst + offset);
dst++;
}
break;
}
if (done)
break;
continue;
}
else
{
bit = queue & 1;
queue >>= 1;
if (queue == 0)
{
queue = *(uint16_t *)src;
src += 2;
bit_p = bit;
bit = queue & 1;
queue >>= 1;
if (bit_p)
queue |= 0x8000;
}
if (bit == 0)
{
switch (data[3])
{
case 0:
*dst++ = *src++;
break;
case 1:
count = 0;
bit = queue & 1;
queue >>= 1;
if (queue == 0)
{
queue = *(uint16_t *)src;
src += 2;
bit_p = bit;
bit = queue & 1;
queue >>= 1;
if (bit_p)
queue |= 0x8000;
count = bit;
bit = queue & 1;
queue >>= 1;
}
else
{
count = bit;
bit = queue & 1;
queue >>= 1;
if (queue == 0)
{
queue = *(uint16_t *)src;
src += 2;
bit_p = bit;
bit = queue & 1;
queue >>= 1;
if (bit_p)
queue |= 0x8000;
}
}
count = (count << 1) | bit;
offset = *(uint8_t *)src;
offset -= 256;
src++;
count += 2;
while (count--)
{
*dst = *(dst + offset);
dst++;
}
break;
case 2:
count = *(uint16_t *)src;
offset = (count >> data[5]) - (1 << (16 - data[5]));
count &= (1 << data[5]) - 1;
src += 2;
if (!count)
{
count = *(uint8_t *)src;
src++;
}
if (!count)
{
done = true;
break;
}
count += 2;
while (count--)
{
*dst = *(dst + offset);
dst++;
}
break;
}
if (done)
break;
continue;
}
else
{
switch (data[4])
{
case 0:
*dst++ = *src++;
break;
case 1:
count = 0;
bit = queue & 1;
queue >>= 1;
if (queue == 0)
{
queue = *(uint16_t *)src;
src += 2;
bit_p = bit;
bit = queue & 1;
queue >>= 1;
if (bit_p)
queue |= 0x8000;
count = bit;
bit = queue & 1;
queue >>= 1;
}
else
{
count = bit;
bit = queue & 1;
queue >>= 1;
if (queue == 0)
{
queue = *(uint16_t *)src;
src += 2;
bit_p = bit;
bit = queue & 1;
queue >>= 1;
if (bit_p)
queue |= 0x8000;
}
}
count = (count << 1) | bit;
offset = *(uint8_t *)src;
offset -= 256;
src++;
count += 2;
while (count--)
{
*dst = *(dst + offset);
dst++;
}
break;
case 2:
count = *(uint16_t *)src;
offset = (count >> data[5]) - (1 << (16 - data[5]));
count &= (1 << data[5]) - 1;
src += 2;
if (!count)
{
count = *(uint8_t *)src;
src++;
}
if (!count)
{
done = true;
break;
}
count += 2;
while (count--)
{
*dst = *(dst + offset);
dst++;
}
break;
}
if (done)
break;
continue;
}
}
}
return dst - out;
}
bool CheradPlayer::load(const std::string &filename, const CFileProvider &fp)
{
binistream *f = fp.open(filename); if(!f) return false;
// file validation
if (!fp.extension(filename, ".hsq") &&
!fp.extension(filename, ".sqx") &&
!fp.extension(filename, ".sdb") &&
!fp.extension(filename, ".agd") &&
!fp.extension(filename, ".ha2"))
{
#ifdef DEBUG
AdPlug_LogWrite("HERAD: Unsupported file extension.\n");
#endif
fp.close(f);
return false;
}
int size = fp.filesize(f);
if (size < HERAD_MIN_SIZE)
{
#ifdef DEBUG
AdPlug_LogWrite("HERAD: File size is too small.\n");
#endif
fp.close(f);
return false;
}
if (size > HERAD_MAX_SIZE)
{
#ifdef DEBUG
AdPlug_LogWrite("HERAD: File size is too big.\n");
#endif
fp.close(f);
return false;
}
// Read entire file into memory
uint8_t * data = new uint8_t[size];
f->readString((char *)data, size);
fp.close(f);
// Detect compression
if (isHSQ(data, size))
{
comp = HERAD_COMP_HSQ;
uint8_t * out = new uint8_t[HERAD_MAX_SIZE];
memset(out, 0, HERAD_MAX_SIZE);
size = HSQ_decompress(data, size, out);
delete[] data;
data = new uint8_t[size];
memcpy(data, out, size);
delete[] out;
}
else if (isSQX(data))
{
comp = HERAD_COMP_SQX;
uint8_t * out = new uint8_t[HERAD_MAX_SIZE];
memset(out, 0, HERAD_MAX_SIZE);
size = SQX_decompress(data, size, out);
delete[] data;
data = new uint8_t[size];
memcpy(data, out, size);
delete[] out;
}
else
{
comp = HERAD_COMP_NONE;
}
// Process file header
uint16_t offset;
if (size < HERAD_HEAD_SIZE)
{
#ifdef DEBUG
AdPlug_LogWrite("HERAD: File size is too small.\n");
#endif
goto failure;
}
if ( size < *(uint16_t *)data )
{
#ifdef DEBUG
AdPlug_LogWrite("HERAD: Incorrect offset / file size.\n");
#endif
goto failure;
}
nInsts = (size - *(uint16_t *)data) / HERAD_INST_SIZE;
if ( nInsts == 0 )
{
#ifdef DEBUG
AdPlug_LogWrite("HERAD: M32 files are not supported.\n");
#endif
goto failure;
}
offset = *(uint16_t *)(data + 2);
if ( offset != 0x32 && offset != 0x52 )
{
#ifdef DEBUG
AdPlug_LogWrite("HERAD: Wrong first track offset.\n");
#endif
goto failure;
}
AGD = offset == 0x52;
wLoopStart = *(uint16_t *)(data + 0x2C);
wLoopEnd = *(uint16_t *)(data + 0x2E);
wLoopCount = *(uint16_t *)(data + 0x30);
wSpeed = *(uint16_t *)(data + 0x32);
if (wSpeed == 0)
{
#ifdef DEBUG
AdPlug_LogWrite("HERAD: Speed is not defined.\n");
#endif
goto failure;
}
nTracks = 0;
for (int i = 0; i < HERAD_MAX_TRACKS; i++)
{
if ( *(uint16_t *)(data + 2 + i * 2) == 0 )
break;
nTracks++;
}
track = new herad_trk[nTracks];
chn = new herad_chn[nTracks];
for (int i = 0; i < nTracks; i++)
{
offset = *(uint16_t *)(data + 2 + i * 2) + 2;
uint16_t next = (i < HERAD_MAX_TRACKS - 1 ? *(uint16_t *)(data + 2 + (i + 1) * 2) + 2 : *(uint16_t *)data);
if (next <= 2) next = *(uint16_t *)data;
track[i].size = next - offset;
track[i].data = new uint8_t[track[i].size];
memcpy(track[i].data, data + offset, track[i].size);
}
inst = new herad_inst[nInsts];
offset = *(uint16_t *)data;
v2 = true;
for (int i = 0; i < nInsts; i++)
{
memcpy(inst[i].data, data + offset + i * HERAD_INST_SIZE, HERAD_INST_SIZE);
if (v2 && inst[i].param.mode == HERAD_INSTMODE_SDB1)
v2 = false;
}
delete[] data;
goto good;
failure:
delete[] data;
return false;
good:
rewind(0);
return true;
}
void CheradPlayer::rewind(int subsong)
{
uint32_t j;
wTime = 0;
songend = false;
ticks_pos = -1; // there's always 1 excess tick at start
total_ticks = 0;
loop_pos = -1;
loop_times = 1;
for (int i = 0; i < nTracks; i++)
{
track[i].pos = 0;
j = 0;
while (track[i].pos < track[i].size)
{
j += GetTicks(i);
switch (track[i].data[track[i].pos++] & 0xF0)
{
case 0x80: // Note Off
track[i].pos += (v2 ? 1 : 2);
break;
case 0x90: // Note On
case 0xA0: // Unused
case 0xB0: // Unused
track[i].pos += 2;
break;
case 0xC0: // Program Change
case 0xD0: // Aftertouch
case 0xE0: // Pitch Bend
track[i].pos++;
break;
default:
track[i].pos = track[i].size;
break;
}
}
if (j > total_ticks)
total_ticks = j;
track[i].pos = 0;
track[i].counter = 0;
track[i].ticks = 0;
chn[i].program = 0;
chn[i].playprog = 0;
chn[i].note = 0;
chn[i].keyon = false;
chn[i].bend = HERAD_BEND_CENTER;
chn[i].slide_dur = 0;
}
if (v2)
{
if (!wLoopStart || wLoopCount) wLoopStart = 1; // if loop not specified, start from beginning
if (!wLoopEnd || wLoopCount) wLoopEnd = getpatterns() + 1; // till the end
if (wLoopCount) wLoopCount = 0; // repeats forever
}
opl->init();
opl->write(1, 32); // Enable Waveform Select
opl->write(0xBD, 0); // Disable Percussion Mode
opl->write(8, 64); // Enable Note-Sel
if (AGD)
{
opl->setchip(1);
opl->write(5, 1); // Enable OPL3
opl->write(4, 0); // Disable 4OP Mode
opl->setchip(0);
}
}
/*
* Get delta ticks (t - track index)
*/
uint32_t CheradPlayer::GetTicks(uint8_t t)
{
uint32_t result = 0;
do
{
result <<= 7;
result |= track[t].data[track[t].pos] & 0x7F;
} while (track[t].data[track[t].pos++] & 0x80 && track[t].pos < track[t].size);
return result;
}
/*
* Execute event (t - track index)
*/
void CheradPlayer::executeCommand(uint8_t t)
{
uint8_t status, note, par;
if (t >= nTracks)
return;
if (t >= (AGD ? HERAD_NUM_VOICES * 2 : HERAD_NUM_VOICES))
{
track[t].pos = track[t].size;
return;
}
// execute MIDI command
status = track[t].data[track[t].pos++];
if (status == 0xFF)
{
track[t].pos = track[t].size;
}
else
{
switch (status & 0xF0)
{
case 0x80: // Note Off
note = track[t].data[track[t].pos++];
par = (v2 ? 0 : track[t].data[track[t].pos++]);
ev_noteOff(t, note, par);
break;
case 0x90: // Note On
note = track[t].data[track[t].pos++];
par = track[t].data[track[t].pos++];
ev_noteOn(t, note, par);
break;
case 0xA0: // Unused
case 0xB0: // Unused
track[t].pos += 2;
break;
case 0xC0: // Program Change
par = track[t].data[track[t].pos++];
ev_programChange(t, par);
break;
case 0xD0: // Aftertouch
par = track[t].data[track[t].pos++];
ev_aftertouch(t, par);
break;
case 0xE0: // Pitch Bend
par = track[t].data[track[t].pos++];
ev_pitchBend(t, par);
break;
default:
track[t].pos = track[t].size;
break;
}
}
}
void CheradPlayer::ev_noteOn(uint8_t ch, uint8_t note, uint8_t vel)
{
int8_t macro;
if (chn[ch].keyon)
{
// turn off last active note
chn[ch].keyon = false;
playNote(ch, chn[ch].note, HERAD_NOTE_OFF);
}
if (v2 && inst[chn[ch].program].param.mode == HERAD_INSTMODE_KMAP)
{
// keymap is used
int8_t mp = note - (inst[chn[ch].program].keymap.offset + 24);
if (mp < 0 || mp >= HERAD_INST_SIZE - 4)
return; // if not in range, skip note
chn[ch].playprog = inst[chn[ch].program].keymap.index[mp];
changeProgram(ch, chn[ch].playprog);
}
chn[ch].note = note;
chn[ch].keyon = true;
chn[ch].bend = HERAD_BEND_CENTER;
if (v2 && inst[chn[ch].playprog].param.mode == HERAD_INSTMODE_KMAP)
return; // single keymapped instrument can't be keymap (avoid recursion)
playNote(ch, note, HERAD_NOTE_ON);
macro = inst[chn[ch].playprog].param.mc_mod_out_vel;
if (macro != 0)
macroModOutput(ch, chn[ch].playprog, macro, vel);
macro = inst[chn[ch].playprog].param.mc_car_out_vel;
if (macro != 0)
macroCarOutput(ch, chn[ch].playprog, macro, vel);
macro = inst[chn[ch].playprog].param.mc_fb_vel;
if (macro != 0)
macroFeedback(ch, chn[ch].playprog, macro, vel);
}
void CheradPlayer::ev_noteOff(uint8_t ch, uint8_t note, uint8_t vel)
{
if (note != chn[ch].note || !chn[ch].keyon)
return;
chn[ch].keyon = false;
playNote(ch, note, HERAD_NOTE_OFF);
}
void CheradPlayer::ev_programChange(uint8_t ch, uint8_t prog)
{
if (prog >= nInsts) // out of index
return;
chn[ch].program = prog;
chn[ch].playprog = prog;
changeProgram(ch, prog);
}
void CheradPlayer::ev_aftertouch(uint8_t ch, uint8_t vel)
{
int8_t macro;
if (v2) // version 2 ignores this event
return;
macro = inst[chn[ch].playprog].param.mc_mod_out_at;
if (macro != 0)
macroModOutput(ch, chn[ch].playprog, macro, vel);
macro = inst[chn[ch].playprog].param.mc_car_out_at;
if (macro != 0 && inst[chn[ch].playprog].param.mc_car_out_vel != 0)
macroCarOutput(ch, chn[ch].playprog, macro, vel);
macro = inst[chn[ch].playprog].param.mc_fb_at;
if (macro != 0)
macroFeedback(ch, chn[ch].playprog, macro, vel);
}
void CheradPlayer::ev_pitchBend(uint8_t ch, uint8_t bend)
{
chn[ch].bend = bend;
if (chn[ch].keyon) // update pitch
playNote(ch, chn[ch].note, HERAD_NOTE_UPDATE);
}
/*
* Play Note (c - channel, note number, note state - see HERAD_NOTE_*)
*/
void CheradPlayer::playNote(uint8_t c, uint8_t note, uint8_t state)
{
if (inst[chn[c].playprog].param.mc_transpose != 0)
macroTranspose(&note, chn[c].playprog);
note = (note - 24) & 0xFF;
if (note >= 0x60) note = 0; // clip too low/high notes
int8_t oct = note / HERAD_NUM_NOTES;
int8_t key = note % HERAD_NUM_NOTES;
if (state != HERAD_NOTE_UPDATE && inst[chn[c].playprog].param.mc_slide_dur)
{
chn[c].slide_dur = (state == HERAD_NOTE_ON ? inst[chn[c].playprog].param.mc_slide_dur : 0);
}
uint8_t bend = chn[c].bend;
int16_t amount, detune = 0;
uint8_t amount_lo, amount_hi;
if (!(inst[chn[c].playprog].param.mc_slide_coarse & 1))
{ // fine tune
if (bend - HERAD_BEND_CENTER < 0)
{ // slide down
amount = HERAD_BEND_CENTER - bend;
amount_lo = (amount >> 5);
amount_hi = (amount << 3) & 0xFF;
key -= amount_lo;
if (key < 0)
{
key += HERAD_NUM_NOTES;
oct--;
}
if (oct < 0)
{
key = 0;
oct = 0;
}
detune = -1 * ((fine_bend[key] * amount_hi) >> 8);
}
else
{ // slide up
amount = (bend - HERAD_BEND_CENTER) + 1;
amount_lo = (amount >> 5);
amount_hi = (amount << 3) & 0xFF;
key += amount_lo;
if (key >= HERAD_NUM_NOTES)
{
key -= HERAD_NUM_NOTES;
oct++;
}
detune = (fine_bend[key + 1] * amount_hi) >> 8;
}
}
else
{ // coarse tune
uint8_t offset;
if (bend - HERAD_BEND_CENTER < 0)
{ // slide down
amount = HERAD_BEND_CENTER - bend;
key -= amount / 5;
if (key < 0)
{
key += HERAD_NUM_NOTES;
oct--;
}
if (oct < 0)
{
key = 0;
oct = 0;
}
offset = (amount % 5) + (key >= 6 ? 5 : 0);
detune = -1 * coarse_bend[offset];
}
else
{ // slide up
amount = bend - HERAD_BEND_CENTER;
key += amount / 5;
if (key >= HERAD_NUM_NOTES)
{
key -= HERAD_NUM_NOTES;
oct++;
}
offset = (amount % 5) + (key >= 6 ? 5 : 0);
detune = coarse_bend[offset];
}
}
setFreq(c, oct, FNum[key] + detune, state != HERAD_NOTE_OFF);
}
/*
* Set Frequency and Key (c - channel, octave, frequency, note on)
*/
void CheradPlayer::setFreq(uint8_t c, uint8_t oct, uint16_t freq, bool on)
{
uint8_t reg, val;
if (c >= HERAD_NUM_VOICES) opl->setchip(1);
reg = 0xA0 + (c % HERAD_NUM_VOICES);
val = freq & 0xFF;
opl->write(reg, val);
reg = 0xB0 + (c % HERAD_NUM_VOICES);
val = ((freq >> 8) & 3) |
((oct & 7) << 2) |
((on ? 1 : 0) << 5);
opl->write(reg, val);
if (c >= HERAD_NUM_VOICES) opl->setchip(0);
}
/*
* Change Program (c - channel, i - instrument index)
*/
void CheradPlayer::changeProgram(uint8_t c, uint8_t i)
{
uint8_t reg, val;
if (v2 && inst[i].param.mode == HERAD_INSTMODE_KMAP)
return;
if (c >= HERAD_NUM_VOICES) opl->setchip(1);
// Amp Mod / Vibrato / EG type / Key Scaling / Multiple
reg = 0x20 + slot_offset[c % HERAD_NUM_VOICES];
val = (inst[i].param.mod_mul & 15) |
((inst[i].param.mod_ksr & 1) << 4) |
((inst[i].param.mod_eg > 0 ? 1 : 0) << 5) |
((inst[i].param.mod_vib & 1) << 6) |
((inst[i].param.mod_am & 1) << 7);
opl->write(reg, val);
reg += 3;
val = (inst[i].param.car_mul & 15) |
((inst[i].param.car_ksr & 1) << 4) |
((inst[i].param.car_eg > 0 ? 1 : 0) << 5) |
((inst[i].param.car_vib & 1) << 6) |
((inst[i].param.car_am & 1) << 7);
opl->write(reg, val);
// Key scaling level / Output level
reg = 0x40 + slot_offset[c % HERAD_NUM_VOICES];
val = (inst[i].param.mod_out & 63) |
((inst[i].param.mod_ksl & 3) << 6);
opl->write(reg, val);
reg += 3;
val = (inst[i].param.car_out & 63) |
((inst[i].param.car_ksl & 3) << 6);
opl->write(reg, val);
// Attack Rate / Decay Rate
reg = 0x60 + slot_offset[c % HERAD_NUM_VOICES];
val = (inst[i].param.mod_D & 15) |
((inst[i].param.mod_A & 15) << 4);
opl->write(reg, val);
reg += 3;
val = (inst[i].param.car_D & 15) |
((inst[i].param.car_A & 15) << 4);
opl->write(reg, val);
// Sustain Level / Release Rate
reg = 0x80 + slot_offset[c % HERAD_NUM_VOICES];
val = (inst[i].param.mod_R & 15) |
((inst[i].param.mod_S & 15) << 4);
opl->write(reg, val);
reg += 3;
val = (inst[i].param.car_R & 15) |
((inst[i].param.car_S & 15) << 4);
opl->write(reg, val);
// Panning / Feedback strength / Connection type
reg = 0xC0 + (c % HERAD_NUM_VOICES);
val = (inst[i].param.con > 0 ? 0 : 1) |
((inst[i].param.feedback & 7) << 1) |
((AGD ? (inst[i].param.pan == 0 || inst[i].param.pan > 3 ? 3 : inst[i].param.pan) : 0) << 4);
opl->write(reg, val);
// Wave Select
reg = 0xE0 + slot_offset[c % HERAD_NUM_VOICES];
val = inst[i].param.mod_wave & (AGD ? 7 : 3);
opl->write(reg, val);
reg += 3;
val = inst[i].param.car_wave & (AGD ? 7 : 3);
opl->write(reg, val);
if (c >= HERAD_NUM_VOICES) opl->setchip(0);
}
/*
* Macro: Change Modulator Output (c - channel, i - instrument index, sensitivity, level)
*/
void CheradPlayer::macroModOutput(uint8_t c, uint8_t i, int8_t sens, uint8_t level)
{
uint8_t reg, val;
uint16_t output;
if (sens < -4 || sens > 4)
return;
if (sens < 0)
{
output = (level >> (sens + 4) > 63 ? 63 : level >> (sens + 4));
}
else
{
output = ((0x80 - level) >> (4 - sens) > 63 ? 63 : (0x80 - level) >> (4 - sens));
}
output += inst[i].param.mod_out;
if (output > 63) output = 63;
if (c >= HERAD_NUM_VOICES) opl->setchip(1);
// Key scaling level / Output level
reg = 0x40 + slot_offset[c % HERAD_NUM_VOICES];
val = (output & 63) |
((inst[i].param.mod_ksl & 3) << 6);
opl->write(reg, val);
if (c >= HERAD_NUM_VOICES) opl->setchip(0);
}
/*
* Macro: Change Carrier Output (c - channel, i - instrument index, sensitivity, level)
*/
void CheradPlayer::macroCarOutput(uint8_t c, uint8_t i, int8_t sens, uint8_t level)
{
uint8_t reg, val;
uint16_t output;
if (sens < -4 || sens > 4)
return;
if (sens < 0)
{
output = (level >> (sens + 4) > 63 ? 63 : level >> (sens + 4));
}
else
{
output = ((0x80 - level) >> (4 - sens) > 63 ? 63 : (0x80 - level) >> (4 - sens));
}
output += inst[i].param.car_out;
if (output > 63) output = 63;
if (c >= HERAD_NUM_VOICES) opl->setchip(1);
// Key scaling level / Output level
reg = 0x43 + slot_offset[c % HERAD_NUM_VOICES];
val = (output & 63) |
((inst[i].param.car_ksl & 3) << 6);
opl->write(reg, val);
if (c >= HERAD_NUM_VOICES) opl->setchip(0);
}
/*
* Macro: Change Feedback (c - channel, i - instrument index, sensitivity, level)
*/
void CheradPlayer::macroFeedback(uint8_t c, uint8_t i, int8_t sens, uint8_t level)
{
uint8_t reg, val;
uint8_t feedback;
if (sens < -6 || sens > 6)
return;
if (sens < 0)
{
feedback = (level >> (sens + 7) > 7 ? 7 : level >> (sens + 7));
}
else
{
feedback = ((0x80 - level) >> (7 - sens) > 7 ? 7 : (0x80 - level) >> (7 - sens));
}
feedback += inst[i].param.feedback;
if (feedback > 7) feedback = 7;
if (c >= HERAD_NUM_VOICES) opl->setchip(1);
// Panning / Feedback strength / Connection type
reg = 0xC0 + (c % HERAD_NUM_VOICES);
val = (inst[i].param.con > 0 ? 0 : 1) |
((feedback & 7) << 1) |
((AGD ? (inst[i].param.pan == 0 || inst[i].param.pan > 3 ? 3 : inst[i].param.pan) : 0) << 4);
opl->write(reg, val);
if (c >= HERAD_NUM_VOICES) opl->setchip(0);
}
/*
* Macro: Root Note Transpose (note, i - instrument index)
*/
void CheradPlayer::macroTranspose(uint8_t * note, uint8_t i)
{
uint8_t tran = inst[i].param.mc_transpose;
uint8_t diff = (tran - 0x31) & 0xFF;
if (v2 && diff < 0x60)
*note = (diff + 0x18) & 0xFF;
else
*note = (*note + tran) & 0xFF;
}
/*
* Macro: Pitch Bend Slide (c - channel)
*/
void CheradPlayer::macroSlide(uint8_t c)
{
if (!chn[c].slide_dur)
return;
chn[c].slide_dur--;
chn[c].bend += inst[chn[c].playprog].param.mc_slide_range;
if (!(chn[c].note & 0x7F))
return;
playNote(c, chn[c].note, HERAD_NOTE_UPDATE);
}
void CheradPlayer::processEvents()
{
uint8_t i;
songend = true;
if (wLoopStart && wLoopEnd && (ticks_pos + 1) % HERAD_MEASURE_TICKS == 0 && (ticks_pos + 1) / HERAD_MEASURE_TICKS + 1 == wLoopStart)
{
loop_pos = ticks_pos;
for (i = 0; i < nTracks; i++)
{
loop_data[i].counter = track[i].counter;
loop_data[i].ticks = track[i].ticks;
loop_data[i].pos = track[i].pos;
}
}
for (i = 0; i < nTracks; i++)
{
if (chn[i].slide_dur > 0 && chn[i].keyon)
macroSlide(i);
if (track[i].pos >= track[i].size)
continue;
songend = false; // track is not finished
if (!track[i].counter)
{
bool first = track[i].pos == 0;
track[i].ticks = GetTicks(i);
if (first && track[i].ticks)
track[i].ticks++; // workaround to synchronize tracks (there's always 1 excess tick at start)
}
if (++track[i].counter >= track[i].ticks)
{
track[i].counter = 0;
while (track[i].pos < track[i].size)
{
executeCommand(i);
if (track[i].pos >= track[i].size) {
break;
}
else if (!track[i].data[track[i].pos]) // if next delay is zero
{
track[i].pos++;
}
else break;
}
}
else if (track[i].ticks >= 0x8000)
{
track[i].pos = track[i].size;
track[i].counter = track[i].ticks;
}
}
if (!songend)
ticks_pos++;
if (wLoopStart && wLoopEnd && (ticks_pos == total_ticks || (ticks_pos % HERAD_MEASURE_TICKS == 0 && ticks_pos / HERAD_MEASURE_TICKS + 1 == wLoopEnd)))
{
#ifdef HERAD_USE_LOOPING
if (!wLoopCount)
songend = true;
else if (songend && loop_times < wLoopCount)
songend = false;
if (!wLoopCount || loop_times < wLoopCount)
{
ticks_pos = loop_pos;
for (i = 0; i < nTracks; i++)
{
track[i].counter = loop_data[i].counter;
track[i].ticks = loop_data[i].ticks;
track[i].pos = loop_data[i].pos;
}
if (wLoopCount)
loop_times++;
}
#endif
}
}
bool CheradPlayer::update()
{
wTime = wTime - 256;
if (wTime < 0)
{
wTime = wTime + wSpeed;
processEvents();
}
return !songend;
}