Code/SFXRComponent.cs
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
{
/// <summary>
/// The base Waveform
/// (Default: Square)
/// </summary>
[Property, Group( "Sound" )]
public Waveform Waveform { get; set; } = Waveform.Square;
/// <summary>
/// The sample rate of the sound
/// </summary>
[Property, Group( "Sound" )]
public SampleRate SampleRate { get; set; } = SampleRate.Hz44100;
/// <summary>
/// The bit depth of the sound
/// </summary>
// [Property, Group( "Sound" )]
public BitDepth BitDepth { get; set; } = BitDepth.Bit16;
/// <summary>
/// The length of the sound in seconds
/// </summary>
[Property, Group( "Sound" ), Range( 0f, 20f, 0.01f )]
public float Length { get; set; } = 0.5f;
/// <summary>
/// The volume of the sound
/// (Default: 0.5)
/// </summary>
[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;
}
/// <summary>
/// The random seed
/// </summary>
[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<SFXRNote> NotesPlaying = new();
/// <summary>
/// Plays the sound defined by the component
/// </summary>
/// <returns>The sound handle of the sound. This can be used to change position, pitch, ect</returns>
public SoundHandle PlaySound()
{
var sfx = Generate( (int)(Length * (int)SampleRate) );
var handle = sfx.Play();
// DestroyStream(sfx, Length);
return handle;
}
/// <summary>
/// Plays the sound defined by the component (Via a frequency trigger. This will play indefinitely until released)
/// </summary>
/// <param name="frequency">The frequency of the sound</param>
/// <param name="volume">The volume of the trigger </param>
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 );
}
/// <summary>
/// Releases a note playing at the given frequency
/// </summary>
/// <param name="frequency">The frequency of the sound</param>
public void TriggerNoteRelease( float frequency )
{
foreach ( var note in NotesPlaying.Where( x => x.Frequency == frequency ) )
{
note.Release();
}
}
/// <summary>
/// Releases all notes playing
/// </summary>
public void TriggerReleaseAll()
{
foreach ( var note in NotesPlaying )
{
note.Release();
}
}
/// <summary>
/// Generates a sound stream from the component
/// </summary>
/// <param name="sampleCount">How many samples the stream should be filled with</param>
/// <returns></returns>
public SoundStream Generate( int sampleCount )
{
List<SFXREffect> 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 );
}
/// <summary>
/// Generates a sound stream from the component with the given effects
/// </summary>
/// <param name="sampleCount">The number of samples</param>
/// <param name="effects">A list of the effects to apply</param>
/// <returns></returns>
public SoundStream Generate( int sampleCount, List<SFXREffect> 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;
}
/// <summary>
/// Randomizes the component's parameters
/// </summary>
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();
}
/// <summary>
/// Mutates the component's parameters slightly
/// </summary>
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<SFXREnvelope>();
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<SFXREnvelope>();
var highpass = Components.GetOrCreate<SFXRHighPass>();
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<SFXREnvelope>();
var vibrato = Components.GetOrCreate<SFXRVibrato>();
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<SFXREnvelope>();
var vibrato = Components.GetOrCreate<SFXRVibrato>();
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<SFXREnvelope>();
var highpass = Components.GetOrCreate<SFXRHighPass>();
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<SFXREnvelope>();
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<SFXREnvelope>();
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 );
}
}