Audio Equalizer as WaveStream and with real-time band-changing.
using System; | |
using System.ComponentModel; | |
using System.Linq; | |
using GalaSoft.MvvmLight; | |
using NAudio.Dsp; | |
using NAudio.Wave; | |
namespace GM.SP.Audio | |
{ | |
public class EqualizerBand : ObservableObject | |
{ | |
public int Frequency { get; set; } | |
public float Gain { get; set; } | |
public float Bandwidth { get; set; } | |
} | |
public class Equalizer : WaveStream, IDisposable | |
{ | |
private readonly WaveStream source; | |
private readonly int channels; | |
private readonly int bytesPerSample; | |
private readonly BiQuadFilter[,] filters; | |
private readonly EqualizerBand[] bands; | |
public Equalizer(WaveStream waveStream, int bandCount) : this(waveStream,CreateBands(waveStream.WaveFormat.SampleRate, bandCount)) { } | |
/// <summary> | |
/// Note: Bands can be changed in realtime. | |
/// </summary> | |
public Equalizer(WaveStream waveStream,EqualizerBand[] bands) | |
{ | |
source = waveStream; | |
channels = waveStream.WaveFormat.Channels; | |
bytesPerSample = waveStream.WaveFormat.BitsPerSample / 8; | |
filters = new BiQuadFilter[bands.Length, channels]; | |
this.bands = bands; | |
foreach(EqualizerBand band in bands) | |
band.PropertyChanged += Band_PropertyChanged; | |
CreateFilters(); | |
} | |
public new void Dispose() | |
{ | |
base.Dispose(); | |
source?.Dispose(); | |
foreach(EqualizerBand band in bands) | |
band.PropertyChanged -= Band_PropertyChanged; | |
} | |
private void CreateFilters() | |
{ | |
// Low shelf on the lower end | |
for(int j = channels - 1; j >= 0; --j) { | |
filters[0, j] = BiQuadFilter.LowShelf(source.WaveFormat.SampleRate, bands[0].Frequency, bands[0].Bandwidth, bands[0].Gain); | |
} | |
// High shelf on the higher end | |
for(int j = channels - 1; j >= 0; --j) { | |
filters[bands.Length - 1, j] = BiQuadFilter.HighShelf(source.WaveFormat.SampleRate, bands[bands.Length - 1].Frequency, bands[bands.Length - 1].Bandwidth, bands[bands.Length - 1].Gain); | |
} | |
// Peaking EQ inbetween | |
for(int i = bands.Length - 2; i > 0; --i) { | |
EqualizerBand band = bands[i]; | |
for(int j = channels - 1; j >= 0; --j) { | |
filters[i, j] = BiQuadFilter.PeakingEQ(source.WaveFormat.SampleRate, band.Frequency, band.Bandwidth, band.Gain); | |
} | |
} | |
} | |
private void Band_PropertyChanged(object sender, PropertyChangedEventArgs e) | |
{ | |
// reset the filters of the band that was changed | |
EqualizerBand band = sender as EqualizerBand; | |
// find the index of the band | |
int index=-1; | |
for(int i = bands.Length - 1; i >= 0; --i) { | |
if(bands[i].Frequency == band.Frequency) { | |
index = i; | |
break; | |
} | |
} | |
// update the filters at that index | |
for(int j = channels - 1; j >= 0; --j) { | |
filters[index, j].SetPeakingEq(source.WaveFormat.SampleRate, band.Frequency, band.Bandwidth, band.Gain); | |
} | |
} | |
private void Transform(double[] samples) | |
{ | |
int ch = 0; | |
double[] filtered = new double[filters.Length]; | |
// for every sample ... | |
for(int i = 0; i < samples.Length; ++i,ch=i%channels) { | |
float sample = (float)samples[i]; | |
// for every filter ... | |
for(int j = bands.Length - 1; j >= 0; --j) { | |
filtered[j] = filters[j,ch].Transform(sample); | |
} | |
// combine filtered values | |
samples[i] = filtered.Sum(); | |
} | |
Normalize(samples); | |
} | |
public static void Normalize(double[] data) | |
{ | |
double max = 0; | |
for(int n = 0; n < data.Length; n++) | |
max = Math.Max(max, Math.Abs(data[n])); | |
if(max > 1.0) | |
for(int n = 0; n < data.Length; n++) | |
data[n] /= max; | |
} | |
public static EqualizerBand[] CreateBands(int sampleRate, int bandCount) | |
{ | |
// concentrate on frequencies between 100-12000 | |
if(sampleRate > 24200) | |
sampleRate = 24200; | |
// set first and last frequency of the bands | |
int firstFreq = 100; | |
int lastFreq = sampleRate / 2 - 100; | |
// arrange frequency of the bands in a logarithmic scale | |
double logFirstFreq = Math.Log(firstFreq, 2); | |
double logLastFreq = Math.Log(lastFreq, 2); | |
double logStep = (logLastFreq - logFirstFreq) / (bandCount - 1); | |
EqualizerBand[] bands = new EqualizerBand[bandCount]; | |
bands[0] = new EqualizerBand() { Frequency = firstFreq, Bandwidth = 0.8f }; | |
bands[bandCount - 1] = new EqualizerBand() { Frequency = lastFreq, Bandwidth = 0.8f }; | |
double logCurrent = logLastFreq - logStep; | |
for(int i = bandCount - 2; i > 0; --i, logCurrent -= logStep) { | |
int frequency = (int)(0.5+Math.Pow(2, logCurrent)); | |
bands[i] = new EqualizerBand() { Frequency = frequency, Bandwidth = 0.8f }; | |
} | |
return bands; | |
} | |
#region WaveStream | |
public override WaveFormat WaveFormat | |
{ | |
get { return source?.WaveFormat; } | |
} | |
public override long Length | |
{ | |
get { return source.Length; } | |
} | |
public override long Position | |
{ | |
get { return source.Position; } | |
set { source.Position = value; } | |
} | |
public override int Read(byte[] buffer, int offset, int count) | |
{ | |
// read from source | |
int readCount = source.Read(buffer, offset, count); | |
// convert bytes to doubles | |
double[] samples = new double[readCount / bytesPerSample]; | |
for(int i = samples.Length-1, j = offset+i*bytesPerSample; i >=0; --i, j -= bytesPerSample) { | |
// note: this assumes that 16 bits are used for one sample | |
short valueShort = (short)((buffer[j + 1] << 8) | buffer[j]); | |
double sample = valueShort / ((valueShort > 0) ? 32767.0 : 32768.0); | |
samples[i] = sample; | |
} | |
// apply transform | |
Transform(samples); | |
// convert doubles back to bytes | |
for(int i = samples.Length - 1, j = offset + i * bytesPerSample; i >= 0; --i, j -= bytesPerSample) { | |
// note: this assumes that 16 bits are used for one sample | |
double sample = samples[i]; | |
short valueShort = (short)(sample * ((sample > 0) ? 32767 : 32768)); | |
buffer[j] = (byte)valueShort; | |
buffer[j + 1] = (byte)(valueShort >> 8); | |
} | |
return readCount; | |
} | |
#endregion // WaveStream | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment