// -----------------------------------------------------------
// music.c - SID music driver
//   called once per frame from wait_frame / the main loop
//   music_init: clear SID, set instruments, restart the song
//   music_play: pattern player tick (once per frame)
//   music_mute / music_unmute: volume control
//
//   The tune is track 0 ("room work") of the Impostor TIC-80
//   cart, converted from inc/meta/meta.assets.lua:
//     voice 1: bass line (sfx 59 -> SID 50% pulse; the original
//              is a soft sine, but pulse carries better here)
//     voice 2: drums (sfx 61, TIC-80 noise -> SID noise,
//              C-1 = kick, C-4 = snare)
//     voice 3: echo canon: repeats the bass melody 2 rows
//              (~170 ms) later, 3 octaves up, with the sfx 56
//              pluck timbre (triangle, decaying envelope);
//              repeated notes bounce up one more octave. The
//              echo data lives on the same 128-row grid as the
//              bass, so the two voices can never drift apart.
//   Original timing: tempo 150, speed 5 = 12 rows/s at 60 Hz.
//   On PAL (50 Hz) we run 4 frames/row = 12.5 rows/s, a touch
//   faster than the original on purpose (see spd_tbl).
// -----------------------------------------------------------
#include <c64/sid.h>
#include "music.h"

// ---- waveform control values ----
#define BASS_ON   0x41               // pulse + gate (much louder than triangle,
#define BASS_OFF  0x40               //   so the bass stands out from the drums)
#define DRUM_ON   0x81               // noise + gate
#define DRUM_OFF  0x80
#define LEAD_ON   0x11               // triangle + gate (soft pluck, stays
#define LEAD_OFF  0x10               //   behind the bass and the drums)

#define SONG_LEN  128                // 2 patterns x 64 rows

// ---- state ----
char mus_muted;
static char mus_row;                 // song position (0..SONG_LEN-1)
static char mus_tick;                // frames left of the current row
static char mus_spd_ix;              // index into spd_tbl
static char mus_bcut;                // frames until the pre-note bass gate-off
                                     //   (0 = none scheduled)

static const char spd_tbl[6] = { 4, 4, 4, 4, 4, 4 };
                                     // 4 frames/row = 12.5 rows/s, a hair
                                     //   faster than the original 12
                                     //   (4,4,4,4,4,5 would be exact)

// ---- note tables (PAL SID: reg = Hz * 16777216 / 985248) ----
// bass: 0 = C-2, 1 = A#1, 2 = D#2, 3 = F-2
static const unsigned bass_freq[4] = { 0x045a, 0x03e0, 0x052d, 0x05cf };
// drums: noise pitch from the TIC-80 note (x32 waveform steps)
//        0 = C-1 kick (low rumble), 1 = C-4 snare (bright)
static const unsigned drum_freq[2] = { 0x459c, 0xffff };
// lead echo: bass notes +3 octaves (0-3), +4 octaves (4-7)
//   0 = C-5, 1 = A#4, 2 = D#5, 3 = F-5, 4 = C-6, 5 = A#5, 6 = D#6, 7 = F-6
static const unsigned lead_freq[8] = {
	0x22ce, 0x1f02, 0x2964, 0x2e76, 0x459c, 0x3e04, 0x52c8, 0x5ceb
};

// ---- pattern data: one byte per row per voice ----
//   0x00 = nothing, 0x01 = key off, 0x80|n = play note n
static const char bass_pat[SONG_LEN] = {
	0x80,0x00,0x00,0x00,0x01,0x00,0x80,0x00,0x01,0x00,0x00,0x00,0x81,0x00,0x80,0x00,
	0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x80,0x00,0x00,0x00,0x01,0x00,0x80,0x00,0x01,0x00,0x00,0x00,0x81,0x00,0x80,0x00,
	0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x80,0x00,0x00,0x00,0x01,0x00,0x80,0x00,0x01,0x00,0x00,0x00,0x81,0x00,0x80,0x00,
	0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x82,0x00,0x82,0x00,0x01,0x00,0x82,0x00,0x01,0x00,0x00,0x00,0x82,0x00,0x83,0x00,
	0x01,0x00,0x83,0x00,0x01,0x00,0x83,0x00,0x83,0x00,0x01,0x00,0x83,0x00,0x01,0x00
};
static const char drum_pat[SONG_LEN] = {
	0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x81,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x80,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x81,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x81,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x80,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x81,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x81,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x80,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x81,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x81,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x80,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x81,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};
