ChipStudio.cs
namespace PixelPusher;

public static class ChipStudio
{
	public static readonly string[] ChannelNames = { "PULSE", "TRI", "NOISE", "DRUM" };
	public static readonly string[] NoteNames = { "", "C", "CS", "D", "DS", "E", "F", "FS", "G", "GS", "A", "AS", "B" };
	public static readonly bool[] Mute = { false, false, false, false };

	public static int CursorCh;
	public static int CursorStep;
	public static bool Playing;
	public static int Head;
	public static bool PadAim;

	static float _stepT;
	static float _padAccX;
	static float _padAccY;

	public static ChipDoc Doc => Desk.Chip;

	public static void TogglePlay()
	{
		Playing = !Playing;
		if ( Playing )
		{
			Head = 0;
			_stepT = 0;
			Fire( 0 );
		}
		GameFlow.Bump();
	}

	public static void Clear()
	{
		Doc?.Clear();
		Desk.Touch();
		GameFlow.Bump();
	}

	public static void SetBpm( int bpm )
	{
		if ( Doc is null )
			return;
		Doc.Bpm = Math.Clamp( bpm, 60, 240 );
		Desk.Touch();
		GameFlow.Bump();
	}

	public const int GridX = 88;
	public const int GridY = 72;
	public const int CellW = 32;
	public const int CellH = 62;
	public const int LabelW = 80;

	public static void ToggleMute( int ch )
	{
		if ( (uint)ch >= ChipDoc.Channels )
			return;
		Mute[ch] = !Mute[ch];
		GameFlow.Bump();
	}

	public static void DragPaint( int mx, int my, bool erase )
	{
		var ch = (my - GridY) / CellH;
		var step = (mx - GridX) / CellW;
		if ( DeskNet.GuestLocked )
			return;
		if ( (uint)ch >= ChipDoc.Channels || (uint)step >= ChipDoc.Steps || Doc is null )
			return;
		CursorCh = ch;
		CursorStep = step;
		var want = erase ? (byte)0 : (byte)8;
		if ( Doc.Get( ch, step ) == want )
			return;
		if ( erase )
			Doc.Set( ch, step, 0 );
		else if ( Doc.Get( ch, step ) == 0 )
			Doc.Set( ch, step, 8 );
		Desk.Touch();
		GameFlow.Bump();
	}

	public static void Hit( int mx, int my, bool right )
	{
		var ch = (my - GridY) / CellH;
		if ( (uint)ch >= ChipDoc.Channels )
			return;
		if ( mx < GridX )
		{
			ToggleMute( ch );
			return;
		}
		var step = (mx - GridX) / CellW;
		if ( (uint)step >= ChipDoc.Steps )
			return;
		Click( ch, step, right );
	}

	public static void NudgeCursor( int dir )
	{
		if ( Doc is null )
			return;
		Doc.Nudge( CursorCh, CursorStep, dir );
		Desk.Touch();
		ChipVoices.Tone( CursorCh, Doc.Get( CursorCh, CursorStep ) );
		GameFlow.Bump();
	}

	public static void StepCursor( int dir )
	{
		CursorStep = Math.Clamp( CursorStep + dir, 0, ChipDoc.Steps - 1 );
		GameFlow.Bump();
	}

	public static void Click( int ch, int step, bool right )
	{
		if ( Doc is null )
			return;
		if ( DeskNet.GuestLocked )
			return;
		CursorCh = ch;
		CursorStep = step;
		if ( right )
			Doc.Nudge( ch, step, 1 );
		else
			Doc.Toggle( ch, step );
		Desk.Touch();
		ChipVoices.Tone( ch, Doc.Get( ch, step ) );
		GameFlow.Bump();
	}

	public static void Tick()
	{
		if ( GameFlow.Screen != GameScreen.Chip || Doc is null )
			return;

		if ( Input.Pressed( "Jump" ) )
			TogglePlay();
		if ( Input.Pressed( "Reload" ) )
			Doc.Nudge( CursorCh, CursorStep, -1 );
		if ( Input.Pressed( "Use" ) )
			Click( CursorCh, CursorStep, false );
		if ( Input.Pressed( "View" ) )
			Click( CursorCh, CursorStep, true );

		TickPad();
		TickPlay();
	}

	static void TickPad()
	{
		var move = Input.AnalogMove;
		var ax = move.y;
		var ay = -move.x;
		if ( MathF.Abs( ax ) < 0.4f ) ax = 0;
		if ( MathF.Abs( ay ) < 0.4f ) ay = 0;
		if ( ax == 0 && ay == 0 )
		{
			_padAccX = 0;
			_padAccY = 0;
		}
		else
		{
			PadAim = true;
			_padAccX += ax * 10f * Time.Delta;
			_padAccY += ay * 8f * Time.Delta;
			while ( MathF.Abs( _padAccX ) >= 1f )
			{
				CursorStep = Math.Clamp( CursorStep + Math.Sign( _padAccX ), 0, ChipDoc.Steps - 1 );
				_padAccX -= Math.Sign( _padAccX );
				GameFlow.Bump();
			}
			while ( MathF.Abs( _padAccY ) >= 1f )
			{
				CursorCh = Math.Clamp( CursorCh + Math.Sign( _padAccY ), 0, ChipDoc.Channels - 1 );
				_padAccY -= Math.Sign( _padAccY );
				GameFlow.Bump();
			}
		}

		if ( Input.Pressed( "SlotPrev" ) )
		{
			CursorStep = Math.Max( 0, CursorStep - 1 );
			GameFlow.Bump();
		}
		if ( Input.Pressed( "SlotNext" ) )
		{
			CursorStep = Math.Min( ChipDoc.Steps - 1, CursorStep + 1 );
			GameFlow.Bump();
		}
	}

	static void TickPlay()
	{
		if ( !Playing )
			return;
		var bpm = Math.Clamp( Doc.Bpm, 60, 240 );
		var stepSec = 60f / bpm / 4f;
		_stepT += Time.Delta;
		if ( _stepT < stepSec )
			return;
		_stepT -= stepSec;
		Head = (Head + 1) % ChipDoc.Steps;
		Fire( Head );
		GameFlow.Bump();
	}

	static void Fire( int step )
	{
		for ( var ch = 0; ch < ChipDoc.Channels; ch++ )
		{
			if ( Mute[ch] )
				continue;
			ChipVoices.Tone( ch, Doc.Get( ch, step ) );
		}
	}
}