ChipDoc.cs
namespace PixelPusher;

public sealed class ChipDoc
{
	public const int Channels = 4;
	public const int Steps = 16;

	public string Name { get; set; } = "cooler loop";
	public int Bpm { get; set; } = 112;
	public int Version { get; private set; }
	public byte[] Notes { get; set; }

	public ChipDoc()
	{
		Notes = new byte[Channels * Steps];
		StockCoolerHook();
	}

	public byte Get( int ch, int step )
	{
		if ( (uint)ch >= Channels || (uint)step >= Steps )
			return 0;
		return Notes[ch * Steps + step];
	}

	public void Set( int ch, int step, byte note )
	{
		if ( (uint)ch >= Channels || (uint)step >= Steps )
			return;
		Notes[ch * Steps + step] = note;
		Version++;
	}

	public void Toggle( int ch, int step )
	{
		var n = Get( ch, step );
		Set( ch, step, n == 0 ? (byte)8 : (byte)0 );
	}

	public void Nudge( int ch, int step, int dir )
	{
		var n = Get( ch, step );
		if ( n == 0 )
			n = 8;
		else
			n = (byte)Math.Clamp( n + dir, 1, 12 );
		Set( ch, step, n );
	}

	public void Clear()
	{
		Array.Clear( Notes );
		Version++;
	}

	public ChipSave ToSave() => new()
	{
		Name = Name,
		Bpm = Bpm,
		Notes = PixelDoc.CopyBytes( Notes )
	};

	public static ChipDoc FromSave( ChipSave save )
	{
		var doc = new ChipDoc();
		if ( save is null )
			return doc;
		if ( !string.IsNullOrWhiteSpace( save.Name ) )
			doc.Name = save.Name;
		if ( save.Bpm > 0 )
			doc.Bpm = Math.Clamp( save.Bpm, 60, 240 );
		if ( save.Notes is { Length: > 0 } )
		{
			var n = Math.Min( doc.Notes.Length, save.Notes.Length );
			Array.Copy( save.Notes, doc.Notes, n );
		}
		return doc;
	}

	void StockCoolerHook()
	{
		// Pulse bass
		Set( 0, 0, 5 );
		Set( 0, 4, 5 );
		Set( 0, 8, 8 );
		Set( 0, 12, 5 );
		// Triangle hook
		Set( 1, 2, 10 );
		Set( 1, 6, 12 );
		Set( 1, 10, 10 );
		Set( 1, 14, 8 );
		// Noise hats
		Set( 2, 4, 6 );
		Set( 2, 12, 6 );
		// Drum
		Set( 3, 0, 8 );
		Set( 3, 8, 8 );
		Set( 3, 10, 4 );
	}
}

public sealed class ChipSave
{
	public string Name { get; set; }
	public int Bpm { get; set; }
	public byte[] Notes { get; set; }
}