Code/Demos/BeatPad/RackTracks.cs
using System.Collections.Generic;

namespace Sandbox.BeatPad;

// ordered set of active rack tracks, separate from the StepPattern hit grid so emptied tracks render until explicitly removed. no engine types; compiles headless.
public sealed class RackTracks
{
    readonly List<int> _order = new();
    readonly HashSet<int> _muted = new();
    readonly HashSet<int> _soloed = new();

    public int Count => _order.Count;
    public IReadOnlyList<int> Order => _order;

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

    public bool Contains(int pad) => _order.Contains(pad);

    public void EnsureTrack(int pad)
    {
        if (!InRange(pad) || _order.Contains(pad)) return;
        _order.Add(pad);
    }

    public void Remove(int pad)
    {
        _order.Remove(pad);
        _muted.Remove(pad);
        _soloed.Remove(pad);
    }

    public void Clear()
    {
        _order.Clear();
        _muted.Clear();
        _soloed.Clear();
    }

    public bool IsMuted(int pad) => _muted.Contains(pad);
    public bool IsSoloed(int pad) => _soloed.Contains(pad);
    public bool AnySolo => _soloed.Count > 0;

    public void ToggleMute(int pad)
    {
        if (!InRange(pad)) return;
        if (!_muted.Add(pad)) _muted.Remove(pad);
    }

    public void ToggleSolo(int pad)
    {
        if (!InRange(pad)) return;
        if (!_soloed.Add(pad)) _soloed.Remove(pad);
    }

    // A track sounds unless muted; if any track is soloed, only soloed tracks sound.
    // Mute beats solo on the same track.
    public bool ShouldPlay(int pad) => !IsMuted(pad) && (!AnySolo || IsSoloed(pad));
}