Skip to content

Commit

Permalink
Start setting up more detailed tests for octave frequency scales
Browse files Browse the repository at this point in the history
Issue #332
  • Loading branch information
towsey committed Aug 3, 2020
1 parent c20e4ca commit ea1e55f
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 139 deletions.
193 changes: 94 additions & 99 deletions src/AudioAnalysisTools/DSP/OctaveFreqScale.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ public static class OctaveFreqScale
/// </summary>
public static void GetOctaveScale(FrequencyScale scale)
{
int finalBinCount = 256;
int sr, frameSize, octaveDivisions;
int sr, frameSize;

// NOTE: octaveDivisions = the number of fractional Hz steps within one octave. Piano octave contains 12 steps per octave.

Expand All @@ -32,70 +31,63 @@ public static void GetOctaveScale(FrequencyScale scale)
case FreqScaleType.LinearOctaveStandard:
//This is a split linear-octave frequency scale.
// Valid values for linearUpperBound are 125, 250, 500, 1000.
int linearUpperBound = 250;
int linearUpperBound = 1000;
scale = GetStandardOctaveScale(scale, linearUpperBound);
return;

case FreqScaleType.OctaveDataReduction:
// This data conversion is for data reduction purposes.
// The remainder of the spectrum will be reduced over four 6-tone octaves
sr = 22050;
frameSize = 512;
finalBinCount = 45;
scale.OctaveCount = 4;
octaveDivisions = 6; // tone steps within one octave.
scale.LinearBound = 1000;
scale.Nyquist = 11025;
break;
// This spectral conversion is for data reduction purposes.
// It is a split linear-octave frequency scale.
scale = GetDataReductionScale(scale);
return;

case FreqScaleType.Linear62Octaves7Tones31Nyquist11025:
sr = 22050;
frameSize = 8192;
scale.OctaveCount = 7;
octaveDivisions = 31; // tone steps within one octave. Note: piano = 12 steps per octave.
scale.LinearBound = 62;
scale.Nyquist = 11025;
scale.OctaveCount = 7;
scale.ToneCount = 31; // tone steps within one octave. Note: piano = 12 steps per octave.
scale.FinalBinCount = 253;
break;

case FreqScaleType.Linear125Octaves6Tones30Nyquist11025:
// constants required for split linear-octave scale when sr = 22050
sr = 22050;
frameSize = 8192;
scale.OctaveCount = 6;
octaveDivisions = 32; // tone steps within one octave. Note: piano = 12 steps per octave.
scale.LinearBound = 125;
scale.Nyquist = 11025;
scale.OctaveCount = 6;
scale.ToneCount = 32; // tone steps within one octave. Note: piano = 12 steps per octave.
scale.FinalBinCount = 255;
break;

case FreqScaleType.Octaves24Nyquist32000:
//// constants required for full octave scale when sr = 64000
sr = 64000;
frameSize = 16384;
scale.OctaveCount = 8;
octaveDivisions = 24; // tone steps within one octave. Note: piano = 12 steps per octave.
scale.LinearBound = 15;
scale.Nyquist = 32000;
scale.OctaveCount = 8;
scale.ToneCount = 24; // tone steps within one octave. Note: piano = 12 steps per octave.
scale.FinalBinCount = 253;
break;

case FreqScaleType.Linear125Octaves7Tones28Nyquist32000:
// constants required for split linear-octave scale when sr = 64000
sr = 64000;
frameSize = 16384; // = 2*8192 or 4*4096;
scale.OctaveCount = 7;
octaveDivisions = 28; // tone steps within one octave. Note: piano = 12 steps per octave.
scale.LinearBound = 125;
scale.Nyquist = 32000;
scale.OctaveCount = 7;
scale.ToneCount = 28; // tone steps within one octave. Note: piano = 12 steps per octave.
scale.FinalBinCount = 253;
break;

default:
LoggedConsole.WriteErrorLine("WARNING: GetOctaveScale() was passed UNKNOWN OCTAVE SCALE.");
return;
}

scale.Nyquist = sr / 2;
scale.WindowSize = frameSize; // = 2*8192 or 4*4096
scale.FinalBinCount = finalBinCount;
scale.ToneCount = octaveDivisions;
scale.BinBounds = LinearToSplitLinearOctaveScale(sr, frameSize, finalBinCount, scale.LinearBound, scale.Nyquist, scale.ToneCount);
scale.BinBounds = LinearToSplitLinearOctaveScale(sr, frameSize, scale.FinalBinCount, scale.LinearBound, scale.Nyquist, scale.ToneCount);
scale.GridLineLocations = GetGridLineLocations(fst, scale.BinBounds);
}

Expand Down Expand Up @@ -381,11 +373,21 @@ public static FrequencyScale GetStandardOctaveScale(FrequencyScale scale, int li

/// <summary>
/// Converts a single linear spectrum to octave scale spectrum.
/// WARNING: THis method assumes that the ocatve spectrum is to be same length as linear spectrum.
/// Therefore the index values in the octaveBinBounds matrix should NOT exceed bounds of the linear spectrum.
/// </summary>
public static double[] OctaveSpectrum(int[,] octaveBinBounds, double[] linearSpectrum)
{
int length = octaveBinBounds.GetLength(0);
var octaveSpectrum = new double[length];

// Fill in the first value of the octave spectrum
int lowIndex1 = octaveBinBounds[0, 0];
int centreIndex1 = octaveBinBounds[0, 0];
int highIndex1 = octaveBinBounds[1, 0];
octaveSpectrum[0] = FilterbankIntegral(linearSpectrum, lowIndex1, centreIndex1, highIndex1);

// fill in remainer except last
for (int i = 1; i < length - 1; i++)
{
int lowIndex = octaveBinBounds[i - 1, 0];
Expand All @@ -399,17 +401,16 @@ public static double[] OctaveSpectrum(int[,] octaveBinBounds, double[] linearSpe
octaveSpectrum[i] = FilterbankIntegral(linearSpectrum, lowIndex, centreIndex, highIndex);
}

// now fill in the first value of the octave spectrum
int lowIndex1 = octaveBinBounds[0, 0];
int centreIndex1 = octaveBinBounds[0, 0];
int highIndex1 = octaveBinBounds[1, 0];
octaveSpectrum[0] = FilterbankIntegral(linearSpectrum, lowIndex1, centreIndex1, highIndex1);

// now fill in the last value of the octave spectrum
int lowIndex2 = octaveBinBounds[length - 2, 0];
int centreIndex2 = octaveBinBounds[length - 1, 0];
int highIndex2 = octaveBinBounds[length - 1, 0];
octaveSpectrum[length - 1] = FilterbankIntegral(linearSpectrum, lowIndex2, centreIndex2, highIndex2);

if (highIndex2 != 0)
{
octaveSpectrum[length - 1] = FilterbankIntegral(linearSpectrum, lowIndex2, centreIndex2, highIndex2);
}

return octaveSpectrum;
}

Expand Down Expand Up @@ -451,14 +452,67 @@ public static double[] OctaveSpectrum(int[,] octaveBinBounds, double[] linearSpe
splitLinearOctaveIndexBounds[finalBinCount - 1, 0] = linearFreqScale.Length - 1;
splitLinearOctaveIndexBounds[finalBinCount - 1, 1] = (int)Math.Round(linearFreqScale[linearFreqScale.Length - 1]);

// A HACK!!! Make sure second last index has values if they are zero
if (splitLinearOctaveIndexBounds[finalBinCount - 2, 0] == 0)
return splitLinearOctaveIndexBounds;
}

/// <summary>
/// This method assumes that the linear spectrum is derived from a 512 frame with sr = 22050.
/// It is a split linear-octave scale.
/// The linear part is from 0-2 kHz with reduction by averaging every 6 frequency bins.
/// The octave part is obtained by setting octave divisions or tone count = 5.
/// </summary>
/// <returns>a frequency scale for spectral-data reduction purposes.</returns>
public static FrequencyScale GetDataReductionScale(FrequencyScale scale)
{
int sr = 22050;
int frameSize = 512;
scale.Nyquist = sr / 2;

// linear reduction of the lower spectrum from 0 - 2 kHz.
scale.LinearBound = 2000;
int linearReductionFactor = 6;

// REduction of upper spectrum 2-11 kHz: Octave count and tone steps within one octave.
scale.OctaveCount = 2.7;
scale.ToneCount = 5;

var octaveBandsLowerBounds = GetFractionalOctaveBands(scale.LinearBound, scale.Nyquist, scale.ToneCount);
int spectrumBinCount = frameSize / 2;
var linearFreqScale = GetLinearFreqScale(scale.Nyquist, spectrumBinCount);

double linearBinWidth = scale.Nyquist / (double)spectrumBinCount;
int topLinearIndex = (int)Math.Round(scale.LinearBound / linearBinWidth);
int linearReducedBinCount = topLinearIndex / linearReductionFactor;
int finalBinCount = linearReducedBinCount + (int)Math.Floor(scale.OctaveCount * scale.ToneCount);
var splitLinearOctaveIndexBounds = new int[finalBinCount, 2];

// fill in the linear part of the freq scale
for (int i = 0; i < linearReducedBinCount; i++)
{
splitLinearOctaveIndexBounds[finalBinCount - 2, 0] = linearFreqScale.Length - 1;
splitLinearOctaveIndexBounds[finalBinCount - 2, 1] = (int)Math.Round(linearFreqScale[linearFreqScale.Length - 1]);
splitLinearOctaveIndexBounds[i, 0] = i;
splitLinearOctaveIndexBounds[i, 1] = (int)Math.Round(linearFreqScale[i * linearReductionFactor]);
}

return splitLinearOctaveIndexBounds;
// fill in the octave part of the freq scale
for (int i = linearReducedBinCount; i < finalBinCount; i++)
{
for (int j = 0; j < linearFreqScale.Length; j++)
{
if (linearFreqScale[j] > octaveBandsLowerBounds[i - linearReducedBinCount])
{
splitLinearOctaveIndexBounds[i, 0] = j;
splitLinearOctaveIndexBounds[i, 1] = (int)Math.Round(linearFreqScale[j]);
break;
}
}
}

// make sure last index extends to last bin of the linear spectrum.
splitLinearOctaveIndexBounds[finalBinCount - 1, 0] = linearFreqScale.Length - 1;
splitLinearOctaveIndexBounds[finalBinCount - 1, 1] = (int)Math.Round(linearFreqScale[linearFreqScale.Length - 1]);

scale.BinBounds = splitLinearOctaveIndexBounds;
return scale;
}

/// <summary>
Expand Down Expand Up @@ -627,64 +681,5 @@ public static double FilterbankIntegral(double[] spectrum, int lowIndex, int cen
integral /= area;
return integral;
}

/// <summary>
/// Returns a simple spectrogram for test purposes.
/// Write code for simple test. Different spectra tried so far:
/// (1) Uniform spectrum = 1.0
/// (2) Ramp spectrum
/// (3) SPike spectrum.
/// </summary>
public static double[] GetSimpleTestSpectrum(int sr, int frameSize)
{
// int nyquist = sr / 2;
int binCount = frameSize / 2;
double[] spectrum = new double[binCount];

// return a linear frequency scale
// double freqStep = nyquist / (double)binCount;
for (int i = 0; i < binCount; i++)
{
// ramp spectrum
//spectrum[i] = freqStep * i;

//Uniform spectrum
spectrum[i] = 1.0;
}

// Spike spectrum
//spectrum[500] = 1.0;

return spectrum;
}

public static void TestOctaveScale(FreqScaleType fst)
{
var freqScale = new FrequencyScale(fst);
var octaveBinBounds = freqScale.BinBounds;

// now test the octave scale using a test spectrum
int sr = 22050;
int frameSize = 8192; // default for sr = 22050

if (fst == FreqScaleType.Octaves24Nyquist32000 || fst == FreqScaleType.Linear125Octaves7Tones28Nyquist32000)
{
sr = 64000;
frameSize = 16384; // default for sr = 64000
}

// Get a simple test spectrum
var linearSpectrum = GetSimpleTestSpectrum(sr, frameSize);

//do the test
var octaveSpectrum = OctaveSpectrum(octaveBinBounds, linearSpectrum);

// write output
int rowCount = octaveBinBounds.GetLength(0);
for (int i = 0; i < rowCount; i++)
{
Console.WriteLine(i + " bin-" + octaveBinBounds[i, 0] + " " + octaveBinBounds[i, 1] + "Hz " + octaveSpectrum[i]);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,52 +60,17 @@ public override void Make(double[,] amplitudeM)
//##################################################################################################################################

/// <summary>
/// NOTE!!!! The decibel array has been normalised in 0 - 1.
/// Converts amplitude spectrogram to octave scale using one of the possible octave scale types.
/// </summary>
public static double[,] MakeOctaveScaleSpectrogram(SonogramConfig config, double[,] matrix, int sampleRate, int linearLimit)
{
var freqScale = new FrequencyScale(FreqScaleType.LinearOctaveStandard);
//var freqScale = new FrequencyScale(FreqScaleType.LinearOctaveStandard);
//var freqScale = new FrequencyScale(FreqScaleType.OctaveDataReduction);
//var freqScale = new FrequencyScale(FreqScaleType.Linear1000Octaves4Tones6Nyquist11025);
//var freqScale = new FrequencyScale(FreqScaleType.Linear125Octaves6Tones30Nyquist11025);
var freqScale = new FrequencyScale(FreqScaleType.Linear62Octaves7Tones31Nyquist11025);

// THIS IS THE CRITICAL LINE.
// TODO: SHOULD DEVELOP A SEPARATE UNIT TEST for this method
double[,] m = OctaveFreqScale.ConvertAmplitudeSpectrogramToDecibelOctaveScale(matrix, freqScale);
return m;
}

/// <summary>
/// This method takes an audio recording and returns an octave scale spectrogram.
/// At the present time it only works for recordings with 64000 sample rate and returns a 256 bin sonogram.
/// TODO: generalise this method for other recordings and octave scales.
/// </summary>
public static BaseSonogram ConvertRecordingToOctaveScaleSonogram(AudioRecording recording, FreqScaleType fst)
{
var freqScale = new FrequencyScale(fst);
double windowOverlap = 0.75;
var sonoConfig = new SonogramConfig
{
WindowSize = freqScale.WindowSize,
WindowOverlap = windowOverlap,
SourceFName = recording.BaseName,
NoiseReductionType = NoiseReductionType.None,
NoiseReductionParameter = 0.0,
};

// Generate amplitude sonogram and then conver to octave scale
var sonogram = new AmplitudeSonogram(sonoConfig, recording.WavReader);

// THIS IS THE CRITICAL LINE.
// TODO: SHOULD DEVELOP A SEPARATE UNIT TEST for this method
sonogram.Data = OctaveFreqScale.ConvertAmplitudeSpectrogramToDecibelOctaveScale(sonogram.Data, freqScale);

// DO NOISE REDUCTION
var dataMatrix = SNR.NoiseReduce_Standard(sonogram.Data);
sonogram.Data = dataMatrix;
int windowSize = freqScale.FinalBinCount * 2;
sonogram.Configuration.WindowSize = windowSize;
sonogram.Configuration.WindowStep = (int)Math.Round(windowSize * (1 - windowOverlap));
return sonogram;
}
} // end class SpectrogramOctaveScale
}
}
33 changes: 33 additions & 0 deletions tests/Acoustics.Test/AudioAnalysisTools/DSP/FrequencyScaleTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,39 @@ public void LinearFrequencyScale()
Assert.AreEqual(1621, image.Width);
}

