using Godot; using System.Reflection; using System.Collections.Generic; using System.Text; namespace Rokojori { public class DuoWaveTable { float[] _samples; float _lowPitch; float _highPitch; int _maxSamples; float _weight = 0.5f; public DuoWaveTable( float[] samples, float lowPitch, float highPitch ) { _samples = samples; _lowPitch = lowPitch; _highPitch = highPitch; _maxSamples = samples.Length / 2; } public static DuoWaveTable FromWaveTables( WaveTable low, WaveTable high, float lowPitch, float highPitch ) { var samples = new float[ low.samples.Length * 2 ]; for ( int i = 0; i < low.samples.Length; i++ ) { samples[ i * 2 ] = low.samples[ i ]; } for ( int i = 0; i < high.samples.Length; i++ ) { samples[ i * 2 + 1 ] = high.samples[ i ] - low.samples[ i ]; } return new DuoWaveTable( samples, lowPitch, highPitch ); } public float ComputeWeightForPitch( float pitch ) { return MathX.NormalizeClamped( pitch, _lowPitch, _highPitch ); } public void SetWeight( float weight ) { this._weight = weight; } public void SetWeightForPitch( float pitch ) { SetWeight( ComputeWeightForPitch( pitch ) ); } public float Get( float phase ) { var max = _maxSamples; var floatingSamplePosition = phase * max; var p0 = (int) floatingSamplePosition; var p1 = p0 == ( max - 1 ) ? 0 : ( p0 + 1 ); var mixAmount = floatingSamplePosition - p0; var p0Index = p0 << 1; var p1Index = p1 << 1; var low0 = _samples[ p0Index ]; var diff0 = _samples[ p0Index + 1 ]; var low1 = _samples[ p1Index ]; var diff1 = _samples[ p1Index + 1 ]; var mixed0 = low0 + _weight * diff0; var mixed1 = low1 + _weight * diff1; return mixed0 + mixAmount * ( mixed1 - mixed0 ); } } public class BandLimitedWaveTable:iPhaseGenerator { List entries = new List(); int entriesPerOctave = 3; float lowestPitch; float highestPitch; int _activeEntryIndex = 0; DuoWaveTable _activeEntry; public int GetEntryForPitch( float pitch ) { var entry = Mathf.Min( MathX.RemapClamped( pitch, lowestPitch, highestPitch, 0, entries.Count ), entries.Count - 1 ); return (int) entry; } public void SetPitchRange( float minPitch, float maxPitch ) { _activeEntryIndex = GetEntryForPitch( maxPitch ); _activeEntry = entries[ _activeEntryIndex ]; _activeEntry.SetWeightForPitch( maxPitch ); } public float Get( float phase ) { return _activeEntry.Get( phase ); } public BandLimitedWaveTable FromSines( Vector2[] sines, int size = 4096, int entriesPerOctave = 3, float lowestPitch = 0 ) { var blwt = new BandLimitedWaveTable(); blwt.entriesPerOctave = entriesPerOctave; blwt.lowestPitch = lowestPitch; float frequency = MathAudio.PitchToFrequency( blwt.lowestPitch ); float maxFrequency = 18000; while ( frequency < maxFrequency ) { for ( int i = 0; i < entriesPerOctave; i++ ) { var lowPitchOffset = i / (float) entriesPerOctave; var lowFrequency = MathAudio.OffsetFrequencyBySemitones( frequency, lowPitchOffset ); if ( lowFrequency > maxFrequency ) { continue; } var highPitchOffset = ( i + 1 ) / (float) entriesPerOctave; var highFrequency = MathAudio.OffsetFrequencyBySemitones( frequency, highPitchOffset ); var lowWaveTable = CreateWaveTable( lowFrequency, sines, size ); var highWaveTable = CreateWaveTable( highFrequency, sines, size ); var lowPitch = MathAudio.FrequencyToPitch( lowFrequency ); var highPitch = MathAudio.FrequencyToPitch( highFrequency ); var duoWaveTable = DuoWaveTable.FromWaveTables( lowWaveTable, highWaveTable, lowPitch, highPitch ); blwt.highestPitch = lowPitch; entries.Add( duoWaveTable ); } frequency *= 2; } return blwt; } WaveTable CreateWaveTable( float frequency, Vector2[] sines, int size ) { var filterStart = FindFilterStart( frequency, sines.Length ); var filterEnd = FindFilterEnd( frequency, sines.Length, filterStart ); return WaveTable.FromSines( sines, filterStart, filterEnd, size ); } int FindFilterStart( float baseFrequency, int numSines, float frequencyTreshold = 14000f ) { for ( int i = 0; i < numSines; i++ ) { var binFrequency = i * baseFrequency; if ( binFrequency >= frequencyTreshold ) { return i; } } return numSines; } int FindFilterEnd( float baseFrequency, int numSines, int start, float frequencyTreshold = 18000f ) { for ( int i = start; i < numSines; i++ ) { var binFrequency = i * baseFrequency; if ( binFrequency >= frequencyTreshold ) { return i; } } return numSines; } } }