198 lines
5.2 KiB
C#
198 lines
5.2 KiB
C#
|
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;
|
||
|
}
|
||
|
}
|
||
|
}
|