Skip to content

Instantly share code, notes, and snippets.

@GregaMohorko
Last active April 25, 2017 22:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save GregaMohorko/3d3feb4c67d860006c0100cf7e217b9a to your computer and use it in GitHub Desktop.
Save GregaMohorko/3d3feb4c67d860006c0100cf7e217b9a to your computer and use it in GitHub Desktop.
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