Mercurial > hg > may
changeset 396:0430226a0ea2 resample
Start work on direct resampler
author | Chris Cannam |
---|---|
date | Wed, 25 Sep 2013 18:32:55 +0100 |
parents | 23d1ab8908d9 |
children | aa08b26d4a83 |
files | .hgsubstate src/may/signal/window.yeti src/may/stream/filter.yeti |
diffstat | 3 files changed, 134 insertions(+), 10 deletions(-) [+] |
line wrap: on
line diff
--- a/.hgsubstate Tue Sep 24 17:31:14 2013 +0100 +++ b/.hgsubstate Wed Sep 25 18:32:55 2013 +0100 @@ -1,1 +1,1 @@ -43584910bba1d16db21b2ce6d8feafe87c9361fa ext +4b91cd40a51b64283ba6b5550709e89f6ec492db ext
--- a/src/may/signal/window.yeti Tue Sep 24 17:31:14 2013 +0100 +++ b/src/may/signal/window.yeti Wed Sep 25 18:32:55 2013 +0100 @@ -116,7 +116,7 @@ done [0..n-1])); /** - Kaiser window with sidelobe attenuation of 𝛼 dB and window length n + Kaiser window with sidelobe attenuation of alpha dB and window length n */ kaiserForAttenuation alpha n = (beta =
--- a/src/may/stream/filter.yeti Tue Sep 24 17:31:14 2013 +0100 +++ b/src/may/stream/filter.yeti Wed Sep 25 18:32:55 2013 +0100 @@ -11,6 +11,8 @@ syn = load may.stream.syntheticstream; cplx = load may.complex; +plot = load may.plot; + load may.stream.type; load may.vector.type; load may.matrix.type; @@ -426,11 +428,24 @@ /** Produce a sinc window of width equal to zc zero crossings per half, with n samples from peak to first zero crossing, multiplied by a - Kaiser window with attenuation 𝛼 dB + Kaiser window with attenuation alpha dB */ -kaiserSincWindow zc n 𝛼 = +kaiserSincWindow zc n alpha = (sw = win.sinc (n*2) (n*zc*2 + 1); - kw = win.kaiserForAttenuation 𝛼 (vec.length sw); + kw = win.kaiserForAttenuation alpha (vec.length sw); + bf.multiply sw kw); + +/** + * Produce a sinc window with n samples from peak to first zero + * crossing, multiplied by a Kaiser window with attenuation alpha dB + * and transition bandwidth Hz at the given sampleRate. The filter + * will contain an odd number of samples. + */ +kaiserSincFilterFor { n, alpha, bandwidth, sampleRate } = + (kw = win.kaiserForBandwidth alpha bandwidth sampleRate; + kw = if (vec.length kw) % 2 == 0 then vec.concat [vec.zeros 1, kw] else kw fi; + sw = win.sinc (n*2) (vec.length kw); + println "kaiserSincFilterFor: filter length is \(vec.length sw)"; bf.multiply sw kw); bandpassed f0 f1 attenuation bandwidth s = @@ -499,10 +514,9 @@ interpolated factor s = //!!! factor must be an integer [how to enforce this??] if factor == 1 then s else - nzc = 13; //!!! where does this magic come from? + nzc = 13; attenuation = 80; filter = kaiserSincWindow nzc factor attenuation; -println "int: filter length for nzc \(nzc), attn \(attenuation), factor \(factor) is \(vec.length filter)"; out = delayedBy (- (nzc * factor)) (convolvedWith [Framesize 1024] (mat.newMatrix (RowMajor ()) (map \filter [1..s.channels])) @@ -533,7 +547,6 @@ nzc = 13; attenuation = 80; filter = kaiserSincWindow nzc factor attenuation; -println "dec: filter length for nzc \(nzc), attn \(attenuation), factor \(factor) is \(vec.length filter)"; filtered = (convolvedWith [Framesize 1024] (mat.newMatrix (RowMajor ()) (map \filter [1..s.channels])) s); @@ -546,11 +559,122 @@ gcd a b = (c = a % b; if c == 0 then b else gcd b c fi); //!!! slow -- can't in this form be used for e.g. 44100 -> 48000 conversion -resampledTo rate s = +resampledSlowlyTo rate s = (g = gcd rate s.sampleRate; println "orig = \(s.sampleRate), target = \(rate), g = \(g), up = \(rate/g), down = \(s.sampleRate/g)"; decimated (s.sampleRate / g) (interpolated (rate/g) s)); +resampledSplendidlyTo targetRate s = + ( + // We need a low-pass filter with a cutoff of the lower of the two + // (nyquist frequencies of the) sample rates. This filter needs to + // be sampled at a sufficient resolution that we can get values + // from it at every sample point in the source rate and in the + // target. + + sourceRate = s.sampleRate; + higher = max sourceRate targetRate; + lower = min sourceRate targetRate; + g = gcd higher lower; + + // Example: ratio of 3:4 in sample periods, corresponding to + // e.g. resampling from 48kHz to 36kHz. The lower rate has the + // longer sample period. We need a filter with n=4 values from + // peak to first pole -- this is the longer sample period relative + // to the shorter and is obtained by dividing the *higher* rate by + // the gcd (because the ratio of sample periods is the reciprocal + // of the ratio of sample rates). + + n = higher / g; // would be 4 in the above example + + println "for target rate \(targetRate) and source rate \(sourceRate), gcd = \(g) and n = \(n), would be \(lower / g) if we used min"; + + // Our filter is a sinc function with peak-to-pole length n, + // multiplied by a Kaiser window of transition bandwidth narrow + // enough at the effective sample rate n to exclude... what? + + //!!! ponder +// filt = kaiserSincFilterFor { n, alpha = 80, bandwidth = 10, sampleRate = lower }; + filt = kaiserSincFilterFor { n, alpha = 80, bandwidth = 1, sampleRate = n }; + +//\() ( plot.plot [Vector filt] ); + + // Now we have a filter of (odd) length flen in which the lower + // sample rate corresponds to every n'th point and the higher rate + // to every m'th where n and m are higher and lower rates divided + // by their gcd respectively. So if x coordinates are on the same + // scale as our filter resolution, then source sample i is at i * + // (targetRate / gcd) and target sample j is at j * (sourceRate / + // gcd). + + // To reconstruct a single target sample, we want a buffer (real + // or virtual) of flen values formed of source samples spaced at + // intervals of (targetRate / gcd), in our example case 3. This + // is initially formed with the first sample at the filter peak. + // + // 0 0 0 0 a 0 0 b 0 + // + // and of course we have our filter + // + // f1 f2 f3 f4 f5 f6 f7 f8 f9 + // + // We take the sum of products of non-zero values from this buffer + // with corresponding values in the filter + // + // a * f5 + b * f8 + // + // Then we drop (sourceRate / gcd) values, in our example case 4, + // from the start of the buffer and fill until it has flen values + // again + // + // a 0 0 b 0 0 c 0 0 + // + // repeat to reconstruct the next target sample + // + // a * f1 + b * f4 + c * f7 + // + // and so on. + + flen = vec.length filt; + halflen = int (flen/2); // actual filter length is halflen + halflen + 1 + spacedInput = spaced (targetRate / g) s; + var buffer = mat.toRowMajor + (mat.concat (Horizontal ()) + [mat.zeroMatrix { rows = s.channels, columns = halflen + 1 }, + spacedInput.read halflen]); + var pos = 0; + + reconstructOne ch = + (var out = 0; + var series = mat.getRow ch buffer; + for [0..(vec.length series)-1] do i: + x = vec.at series i; + if x != 0 then + out := out + x * (vec.at filt i); + fi; + done; + out); + + readOne () = + (result = mat.newColumnVector + (vec.fromList (map reconstructOne [0 .. s.channels-1])); + m = sourceRate / g; + buffer := mat.concat (Horizontal ()) + [mat.columnSlice buffer m (flen - m), + spacedInput.read m]; + result); + + s with + { + get sampleRate () = targetRate, + get position () = pos, + get available () = Unknown (), //!!! todo + read n = mat.toRowMajor + (mat.concat (Horizontal ()) (map do _: readOne () done [1..n])), + }); + +resampledTo = resampledSplendidlyTo; + { withDuration is number -> stream -> stream, delayedBy is number -> stream -> stream, @@ -564,7 +688,7 @@ repeated is stream -> stream, duplicated is number -> stream -> array<stream>, convolvedWith is list<Fast boolean | Framesize number> -> matrix -> stream -> stream, - kaiserSincWindow, + kaiserSincWindow, kaiserSincFilterFor, lowpassed, bandpassed, highpassed, spaced, interpolated, decimated, picked, resampledTo,