Demos/BeatPad/StepPattern.cs
using System.Collections.Generic;

namespace Sandbox.BeatPad;

// The recorded pattern: 16 steps, each holding a set of pad indices. No timing
// knowledge -- the clock tells it which step is playing. Backed by a dense bool
// grid so there is no per-frame allocation. Pad count tracks the kit (16).
public sealed class StepPattern
{
    public const int Steps = QuantizeMath.StepsPerBar; // 16
    public static readonly int Pads = BeatPadKits.PadCount; // 16

    readonly bool[,] _hits = new bool[Steps, BeatPadKits.PadCount];

    static bool InRange(int step, int pad)
        => step >= 0 && step < Steps && pad >= 0 && pad < BeatPadKits.PadCount;

    public void Record(int step, int pad)
    {
        if (InRange(step, pad)) _hits[step, pad] = true;
    }

    // Flip the cell at (step, pad) for click-to-edit. Returns the new state
    // (true = now on). Out-of-range is ignored and returns false.
    public bool Toggle(int step, int pad)
    {
        if (!InRange(step, pad)) return false;
        bool now = !_hits[step, pad];
        _hits[step, pad] = now;
        return now;
    }

    public bool Has(int step, int pad) => InRange(step, pad) && _hits[step, pad];

    public bool HasAnyAt(int step)
    {
        if (step < 0 || step >= Steps) return false;
        for (int pad = 0; pad < BeatPadKits.PadCount; pad++)
            if (_hits[step, pad]) return true;
        return false;
    }

    // Append the pad indices recorded at this step (ascending) into `into`. Caller
    // clears/reuses the list to avoid allocation in the playback hot path.
    public void CollectPadsAt(int step, List<int> into)
    {
        into.Clear();
        if (step < 0 || step >= Steps) return;
        for (int pad = 0; pad < BeatPadKits.PadCount; pad++)
            if (_hits[step, pad]) into.Add(pad);
    }

    public void Erase(int pad)
    {
        if (pad < 0 || pad >= BeatPadKits.PadCount) return;
        for (int step = 0; step < Steps; step++) _hits[step, pad] = false;
    }

    public void ClearAll()
    {
        for (int step = 0; step < Steps; step++)
            for (int pad = 0; pad < BeatPadKits.PadCount; pad++)
                _hits[step, pad] = false;
    }
}