/// <summary>
/// Test of the default standard split LINEAR-Octave FREQ SCALE
/// Check it on pure tone spectrum
/// </summary>
[TestMethod]
public void TestSplitLinearOctaveFrequencyScale()
{
// Test default octave scale where default linear portion is 0-1000Hz.
//var fst = FreqScaleType.Linear125Octaves6Tones30Nyquist11025;
var fst = FreqScaleType.LinearOctaveStandard;
var freqScale = new FrequencyScale(fst);

int[,] octaveBinBounds = freqScale.BinBounds;

// generate pure tone spectrum.
double[] linearSpectrum = new double[256];
linearSpectrum[128] = 1.0;

double[] octaveSpectrum = OctaveFreqScale.OctaveSpectrum(octaveBinBounds, linearSpectrum);

Assert.AreEqual(103, octaveSpectrum.Length);
Assert.AreEqual(0.0, octaveSpectrum[78]);
Assert.AreEqual(0.125, octaveSpectrum[79]);
Assert.AreEqual(0.125, octaveSpectrum[80]);
Assert.AreEqual(0.0, octaveSpectrum[81]);

//var expectedBinBounds = new[,]
//{
//}

//Assert.That.MatricesAreEqual(expectedBinBounds, freqScale.BinBounds);
}

/// <summary>
/// METHOD TO CHECK IF Octave FREQ SCALE IS WORKING
/// Check it on standard one minute recording, SR=22050.
Expand Down

0 comments on commit ea1e55f

Please sign in to comment.