/* 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
static 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
};

static 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, envCtl, vibrato, pwMod, autoFx, flags
// Instruments 16-17 are stored between $1090-$109F (past the nominal 16-entry table)
static 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)
};

// Envelope volume tables ($154B and $156B, 16 entries each)
static const uint8 kEnvVolume[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)
static 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)
static const uint8 kArpIntervals[8] = { 3, 4, 5, 7, 8, 9, 10, 12 };

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

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

static const uint8 kPattern00[] = { 0xC0, 0xBF, 0x00, 0xFF };
static const uint8 kPattern01[] = { 0xF5, 0xC2, 0xBF, 0x10, 0x10, 0xFF };
static const uint8 kPattern02[] = { 0xC1, 0x8F, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0xFF };
static 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 };
static const uint8 kPattern04[] = { 0xC6, 0x80, 0x7A, 0x08, 0x34, 0x36, 0x9D, 0x37, 0xFF };
static const uint8 kPattern05[] = { 0x81, 0x34, 0x36, 0x34, 0x2F, 0x32, 0x30, 0x2B, 0x91, 0x2F, 0xFF };
static const uint8 kPattern06[] = { 0x83, 0x34, 0x37, 0x34, 0x8F, 0x3C, 0x83, 0x3B, 0x8F, 0x3A, 0x8F, 0x36, 0xFF };
static const uint8 kPattern07[] = { 0xC1, 0x81, 0x10, 0x10, 0x12, 0x10, 0x13, 0x10, 0x15, 0x13, 0xFF };
static const uint8 kPattern08[] = { 0xC5, 0x9F, 0x7D, 0x89, 0x40, 0x7D, 0x91, 0x40, 0x7D, 0xA1, 0x40, 0x7D, 0x91, 0x40, 0xFF };
static const uint8 kPattern09[] = { 0xC0, 0x9F, 0x00, 0xFF };
static 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 };
static const uint8 kPattern11[] = { 0xC7, 0xFF };
static const uint8 kPattern12[] = { 0xC2, 0xBF, 0x10, 0xFF };
static 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 };
static 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 };
static 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 };
static 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 };
static const uint8 kPattern17[] = { 0xCC, 0xBF, 0x7D, 0x89, 0x40, 0x7C, 0xA2, 0x3E, 0x3C, 0x7D, 0x91, 0x3F, 0xFF };
static const uint8 kPattern18[] = { 0x83, 0x1C, 0xFF };
static const uint8 kPattern19[] = { 0xC0, 0x93, 0x00, 0xC1, 0x81, 0x23, 0x83, 0x21, 0x81, 0x1F, 0x83, 0x1E, 0xFF };
static 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 };
static const uint8 kPattern21[] = { 0xC8, 0xFF };
static const uint8 kPattern22[] = { 0xC4, 0xFF };
static 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 };
static 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 };
static 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 };
static const uint8 kPattern26[] = { 0xCC, 0xBF, 0x7D, 0x8A, 0x40, 0x7D, 0x91, 0x3F, 0x7D, 0x94, 0x3D, 0x7D, 0x91, 0x3F, 0xFF };
static 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 };
static 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 };

static const uint8 *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) ----

static 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
};

static 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
};

static 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
};

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

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

void DarkSideC64MusicPlayer::ChannelState::reset() {
	orderData = nullptr;
	orderPos = 0;
	patData = nullptr;
	patOffset = 0;
	instIdx = 0;
	noteActive = 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));
	portaDelta = 0;
	portaTarget = 0;
	vibPhase = 0;
	vibCounter = 0;
	pwDirection = 0;
	delayCounter = 0;
	envCounter = 0;
	envTable = 0;
	envSeqActive = false;
	sustainMode = false;
	waveform = 0;
}

DarkSideC64MusicPlayer::DarkSideC64MusicPlayer() {
	_sid = nullptr;
	_musicActive = false;
	_speedDiv = 1;
	_speedCounter = 1;
	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();

	// Set filter: LP+HP on, full volume ($5F)
	sidWrite(0x18, 0x5F);
	sidWrite(0x17, 0x00);

	_speedDiv = 1;
	_speedCounter = 1;

	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;
}

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

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

	// Increment envelope counters for all channels
	for (int ch = 0; ch < 3; ch++) {
		if (_ch[ch].envCounter < 15)
			_ch[ch].envCounter++;
	}

	// Speed counter
	_speedCounter--;
	bool newBeat = (_speedCounter == 0);
	if (newBeat)
		_speedCounter = _speedDiv;

	// Process channels (2 down to 0, matching original)
	for (int ch = 2; ch >= 0; ch--)
		processChannel(ch, newBeat);
}

void DarkSideC64MusicPlayer::processChannel(int ch, bool newBeat) {
	int off = kSIDOffset[ch];

	// Handle delay countdown
	if (_ch[ch].delayCounter > 0) {
		_ch[ch].delayCounter--;
		if (_ch[ch].delayCounter == 0 && _ch[ch].noteActive) {
			// Delay expired: gate on now
			sidWrite(off + 4, _ch[ch].waveform | 0x01);
		}
	}

	if (newBeat) {
		if (_ch[ch].durCounter > 0)
			_ch[ch].durCounter--;

		if (_ch[ch].durCounter == 0) {
			parseCommands(ch);
		}
	}

	// Apply continuous effects
	applyContinuousEffects(ch);

	// Gate-off at half duration (if not in sustain mode and not in envelope seq mode)
	if (!_ch[ch].sustainMode && !_ch[ch].envSeqActive && _ch[ch].noteActive) {
		if (_ch[ch].durReload > 1 && _ch[ch].durCounter == _ch[ch].durReload / 2) {
			sidWrite(off + 4, _ch[ch].waveform & 0xFE);
		}
	}
}

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

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

		if (cmd == 0xFF) {
			// End of pattern: load next from order list
			loadNextPattern(ch);
			continue;
		}

		if (cmd == 0xFE) {
			// End of song: stop
			stopMusic();
			return;
		}

		if (cmd == 0xFD) {
			// Filter control: read 1 parameter byte
			uint8 filterVal = readPatByte(ch);
			sidWrite(0x18, filterVal);
			sidWrite(0x17, (filterVal >> 4) | 0x07);
			continue;
		}

		if (cmd >= 0xF0) {
			// Speed: low nybble
			_speedDiv = cmd & 0x0F;
			if (_speedDiv == 0)
				_speedDiv = 1;
			continue;
		}

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

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

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

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

		if (cmd == 0x7D) {
			// Vibrato mode 1
			_ch[ch].effectMode = 1;
			_ch[ch].effectParam = readPatByte(ch);
			_ch[ch].arpPattern = 0;
			_ch[ch].vibPhase = 0;
			_ch[ch].vibCounter = 0;
			continue;
		}

		if (cmd == 0x7C) {
			// Vibrato mode 2
			_ch[ch].effectMode = 2;
			_ch[ch].effectParam = readPatByte(ch);
			_ch[ch].vibPhase = 0;
			_ch[ch].vibCounter = 0;
			continue;
		}

		if (cmd == 0x7B) {
			// Arpeggio: read 2 bytes
			uint8 arpX = readPatByte(ch);
			uint8 arpY = readPatByte(ch);
			_ch[ch].effectMode = 1;
			_ch[ch].effectParam = 0;
			_ch[ch].arpPattern = (arpX + _ch[ch].transpose) & 0xFF;
			_ch[ch].arpParam2 = arpY;
			_ch[ch].arpSeqPos = 0;
			unpackArpeggio(ch);
			continue;
		}

		if (cmd == 0x7A) {
			// Delay: read 1 byte (delay value)
			_ch[ch].delayCounter = readPatByte(ch);
			continue;
		}

		// Note (0x00-0x5F)
		applyNote(ch, cmd);
		break;
	}
}

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

void DarkSideC64MusicPlayer::applyNote(int ch, uint8 note) {
	int off = kSIDOffset[ch];
	uint8 instBase = _ch[ch].instIdx;

	if (note == 0) {
		// Rest: gate off
		_ch[ch].noteActive = 0;
		sidWrite(off + 4, _ch[ch].waveform & 0xFE);
		_ch[ch].durCounter = _ch[ch].durReload;
		return;
	}

	// Compute actual note index with transpose
	uint8 actualNote = (note + _ch[ch].transpose) & 0xFF;
	if (actualNote > 94)
		actualNote = 94;

	// Previous frequency (for portamento)
	uint16 prevFreq = ((uint16)_ch[ch].freqHi << 8) | _ch[ch].freqLo;

	// Look up new frequency
	_ch[ch].freqLo = kFreqLo[actualNote];
	_ch[ch].freqHi = kFreqHi[actualNote];
	_ch[ch].curNote = actualNote;
	_ch[ch].noteActive = 1;

	// Load instrument parameters
	uint8 ctrl = kInstruments[instBase + 0];
	uint8 ad = kInstruments[instBase + 1];
	uint8 sr = kInstruments[instBase + 2];
	uint8 vibrato = kInstruments[instBase + 4];
	uint8 autoFx = kInstruments[instBase + 6];
	uint8 flags = kInstruments[instBase + 7];

	_ch[ch].waveform = ctrl;
	_ch[ch].sustainMode = (flags & 0x08) != 0;
	_ch[ch].envSeqActive = (flags & 0x01) != 0;
	_ch[ch].envTable = (vibrato >> 4) & 0x01;

	// Write ADSR
	sidWrite(off + 5, ad);
	sidWrite(off + 6, sr);

	// Write PW
	sidWrite(off + 2, _ch[ch].pwLo);
	sidWrite(off + 3, _ch[ch].pwHi & 0x0F);

	// Write frequency
	sidWrite(off + 0, _ch[ch].freqLo);
	sidWrite(off + 1, _ch[ch].freqHi);

	// Setup portamento if active
	if (_ch[ch].effectMode == 0xDE || _ch[ch].effectMode == 0xFE) {
		uint16 newFreq = ((uint16)_ch[ch].freqHi << 8) | _ch[ch].freqLo;
		_ch[ch].portaTarget = newFreq;
		// Slide from previous frequency
		_ch[ch].freqLo = prevFreq & 0xFF;
		_ch[ch].freqHi = (prevFreq >> 8) & 0xFF;
		if (_ch[ch].durReload > 0) {
			_ch[ch].portaDelta = (int16)(newFreq - prevFreq) / (int16)_ch[ch].durReload;
		}
		sidWrite(off + 0, _ch[ch].freqLo);
		sidWrite(off + 1, _ch[ch].freqHi);
	}

	// Setup auto-effect from instrument
	if (autoFx != 0 && _ch[ch].effectMode == 0) {
		_ch[ch].effectMode = 1;
		_ch[ch].effectParam = autoFx;
	}

	// Gate on (unless delay is active)
	if (_ch[ch].delayCounter > 0) {
		// Delay: don't gate on yet, gate off first
		sidWrite(off + 4, ctrl & 0xFE);
	} else if (_ch[ch].envSeqActive) {
		// Envelope sequencer controls the gate
		_ch[ch].envCounter = 0;
		sidWrite(off + 4, kEnvControl[_ch[ch].envTable][0]);
	} else {
		sidWrite(off + 4, ctrl | 0x01);
	}

	// Reset envelope counter
	_ch[ch].envCounter = 0;

	// Set duration
	_ch[ch].durCounter = _ch[ch].durReload;
}

// ---- Continuous effects ----

void DarkSideC64MusicPlayer::applyContinuousEffects(int ch) {
	// PW modulation
	applyPWModulation(ch);

	// Frequency effects (mutually exclusive based on effectMode)
	switch (_ch[ch].effectMode) {
	case 1:
		if (_ch[ch].arpPattern != 0)
			applyArpeggio(ch);
		else
			applyVibrato(ch);
		break;
	case 2:
		applyVibrato(ch);
		break;
	case 0xDE:
	case 0xFE:
		applyPortamento(ch);
		break;
	default:
		break;
	}

	// Envelope sequencer
	if (_ch[ch].envSeqActive)
		applyEnvelope(ch);

	// Write frequency to SID
	int off = kSIDOffset[ch];
	sidWrite(off + 0, _ch[ch].freqLo);
	sidWrite(off + 1, _ch[ch].freqHi);

	// Write PW to SID
	sidWrite(off + 2, _ch[ch].pwLo);
	sidWrite(off + 3, _ch[ch].pwHi & 0x0F);
}

void DarkSideC64MusicPlayer::applyVibrato(int ch) {
	if (!_ch[ch].noteActive)
		return;

	uint8 vibParam = _ch[ch].effectParam;
	if (vibParam == 0)
		return;

	uint8 depth = vibParam >> 4;
	uint8 speed = vibParam & 0x0F;
	if (depth == 0 || speed == 0)
		return;

	uint8 note = _ch[ch].curNote;
	if (note == 0 || note >= 94)
		return;

	// Compute frequency delta between this note and the next semitone
	uint16 curFreq = ((uint16)kFreqHi[note] << 8) | kFreqLo[note];
	uint16 nextFreq = ((uint16)kFreqHi[note + 1] << 8) | kFreqLo[note + 1];
	int16 semitoneDelta = (int16)(nextFreq - curFreq);

	// Scale by depth
	int16 vibDelta = semitoneDelta * depth / 16;

	// Oscillate: counter counts up to speed, then phase flips
	_ch[ch].vibCounter++;
	if (_ch[ch].vibCounter >= speed) {
		_ch[ch].vibCounter = 0;
		_ch[ch].vibPhase ^= 1;
	}

	// Apply offset
	int16 offset = _ch[ch].vibPhase ? vibDelta : -vibDelta;
	int16 baseFreq = (int16)curFreq;
	uint16 newFreq = (uint16)(baseFreq + offset);

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

void DarkSideC64MusicPlayer::applyArpeggio(int ch) {
	if (!_ch[ch].noteActive || _ch[ch].arpSeqLen == 0)
		return;

	// Cycle through unpacked arpeggio intervals
	uint8 interval = _ch[ch].arpSeqData[_ch[ch].arpSeqPos];
	_ch[ch].arpSeqPos++;
	if (_ch[ch].arpSeqPos >= _ch[ch].arpSeqLen)
		_ch[ch].arpSeqPos = 0;

	// Compute arpeggiated note
	uint8 arpNote = _ch[ch].curNote + interval;
	if (arpNote > 94)
		arpNote = 94;

	_ch[ch].freqLo = kFreqLo[arpNote];
	_ch[ch].freqHi = kFreqHi[arpNote];
}

void DarkSideC64MusicPlayer::unpackArpeggio(int ch) {
	uint8 bits = _ch[ch].arpPattern;

	// First entry: base note (interval 0)
	_ch[ch].arpSeqData[0] = 0;
	uint8 len = 1;

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

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

void DarkSideC64MusicPlayer::applyPortamento(int ch) {
	if (!_ch[ch].noteActive)
		return;

	uint16 curFreq = ((uint16)_ch[ch].freqHi << 8) | _ch[ch].freqLo;
	int32 newFreq = (int32)curFreq + _ch[ch].portaDelta;

	// Clamp and check if target reached
	if (_ch[ch].portaDelta > 0 && (uint16)newFreq >= _ch[ch].portaTarget) {
		newFreq = _ch[ch].portaTarget;
	} else if (_ch[ch].portaDelta < 0 && (uint16)newFreq <= _ch[ch].portaTarget) {
		newFreq = _ch[ch].portaTarget;
	}

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

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

	uint8 flags = kInstruments[instBase + 7];

	uint16 pw = ((uint16)_ch[ch].pwHi << 8) | _ch[ch].pwLo;

	if (flags & 0x04) {
		// Direct add (one-directional sweep)
		pw += pwMod;
	} else {
		// Triangle sweep between pwHi=$08 and pwHi=$0F
		if (_ch[ch].pwDirection == 0) {
			pw += pwMod;
			if ((_ch[ch].pwHi + (pw >> 8)) >= 0x0F || (pw >> 8) >= 0x0F) {
				pw = ((uint16)_ch[ch].pwHi << 8) | _ch[ch].pwLo;
				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;
}

void DarkSideC64MusicPlayer::applyEnvelope(int ch) {
	if (_ch[ch].envCounter >= 15)
		return;

	int off = kSIDOffset[ch];
	uint8 tbl = _ch[ch].envTable;

	// Write waveform control from envelope table
	sidWrite(off + 4, kEnvControl[tbl][_ch[ch].envCounter]);

	// Write AD register from envelope volume table
	sidWrite(off + 5, kEnvVolume[tbl][_ch[ch].envCounter]);
}

} // namespace Freescape
