Mercurial > hg > may
view src/may/stream/test/test_resample.yeti @ 568:e9716e9a44e4
Fixes to tests for frequency preservation in resampling
author | Chris Cannam |
---|---|
date | Mon, 12 May 2014 17:46:31 +0100 |
parents | 8c1b62eb33c7 |
children |
line wrap: on
line source
module may.stream.test.test_resample; vec = load may.vector; mat = load may.matrix; syn = load may.stream.syntheticstream; manip = load may.stream.manipulate; channels = load may.stream.channels; resample = load may.stream.resample; win = load may.signal.window; waves = load may.stream.waves; fft = load may.transform.fft; { compare, compareUsing, compareMatrices, assert, time } = load may.test; eps = 1e-14; measureFrequency cycles stream = (case stream.available of Known n: (v = channels.mixedDown (stream.read n); var firstPeak = -1; var lastPeak = -1; var nPeaks = 0; // count +ve peaks len = vec.length v; for [int(len/4) .. len - 2] do i: // allow some fuzz x0 = int (10000 * vec.at v (i-1)); x1 = int (10000 * vec.at v (i)); x2 = int (10000 * vec.at v (i+1)); if nPeaks < (cycles + 1) and x1 > 0 and x1 > x0 and x1 >= x2 then if firstPeak < 0 then firstPeak := i; fi; lastPeak := i; nPeaks := nPeaks + 1; fi; done; if nPeaks < 2 then 0 else cycle = (lastPeak - firstPeak) / (nPeaks - 1); stream.sampleRate / cycle; fi); _: failWith "Expected stream duration to be known"; esac); testFrequencyIntegrityWith sort freq sourceRate forward backward = (// Test that downsample and then upsample again (or vice versa, // depending on forward and backward manipulators) produces a // signal at the same frequency as the original (i.e. the overall // speed is retained exactly, regardless of the SNR on the way). nCycles = 200; duration = int (nCycles * sourceRate / freq); originals = manip.duplicated 3 (manip.withDuration duration (syn.sinusoid sourceRate freq)); there = manip.duplicated 3 (forward originals[0]); back = manip.duplicated 3 (backward there[0]); origFreq = measureFrequency (nCycles/2) originals[1]; backFreq = measureFrequency (nCycles/2) back[1]; if not (compare backFreq origFreq) then eprintln "** note: rate conversion \(sourceRate) -> \(there[1].sampleRate) -> \(sourceRate)"; examples = array (map do s: channels.mixedDown (s[2].read 500) done [ originals, there, back ]); false else true fi); testFrequencyIntegrity freq sourceRate targetRate = if sourceRate / targetRate == int (sourceRate / targetRate) then testFrequencyIntegrityWith "decimated" freq sourceRate (resample.decimated (sourceRate / targetRate)) (resample.interpolated (sourceRate / targetRate)) elif targetRate / sourceRate == int (targetRate / sourceRate) then testFrequencyIntegrityWith "interpolated" freq sourceRate (resample.interpolated (targetRate / sourceRate)) (resample.decimated (targetRate / sourceRate)) else true fi and testFrequencyIntegrityWith (if sourceRate > targetRate then "downsampled" else "upsampled" fi) freq sourceRate (resample.resampledTo targetRate) (resample.resampledTo sourceRate); [ // Test for duration of decimated stream (does not test contents, that // happens in the filters tests below). // Resampled streams only have accurate duration when their inputs // have known duration (otherwise they may be rounded up to the // processing block size). "dec-duration": \( original = manip.withDuration 8 (syn.precalculatedMono 4 (vec.fromList [1,2,3,4,5,6,7,8])); str = resample.decimated 2 original; compare str.position 0 and compare str.channels 1 and compare str.sampleRate 2 and compare str.available (Known 4) and compare str.finished? false and (r = str.read 3; compare (mat.size r) { rows = 1, columns = 3 }) and compare str.position 3 and compare str.available (Known 1) and compare str.finished? false and (r = str.read 3; compare (mat.size r) { rows = 1, columns = 1 }) and compare str.position 4 and compare str.available (Known 0) and compare str.finished? true and ( str.close (); true ) ), // Test for duration of interpolated stream (does not test contents, // that happens in the filters tests below) // Resampled streams only have accurate duration when their inputs // have known duration (otherwise they may be rounded up to the // processing block size). "int-duration": \( original = manip.withDuration 8 (syn.precalculatedMono 4 (vec.fromList [1,2,3,4,5,6,7,8])); str = resample.interpolated 2 original; compare str.position 0 and compare str.channels 1 and compare str.sampleRate 8 and compare str.available (Known 16) and compare str.finished? false and compare (mat.size (str.read 12)) { rows = 1, columns = 12 } and compare str.position 12 and compare str.available (Known 4) and compare str.finished? false and compare (mat.size (str.read 12)) { rows = 1, columns = 4 } and compare str.position 16 and compare str.available (Known 0) and compare str.finished? true and ( str.close (); true ) ), // Test for duration of resampled stream (does not test contents, // that happens in the filters tests below) // Resampled streams only have accurate duration when their inputs // have known duration (otherwise they may be rounded up to the // processing block size). "resamp-duration": \( original = manip.withDuration 8 (syn.precalculatedMono 4 (vec.fromList [1,2,3,4,5,6,7,8])); str = resample.resampledTo 6 original; compare str.position 0 and compare str.channels 1 and compare str.sampleRate 6 and compare str.available (Known 12) and compare str.finished? false and compare (mat.size (str.read 9)) { rows = 1, columns = 9 } and compare str.position 9 and compare str.available (Known 3) and compare str.finished? false and compare (mat.size (str.read 9)) { rows = 1, columns = 3 } and compare str.position 12 and compare str.available (Known 0) and compare str.finished? true and ( str.close (); true ) ), "interpolated-sine": \( // Interpolating a sinusoid should give us a sinusoid, once we've // dropped the first few samples sinusoid = syn.sinusoid 8 2; // 2Hz sine sampled at 8Hz: [ 0, 1, 0, -1 ] etc output = resample.interpolated 2 sinusoid; \() (output.read 300); result = output.read 200; reference = syn.sinusoid 16 2; \() (reference.read 300); expected = reference.read 200; compareMatrices 1e-4 result expected; ), "interpolated-rs-sine": \( // Just as above, but using resampledTo instead of interpolated sinusoid = syn.sinusoid 8 2; // 2Hz sine sampled at 8Hz: [ 0, 1, 0, -1 ] etc output = resample.resampledTo 16 sinusoid; \() (output.read 300); result = output.read 200; reference = syn.sinusoid 16 2; \() (reference.read 300); expected = reference.read 200; compareMatrices 1e-4 result expected; ), "decimated-sine": \( // Decimating a sinusoid should give us a sinusoid, once we've // dropped the first few samples sinusoid = syn.sinusoid 32 1; // 1Hz sine sampled at 32Hz output = resample.decimated 2 sinusoid; \() (output.read 300); result = output.read 200; reference = syn.sinusoid 16 1; \() (reference.read 300); expected = reference.read 200; compareMatrices 1e-4 result expected; ), "decimated-rs-sine": \( // Just as above, but using resampledTo instead of interpolated sinusoid = syn.sinusoid 32 1; // 1Hz sine sampled at 32Hz output = resample.resampledTo 16 sinusoid; \() (output.read 300); result = output.read 200; reference = syn.sinusoid 16 1; \() (reference.read 300); expected = reference.read 200; compareMatrices 1e-4 result expected; ), "resample-spectrum": \( // Principle: Generate a wave with a lot of harmonics, resample by // some ratio, check that the resulting signal has substantially // the same magnitude spectrum as the original. // Read and window a chunk of signal from a stream sigOf str n = (sig = mat.getRow 0 (str.read n); win.windowed win.hann sig); // Return magnitude spectrum, running fft at n points and then // truncating to m specOf sig n m = vec.resizedTo m (vec.divideBy n (fft.realForwardMagnitude n sig)); all id (map do { inrate, outrate }: freq = 500; forms = manip.duplicated 2 (waves.square inrate freq); inform = forms[0]; outform = resample.resampledTo outrate forms[1]; incount = inrate; outcount = outrate; speclen = (min incount outcount) / 2; // We don't compare bins within 4% of the Nyquist freq speclen = speclen - int (speclen / 25); inmag = specOf (sigOf inform incount) incount speclen; outmag = specOf (sigOf outform outcount) outcount speclen; compareMatrices 1e-7 (mat.newRowVector outmag) (mat.newRowVector inmag); done [ { inrate = 8000, outrate = 44100 }, { inrate = 48000, outrate = 44100 }, { inrate = 44100, outrate = 22050 }, { inrate = 48000, outrate = 8000 }, ]); ), "interpolated-misc": \( // Interpolating any signal by N should give a signal in which // every Nth sample is the original signal data = vec.fromList [ 0, 0.1, -0.3, -0.4, -0.3, 0, 0.5, 0.2, 0.8, -0.1 ]; data = vec.concat [ data, vec.scaled (5/4) data, vec.scaled (3/4) data, data ]; data = vec.concat [ data, data ]; input = manip.withDuration (vec.length data) (syn.precalculatedMono 4 data); factor = 3; up = resample.interpolated factor input; result = mat.getRow 0 (up.read (factor * vec.length data)); b = vec.fromList (map do i: vec.at result (i*factor) done [0..vec.length data - 1]); compareMatrices 1e-5 (mat.newRowVector b) (mat.newRowVector data); ), "interpolated-rs-misc": \( // Just as above, but using resampledTo instead of interpolated data = vec.fromList [ 0, 0.1, -0.3, -0.4, -0.3, 0, 0.5, 0.2, 0.8, -0.1 ]; data = vec.concat [ data, vec.scaled (5/4) data, vec.scaled (3/4) data, data ]; data = vec.concat [ data, data ]; input = manip.withDuration (vec.length data) (syn.precalculatedMono 4 data); factor = 3; up = resample.resampledTo (factor * input.sampleRate) input; result = mat.getRow 0 (up.read (factor * vec.length data)); b = vec.fromList (map do i: vec.at result (i*factor) done [0..vec.length data - 1]); compareMatrices 1e-5 (mat.newRowVector b) (mat.newRowVector data); ), "down-up-2": \( testFrequencyIntegrity 441 44100 22050; ), "down-up-16": \( testFrequencyIntegrity 300 48000 3000; ), "up-down-2": \( testFrequencyIntegrity 441 44100 88200; ), "up-down-16": \( testFrequencyIntegrity 300 3000 48000; ), "same-rate": \( // Test that resample to the original rate leaves data unchanged data = vec.fromList [ 0, 0.1, -0.3, -0.4, -0.3, 0, 0.5, 0.2, 0.8, -0.1 ]; data = vec.concat [ data, vec.scaled (5/4) data, vec.scaled (3/4) data, data ]; data = vec.concat [ data, data ]; input = syn.precalculatedMono 4 data; output = resample.resampledTo (input.sampleRate) input; b = output.read (vec.length data); compareMatrices 1e-8 b (mat.newRowVector data); ), ] is hash<string, () -> boolean>;