Audio Equalizer as WaveStream and with real-time band-changing.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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