/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "common/debug.h"
#include "common/endian.h"
#include "common/textconsole.h"
#include "freescape/games/dark/c64.music.h"

namespace Freescape {

// ============================================================
// Data tables extracted from darkside.prg (load address $0400)
// ============================================================

// Frequency table: hi bytes at $0F38, lo bytes at $0F97 (95 entries each)
// Index 0 = rest (freq 0), indices 1-94 = notes spanning 8 octaves
const uint8 kFreqHi[96] = {
	0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02,
	0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x06, 0x06,
	0x06, 0x07, 0x07, 0x08, 0x08, 0x09, 0x09, 0x0A, 0x0A, 0x0B, 0x0C, 0x0C, 0x0D, 0x0E, 0x0F, 0x10,
	0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x18, 0x19, 0x1B, 0x1C, 0x1E, 0x20, 0x22, 0x24, 0x26, 0x28,
	0x2B, 0x2D, 0x30, 0x33, 0x36, 0x39, 0x3D, 0x40, 0x44, 0x48, 0x4C, 0x51, 0x56, 0x5B, 0x60, 0x66,
	0x6C, 0x73, 0x7A, 0x81, 0x89, 0x91, 0x99, 0xA3, 0xAC, 0xB7, 0xC1, 0xCD, 0xD9, 0xE6, 0xF4,
	0x00 // safety padding
};

const uint8 kFreqLo[96] = {
	0x00, 0x23, 0x34, 0x46, 0x5A, 0x6E, 0x84, 0x9B, 0xB3, 0xCD, 0xE9, 0x06, 0x25, 0x45, 0x68, 0x8C,
	0xB3, 0xDC, 0x08, 0x36, 0x67, 0x9B, 0xD2, 0x0C, 0x49, 0x8B, 0xD0, 0x19, 0x67, 0xB9, 0x10, 0x6C,
	0xCE, 0x35, 0xA3, 0x17, 0x93, 0x15, 0x9F, 0x3C, 0xCD, 0x72, 0x20, 0xD8, 0x9C, 0x6B, 0x46, 0x2F,
	0x25, 0x2A, 0x3F, 0x64, 0x9A, 0xE3, 0x3F, 0xB1, 0x38, 0xD6, 0x8D, 0x5E, 0x4B, 0x55, 0x7E, 0xC8,
	0x34, 0xC6, 0x7F, 0x61, 0x6F, 0xAC, 0x7E, 0xBC, 0x95, 0xA9, 0xFC, 0xA1, 0x69, 0x8C, 0xFE, 0xC2,
	0xDF, 0x58, 0x34, 0x78, 0x2B, 0x53, 0xF7, 0x1F, 0xD2, 0x19, 0xFC, 0x85, 0xBD, 0xB0, 0x67,
	0x00 // safety padding
};

// Instrument table at $1010 (18 instruments x 8 bytes)
// Bytes: ctrl, AD, SR, initPW, vib/env mode, pwMod, autoFx, flags
// Instruments 16-17 are stored between $1090-$109F (past the nominal 16-entry table)
const uint8 kInstruments[18 * 8] = {
	0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0: Pulse (silence)
	0x41, 0x0C, 0xDC, 0x02, 0x00, 0x33, 0x00, 0x08, // 1: Pulse+G
	0x41, 0xDC, 0xDD, 0x00, 0x00, 0x15, 0x00, 0x80, // 2: Pulse+G
	0x41, 0x1A, 0x04, 0x00, 0x00, 0x05, 0x00, 0x04, // 3: Pulse+G
	0x11, 0x32, 0x3A, 0x00, 0x00, 0x11, 0x00, 0x80, // 4: Triangle+G
	0x41, 0x0B, 0xAC, 0x20, 0x00, 0x01, 0x00, 0x04, // 5: Pulse+G
	0x41, 0x0B, 0x6C, 0x40, 0x33, 0x10, 0x00, 0x00, // 6: Pulse+G
	0x45, 0x0A, 0x8B, 0x88, 0x46, 0x13, 0x00, 0x80, // 7: Pulse+Ring+G
	0x41, 0x16, 0x00, 0x60, 0x00, 0x17, 0x00, 0x80, // 8: Pulse+G
	0x21, 0x08, 0x17, 0x00, 0x00, 0x00, 0x80, 0x00, // 9: Sawtooth+G
	0x15, 0xCC, 0xDC, 0x40, 0x00, 0x11, 0x00, 0x80, // 10: Tri+Ring+G
	0x81, 0x42, 0x38, 0x10, 0x00, 0x01, 0x03, 0x02, // 11: Noise+G
	0x41, 0x8B, 0xAF, 0x80, 0x00, 0x00, 0x00, 0x04, // 12: Pulse+G
	0x41, 0x1B, 0x4A, 0x14, 0x53, 0x02, 0x00, 0x00, // 13: Pulse+G
	0x81, 0x04, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, // 14: Noise+G
	0x41, 0x0B, 0x6C, 0x62, 0x62, 0x42, 0x00, 0x00, // 15: Pulse+G
	0x41, 0x0C, 0x00, 0x88, 0x11, 0x00, 0x00, 0x01, // 16: Pulse+G (percussion, envelope seq)
	0x81, 0x0B, 0x20, 0x66, 0x00, 0x00, 0x00, 0x01, // 17: Noise+G (percussion, envelope seq)
};

// Auxiliary envelope data tables ($154B and $156B, 16 entries each)
const uint8 kEnvData[2][16] = {
	{ 0xFA, 0x01, 0xFF, 0x20, 0x0A, 0x12, 0x04, 0x16, 0x0E, 0x0C, 0x0A, 0x08, 0x06, 0x04, 0x02, 0x00 },
	{ 0x10, 0x0A, 0x06, 0x00, 0x04, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00 },
};

// Envelope waveform control tables ($155B and $157B, 16 entries each)
const uint8 kEnvControl[2][16] = {
	{ 0x81, 0x41, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10 },
	{ 0x81, 0x41, 0x81, 0x80, 0x40, 0x40, 0x40, 0x40, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10 },
};

// Arpeggio semitone intervals ($1591)
const uint8 kArpIntervals[8] = { 3, 4, 5, 7, 8, 9, 10, 12 };

// SID register base offset per channel
const int kSIDOffset[3] = { 0, 7, 14 };

// ---- Pattern data (29 patterns from $122D-$1542) ----

const uint8 kPattern00[] = { 0xC0, 0xBF, 0x00, 0xFF };
const uint8 kPattern01[] = { 0xF5, 0xC2, 0xBF, 0x10, 0x10, 0xFF };
const uint8 kPattern02[] = { 0xC1, 0x8F, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0xFF };
const uint8 kPattern03[] = { 0xC4, 0x80, 0x34, 0x2F, 0x37, 0x36, 0x2F, 0x37, 0x34, 0x2F, 0x34, 0x2F, 0x37, 0x36, 0x2F, 0x37, 0x34, 0x2F, 0x34, 0x2F, 0x37, 0x36, 0x2F, 0x37, 0x34, 0x2F, 0x34, 0x2F, 0x37, 0x36, 0x2F, 0x37, 0x34, 0x2F, 0xFF };
const uint8 kPattern04[] = { 0xC6, 0x80, 0x7A, 0x08, 0x34, 0x36, 0x9D, 0x37, 0xFF };
const uint8 kPattern05[] = { 0x81, 0x34, 0x36, 0x34, 0x2F, 0x32, 0x30, 0x2B, 0x91, 0x2F, 0xFF };
const uint8 kPattern06[] = { 0x83, 0x34, 0x37, 0x34, 0x8F, 0x3C, 0x83, 0x3B, 0x8F, 0x3A, 0x8F, 0x36, 0xFF };
const uint8 kPattern07[] = { 0xC1, 0x81, 0x10, 0x10, 0x12, 0x10, 0x13, 0x10, 0x15, 0x13, 0xFF };
const uint8 kPattern08[] = { 0xC5, 0x9F, 0x7D, 0x89, 0x40, 0x7D, 0x91, 0x40, 0x7D, 0xA1, 0x40, 0x7D, 0x91, 0x40, 0xFF };
const uint8 kPattern09[] = { 0xC0, 0x9F, 0x00, 0xFF };
const uint8 kPattern10[] = { 0xCA, 0x9F, 0x7E, 0x0A, 0x7F, 0x4C, 0x83, 0x1C, 0x23, 0x28, 0x2F, 0x1C, 0x23, 0x28, 0x2F, 0x8F, 0x7F, 0x40, 0x7E, 0x10, 0x9F, 0x7B, 0x40, 0x3B, 0x21, 0x8F, 0x34, 0x40, 0x9F, 0x7B, 0x0D, 0x3B, 0x4C, 0x7F, 0x3C, 0xFF };
const uint8 kPattern11[] = { 0xC7, 0xFF };
const uint8 kPattern12[] = { 0xC2, 0xBF, 0x10, 0xFF };
const uint8 kPattern13[] = { 0xC8, 0x80, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0xFF };
const uint8 kPattern14[] = { 0x80, 0x28, 0x34, 0x34, 0x28, 0x34, 0x28, 0x28, 0x34, 0x28, 0x34, 0x34, 0x28, 0x28, 0x34, 0x28, 0x34, 0x28, 0x28, 0x34, 0x34, 0x28, 0x28, 0x34, 0x28, 0x28, 0x34, 0x34, 0x28, 0x34, 0x28, 0x28, 0x34, 0xFF };
const uint8 kPattern15[] = { 0xCB, 0x81, 0x54, 0x52, 0x50, 0xC0, 0x00, 0xCB, 0x54, 0x52, 0x50, 0xC0, 0x00, 0xCB, 0x54, 0x52, 0x50, 0xC0, 0x00, 0xCB, 0x54, 0x52, 0xFF };
const uint8 kPattern16[] = { 0xC1, 0x87, 0x10, 0x1C, 0x10, 0x1C, 0x10, 0x1C, 0x10, 0x83, 0x1C, 0x81, 0x10, 0x0E, 0x87, 0x0B, 0x17, 0x0B, 0x17, 0x0B, 0x17, 0x0B, 0x83, 0x17, 0x81, 0x0B, 0x0A, 0x87, 0x09, 0x15, 0x09, 0x15, 0x09, 0x15, 0x09, 0x83, 0x15, 0x81, 0x09, 0x0A, 0x87, 0x0B, 0x17, 0x0B, 0x17, 0x0B, 0x17, 0x0B, 0x83, 0x17, 0x81, 0x0B, 0x0E, 0xFF };
const uint8 kPattern17[] = { 0xCC, 0xBF, 0x7D, 0x89, 0x40, 0x7C, 0xA2, 0x3E, 0x3C, 0x7D, 0x91, 0x3F, 0xFF };
const uint8 kPattern18[] = { 0x83, 0x1C, 0xFF };
const uint8 kPattern19[] = { 0xC0, 0x93, 0x00, 0xC1, 0x81, 0x23, 0x83, 0x21, 0x81, 0x1F, 0x83, 0x1E, 0xFF };
const uint8 kPattern20[] = { 0xC1, 0x8B, 0x1C, 0x83, 0x23, 0x97, 0x23, 0x83, 0x21, 0x23, 0x21, 0x1F, 0x1E, 0x1F, 0x8B, 0x1E, 0x83, 0x23, 0x9B, 0x23, 0x81, 0x21, 0x23, 0x83, 0x21, 0x1F, 0x1E, 0x1F, 0x8B, 0x21, 0x83, 0x23, 0x93, 0x21, 0x83, 0x21, 0x23, 0x24, 0x26, 0x24, 0x23, 0x21, 0x9B, 0x1E, 0x83, 0x23, 0x93, 0x23, 0xD1, 0x81, 0x30, 0x30, 0xD0, 0x12, 0xD1, 0x30, 0x30, 0xD0, 0x7A, 0x18, 0x12, 0xFF };
const uint8 kPattern21[] = { 0xC8, 0xFF };
const uint8 kPattern22[] = { 0xC4, 0xFF };
const uint8 kPattern23[] = { 0xCF, 0xA3, 0x7B, 0x37, 0x02, 0x36, 0x83, 0x2F, 0x34, 0x36, 0x7B, 0x39, 0x02, 0x37, 0x37, 0x36, 0x34, 0x8B, 0x7B, 0x36, 0x02, 0x34, 0x83, 0x2F, 0x93, 0x2F, 0x83, 0x36, 0x36, 0x37, 0x7B, 0x39, 0x02, 0x37, 0x37, 0x36, 0x7B, 0x34, 0x04, 0x37, 0x8B, 0x34, 0x81, 0x36, 0x37, 0x93, 0x34, 0x83, 0x34, 0x36, 0x37, 0x39, 0x37, 0x36, 0x34, 0xA3, 0x7B, 0x36, 0x04, 0x34, 0xD1, 0x81, 0x30, 0x30, 0xD0, 0x12, 0xC0, 0x85, 0x00, 0xD1, 0x81, 0x30, 0x30, 0xD0, 0x12, 0xC0, 0x83, 0x00, 0xD1, 0x81, 0x30, 0x30, 0xD0, 0x12, 0xFF };
const uint8 kPattern24[] = { 0xC1, 0x87, 0x10, 0x85, 0x1C, 0x81, 0x10, 0x83, 0x10, 0x10, 0x81, 0x1C, 0x83, 0x10, 0x81, 0x1C, 0x87, 0x10, 0x85, 0x1C, 0x81, 0x10, 0x83, 0x10, 0x10, 0x81, 0x1C, 0x83, 0x10, 0x81, 0x1C, 0xFF };
const uint8 kPattern25[] = { 0xCF, 0x99, 0x7B, 0x34, 0x04, 0x32, 0x85, 0x7B, 0x32, 0x02, 0x34, 0x93, 0x7B, 0x34, 0x04, 0x32, 0x83, 0x7B, 0x37, 0x02, 0x36, 0x81, 0x37, 0x85, 0x7B, 0x32, 0x02, 0x34, 0x93, 0x32, 0x83, 0x32, 0x36, 0x7B, 0x36, 0x02, 0x37, 0x9B, 0x36, 0x83, 0x7B, 0x30, 0x03, 0x32, 0x30, 0x32, 0x81, 0x30, 0x85, 0x7B, 0x30, 0x02, 0x32, 0x93, 0x30, 0x83, 0x30, 0x32, 0x34, 0x8B, 0x7B, 0x36, 0x04, 0x37, 0x83, 0x34, 0x97, 0x7B, 0x36, 0x04, 0x34, 0x81, 0x36, 0x37, 0x36, 0x34, 0x99, 0x7B, 0x36, 0x04, 0x34, 0xD1, 0x81, 0x12, 0x83, 0x12, 0xFF };
const uint8 kPattern26[] = { 0xCC, 0xBF, 0x7D, 0x8A, 0x40, 0x7D, 0x91, 0x3F, 0x7D, 0x94, 0x3D, 0x7D, 0x91, 0x3F, 0xFF };
const uint8 kPattern27[] = { 0xCF, 0x83, 0x2F, 0x34, 0x91, 0x34, 0x85, 0x7B, 0x34, 0x12, 0x36, 0x2F, 0x81, 0x34, 0x34, 0x36, 0x8D, 0x34, 0x85, 0x7B, 0x33, 0x12, 0x34, 0x93, 0x33, 0x83, 0x33, 0x81, 0x33, 0x85, 0x7B, 0x33, 0x12, 0x34, 0x99, 0x33, 0x81, 0x2F, 0x83, 0x7B, 0x2D, 0x12, 0x2F, 0x8B, 0x2D, 0x83, 0x34, 0x34, 0x8B, 0x7B, 0x37, 0x22, 0x36, 0x93, 0x37, 0x87, 0x7B, 0x34, 0x04, 0x36, 0x83, 0x34, 0xA1, 0x7B, 0x36, 0x12, 0x34, 0xD1, 0x81, 0x1C, 0x8D, 0x1C, 0x81, 0x1C, 0x83, 0x1C, 0x1C, 0x81, 0x1C, 0x1C, 0xFF };
const uint8 kPattern28[] = { 0xCF, 0x80, 0x7A, 0x08, 0x34, 0x36, 0x9D, 0x37, 0xD0, 0x83, 0x1C, 0x81, 0x18, 0x83, 0x1C, 0x81, 0x18, 0x83, 0x1C, 0x1C, 0x81, 0x18, 0x83, 0x1C, 0x81, 0x18, 0x83, 0x1C, 0xCF, 0x80, 0x32, 0x34, 0x9D, 0x36, 0xD0, 0x81, 0x12, 0x83, 0x12, 0x12, 0x81, 0x0E, 0x83, 0x12, 0x1C, 0x85, 0x12, 0x81, 0x0E, 0x83, 0x12, 0xCF, 0x80, 0x30, 0x32, 0x9D, 0x34, 0xD1, 0x83, 0x1C, 0x1C, 0x81, 0x18, 0x83, 0x1C, 0x81, 0x18, 0x83, 0x1C, 0x81, 0x18, 0x1C, 0xCF, 0x87, 0x30, 0x80, 0x33, 0x34, 0x9D, 0x36, 0xC0, 0x9B, 0x00, 0xD1, 0x81, 0x18, 0x18, 0xFF };

const uint8 *const kPatterns[29] = {
	kPattern00, kPattern01, kPattern02, kPattern03, kPattern04,
	kPattern05, kPattern06, kPattern07, kPattern08, kPattern09,
	kPattern10, kPattern11, kPattern12, kPattern13, kPattern14,
	kPattern15, kPattern16, kPattern17, kPattern18, kPattern19,
	kPattern20, kPattern21, kPattern22, kPattern23, kPattern24,
	kPattern25, kPattern26, kPattern27, kPattern28,
};

// ---- Order lists (song 0, 3 channels) ----

const uint8 kOrderList0[] = {
	0xC2, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
	0x02, 0x02, 0x02, 0x02, 0x01, 0x10, 0x10, 0x10, 0x10, 0x18, 0xC9, 0x18, 0xC7, 0x18, 0xC9,
	0x18, 0xC2, 0x18, 0xC9, 0x18, 0xC7, 0x18, 0xC9, 0x18, 0xC2, 0x18, 0xC9, 0x18, 0xC7, 0x18,
	0xC9, 0x18, 0xC2, 0x18, 0xC9, 0x18, 0xC7, 0x18, 0xC9, 0x18, 0xC2, 0x18, 0x18, 0x18, 0x18,
	0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x10, 0x10, 0x01,
	0x01, 0xFF
};

const uint8 kOrderList1[] = {
	0xCE, 0x09, 0x01, 0x0C, 0x09, 0x01, 0x01, 0x01, 0x09, 0x01, 0x01, 0x09, 0x00, 0x03, 0x03,
	0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0xC2,
	0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
	0x03, 0x08, 0x08, 0x02, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1A, 0x1A, 0x08, 0x08, 0xB6,
	0x08, 0x08, 0xC2, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
	0x03, 0x03, 0x03, 0x03, 0xCE, 0x0C, 0xC9, 0x0C, 0xC7, 0x0C, 0xC9, 0x0C, 0xC2, 0x16, 0x0E,
	0x0E, 0xBD, 0x0E, 0x0E, 0xBB, 0x0E, 0x0E, 0xBD, 0x0E, 0x0E, 0xCE, 0x09, 0x01, 0x0C, 0x09,
	0xFF
};

const uint8 kOrderList2[] = {
	0xC2, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x09, 0x04, 0x05, 0x09, 0x04, 0x06, 0x09, 0x08, 0x08,
	0x08, 0x08, 0x0B, 0x05, 0x06, 0x09, 0xAA, 0x04, 0xC2, 0x09, 0xB6, 0x04, 0xC2, 0x09, 0x0D,
	0x09, 0xC4, 0x0D, 0xC2, 0x09, 0xC5, 0x0D, 0xC2, 0x09, 0xC7, 0x0D, 0xC2, 0x09, 0xCE, 0x0D,
	0x12, 0xC0, 0x0F, 0xD0, 0x0D, 0x12, 0xC0, 0x0F, 0xD1, 0x0D, 0x12, 0xC0, 0x0F, 0xD3, 0x0D,
	0x12, 0xC0, 0x0F, 0xB6, 0x15, 0x0E, 0x0E, 0x15, 0x0E, 0x0E, 0xC2, 0x15, 0x0E, 0x0E, 0x16,
	0x0E, 0x0E, 0x09, 0xCE, 0x0C, 0xC2, 0x13, 0x14, 0x14, 0x17, 0xCE, 0x17, 0xC2, 0x19, 0xCE,
	0x19, 0xC2, 0x1B, 0xB6, 0x1B, 0xCE, 0x01, 0xC2, 0x01, 0xCE, 0x03, 0x03, 0x03, 0x03, 0xC2,
	0x03, 0x03, 0x03, 0x03, 0x0B, 0x05, 0x06, 0x09, 0xAA, 0x04, 0xC2, 0x09, 0xB6, 0x04, 0xC2,
	0x09, 0x0D, 0x12, 0x0F, 0xC4, 0x0D, 0x12, 0xC2, 0x0F, 0xC5, 0x0D, 0x12, 0xC2, 0x0F, 0xC7,
	0x0D, 0x12, 0xC2, 0x0F, 0x15, 0x0E, 0x0E, 0xBD, 0x0E, 0x0E, 0xBB, 0x0E, 0x0E, 0xBD, 0x0E,
	0x0E, 0xC2, 0x1C, 0x02, 0x02, 0xFF
};

const uint8 *const kOrderLists[3] = { kOrderList0, kOrderList1, kOrderList2 };

// ============================================================
// Implementation
// ============================================================

void DarkSideC64MusicPlayer::ChannelState::reset() {
	orderData = nullptr;
	orderPos = 0;
	patData = nullptr;
	patOffset = 0;
	instIdx = 0;
	curNote = 0;
	transpose = 0;
	freqLo = 0;
	freqHi = 0;
	pwLo = 0;
	pwHi = 0;
	durReload = 1;
	durCounter = 1;
	effectMode = 0;
	effectParam = 0;
	arpPattern = 0;
	arpParam2 = 0;
	arpSeqPos = 0;
	arpSeqLen = 0;
	memset(arpSeqData, 0, sizeof(arpSeqData));
	noteStepCommand = 0;
	stepDownCounter = 0;
	vibPhase = 0;
	vibCounter = 0;
	pwDirection = 0;
	delayValue = 0;
	delayCounter = 0;
	envCounter = 0;
	gateOffDisabled = false;
	gateModeControl = false;
	specialAttack = false;
	attackDone = false;
	waveform = 0;
	instFlags = 0;
}

DarkSideC64MusicPlayer::DarkSideC64MusicPlayer() {
	_sid = nullptr;
	_musicActive = false;
	_speedDiv = 1;
	_speedCounter = 0;
	for (int i = 0; i < 3; i++)
		_ch[i].reset();
	initSID();
}

DarkSideC64MusicPlayer::~DarkSideC64MusicPlayer() {
	destroySID();
}

void DarkSideC64MusicPlayer::destroySID() {
	if (_sid) {
		_sid->stop();
		delete _sid;
		_sid = nullptr;
	}
}

void DarkSideC64MusicPlayer::initSID() {
	if (_sid) {
		_sid->stop();
		delete _sid;
	}

	_sid = SID::Config::create(SID::Config::kSidPAL);
	if (!_sid || !_sid->init()) {
		warning("DarkSideC64MusicPlayer: Failed to create SID emulator");
		return;
	}
	_sid->start(new Common::Functor0Mem<void, DarkSideC64MusicPlayer>(this, &DarkSideC64MusicPlayer::onTimer), 50);
}

void DarkSideC64MusicPlayer::sidWrite(int reg, uint8 data) {
	if (_sid)
		_sid->writeReg(reg, data);
}

void DarkSideC64MusicPlayer::silenceAll() {
	for (int i = 0; i <= 0x18; i++)
		sidWrite(i, 0);
}

bool DarkSideC64MusicPlayer::isPlaying() const {
	return _musicActive;
}

void DarkSideC64MusicPlayer::startMusic() {
	setupSong();
}

void DarkSideC64MusicPlayer::stopMusic() {
	_musicActive = false;
	silenceAll();
}

void DarkSideC64MusicPlayer::setupSong() {
	silenceAll();

	// Init routine at $0FF6.
	sidWrite(0x18, 0x5F);
	sidWrite(0x17, 0x00);

	_speedDiv = 1;
	_speedCounter = 0;

	for (int ch = 0; ch < 3; ch++) {
		_ch[ch].reset();
		_ch[ch].orderData = kOrderLists[ch];
		loadNextPattern(ch);
	}

	_musicActive = true;
}

void DarkSideC64MusicPlayer::loadNextPattern(int ch) {
	int safety = 200;
	while (safety-- > 0) {
		uint8 byte = _ch[ch].orderData[_ch[ch].orderPos];

		if (byte == 0xFF) {
			// Loop to start
			_ch[ch].orderPos = 0;
			continue;
		}

		_ch[ch].orderPos++;

		if (byte >= 0x80) {
			// Transpose: (byte + $40) & $FF
			_ch[ch].transpose = (byte + 0x40) & 0xFF;
			continue;
		}

		// Pattern number
		if (byte < 29) {
			_ch[ch].patData = kPatterns[byte];
			_ch[ch].patOffset = 0;
		}
		break;
	}
}

uint8 DarkSideC64MusicPlayer::readPatByte(int ch) {
	uint8 b = _ch[ch].patData[_ch[ch].patOffset];
	_ch[ch].patOffset++;
	return b;
}

void DarkSideC64MusicPlayer::buildEffectArpeggio(int ch) {
	uint8 bits = _ch[ch].effectParam;
	uint8 len = 1;

	_ch[ch].arpSeqData[0] = 0;
	for (int i = 0; i < 8 && len < sizeof(_ch[ch].arpSeqData); i++) {
		if (bits & (1 << i))
			_ch[ch].arpSeqData[len++] = kArpIntervals[i];
	}

	_ch[ch].arpSeqLen = len;
	_ch[ch].arpSeqPos = 0;
	_ch[ch].arpPattern = 0;
}

void DarkSideC64MusicPlayer::loadCurrentFrequency(int ch) {
	int off = kSIDOffset[ch];
	uint8 note = (_ch[ch].curNote > 94) ? 94 : _ch[ch].curNote;

	_ch[ch].freqLo = kFreqLo[note];
	_ch[ch].freqHi = kFreqHi[note];
	sidWrite(off + 0, _ch[ch].freqLo);
	sidWrite(off + 1, _ch[ch].freqHi);
}

void DarkSideC64MusicPlayer::finalizeChannel(int ch) {
	int off = kSIDOffset[ch];

	if (_ch[ch].durReload != 0 && !_ch[ch].gateOffDisabled) {
		if ((_ch[ch].durReload >> 1) == _ch[ch].durCounter)
			sidWrite(off + 4, _ch[ch].waveform & 0xFE);
	}

	applyPWModulation(ch);
	sidWrite(off + 2, _ch[ch].pwLo);
	sidWrite(off + 3, _ch[ch].pwHi & 0x0F);
}

// ---- Main timer callback (50 Hz) ----

void DarkSideC64MusicPlayer::onTimer() {
	if (!_musicActive)
		return;

	for (int ch = 0; ch < 3; ch++)
		_ch[ch].envCounter++;

	bool newBeat = (_speedCounter == 0);

	for (int ch = 2; ch >= 0; ch--)
		processChannel(ch, newBeat);

	if (!_musicActive)
		return;

	if (newBeat)
		_speedCounter = _speedDiv;
	else
		_speedCounter--;
}

void DarkSideC64MusicPlayer::processChannel(int ch, bool newBeat) {
	if (newBeat) {
		_ch[ch].durCounter--;
		if (_ch[ch].durCounter == 0xFF) {
			parseCommands(ch);
			if (!_musicActive)
				return;
			finalizeChannel(ch);
			return;
		}

		if (_ch[ch].noteStepCommand != 0) {
			if (_ch[ch].noteStepCommand == 0xDE) {
				if (_ch[ch].curNote > 0)
					_ch[ch].curNote--;
			} else if (_ch[ch].curNote < 94) {
				_ch[ch].curNote++;
			}
			loadCurrentFrequency(ch);
			finalizeChannel(ch);
			return;
		}
	} else if (_ch[ch].stepDownCounter != 0) {
		_ch[ch].stepDownCounter--;
		if (_ch[ch].curNote > 0)
			_ch[ch].curNote--;
		loadCurrentFrequency(ch);
		finalizeChannel(ch);
		return;
	}

	applyFrameEffects(ch);
	finalizeChannel(ch);
}

// ---- Command parser ----

void DarkSideC64MusicPlayer::parseCommands(int ch) {
	if (_ch[ch].effectMode != 2) {
		_ch[ch].effectParam = 0;
		_ch[ch].effectMode = 0;
		_ch[ch].arpSeqLen = 0;
		_ch[ch].arpSeqPos = 0;
	}
	_ch[ch].arpPattern = 0;
	_ch[ch].noteStepCommand = 0;

	int safety = 200;
	while (safety-- > 0) {
		uint8 cmd = readPatByte(ch);

		if (cmd == 0xFF) {
			loadNextPattern(ch);
			continue;
		}

		if (cmd == 0xFE) {
			stopMusic();
			return;
		}

		if (cmd == 0xFD) {
			uint8 filterVal = readPatByte(ch);
			sidWrite(0x18, filterVal);
			sidWrite(0x17, (filterVal >> 4) | 0x07);
			cmd = readPatByte(ch);
			if (cmd == 0xFF) {
				loadNextPattern(ch);
				continue;
			}
		}

		if (cmd >= 0xF0) {
			_speedDiv = cmd & 0x0F;
			continue;
		}

		if (cmd >= 0xC0) {
			uint8 instNum = cmd & 0x1F;
			if (instNum < 18)
				_ch[ch].instIdx = instNum * 8;
			continue;
		}

		if (cmd >= 0x80) {
			_ch[ch].durReload = cmd & 0x3F;
			continue;
		}

		if (cmd == 0x7F) {
			_ch[ch].noteStepCommand = 0xDE;
			_ch[ch].effectMode = 0xDE;
			continue;
		}

		if (cmd == 0x7E) {
			_ch[ch].noteStepCommand = 0xFE;
			_ch[ch].effectMode = 0xFE;
			continue;
		}

		if (cmd == 0x7D) {
			_ch[ch].effectMode = 1;
			_ch[ch].effectParam = readPatByte(ch);
			buildEffectArpeggio(ch);
			continue;
		}

		if (cmd == 0x7C) {
			_ch[ch].effectMode = 2;
			_ch[ch].effectParam = readPatByte(ch);
			buildEffectArpeggio(ch);
			continue;
		}

		if (cmd == 0x7B) {
			_ch[ch].effectParam = 0;
			_ch[ch].effectMode = 1;
			_ch[ch].arpPattern = (readPatByte(ch) + _ch[ch].transpose) & 0xFF;
			_ch[ch].arpParam2 = readPatByte(ch);
			continue;
		}

		if (cmd == 0x7A) {
			_ch[ch].delayValue = readPatByte(ch);
			cmd = readPatByte(ch);
		}

		applyNote(ch, cmd);
		return;
	}
}

// ---- Note handling ----

void DarkSideC64MusicPlayer::applyNote(int ch, uint8 note) {
	int off = kSIDOffset[ch];
	uint8 instBase = _ch[ch].instIdx;
	uint8 ctrl = kInstruments[instBase + 0];
	uint8 ad = kInstruments[instBase + 1];
	uint8 sr = kInstruments[instBase + 2];
	uint8 initialPW = kInstruments[instBase + 3];
	uint8 autoFx = kInstruments[instBase + 6];
	uint8 flags = kInstruments[instBase + 7];
	uint8 actualNote = note;

	if (actualNote != 0)
		actualNote = (actualNote + _ch[ch].transpose) & 0xFF;
	if (actualNote > 94)
		actualNote = 94;

	_ch[ch].curNote = actualNote;
	_ch[ch].waveform = ctrl;
	_ch[ch].instFlags = flags;
	_ch[ch].attackDone = false;
	_ch[ch].envCounter = 0xFF;
	_ch[ch].gateModeControl = (flags & 0x80) != 0;
	_ch[ch].specialAttack = (flags & 0x08) != 0;
	_ch[ch].stepDownCounter = 0;

	if (actualNote != 0 && _ch[ch].effectParam == 0 && autoFx != 0) {
		_ch[ch].effectParam = autoFx;
		buildEffectArpeggio(ch);
	}

	if (actualNote != 0 && (flags & 0x02)) {
		_ch[ch].stepDownCounter = 2;
		_ch[ch].curNote = (_ch[ch].curNote > 92) ? 94 : _ch[ch].curNote + 2;
	}

	loadCurrentFrequency(ch);

	if (!_ch[ch].gateModeControl) {
		_ch[ch].pwLo = initialPW & 0xF0;
		_ch[ch].pwHi = initialPW & 0x0F;
		sidWrite(off + 2, _ch[ch].pwLo);
		sidWrite(off + 3, _ch[ch].pwHi & 0x0F);
	}

	sidWrite(off + 5, ad);
	sidWrite(off + 6, sr);
	_ch[ch].gateOffDisabled = (sr & 0x0F) == 0x0F;
	sidWrite(off + 4, 0);
	sidWrite(off + 4, ctrl);
	_ch[ch].durCounter = _ch[ch].durReload;
	_ch[ch].delayCounter = _ch[ch].delayValue;
	_ch[ch].arpSeqPos = 0;
}

void DarkSideC64MusicPlayer::applyFrameEffects(int ch) {
	if (_ch[ch].curNote == 0)
		return;

	if (applySpecialAttack(ch))
		return;

	if (applyEnvelopeSequence(ch))
		return;

	if (applyInstrumentVibrato(ch))
		return;

	applyEffectArpeggio(ch);
	applyTimedSlide(ch);
}

bool DarkSideC64MusicPlayer::applySpecialAttack(int ch) {
	if (!_ch[ch].specialAttack || _ch[ch].attackDone)
		return false;

	int off = kSIDOffset[ch];
	if (_ch[ch].envCounter < 1) {
		sidWrite(off + 0, 0x00);
		sidWrite(off + 1, 0x48);
		sidWrite(off + 4, 0x81);
	} else {
		loadCurrentFrequency(ch);
		_ch[ch].attackDone = true;
		sidWrite(off + 4, _ch[ch].waveform);
	}
	return true;
}

bool DarkSideC64MusicPlayer::applyEnvelopeSequence(int ch) {
	if ((_ch[ch].instFlags & 0x01) == 0 || _ch[ch].envCounter >= 15)
		return false;

	int off = kSIDOffset[ch];
	uint8 instBase = _ch[ch].instIdx;
	uint8 envMode = kInstruments[instBase + 4];
	uint8 table = (envMode & 0x0F) ? 1 : 0;
	uint8 data = kEnvData[table][_ch[ch].envCounter];

	sidWrite(off + 4, kEnvControl[table][_ch[ch].envCounter]);
	if (envMode & 0x10) {
		uint8 note = (_ch[ch].curNote + data > 94) ? 94 : _ch[ch].curNote + data;
		sidWrite(off + 0, kFreqLo[note]);
		sidWrite(off + 1, kFreqHi[note]);
	} else {
		sidWrite(off + 0, 0x00);
		sidWrite(off + 1, data + 0x0D);
	}
	return true;
}

bool DarkSideC64MusicPlayer::applyInstrumentVibrato(int ch) {
	uint8 instBase = _ch[ch].instIdx;
	uint8 vib = kInstruments[instBase + 4];
	if (vib == 0 || _ch[ch].curNote >= 94)
		return false;

	uint8 shift = vib & 0x0F;
	uint8 span = vib >> 4;
	if (span == 0)
		return false;

	uint16 noteFreq = ((uint16)kFreqHi[_ch[ch].curNote] << 8) | kFreqLo[_ch[ch].curNote];
	uint16 nextFreq = ((uint16)kFreqHi[_ch[ch].curNote + 1] << 8) | kFreqLo[_ch[ch].curNote + 1];
	uint16 delta = nextFreq - noteFreq;

	while (shift-- != 0)
		delta >>= 1;

	if (_ch[ch].vibPhase & 0x80) {
		if (_ch[ch].vibCounter != 0)
			_ch[ch].vibCounter--;
		if (_ch[ch].vibCounter == 0)
			_ch[ch].vibPhase = 0;
	} else {
		_ch[ch].vibCounter++;
		if (_ch[ch].vibCounter >= span)
			_ch[ch].vibPhase = 0xFF;
	}

	if (_ch[ch].delayCounter != 0) {
		_ch[ch].delayCounter--;
		return false;
	}

	int32 freq = ((uint16)_ch[ch].freqHi << 8) | _ch[ch].freqLo;
	for (uint8 i = 0; i < (span >> 1); i++)
		freq -= delta;
	for (uint8 i = 0; i < _ch[ch].vibCounter; i++)
		freq += delta;

	if (freq < 0)
		freq = 0;
	if (freq > 0xFFFF)
		freq = 0xFFFF;

	int off = kSIDOffset[ch];
	sidWrite(off + 0, freq & 0xFF);
	sidWrite(off + 1, (freq >> 8) & 0xFF);
	return true;
}

void DarkSideC64MusicPlayer::applyEffectArpeggio(int ch) {
	if (_ch[ch].effectParam == 0 || _ch[ch].arpSeqLen == 0)
		return;

	if (_ch[ch].arpSeqPos >= _ch[ch].arpSeqLen)
		_ch[ch].arpSeqPos = 0;

	uint8 note = (_ch[ch].curNote + _ch[ch].arpSeqData[_ch[ch].arpSeqPos] > 94) ? 94 : _ch[ch].curNote + _ch[ch].arpSeqData[_ch[ch].arpSeqPos];
	_ch[ch].arpSeqPos++;

	int off = kSIDOffset[ch];
	sidWrite(off + 0, kFreqLo[note]);
	sidWrite(off + 1, kFreqHi[note]);
}

void DarkSideC64MusicPlayer::applyTimedSlide(int ch) {
	if (_ch[ch].arpPattern == 0)
		return;

	uint8 total = _ch[ch].durReload;
	uint8 remaining = _ch[ch].durCounter;
	uint8 start = _ch[ch].arpParam2 >> 4;
	uint8 span = _ch[ch].arpParam2 & 0x0F;
	uint8 elapsed = total - remaining;

	if (elapsed <= start || elapsed > start + span || span == 0)
		return;

	uint8 currentNote = (_ch[ch].curNote > 94) ? 94 : _ch[ch].curNote;
	uint8 targetNote = (_ch[ch].arpPattern > 94) ? 94 : _ch[ch].arpPattern;
	if (currentNote == targetNote)
		return;

	uint16 currentFreq = ((uint16)_ch[ch].freqHi << 8) | _ch[ch].freqLo;
	uint16 sourceFreq = ((uint16)kFreqHi[currentNote] << 8) | kFreqLo[currentNote];
	uint16 targetFreq = ((uint16)kFreqHi[targetNote] << 8) | kFreqLo[targetNote];
	uint16 diff = (sourceFreq > targetFreq) ? (sourceFreq - targetFreq) : (targetFreq - sourceFreq);
	uint16 divisor = span * (_speedDiv + 1);
	if (divisor == 0)
		return;

	uint16 delta = diff / divisor;
	if (delta == 0)
		return;

	if (targetFreq > sourceFreq)
		currentFreq += delta;
	else
		currentFreq -= delta;

	_ch[ch].freqLo = currentFreq & 0xFF;
	_ch[ch].freqHi = (currentFreq >> 8) & 0xFF;

	int off = kSIDOffset[ch];
	sidWrite(off + 0, _ch[ch].freqLo);
	sidWrite(off + 1, _ch[ch].freqHi);
}

void DarkSideC64MusicPlayer::applyPWModulation(int ch) {
	uint8 instBase = _ch[ch].instIdx;
	uint8 pwMod = kInstruments[instBase + 5];
	if (pwMod == 0)
		return;

	uint8 flags = kInstruments[instBase + 7];

	if (flags & 0x04) {
		_ch[ch].pwLo += pwMod;
	} else {
		uint16 pw = ((uint16)_ch[ch].pwHi << 8) | _ch[ch].pwLo;
		if (_ch[ch].pwDirection == 0) {
			pw += pwMod;
			if ((pw >> 8) >= 0x0F)
				_ch[ch].pwDirection = 1;
		} else {
			pw -= pwMod;
			if ((pw >> 8) < 0x08)
				_ch[ch].pwDirection = 0;
		}

		_ch[ch].pwLo = pw & 0xFF;
		_ch[ch].pwHi = (pw >> 8) & 0xFF;
	}
}

} // namespace Freescape
