Permalink
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(¬e, 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; | |
| } |