Code/SFXRNote.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using Sandbox;

namespace SFXR;

public class SFXRNote
{
    public bool IsTriggered { get; private set; } = false;
    public bool IsPlaying => CurrentTime < EndTime;
    List<Part> Parts = new();
    SoundHandle TriggeredHandle;

    SFXRComponent Sfxr;
    public float Frequency;
    public float Volume;

    public float CurrentTime = 0f;
    float LoopStartTime = 0f;
    float LoopEndTime = 0f;
    float EndTime = 0f;

    public SFXRNote( SFXRComponent sfxr, float frequency, float volume )
    {
        Sfxr = sfxr;
        Frequency = frequency;
        Volume = volume;
    }

    public void Trigger()
    {
        if ( IsTriggered )
        {
            TriggeredHandle.Stop( 0 );
            Parts.Clear();
        }

        IsTriggered = true;

        var effects = new List<SFXREffect>();
        foreach ( var component in Sfxr.GameObject.Components.GetAll() )
        {
            if ( component is not SFXREffect effect || !effect.Enabled ) continue;
            effects.Add( effect );
        }

        int sampleRate = (int)Sfxr.SampleRate;

        SFXREnvelope envelope = null;
        if ( effects.FirstOrDefault( x => x is SFXREnvelope ) is SFXREnvelope env )
        {
            envelope = env;
            effects.Remove( env );

            float cycle = 1f / Frequency;
            int sampleCount = (int)((envelope.GetLength() + cycle) * sampleRate);
            short[] samples = new short[sampleCount];
            float t = 0f;
            for ( int i = 0; i < sampleCount; i++ )
            {
                t += 1f / sampleRate;
                short sampleValue = SFXR.GetWaveformSample( Sfxr.Waveform, t, Frequency );

                sampleValue = (short)((float)sampleValue * Sfxr.MasterVolume * Volume);

                samples[i] = sampleValue;
            }

            foreach ( var effect in effects )
            {
                if ( !effect.Enabled ) continue;
                samples = effect.Apply( samples, Sfxr );
            }

            envelope?.Apply( samples, Sfxr );

            // Attack Stream
            int attackSampleCount = (int)(envelope.Attack * sampleRate);
            short[] attackSamples = new short[attackSampleCount];
            for ( int i = 0; i < attackSampleCount; i++ )
            {
                attackSamples[i] = samples[i];
            }
            Parts.Add( new Part( attackSamples, 0, 0 ) );

            // Decay Stream
            int decaySampleCount = (int)(envelope.Decay * sampleRate);
            short[] decaySamples = new short[decaySampleCount];
            for ( int i = 0; i < decaySampleCount; i++ )
            {
                decaySamples[i] = samples[i + attackSampleCount];
            }
            Parts.Add( new Part( decaySamples, envelope.Attack, 1 ) );

            // Sustain Stream
            int sustainSampleCount = (int)(envelope.SustainTime * sampleRate);
            short[] sustainSamples = new short[sustainSampleCount];
            for ( int i = 0; i < sustainSampleCount; i++ )
            {
                sustainSamples[i] = samples[i + attackSampleCount + decaySampleCount];
            }
            Parts.Add( new Part( sustainSamples, envelope.Attack + envelope.Decay, 2 ) );

            // Release Stream
            int releaseSampleCount = (int)(envelope.Release * sampleRate);
            short[] releaseSamples = new short[releaseSampleCount];
            for ( int i = 0; i < releaseSampleCount; i++ )
            {
                releaseSamples[i] = samples[i + attackSampleCount + decaySampleCount + sustainSampleCount];
            }
            Parts.Add( new Part( releaseSamples, envelope.Attack + envelope.Decay + envelope.SustainTime, 3 ) );

            LoopStartTime = envelope.Attack + envelope.Decay;
            LoopEndTime = envelope.Attack + envelope.Decay + envelope.SustainTime;
            EndTime = envelope.Attack + envelope.Decay + envelope.SustainTime + envelope.Release;
        }
        else
        {
            int sampleCount = (int)(Sfxr.Length * sampleRate);
            short[] samples = new short[sampleCount];
            float t = 0f;
            for ( int i = 0; i < sampleCount; i++ )
            {
                t += 1f / sampleRate;
                short sampleValue = SFXR.GetWaveformSample( Sfxr.Waveform, t, Frequency );

                sampleValue = (short)((float)sampleValue * Sfxr.MasterVolume * Volume);

                samples[i] = sampleValue;
            }

            foreach ( var effect in effects )
            {
                if ( !effect.Enabled ) continue;
                samples = effect.Apply( samples, Sfxr );
            }

            LoopStartTime = 0f;
            LoopEndTime = Sfxr.Length;
            EndTime = Sfxr.Length;


            Parts.Add( new Part( samples, 0, 2 ) );
        }

    }

    public void Release()
    {
        if ( !IsTriggered ) return;

        TriggeredHandle?.Stop();
        var last = Parts.LastOrDefault();
        Parts.Clear();
        if ( last is not null ) Parts.Add( last );
        CurrentTime = LoopEndTime;

        IsTriggered = false;
    }

    public void Update()
    {
        CurrentTime += Time.Delta;
        if ( CurrentTime >= LoopEndTime && IsTriggered )
        {
            CurrentTime = LoopStartTime;

            foreach ( var part in Parts )
            {
                if ( part.Index == 2 )
                {
                    part.Played = false;
                }
            }
        }

        foreach ( var part in Parts )
        {
            if ( part.Played ) continue;

            if ( CurrentTime >= part.Time )
            {
                TriggeredHandle = part.Play( (int)Sfxr.SampleRate );
                part.Played = true;
            }
        }

    }

    // public void DestroyStreams()
    // {
    //     TriggeredHandle.Stop(0);
    //     Parts.Clear();
    // }

    internal class Part
    {
        public short[] Samples;
        public float Time;
        public bool Played = false;
        public int Index = 0;

        public Part( short[] samples, float time, int index = 0 )
        {
            Samples = samples;
            Time = time;
            Index = index;
        }

        public SoundHandle Play( int sampleRate )
        {
            var stream = new SoundStream( sampleRate );
            stream.WriteData( Samples );
            return stream.Play();
        }
    }

}