Code/SFXRSequencer.cs
using System.Collections.Generic;
using System.Linq;
using Sandbox;

namespace SFXR;

[Title( "SFXR Sequencer" )]
[Category( "SFXR" )]
[Icon( "view_comfy" )]
public class SFXRSequencer : Component
{
    [Property, Group( "Settings" )]
    public float BPM { get; set; } = 120;

    [Property, Group( "Settings" )]
    public int BeatsPerBar { get; set; } = 4;

    [Property, Group( "Notes" )]
    public List<Note> Notes { get; set; } = new();

    [Property, Group( "Controls" )]
    public SFXRSequencerControls Controls { get; set; } = new();

    public bool IsPlaying { get; private set; } = false;

    public float CurrentTime { get; private set; } = 0f;

    SFXRComponent Sfxr;
    List<Note> NotesToPlay = new();
    List<Note> NotesToStop = new();

    protected override void OnDisabled()
    {
        Sfxr.TriggerReleaseAll();
        StopSequence();
    }

    protected override void OnUpdate()
    {
        if ( !IsPlaying ) return;

        Sfxr ??= GameObject.Components.Get<SFXRComponent>();

        if ( Sfxr is null )
        {
            Log.Error( "SFXRSequencer requires an SFXRComponent to be attached to the same GameObject" );
            return;
        }

        CurrentTime += Time.Delta;

        for ( int i = 0; i < NotesToStop.Count; i++ )
        {
            var note = NotesToStop[i];
            var time = BeatsToSeconds( note.Time + note.Length );
            if ( CurrentTime >= time )
            {
                Sfxr.TriggerNoteRelease( note.Frequency );
                NotesToStop.RemoveAt( i );
                i--;
            }
        }

        for ( int i = 0; i < NotesToPlay.Count; i++ )
        {
            var note = NotesToPlay[i];
            var time = BeatsToSeconds( note.Time );
            if ( CurrentTime >= time )
            {
                // Sfxr.Frequency.Start = note.Frequency;
                // Sfxr.Length = BeatsToSeconds( note.Length );
                // Sfxr.MasterVolume = note.Volume;
                Sfxr.TriggerNotePress( note.Frequency, note.Volume );

                NotesToStop.Add( note );
                NotesToPlay.RemoveAt( i );
                i--;
            }
        }

        if ( NotesToPlay.Count == 0 && NotesToStop.Count == 0 )
        {
            IsPlaying = false;
        }
    }

    public void PlaySequence()
    {
        if ( Sfxr is null )
        {
            Sfxr = GameObject.Components.Get<SFXRComponent>();
        }
        if ( !Sfxr.Enabled ) return;

        IsPlaying = true;
        CurrentTime = 0f;

        NotesToPlay.Clear();
        NotesToPlay.AddRange( Notes );
    }

    public void StopSequence()
    {
        IsPlaying = false;
        CurrentTime = 0f;
    }

    public void PlayRange( float start, float end )
    {
        IsPlaying = true;
        CurrentTime = BeatsToSeconds( start );

        Sfxr.TriggerReleaseAll();
        NotesToStop.Clear();

        NotesToPlay.Clear();
        NotesToPlay.AddRange( Notes.Where( x => x.Time >= start && x.Time <= end ) );
    }

    float BeatsToSeconds( float beats )
    {
        return beats * (60f / BPM / BeatsPerBar);
    }

    public class Note
    {
        [Property] public float Time { get; set; }
        [Property] public float Frequency { get; set; }
        [Property] public float Length { get; set; }
        [Property] public float Volume { get; set; }

        public Note()
        {
            Time = 0;
            Frequency = 440;
            Length = 1f;
            Volume = 1f;
        }
    }

}