updated spectral and peak tests

This commit is contained in:
2016-04-11 20:15:04 +01:00
parent 67bd063829
commit 8842f591cf
8 changed files with 151 additions and 151 deletions
@@ -40,7 +40,6 @@ class SpectralCentroidAnalysis(Analysis):
self.create_analysis(
self.create_spccntr_analysis,
fft.analysis['frames'][:],
fft.analysis.attrs['win_size'],
self.AnalysedAudioFile.samplerate
)
self.spccntr_window_count = None
@@ -55,11 +54,10 @@ class SpectralCentroidAnalysis(Analysis):
return ({'frames': output, 'times': times}, {})
@staticmethod
def create_spccntr_analysis(fft, length, samplerate, output_format="freq"):
def create_spccntr_analysis(fft, samplerate, output_format="ind"):
'''
Calculate the spectral centroid of the fft frames.
length: the length of the window used to calculate the FFT.
samplerate: the samplerate of the audio analysed.
output_format = Choose either "freq" for output in Hz or "ind" for bin
index output
@@ -74,16 +72,15 @@ class SpectralCentroidAnalysis(Analysis):
return y
# Calculate the centre frequency of each rfft bin.
if output_format == "freq":
freqs = np.fft.rfftfreq(length, 1.0/samplerate)
freqs = np.fft.rfftfreq((np.size(fft, axis=1)*2)-1, 1.0/samplerate)
elif output_format == "ind":
freqs = np.arange(np.size(fft, axis=1))
else:
raise ValueError("\'{0}\' is not a valid output "
"format.".format(output_format))
# Calculate the weighted mean
y = np.sum(magnitudes*freqs, axis=1) / (np.sum(magnitudes, axis=1)+np.finfo(float).eps)
y = np.sum(magnitudes*freqs, axis=1) / (np.sum(magnitudes, axis=1))
# Convert from index to Hz
return y
@staticmethod
@@ -41,8 +41,6 @@ class SpectralCrestFactorAnalysis(Analysis):
self.create_analysis(
self.create_spccf_analysis,
fft.analysis['frames'][:],
fft.analysis.attrs['win_size'],
self.AnalysedAudioFile.samplerate
)
self.spccf_window_count = None
@@ -56,19 +54,14 @@ class SpectralCrestFactorAnalysis(Analysis):
return ({'frames': output, 'times': times}, {})
@staticmethod
def create_spccf_analysis(fft, length, samplerate, output_format="freq"):
def create_spccf_analysis(fft):
'''
Calculate the spectral crest factor of the fft frames.
length: the length of the window used to calculate the FFT.
samplerate: the samplerate of the audio analysed.
output_format = Choose either "freq" for output in Hz or "ind" for bin
index output
'''
# Get the positive magnitudes of each bin.
magnitudes = np.abs(fft)
# Get highest magnitude
if not np.nonzero(magnitudes)[0].any():
if not np.nonzero(magnitudes)[0].size:
y = np.empty(magnitudes.shape[0])
y.fill(np.nan)
return y
@@ -42,8 +42,6 @@ class SpectralFlatnessAnalysis(Analysis):
self.create_analysis(
self.create_spcflatness_analysis,
fft.analysis['frames'][:],
fft.analysis.attrs['win_size'],
self.AnalysedAudioFile.samplerate
)
self.spcflatness_window_count = None
@@ -57,18 +55,13 @@ class SpectralFlatnessAnalysis(Analysis):
return ({'frames': output, 'times': times}, {})
@staticmethod
def create_spcflatness_analysis(fft, length, samplerate, output_format="freq"):
def create_spcflatness_analysis(fft):
'''
Calculate the spectral flatness of the fft frames.
length: the length of the window used to calculate the FFT.
samplerate: the samplerate of the audio analysed.
output_format = Choose either "freq" for output in Hz or "ind" for bin
index output
'''
# Get the positive magnitudes of each bin.
magnitudes = np.abs(fft)
if not np.nonzero(magnitudes)[0].any():
if not np.nonzero(magnitudes)[0].size:
y = np.empty(magnitudes.shape[0])
y.fill(np.nan)
return y
@@ -77,7 +70,7 @@ class SpectralFlatnessAnalysis(Analysis):
with warnings.catch_warnings():
warnings.filterwarnings('ignore')
# Calculate the geometric mean of magnitudes
geo_mean = stats.gmean(magnitudes, axis=1)
geo_mean = np.e**np.mean(np.log(magnitudes), axis=1)
# Calculate the arithmetic mean of magnitudes
arith_mean = np.mean(magnitudes, axis=1)
spectral_flatness = geo_mean / arith_mean
@@ -40,8 +40,6 @@ class SpectralFluxAnalysis(Analysis):
self.create_analysis(
self.create_spcflux_analysis,
fft.analysis['frames'][:],
fft.analysis.attrs['win_size'],
self.AnalysedAudioFile.samplerate
)
self.spcflux_window_count = None
@@ -55,7 +53,7 @@ class SpectralFluxAnalysis(Analysis):
return ({'frames': output, 'times': times}, {})
@staticmethod
def create_spcflux_analysis(fft, length, samplerate, output_format="freq"):
def create_spcflux_analysis(fft, samplerate):
'''
Calculate the spectral flux of the fft frames.
@@ -67,7 +65,7 @@ class SpectralFluxAnalysis(Analysis):
# Get the positive magnitudes of each bin.
magnitudes = np.abs(fft)
# Get highest magnitude
if not np.nonzero(magnitudes)[0].any():
if not np.nonzero(magnitudes)[0].size:
y = np.empty(magnitudes.shape[0])
y.fill(np.nan)
return y
@@ -76,7 +74,7 @@ class SpectralFluxAnalysis(Analysis):
# magnitude.
rolled_mags = np.roll(magnitudes, 1, axis=0)[1:]
sum_of_squares = np.sum((magnitudes[1:]-rolled_mags)**2., axis=1)
spectral_flux = np.sqrt(sum_of_squares) / (length/2)
spectral_flux = np.sqrt(sum_of_squares) / (np.size(fft, axis=1)/2)
return spectral_flux
@@ -44,7 +44,6 @@ class SpectralSpreadAnalysis(Analysis):
self.create_analysis(
fft.analysis['frames'][:],
spccntr.analysis['frames'][:],
fft.analysis.attrs['win_size'],
self.AnalysedAudioFile.samplerate
)
self.spccntr_window_count = None
@@ -59,7 +58,7 @@ class SpectralSpreadAnalysis(Analysis):
return ({'frames': output, 'times': times}, {})
@staticmethod
def create_spcsprd_analysis(fft, spectral_centroid, length, samplerate, output_format = "freq"):
def create_spcsprd_analysis(fft, spectral_centroid, samplerate, output_format = "ind"):
'''
Calculate the spectral spread of the fft frames.
@@ -79,17 +78,17 @@ class SpectralSpreadAnalysis(Analysis):
if output_format == "ind":
freqs = np.arange(np.size(fft, axis=1))
elif output_format == "freq":
freqs = np.fft.rfftfreq(length, 1.0/samplerate)
freqs = np.fft.rfftfreq((np.size(fft, axis=1)*2)-1, 1.0/samplerate)
else:
raise ValueError("\'{0}\' is not a valid output "
"format.".format(output_format))
spectral_centroid = np.vstack(spectral_centroid)
a = np.power(freqs-spectral_centroid, 2)
mag_sqrd = np.power(magnitudes, 2)
a = (freqs-spectral_centroid)**2
mag_sqrd = magnitudes**2
# Calculate the weighted mean
y = np.sqrt(np.sum(a*mag_sqrd, axis=1) / (np.sum(mag_sqrd, axis=1)+np.finfo(float).eps))
y = np.sqrt(np.sum(a*mag_sqrd, axis=1) / (np.sum(mag_sqrd, axis=1)))
return y
+13 -13
View File
@@ -1,24 +1,24 @@
# Specify analysis parameters for root mean square analysis.
rms = {
"window_size": 70,
"window_size": 120,
"overlap": 2,
}
# Specify analysis parameters for variance analysis.
variance = {
"window_size": 70,
"window_size": 120,
"overlap": 2
}
# Specify analysis parameters for temporal kurtosis analysis.
kurtosis = {
"window_size": 70,
"window_size": 120,
"overlap": 2
}
# Specify analysis parameters for temporal skewness analysis.
skewness = {
"window_size": 70,
"window_size": 120,
"overlap": 2
}
@@ -49,7 +49,7 @@ matcher_weightings = {
"kurtosis": 1.,
"skewness": 1.,
"variance": 3.,
"harm_ratio": 1.
"harm_ratio": 3.
}
# Specifies the method for averaging analysis frames to create a single value
@@ -59,11 +59,11 @@ analysis_dict = {
"f0": "log2_median",
"rms": "mean",
"zerox": "mean",
"spccntr": "mean",
"spcsprd": "mean",
"spcflux": "mean",
"spccf": "mean",
"spcflatness": "mean",
"spccntr": "median",
"spcsprd": "median",
"spcflux": "median",
"spccf": "median",
"spcflatness": "median",
"peak": "mean",
"centroid": "mean",
"kurtosis": "mean",
@@ -82,11 +82,11 @@ analysis = {
matcher = {
# Force the re-matching of analyses
"rematch": True,
"grain_size": 70,
"grain_size": 120,
"overlap": 2,
# Defines the number of matches to keep for synthesis. Note that this must
# also be specified in the synthesis config
"match_quantity": 1,
"match_quantity": 20,
# Choose the algorithm used to perform matching. kdtree is recommended for
# larger datasets.
"method": 'kdtree'
@@ -103,7 +103,7 @@ synthesizer = {
"enforce_f0": True,
# Specify the ratio limit that is the grain can be modified by.
"enf_f0_ratio_limit": 10.,
"grain_size": 70,
"grain_size": 120,
"overlap": 2,
# Normalize output, avoid clipping of final output by scaling the final
# frames.
+22 -4
View File
@@ -365,11 +365,25 @@ class Matcher:
length = entry.samps_to_ms(entry.frames)
hop_size = grain_length / overlap
grain_indexes[ind][0] = int(length / hop_size) - 1
grain_indexes[:, 1] = np.cumsum(grain_indexes[:, 0])
grain_indexes[:, 1] = np.cumsum(grain_indexes[:, 0]).astype(int)
grain_indexes[:, 0] = grain_indexes[:, 1] - grain_indexes[:, 0]
return grain_indexes
def kdtree_matcher(self, grain_size, overlap):
invalid_inds = []
for i, entry in enumerate(self.target_db.analysed_audio):
entry.generate_grain_times(grain_size, overlap, save_times=True)
if not entry.times.size:
invalid_inds.append(i)
for i in sorted(invalid_inds, reverse=True):
del self.target_db.analysed_audio[i]
invalid_inds = []
for i, entry in enumerate(self.source_db.analysed_audio):
entry.generate_grain_times(grain_size, overlap, save_times=True)
if not entry.times.size:
invalid_inds.append(i)
for i in sorted(invalid_inds, reverse=True):
del self.source_db.analysed_audio[i]
# Count grains of the source database
source_sample_indexes = self.count_grains(self.source_db, grain_size, overlap)
try:
@@ -385,6 +399,7 @@ class Matcher:
else:
weightings = {x: 1. for x in self.matcher_analyses}
for tind, target_entry in enumerate(self.target_db.analysed_audio):
# Check if match data already exists and use it rather than
# regenerating if it does.
@@ -395,7 +410,7 @@ class Matcher:
continue
# Create an array of grain times for target sample
target_times = target_entry.generate_grain_times(grain_size, overlap, save_times=True)
target_times = target_entry.times
x_size = target_times.shape[0]
match_indexes = np.empty((x_size, self.match_quantity))
match_vals = np.empty((x_size, self.match_quantity))
@@ -420,7 +435,7 @@ class Matcher:
for sind, source_entry in enumerate(self.source_db.analysed_audio):
self.logger.info("K-d Tree Matching: {0} to {1}".format(source_entry.name, target_entry.name))
# Create an array of grain times for source sample
source_times = source_entry.generate_grain_times(grain_size, overlap, save_times=True)
source_times = source_entry.times
if not source_times.size:
continue
@@ -804,7 +819,10 @@ class Synthesizer:
match_sample.generate_grain_times(match_grain_size, match_overlap, save_times=True)
# TODO: Make proper fix for grain index offset of 1
match_grain = match_sample[match_grain_ind-1]
try:
match_grain = match_sample[match_grain_ind-1]
except:
pdb.set_trace()
if self.enforce_rms_bool:
# Get the target sample from the database
+100 -98
View File
@@ -10,8 +10,40 @@ from fileops import pathops
import pdb
import os
import config
import math
class NumericAssertions:
"""
This class is following the UnitTest naming conventions.
It is meant to be used along with unittest.TestCase like so :
class MyTest(unittest.TestCase, NumericAssertions):
...
It needs python >= 2.6
"""
def assertIsNaN(self, value, msg=None):
"""
Fail if provided value is not NaN
"""
standardMsg = "%s is not NaN" % str(value)
try:
if not math.isnan(value):
self.fail(self._formatMessage(msg, standardMsg))
except:
self.fail(self._formatMessage(msg, standardMsg))
def assertIsNotNaN(self, value, msg=None):
"""
Fail if provided value is NaN
"""
standardMsg = "Provided value is NaN"
try:
if math.isnan(value):
self.fail(self._formatMessage(msg, standardMsg))
except:
pass
class globalTests(unittest.TestCase):
"""Includes functions that are accesible to all audiofile tests."""
@@ -478,141 +510,111 @@ class PeakAnalysisTests(globalTests):
"""Tests Peak analysis generation"""
def setUp(self):
# TODO: Write this test...
"""Create functions and variables before each test is run."""
self.sr = 44100
self.f = 440
x = np.arange(88200)
self.sine_wave = np.sin(2*np.pi*self.f/self.sr*x)
self.silence = np.zeros(512)
self.positive_max = np.ones(512)
self.negative_max = np.ones(512)-2
def test_GeneratePeak(self):
"""Check that RMS values generated are the expected values"""
output = analysis.PeakAnalysis.create_peak_analysis(self.sine_wave)
self.assertTrue(np.all(output > 0.8))
output = analysis.PeakAnalysis.create_peak_analysis(self.silence)
output1 = analysis.PeakAnalysis.create_peak_analysis(self.positive_max)
output2 = analysis.PeakAnalysis.create_peak_analysis(self.negative_max)
class SpectralCentroidAnalysisTests(globalTests):
np.testing.assert_array_equal(output, 0)
np.testing.assert_array_equal(output1, 1)
np.testing.assert_array_equal(output2, 1)
class SpectralCentroidAnalysisTests(globalTests, NumericAssertions):
"""Tests Spectral Centroid analysis generation."""
def setUp(self):
"""Create functions and variables before each test is run."""
self.TestAudio = self.create_test_audio()
# Specify frequency of the sine wave
self.sr = 44100
self.f = 440
x = np.arange(88200)
self.sine_wave = np.sin(2*np.pi*self.f/self.sr*x)
self.silence = np.zeros(512)
self.equal_mag = np.ones(512)
self.peak = self.silence.copy()
self.peak[256] = 1
def test_GenerateSpectralCentroid(self):
fft = analysis.FFTAnalysis.stft(self.sine_wave, 512)
output = analysis.SpectralCentroidAnalysis.create_spccntr_analysis(fft, 512, self.sr)
average_output = np.median(output)
self.assertTrue(self.f-2 <= average_output <= self.f+2)
output = analysis.SpectralCentroidAnalysis.create_spccntr_analysis([self.silence], 44100)
output1 = analysis.SpectralCentroidAnalysis.create_spccntr_analysis([self.peak], 44100)
output2 = analysis.SpectralCentroidAnalysis.create_spccntr_analysis([self.equal_mag], 44100)
self.assertIsNaN(output)
self.assertEqual(output1, 256)
self.assertEqual(output2, 255.5)
def tearDown(self):
"""
Delete anything that is left over once tests are complete.
For example, remove all temporary test audio files generated during the
tests.
"""
del self.TestAudio
pathops.delete_if_exists("./.TestAudio.wav")
class SpectralSpreadAnalysisTests(globalTests):
class SpectralSpreadAnalysisTests(globalTests, NumericAssertions):
"""Tests Spectral Spread analysis generation."""
def setUp(self):
"""Create functions and variables before each test is run."""
self.TestAudio = self.create_test_audio()
# Specify frequency of the sine wave
self.sr = 44100
self.f = 440
x = np.arange(88200)
self.sine_wave = np.sin(2*np.pi*self.f/self.sr*x)
self.white_noise = np.random.random(88200)
self.silence = np.zeros(512)
self.equal_mag = np.ones(512)
self.peak = self.silence.copy()
self.peak[256] = 1
def test_GenerateSpectralSpread(self):
fft = analysis.FFTAnalysis.stft(self.sine_wave, 512)
output = analysis.SpectralCentroidAnalysis.create_spccntr_analysis(fft, 512, self.sr, output_format = 'freq')
average_output = np.median(output)
output = analysis.SpectralSpreadAnalysis.create_spcsprd_analysis(fft, output, 512, self.sr, output_format='freq')
output = output / (44100/2.0)
average_output = np.median(output)
self.assertTrue(0 <= average_output <= 2)
fft = analysis.FFTAnalysis.stft(self.white_noise, 512)
output = analysis.SpectralCentroidAnalysis.create_spccntr_analysis(fft, 512, self.sr, output_format = 'ind')
output = analysis.SpectralSpreadAnalysis.create_spcsprd_analysis(fft, output, 512, self.sr, output_format='freq')
average_output = np.median(output)
output = output / (44100/2.0)
average_output = np.median(output)
# self.assertTrue(8000 <= average_output)
def tearDown(self):
"""
Delete anything that is left over once tests are complete.
For example, remove all temporary test audio files generated during the
tests.
"""
del self.TestAudio
pathops.delete_if_exists("./.TestAudio.wav")
output = analysis.SpectralCentroidAnalysis.create_spccntr_analysis([self.silence], 44100)
output = analysis.SpectralSpreadAnalysis.create_spcsprd_analysis([self.silence], output, 44100)
output1 = analysis.SpectralCentroidAnalysis.create_spccntr_analysis([self.equal_mag], 44100)
output1 = analysis.SpectralSpreadAnalysis.create_spcsprd_analysis([self.equal_mag], output1, 44100)
output2 = analysis.SpectralCentroidAnalysis.create_spccntr_analysis([self.peak], 44100)
output2 = analysis.SpectralSpreadAnalysis.create_spcsprd_analysis([self.peak], output2, 44100)
self.assertIsNaN(output)
np.testing.assert_almost_equal(output1, 147.801387)
self.assertEquals(output2, 0)
class SpectralFluxAnalysisTests(globalTests):
"""Tests Spectral Flux analysis generation."""
def setUp(self):
# Specify frequency of the sine wave
self.sr = 44100
self.f = 440
x = np.arange(44100)
self.sine_wave = np.sin(2*np.pi*self.f/self.sr*x)
self.white_noise = np.random.random(44100)
self.input = np.concatenate((self.sine_wave, self.white_noise))
self.silence = np.zeros(512)
self.equal_mag = np.ones(512)
self.peak = self.silence.copy()
self.peak[256] = 1
def test_GenerateSpectralFlux(self):
fft = analysis.FFTAnalysis.stft(self.input, 512)
output = analysis.SpectralFluxAnalysis.create_spcflux_analysis(fft, 512, self.sr)
x = np.vstack((self.equal_mag, self.peak))
output = analysis.SpectralFluxAnalysis.create_spcflux_analysis(x, 512)
x = np.vstack((self.peak, self.peak))
output1 = analysis.SpectralFluxAnalysis.create_spcflux_analysis(x, 512)
self.assertTrue(output[0] > output1[0])
output_max_index = np.argmax(output)
self.assertTrue(output_max_index == output.size/2)
class SpectralCrestFactorAnalysisTests(globalTests):
class SpectralCrestFactorAnalysisTests(globalTests, NumericAssertions):
"""Tests Spectral Crest Factor analysis generation."""
def setUp(self):
self.sr = 44100
self.f = 440
x = np.arange(44100)
sine_wave = np.sin(2*np.pi*self.f/self.sr*x)
white_noise = np.random.random(44100)
silence = np.zeros(44100)
self.input = np.concatenate((sine_wave, white_noise, silence))
self.silence = np.zeros(512)
self.equal_mag = np.ones(512)
self.peak = self.silence.copy()
self.peak[256] = 1
def test_GenerateSpectralCrestFactor(self):
fft = analysis.FFTAnalysis.stft(self.input, 512)
output = analysis.SpectralCrestFactorAnalysis.create_spccf_analysis(fft, 512, self.sr)
# TODO: Write assertions for results. This isn't testing anything
# otherwise...
output = analysis.SpectralCrestFactorAnalysis.create_spccf_analysis([self.silence])
output1 = analysis.SpectralCrestFactorAnalysis.create_spccf_analysis([self.equal_mag])
output2 = analysis.SpectralCrestFactorAnalysis.create_spccf_analysis([self.peak])
class SpectralFlatnessAnalysisTests(globalTests):
self.assertIsNaN(output)
self.assertEquals(output1, 2./1024.)
self.assertEquals(output2, 1.)
class SpectralFlatnessAnalysisTests(globalTests, NumericAssertions):
"""Tests Spectral Crest Factor analysis generation."""
def setUp(self):
self.sr = 44100
self.f = 440
x = np.arange(44100)
sine_wave = np.sin(2*np.pi*self.f/self.sr*x)
white_noise = np.random.random(44100)
silence = np.zeros(44100)
self.input = np.concatenate((sine_wave, white_noise, silence))
self.silence = np.zeros(512)
self.equal_mag = np.ones(512)
self.peak = self.silence.copy()
self.peak[256] = 1
def test_GenerateSpectralFlatness(self):
fft = analysis.FFTAnalysis.stft(self.input, 512)
output = analysis.SpectralFlatnessAnalysis.create_spcflatness_analysis(fft, 512, self.sr)
# TODO: Write assertions for results. This isn't testing anything
# otherwise...
output = analysis.SpectralFlatnessAnalysis.create_spcflatness_analysis([self.silence])
output1 = analysis.SpectralFlatnessAnalysis.create_spcflatness_analysis([self.equal_mag])
output2 = analysis.SpectralFlatnessAnalysis.create_spcflatness_analysis([self.peak])
self.assertIsNaN(output)
self.assertEqual(output1, 1.)
self.assertEqual(output2, 0.)
class KurtosisAnalysisTests(globalTests):
"""Tests Kurtosis analysis generation."""