using System; using System.Collections.Generic; using System.IO; using System.Linq; using Sandbox; namespace SFXR; [Title( "SFXR Component" )] [Category( "SFXR" )] [Icon( "volume_up" )] public sealed class SFXRComponent : Component { /// /// The base Waveform /// (Default: Square) /// [Property, Group( "Sound" )] public Waveform Waveform { get; set; } = Waveform.Square; /// /// The sample rate of the sound /// [Property, Group( "Sound" )] public SampleRate SampleRate { get; set; } = SampleRate.Hz44100; /// /// The bit depth of the sound /// // [Property, Group( "Sound" )] public BitDepth BitDepth { get; set; } = BitDepth.Bit16; /// /// The length of the sound in seconds /// [Property, Group( "Sound" ), Range( 0f, 20f, 0.01f )] public float Length { get; set; } = 0.5f; /// /// The volume of the sound /// (Default: 0.5) /// [Property, Group( "Sound" ), Range( 0f, 1f, 0.01f )] public float MasterVolume { get; set; } = 0.5f; [Property, Group( "Frequency" ), Range( 0, 3000f, 1f )] float StartFrequency { get => Frequency.Start; set => Frequency.Start = value; } [Property, Group( "Frequency" ), Range( -3000f, 3000f, 1f )] float Slide { get => Frequency.Slide; set => Frequency.Slide = value; } [Property, Group( "Frequency" ), Range( -3000f, 3000f, 1f )] float SlideDelta { get => Frequency.DeltaSlide; set => Frequency.DeltaSlide = value; } /// /// The random seed /// [Property, Group( "Controls" )] public long Seed { get; set; } = 0; [Property, Group( "Controls" )] public SFXRControls Controls { get; set; } = new SFXRControls(); public SFXRFrequency Frequency { get; set; } = new SFXRFrequency(); Random _random = new Random(); List NotesPlaying = new(); /// /// Plays the sound defined by the component /// /// The sound handle of the sound. This can be used to change position, pitch, ect public SoundHandle PlaySound() { var sfx = Generate( (int)(Length * (int)SampleRate) ); var handle = sfx.Play(); // DestroyStream(sfx, Length); return handle; } /// /// Plays the sound defined by the component (Via a frequency trigger. This will play indefinitely until released) /// /// The frequency of the sound /// The volume of the trigger public void TriggerNotePress( float frequency, float volume = 1f ) { foreach ( var note in NotesPlaying.Where( x => x.Frequency == frequency ) ) { note.Release(); } var newNote = new SFXRNote( this, frequency, volume ); newNote.Trigger(); NotesPlaying.Add( newNote ); } /// /// Releases a note playing at the given frequency /// /// The frequency of the sound public void TriggerNoteRelease( float frequency ) { foreach ( var note in NotesPlaying.Where( x => x.Frequency == frequency ) ) { note.Release(); } } /// /// Releases all notes playing /// public void TriggerReleaseAll() { foreach ( var note in NotesPlaying ) { note.Release(); } } /// /// Generates a sound stream from the component /// /// How many samples the stream should be filled with /// public SoundStream Generate( int sampleCount ) { List effects = new(); foreach ( var component in GameObject.Components.GetAll() ) { if ( component is not SFXREffect effect || !effect.Enabled ) continue; effects.Add( effect ); } return Generate( sampleCount, effects ); } /// /// Generates a sound stream from the component with the given effects /// /// The number of samples /// A list of the effects to apply /// public SoundStream Generate( int sampleCount, List effects ) { short[] samples = new short[sampleCount]; float t = 0; for ( int i = 0; i < sampleCount; i++ ) { t += 1f / (int)SampleRate; short sampleValue = SFXR.GetWaveformSample( Waveform, t, Frequency.GetFrequency( t ) ); sampleValue = (short)((float)sampleValue * MasterVolume); samples[i] = sampleValue; } foreach ( var effect in effects ) { if ( !effect.Enabled ) continue; samples = effect.Apply( samples, this ); } var stream = new SoundStream( (int)SampleRate ); stream.WriteData( samples ); return stream; } /// /// Randomizes the component's parameters /// public void Randomize() { if ( Seed != 0 ) _random = new Random( (int)Seed ); var waveform = Waveform; ResetParameters(); Waveform = waveform; Frequency.Start = _random.Next( 10, 3000 ); if ( _random.Next( 2 ) == 0 ) Frequency.Slide = _random.Next( -3000, 3000 ); if ( Frequency.Start > 2000 && Frequency.Slide > 200 ) Frequency.Slide = -Frequency.Slide; else if ( Frequency.Start < 400 && Frequency.Slide < -50 ) Frequency.Slide = -Frequency.Slide; if ( _random.Next( 2 ) == 0 ) Frequency.DeltaSlide = _random.Next( -3000, 3000 ); SanitizeParameters(); } /// /// Mutates the component's parameters slightly /// public void Mutate( float mutation = 0.05f ) { if ( Seed != 0 ) _random = new Random( (int)Seed ); Frequency.Start += _random.Float( -mutation, mutation ) * 1000; if ( Frequency.Start > 2000 && Frequency.Slide > 200 ) Frequency.Slide = -Frequency.Slide; else if ( Frequency.Start < 400 && Frequency.Slide < -50 ) Frequency.Slide = -Frequency.Slide; Frequency.Slide += _random.Float( -mutation, mutation ) * 1000; Frequency.DeltaSlide += _random.Float( -mutation, mutation ) * 1000; if ( Frequency.Slide < -3000 ) Frequency.Slide = -3000; if ( Frequency.Slide > 3000 ) Frequency.Slide = 3000; SanitizeParameters(); } public void RandomizePickup() { if ( Seed != 0 ) _random = new Random( (int)Seed ); ResetParameters(); foreach ( var component in GameObject.Components.GetAll() ) { if ( component is not SFXREffect effect ) continue; effect.Enabled = false; } var envelope = Components.GetOrCreate(); Waveform = (Waveform)_random.Int( 0, 2 ); Frequency.Start = _random.Float( 0.4f, 0.9f ) * 3000; envelope.Enabled = true; envelope.Attack = 0; envelope.Decay = _random.Float( 0.1f, 0.3f ); envelope.Sustain = _random.Float( 0f, 0.1f ); envelope.Release = _random.Float( 0.1f, 0.3f ); Length = envelope.Attack + envelope.Sustain + envelope.Decay + envelope.Release; } public void RandomizeLaser() { if ( Seed != 0 ) _random = new Random( (int)Seed ); ResetParameters(); foreach ( var component in GameObject.Components.GetAll() ) { if ( component is not SFXREffect effect ) continue; effect.Enabled = false; } var envelope = Components.GetOrCreate(); var highpass = Components.GetOrCreate(); Waveform = (Waveform)_random.Int( 0, 2 ); if ( Waveform == Waveform.Sine && _random.Next( 2 ) == 0 ) Waveform = (Waveform)_random.Int( 0, 1 ); Frequency.Start = _random.Float( 0.6f, 0.75f ) * 3000; Frequency.Slide = _random.Float( -0.25f, -0.15f ) * 3000; envelope.Enabled = true; envelope.Attack = 0; envelope.Decay = _random.Float( 0f, 0.4f ); envelope.Sustain = _random.Float( 0.1f, 0.3f ); envelope.Release = _random.Float( 0.25f, 0.3f ); Length = envelope.Attack + envelope.Sustain + envelope.Decay + envelope.Release; if ( _random.Next( 2 ) == 0 ) { highpass.Enabled = true; highpass.Cutoff = _random.Float( 0f, 0.3f ); } } public void RandomizeExplosion() { if ( Seed != 0 ) _random = new Random( (int)Seed ); ResetParameters(); foreach ( var component in GameObject.Components.GetAll() ) { if ( component is not SFXREffect effect ) continue; effect.Enabled = false; } var envelope = Components.GetOrCreate(); var vibrato = Components.GetOrCreate(); Waveform = Waveform.Noise; if ( _random.Next( 2 ) == 0 ) { Frequency.Start = _random.Float( 0.025f, 0.15f ) * 3000; Frequency.Slide = _random.Float( -0.1f, -0.01f ) * 3000; } else { Frequency.Start = _random.Float( 0.1f, 0.2f ) * 3000; Frequency.Slide = _random.Float( -0.6f, 0.6f ) * 3000; } if ( _random.Next( 4 ) == 0 ) Frequency.Slide = 0; envelope.Enabled = true; envelope.Attack = 0; envelope.Sustain = _random.Float( 0.1f, 0.4f ); envelope.Release = _random.Float( 0.1f, 0.3f ); Length = envelope.Attack + envelope.Sustain + envelope.Decay + envelope.Release; if ( _random.Next( 2 ) == 0 ) { vibrato.Enabled = true; vibrato.Depth = _random.Float( 0f, 0.7f ); vibrato.Speed = _random.Float( 0f, 60f ); } else { vibrato.Enabled = false; } if ( -Frequency.Slide > Frequency.Start ) { Frequency.Slide = -Frequency.Start; } } public void RandomizePowerup() { if ( Seed != 0 ) _random = new Random( (int)Seed ); ResetParameters(); foreach ( var component in GameObject.Components.GetAll() ) { if ( component is not SFXREffect effect ) continue; effect.Enabled = false; } var envelope = Components.GetOrCreate(); var vibrato = Components.GetOrCreate(); if ( _random.Next( 2 ) == 0 ) { Waveform = Waveform.Sawtooth; } if ( _random.Next( 2 ) == 0 ) { Frequency.Start = _random.Float( 0.2f, 0.5f ) * 3000; Frequency.Slide = _random.Float( 0.1f, 0.5f ) * 3000; } else { Frequency.Start = _random.Float( 0.25f, 0.5f ) * 3000; Frequency.Slide = _random.Float( 0.05f, 0.25f ) * 3000; if ( _random.Next( 2 ) == 0 ) { vibrato.Enabled = true; vibrato.Depth = _random.Float( 0, 0.7f ); vibrato.Speed = _random.Float( 0, 60f ); } else { vibrato.Enabled = false; } } if ( -Frequency.Slide > Frequency.Start ) { Frequency.Slide = -Frequency.Start; } envelope.Enabled = true; envelope.Attack = 0; envelope.Sustain = _random.Float( 0f, 0.4f ); envelope.Release = _random.Float( 0.1f, 0.5f ); Length = envelope.Attack + envelope.Sustain + envelope.Decay + envelope.Release; } public void RandomizeHit() { if ( Seed != 0 ) _random = new Random( (int)Seed ); ResetParameters(); foreach ( var component in GameObject.Components.GetAll() ) { if ( component is not SFXREffect effect ) continue; effect.Enabled = false; } var envelope = Components.GetOrCreate(); var highpass = Components.GetOrCreate(); Waveform = (Waveform)_random.Int( 0, 3 ); if ( Waveform == Waveform.Sine ) { Waveform = Waveform.Noise; } Frequency.Start = _random.Float( 0.1f, 0.5f ) * 3000; Frequency.Slide = _random.Float( -0.7f, -0.3f ) * 3000; if ( -Frequency.Slide > Frequency.Start ) { Frequency.Slide = -Frequency.Start; } envelope.Enabled = true; envelope.Attack = 0; envelope.Decay = 0; envelope.Sustain = _random.Float( 0.025f, 0.1f ); envelope.Release = _random.Float( 0.1f, 0.3f ); Length = envelope.Attack + envelope.Sustain + envelope.Decay + envelope.Release; if ( _random.Next( 2 ) == 0 ) { highpass.Enabled = true; highpass.Cutoff = _random.Float( 0f, 0.3f ); } else { highpass.Enabled = false; } } public void RandomizeJump() { if ( Seed != 0 ) _random = new Random( (int)Seed ); ResetParameters(); foreach ( var component in GameObject.Components.GetAll() ) { if ( component is not SFXREffect effect ) continue; effect.Enabled = false; } var envelope = Components.GetOrCreate(); Waveform = Waveform.Square; Frequency.Start = _random.Float( 0.3f, 0.6f ) * 3000; Frequency.Slide = _random.Float( 0.1f, 0.3f ) * 3000; if ( -Frequency.Slide > Frequency.Start ) { Frequency.Slide = -Frequency.Start; } envelope.Enabled = true; envelope.Attack = 0; envelope.Sustain = _random.Float( 0.1f, 0.4f ); envelope.Release = _random.Float( 0.1f, 0.3f ); Length = envelope.Attack + envelope.Sustain + envelope.Decay + envelope.Release; } public void RandomizeBlip() { if ( Seed != 0 ) _random = new Random( (int)Seed ); ResetParameters(); foreach ( var component in GameObject.Components.GetAll() ) { if ( component is not SFXREffect effect ) continue; effect.Enabled = false; } var envelope = Components.GetOrCreate(); Waveform = Waveform.Square; Frequency.Start = _random.Float( 0.2f, 0.6f ) * 3000; envelope.Enabled = true; envelope.Attack = 0; envelope.Decay = _random.Float( 0.1f, 0.2f ); envelope.Sustain = _random.Float( 0.025f, 0.1f ); envelope.Release = _random.Float( 0.1f, 0.3f ); Length = envelope.Attack + envelope.Sustain + envelope.Decay + envelope.Release; } public void ResetParameters() { Waveform = Waveform.Square; SampleRate = SampleRate.Hz44100; BitDepth = BitDepth.Bit16; Length = 0.5f; MasterVolume = 0.5f; Frequency = new SFXRFrequency(); Controls = new SFXRControls(); } void SanitizeParameters() { } protected override void OnUpdate() { foreach ( var note in NotesPlaying ) { note.Update(); // if (!note.IsPlaying) // { // note.DestroyStreams(); // } } NotesPlaying.RemoveAll( x => !x.IsPlaying ); } }