// the bass_pat notes shifted 2 rows later; repeated notes take the
// octave-up variant (indices 4-7), alternating with the base octave
static const char lead_pat[SONG_LEN] = {
	0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x84,0x00,0x00,0x00,0x00,0x00,0x81,0x00,
	0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x84,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x81,0x00,
	0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x84,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x81,0x00,
	0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x82,0x00,0x86,0x00,0x00,0x00,0x82,0x00,0x00,0x00,0x00,0x00,0x86,0x00,
	0x83,0x00,0x00,0x00,0x87,0x00,0x00,0x00,0x83,0x00,0x87,0x00,0x00,0x00,0x83,0x00
};

// ---- initialize SID and restart the song ----
void music_init(void)
{
	for (char i = 0; i < 25; i++)
		((volatile char *)&sid)[i] = 0;

	sid.voices[0].attdec = 0x00;     // bass: instant attack, full sustain
	sid.voices[0].susrel = 0xf3;     // short release, so the tail does not
	                                 //   fill the rests between the notes
	sid.voices[0].pwm    = 0x0800;   // bass pulse width: 50% square, the
	                                 //   fattest setting ($0800 of $0fff)
	sid.voices[1].attdec = 0x04;     // drums: instant attack, ~110 ms decay
	sid.voices[1].susrel = 0x00;
	sid.voices[2].attdec = 0x06;     // lead: pluck, ~200 ms decay to zero
	sid.voices[2].susrel = 0x00;     //   (matches the sfx 56 envelope)

	sid.fmodevol = 0x0f;             // master volume = 15

	mus_muted  = 0;
	mus_row    = 0;
	mus_spd_ix = 0;
	mus_bcut   = 0;
	mus_tick   = 1;                  // first music_play tick starts row 0
}

// ---- play one tick: called once per frame ----
void music_play(void)
{
	if (mus_muted)                   // muted: song position frozen too
		return;

	if (mus_bcut)                    // scheduled bass gate-off: opens a small
	{                                //   gap before the next note, so the
		mus_bcut--;                  //   notes don't run into each other
		if (mus_bcut == 0)
			sid.voices[0].ctrl = BASS_OFF;
	}

	mus_tick--;
	if (mus_tick)
		return;

	mus_tick = spd_tbl[mus_spd_ix];  // reload row length
	mus_spd_ix++;
	if (mus_spd_ix == 6)
		mus_spd_ix = 0;

	char row = mus_row;

	// ---- voice 1: bass ----
	char ev = bass_pat[row];
	if (ev == 1)                     // key off
		sid.voices[0].ctrl = BASS_OFF;
	else if (ev)                     // 0x80|n = trigger note n
	{
		sid.voices[0].freq = bass_freq[ev & 0x7f];
		sid.voices[0].ctrl = BASS_OFF;   // gate off + on restarts the envelope
		sid.voices[0].ctrl = BASS_ON;
	}

	// ---- voice 2: drums ----
	ev = drum_pat[row];
	if (ev == 1)
		sid.voices[1].ctrl = DRUM_OFF;
	else if (ev)
	{
		sid.voices[1].freq = drum_freq[ev & 0x7f];
		sid.voices[1].ctrl = DRUM_OFF;
		sid.voices[1].ctrl = DRUM_ON;
	}

	// ---- voice 3: echo lead ----
	ev = lead_pat[row];              // only notes here, no key-off events:
	if (ev)                          //   the envelope decays to silence
	{
		sid.voices[2].freq = lead_freq[ev & 0x7f];
		sid.voices[2].ctrl = LEAD_OFF;
		sid.voices[2].ctrl = LEAD_ON;
	}

	row++;
	if (row == SONG_LEN)             // loop the song
	{
		row = 0;
		mus_spd_ix = 0;              // restart the speed cycle with it
	}
	mus_row = row;

	if (bass_pat[row] & 0x80)        // peek at the next row: if a note comes,
		mus_bcut = mus_tick - 2;     //   cut the bass 2 frames before it
}

// ---- mute: silence SID ----
void music_mute(void)
{
	sid.fmodevol = 0x00;
}

// ---- unmute: restore volume ----
void music_unmute(void)
{
	sid.fmodevol = 0x0f;
}
