rj-action-library/Runtime/Audio/AudioGraph/Generators/BandLimitedWaveTable.cs

198 lines
5.2 KiB
C#
Raw Normal View History

2025-01-03 12:09:23 +00:00
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<DuoWaveTable> entries = new List<DuoWaveTable>();
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;
}
}
}