| Chris@137 | 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */ | 
| Chris@150 | 2 /* | 
| Chris@150 | 3     QM DSP Library | 
| Chris@150 | 4 | 
| Chris@150 | 5     Centre for Digital Music, Queen Mary, University of London. | 
| Chris@150 | 6     This file by Chris Cannam. | 
| Chris@150 | 7 | 
| Chris@150 | 8     This program is free software; you can redistribute it and/or | 
| Chris@150 | 9     modify it under the terms of the GNU General Public License as | 
| Chris@150 | 10     published by the Free Software Foundation; either version 2 of the | 
| Chris@150 | 11     License, or (at your option) any later version.  See the file | 
| Chris@150 | 12     COPYING included with this distribution for more information. | 
| Chris@150 | 13 */ | 
| Chris@137 | 14 | 
| Chris@137 | 15 #include "Resampler.h" | 
| Chris@137 | 16 | 
| Chris@150 | 17 #include "maths/MathUtilities.h" | 
| Chris@150 | 18 #include "base/KaiserWindow.h" | 
| Chris@150 | 19 #include "base/SincWindow.h" | 
| Chris@150 | 20 #include "thread/Thread.h" | 
| Chris@137 | 21 | 
| Chris@137 | 22 #include <iostream> | 
| Chris@138 | 23 #include <vector> | 
| Chris@145 | 24 #include <map> | 
| Chris@147 | 25 #include <cassert> | 
| Chris@138 | 26 | 
| Chris@138 | 27 using std::vector; | 
| Chris@145 | 28 using std::map; | 
| Chris@137 | 29 | 
| Chris@141 | 30 //#define DEBUG_RESAMPLER 1 | 
| Chris@141 | 31 | 
| Chris@137 | 32 Resampler::Resampler(int sourceRate, int targetRate) : | 
| Chris@137 | 33     m_sourceRate(sourceRate), | 
| Chris@137 | 34     m_targetRate(targetRate) | 
| Chris@137 | 35 { | 
| Chris@149 | 36     initialise(100, 0.02); | 
| Chris@149 | 37 } | 
| Chris@149 | 38 | 
| Chris@149 | 39 Resampler::Resampler(int sourceRate, int targetRate, | 
| Chris@149 | 40                      double snr, double bandwidth) : | 
| Chris@149 | 41     m_sourceRate(sourceRate), | 
| Chris@149 | 42     m_targetRate(targetRate) | 
| Chris@149 | 43 { | 
| Chris@149 | 44     initialise(snr, bandwidth); | 
| Chris@137 | 45 } | 
| Chris@137 | 46 | 
| Chris@137 | 47 Resampler::~Resampler() | 
| Chris@137 | 48 { | 
| Chris@137 | 49     delete[] m_phaseData; | 
| Chris@137 | 50 } | 
| Chris@137 | 51 | 
| Chris@146 | 52 // peakToPole -> length -> beta -> window | 
| Chris@156 | 53 static map<double, map<int, map<double, vector<double> > > > | 
| Chris@146 | 54 knownFilters; | 
| Chris@146 | 55 | 
| Chris@146 | 56 static Mutex | 
| Chris@146 | 57 knownFilterMutex; | 
| Chris@146 | 58 | 
| Chris@137 | 59 void | 
| Chris@149 | 60 Resampler::initialise(double snr, double bandwidth) | 
| Chris@137 | 61 { | 
| Chris@137 | 62     int higher = std::max(m_sourceRate, m_targetRate); | 
| Chris@137 | 63     int lower = std::min(m_sourceRate, m_targetRate); | 
| Chris@137 | 64 | 
| Chris@137 | 65     m_gcd = MathUtilities::gcd(lower, higher); | 
| Chris@156 | 66     m_peakToPole = higher / m_gcd; | 
| Chris@137 | 67 | 
| Chris@156 | 68     if (m_targetRate < m_sourceRate) { | 
| Chris@156 | 69         // antialiasing filter, should be slightly below nyquist | 
| Chris@156 | 70         m_peakToPole = m_peakToPole / (1.0 - bandwidth/2.0); | 
| Chris@156 | 71     } | 
| Chris@137 | 72 | 
| Chris@137 | 73     KaiserWindow::Parameters params = | 
| Chris@156 | 74 	KaiserWindow::parametersForBandwidth(snr, bandwidth, higher / m_gcd); | 
| Chris@137 | 75 | 
| Chris@137 | 76     params.length = | 
| Chris@137 | 77 	(params.length % 2 == 0 ? params.length + 1 : params.length); | 
| Chris@137 | 78 | 
| Chris@147 | 79     params.length = | 
| Chris@147 | 80         (params.length > 200001 ? 200001 : params.length); | 
| Chris@147 | 81 | 
| Chris@137 | 82     m_filterLength = params.length; | 
| Chris@145 | 83 | 
| Chris@146 | 84     vector<double> filter; | 
| Chris@146 | 85     knownFilterMutex.lock(); | 
| Chris@137 | 86 | 
| Chris@156 | 87     if (knownFilters[m_peakToPole][m_filterLength].find(params.beta) == | 
| Chris@156 | 88 	knownFilters[m_peakToPole][m_filterLength].end()) { | 
| Chris@146 | 89 | 
| Chris@146 | 90 	KaiserWindow kw(params); | 
| Chris@156 | 91 	SincWindow sw(m_filterLength, m_peakToPole * 2); | 
| Chris@146 | 92 | 
| Chris@146 | 93 	filter = vector<double>(m_filterLength, 0.0); | 
| Chris@146 | 94 	for (int i = 0; i < m_filterLength; ++i) filter[i] = 1.0; | 
| Chris@146 | 95 	sw.cut(filter.data()); | 
| Chris@146 | 96 	kw.cut(filter.data()); | 
| Chris@146 | 97 | 
| Chris@156 | 98 	knownFilters[m_peakToPole][m_filterLength][params.beta] = filter; | 
| Chris@146 | 99     } | 
| Chris@146 | 100 | 
| Chris@156 | 101     filter = knownFilters[m_peakToPole][m_filterLength][params.beta]; | 
| Chris@146 | 102     knownFilterMutex.unlock(); | 
| Chris@137 | 103 | 
| Chris@137 | 104     int inputSpacing = m_targetRate / m_gcd; | 
| Chris@137 | 105     int outputSpacing = m_sourceRate / m_gcd; | 
| Chris@137 | 106 | 
| Chris@141 | 107 #ifdef DEBUG_RESAMPLER | 
| Chris@141 | 108     std::cerr << "resample " << m_sourceRate << " -> " << m_targetRate | 
| Chris@141 | 109 	      << ": inputSpacing " << inputSpacing << ", outputSpacing " | 
| Chris@141 | 110 	      << outputSpacing << ": filter length " << m_filterLength | 
| Chris@141 | 111 	      << std::endl; | 
| Chris@141 | 112 #endif | 
| Chris@137 | 113 | 
| Chris@147 | 114     // Now we have a filter of (odd) length flen in which the lower | 
| Chris@147 | 115     // sample rate corresponds to every n'th point and the higher rate | 
| Chris@147 | 116     // to every m'th where n and m are higher and lower rates divided | 
| Chris@147 | 117     // by their gcd respectively. So if x coordinates are on the same | 
| Chris@147 | 118     // scale as our filter resolution, then source sample i is at i * | 
| Chris@147 | 119     // (targetRate / gcd) and target sample j is at j * (sourceRate / | 
| Chris@147 | 120     // gcd). | 
| Chris@147 | 121 | 
| Chris@147 | 122     // To reconstruct a single target sample, we want a buffer (real | 
| Chris@147 | 123     // or virtual) of flen values formed of source samples spaced at | 
| Chris@147 | 124     // intervals of (targetRate / gcd), in our example case 3.  This | 
| Chris@147 | 125     // is initially formed with the first sample at the filter peak. | 
| Chris@147 | 126     // | 
| Chris@147 | 127     // 0  0  0  0  a  0  0  b  0 | 
| Chris@147 | 128     // | 
| Chris@147 | 129     // and of course we have our filter | 
| Chris@147 | 130     // | 
| Chris@147 | 131     // f1 f2 f3 f4 f5 f6 f7 f8 f9 | 
| Chris@147 | 132     // | 
| Chris@147 | 133     // We take the sum of products of non-zero values from this buffer | 
| Chris@147 | 134     // with corresponding values in the filter | 
| Chris@147 | 135     // | 
| Chris@147 | 136     // a * f5 + b * f8 | 
| Chris@147 | 137     // | 
| Chris@147 | 138     // Then we drop (sourceRate / gcd) values, in our example case 4, | 
| Chris@147 | 139     // from the start of the buffer and fill until it has flen values | 
| Chris@147 | 140     // again | 
| Chris@147 | 141     // | 
| Chris@147 | 142     // a  0  0  b  0  0  c  0  0 | 
| Chris@147 | 143     // | 
| Chris@147 | 144     // repeat to reconstruct the next target sample | 
| Chris@147 | 145     // | 
| Chris@147 | 146     // a * f1 + b * f4 + c * f7 | 
| Chris@147 | 147     // | 
| Chris@147 | 148     // and so on. | 
| Chris@147 | 149     // | 
| Chris@147 | 150     // Above I said the buffer could be "real or virtual" -- ours is | 
| Chris@147 | 151     // virtual. We don't actually store all the zero spacing values, | 
| Chris@147 | 152     // except for padding at the start; normally we store only the | 
| Chris@147 | 153     // values that actually came from the source stream, along with a | 
| Chris@147 | 154     // phase value that tells us how many virtual zeroes there are at | 
| Chris@147 | 155     // the start of the virtual buffer.  So the two examples above are | 
| Chris@147 | 156     // | 
| Chris@147 | 157     // 0 a b  [ with phase 1 ] | 
| Chris@147 | 158     // a b c  [ with phase 0 ] | 
| Chris@147 | 159     // | 
| Chris@147 | 160     // Having thus broken down the buffer so that only the elements we | 
| Chris@147 | 161     // need to multiply are present, we can also unzip the filter into | 
| Chris@147 | 162     // every-nth-element subsets at each phase, allowing us to do the | 
| Chris@147 | 163     // filter multiplication as a simply vector multiply. That is, rather | 
| Chris@147 | 164     // than store | 
| Chris@147 | 165     // | 
| Chris@147 | 166     // f1 f2 f3 f4 f5 f6 f7 f8 f9 | 
| Chris@147 | 167     // | 
| Chris@147 | 168     // we store separately | 
| Chris@147 | 169     // | 
| Chris@147 | 170     // f1 f4 f7 | 
| Chris@147 | 171     // f2 f5 f8 | 
| Chris@147 | 172     // f3 f6 f9 | 
| Chris@147 | 173     // | 
| Chris@147 | 174     // Each time we complete a multiply-and-sum, we need to work out | 
| Chris@147 | 175     // how many (real) samples to drop from the start of our buffer, | 
| Chris@147 | 176     // and how many to add at the end of it for the next multiply.  We | 
| Chris@147 | 177     // know we want to drop enough real samples to move along by one | 
| Chris@147 | 178     // computed output sample, which is our outputSpacing number of | 
| Chris@147 | 179     // virtual buffer samples. Depending on the relationship between | 
| Chris@147 | 180     // input and output spacings, this may mean dropping several real | 
| Chris@147 | 181     // samples, one real sample, or none at all (and simply moving to | 
| Chris@147 | 182     // a different "phase"). | 
| Chris@147 | 183 | 
| Chris@137 | 184     m_phaseData = new Phase[inputSpacing]; | 
| Chris@137 | 185 | 
| Chris@137 | 186     for (int phase = 0; phase < inputSpacing; ++phase) { | 
| Chris@137 | 187 | 
| Chris@137 | 188 	Phase p; | 
| Chris@137 | 189 | 
| Chris@137 | 190 	p.nextPhase = phase - outputSpacing; | 
| Chris@137 | 191 	while (p.nextPhase < 0) p.nextPhase += inputSpacing; | 
| Chris@137 | 192 	p.nextPhase %= inputSpacing; | 
| Chris@137 | 193 | 
| Chris@141 | 194 	p.drop = int(ceil(std::max(0.0, double(outputSpacing - phase)) | 
| Chris@141 | 195 			  / inputSpacing)); | 
| Chris@137 | 196 | 
| Chris@141 | 197 	int filtZipLength = int(ceil(double(m_filterLength - phase) | 
| Chris@141 | 198 				     / inputSpacing)); | 
| Chris@147 | 199 | 
| Chris@137 | 200 	for (int i = 0; i < filtZipLength; ++i) { | 
| Chris@137 | 201 	    p.filter.push_back(filter[i * inputSpacing + phase]); | 
| Chris@137 | 202 	} | 
| Chris@137 | 203 | 
| Chris@137 | 204 	m_phaseData[phase] = p; | 
| Chris@137 | 205     } | 
| Chris@137 | 206 | 
| Chris@137 | 207     // The May implementation of this uses a pull model -- we ask the | 
| Chris@137 | 208     // resampler for a certain number of output samples, and it asks | 
| Chris@137 | 209     // its source stream for as many as it needs to calculate | 
| Chris@137 | 210     // those. This means (among other things) that the source stream | 
| Chris@137 | 211     // can be asked for enough samples up-front to fill the buffer | 
| Chris@137 | 212     // before the first output sample is generated. | 
| Chris@137 | 213     // | 
| Chris@137 | 214     // In this implementation we're using a push model in which a | 
| Chris@137 | 215     // certain number of source samples is provided and we're asked | 
| Chris@137 | 216     // for as many output samples as that makes available. But we | 
| Chris@137 | 217     // can't return any samples from the beginning until half the | 
| Chris@137 | 218     // filter length has been provided as input. This means we must | 
| Chris@137 | 219     // either return a very variable number of samples (none at all | 
| Chris@137 | 220     // until the filter fills, then half the filter length at once) or | 
| Chris@137 | 221     // else have a lengthy declared latency on the output. We do the | 
| Chris@137 | 222     // latter. (What do other implementations do?) | 
| Chris@148 | 223     // | 
| Chris@147 | 224     // We want to make sure the first "real" sample will eventually be | 
| Chris@147 | 225     // aligned with the centre sample in the filter (it's tidier, and | 
| Chris@147 | 226     // easier to do diagnostic calculations that way). So we need to | 
| Chris@147 | 227     // pick the initial phase and buffer fill accordingly. | 
| Chris@147 | 228     // | 
| Chris@147 | 229     // Example: if the inputSpacing is 2, outputSpacing is 3, and | 
| Chris@147 | 230     // filter length is 7, | 
| Chris@147 | 231     // | 
| Chris@147 | 232     //    x     x     x     x     a     b     c ... input samples | 
| Chris@147 | 233     // 0  1  2  3  4  5  6  7  8  9 10 11 12 13 ... | 
| Chris@147 | 234     //          i        j        k        l    ... output samples | 
| Chris@147 | 235     // [--------|--------] <- filter with centre mark | 
| Chris@147 | 236     // | 
| Chris@147 | 237     // Let h be the index of the centre mark, here 3 (generally | 
| Chris@147 | 238     // int(filterLength/2) for odd-length filters). | 
| Chris@147 | 239     // | 
| Chris@147 | 240     // The smallest n such that h + n * outputSpacing > filterLength | 
| Chris@147 | 241     // is 2 (that is, ceil((filterLength - h) / outputSpacing)), and | 
| Chris@147 | 242     // (h + 2 * outputSpacing) % inputSpacing == 1, so the initial | 
| Chris@147 | 243     // phase is 1. | 
| Chris@147 | 244     // | 
| Chris@147 | 245     // To achieve our n, we need to pre-fill the "virtual" buffer with | 
| Chris@147 | 246     // 4 zero samples: the x's above. This is int((h + n * | 
| Chris@147 | 247     // outputSpacing) / inputSpacing). It's the phase that makes this | 
| Chris@147 | 248     // buffer get dealt with in such a way as to give us an effective | 
| Chris@147 | 249     // index for sample a of 9 rather than 8 or 10 or whatever. | 
| Chris@147 | 250     // | 
| Chris@147 | 251     // This gives us output latency of 2 (== n), i.e. output samples i | 
| Chris@147 | 252     // and j will appear before the one in which input sample a is at | 
| Chris@147 | 253     // the centre of the filter. | 
| Chris@147 | 254 | 
| Chris@147 | 255     int h = int(m_filterLength / 2); | 
| Chris@147 | 256     int n = ceil(double(m_filterLength - h) / outputSpacing); | 
| Chris@141 | 257 | 
| Chris@147 | 258     m_phase = (h + n * outputSpacing) % inputSpacing; | 
| Chris@147 | 259 | 
| Chris@147 | 260     int fill = (h + n * outputSpacing) / inputSpacing; | 
| Chris@147 | 261 | 
| Chris@147 | 262     m_latency = n; | 
| Chris@147 | 263 | 
| Chris@147 | 264     m_buffer = vector<double>(fill, 0); | 
| Chris@145 | 265     m_bufferOrigin = 0; | 
| Chris@141 | 266 | 
| Chris@141 | 267 #ifdef DEBUG_RESAMPLER | 
| Chris@141 | 268     std::cerr << "initial phase " << m_phase << " (as " << (m_filterLength/2) << " % " << inputSpacing << ")" | 
| Chris@141 | 269 	      << ", latency " << m_latency << std::endl; | 
| Chris@141 | 270 #endif | 
| Chris@137 | 271 } | 
| Chris@137 | 272 | 
| Chris@137 | 273 double | 
| Chris@141 | 274 Resampler::reconstructOne() | 
| Chris@137 | 275 { | 
| Chris@137 | 276     Phase &pd = m_phaseData[m_phase]; | 
| Chris@141 | 277     double v = 0.0; | 
| Chris@137 | 278     int n = pd.filter.size(); | 
| Chris@147 | 279 | 
| Chris@148 | 280     assert(n + m_bufferOrigin <= (int)m_buffer.size()); | 
| Chris@147 | 281 | 
| Chris@145 | 282     const double *const __restrict__ buf = m_buffer.data() + m_bufferOrigin; | 
| Chris@145 | 283     const double *const __restrict__ filt = pd.filter.data(); | 
| Chris@147 | 284 | 
| Chris@147 | 285 //    std::cerr << "phase = " << m_phase << ", drop = " << pd.drop << ", buffer for reconstruction starts..."; | 
| Chris@147 | 286 //    for (int i = 0; i < 20; ++i) { | 
| Chris@147 | 287 //        if (i % 5 == 0) std::cerr << "\n" << i << " "; | 
| Chris@147 | 288 //        std::cerr << buf[i] << " "; | 
| Chris@147 | 289 //    } | 
| Chris@147 | 290 //    std::cerr << std::endl; | 
| Chris@147 | 291 | 
| Chris@137 | 292     for (int i = 0; i < n; ++i) { | 
| Chris@145 | 293 	// NB gcc can only vectorize this with -ffast-math | 
| Chris@145 | 294 	v += buf[i] * filt[i]; | 
| Chris@137 | 295     } | 
| Chris@149 | 296 | 
| Chris@145 | 297     m_bufferOrigin += pd.drop; | 
| Chris@141 | 298     m_phase = pd.nextPhase; | 
| Chris@137 | 299     return v; | 
| Chris@137 | 300 } | 
| Chris@137 | 301 | 
| Chris@137 | 302 int | 
| Chris@141 | 303 Resampler::process(const double *src, double *dst, int n) | 
| Chris@137 | 304 { | 
| Chris@141 | 305     for (int i = 0; i < n; ++i) { | 
| Chris@141 | 306 	m_buffer.push_back(src[i]); | 
| Chris@137 | 307     } | 
| Chris@137 | 308 | 
| Chris@141 | 309     int maxout = int(ceil(double(n) * m_targetRate / m_sourceRate)); | 
| Chris@141 | 310     int outidx = 0; | 
| Chris@139 | 311 | 
| Chris@141 | 312 #ifdef DEBUG_RESAMPLER | 
| Chris@141 | 313     std::cerr << "process: buf siz " << m_buffer.size() << " filt siz for phase " << m_phase << " " << m_phaseData[m_phase].filter.size() << std::endl; | 
| Chris@141 | 314 #endif | 
| Chris@141 | 315 | 
| Chris@156 | 316     double scaleFactor = (double(m_targetRate) / m_gcd) / m_peakToPole; | 
| Chris@142 | 317 | 
| Chris@141 | 318     while (outidx < maxout && | 
| Chris@145 | 319 	   m_buffer.size() >= m_phaseData[m_phase].filter.size() + m_bufferOrigin) { | 
| Chris@142 | 320 	dst[outidx] = scaleFactor * reconstructOne(); | 
| Chris@141 | 321 	outidx++; | 
| Chris@139 | 322     } | 
| Chris@145 | 323 | 
| Chris@145 | 324     m_buffer = vector<double>(m_buffer.begin() + m_bufferOrigin, m_buffer.end()); | 
| Chris@145 | 325     m_bufferOrigin = 0; | 
| Chris@141 | 326 | 
| Chris@141 | 327     return outidx; | 
| Chris@137 | 328 } | 
| Chris@141 | 329 | 
| Chris@138 | 330 std::vector<double> | 
| Chris@160 | 331 Resampler::process(const double *src, int n) | 
| Chris@160 | 332 { | 
| Chris@160 | 333     int maxout = int(ceil(double(n) * m_targetRate / m_sourceRate)); | 
| Chris@160 | 334     std::vector<double> out(maxout, 0.0); | 
| Chris@160 | 335     int got = process(src, out.data(), n); | 
| Chris@160 | 336     assert(got <= maxout); | 
| Chris@160 | 337     if (got < maxout) out.resize(got); | 
| Chris@160 | 338     return out; | 
| Chris@160 | 339 } | 
| Chris@160 | 340 | 
| Chris@160 | 341 std::vector<double> | 
| Chris@138 | 342 Resampler::resample(int sourceRate, int targetRate, const double *data, int n) | 
| Chris@138 | 343 { | 
| Chris@138 | 344     Resampler r(sourceRate, targetRate); | 
| Chris@138 | 345 | 
| Chris@138 | 346     int latency = r.getLatency(); | 
| Chris@138 | 347 | 
| Chris@143 | 348     // latency is the output latency. We need to provide enough | 
| Chris@143 | 349     // padding input samples at the end of input to guarantee at | 
| Chris@143 | 350     // *least* the latency's worth of output samples. that is, | 
| Chris@143 | 351 | 
| Chris@148 | 352     int inputPad = int(ceil((double(latency) * sourceRate) / targetRate)); | 
| Chris@143 | 353 | 
| Chris@143 | 354     // that means we are providing this much input in total: | 
| Chris@143 | 355 | 
| Chris@143 | 356     int n1 = n + inputPad; | 
| Chris@143 | 357 | 
| Chris@143 | 358     // and obtaining this much output in total: | 
| Chris@143 | 359 | 
| Chris@148 | 360     int m1 = int(ceil((double(n1) * targetRate) / sourceRate)); | 
| Chris@143 | 361 | 
| Chris@143 | 362     // in order to return this much output to the user: | 
| Chris@143 | 363 | 
| Chris@148 | 364     int m = int(ceil((double(n) * targetRate) / sourceRate)); | 
| Chris@143 | 365 | 
| Chris@148 | 366 //    std::cerr << "n = " << n << ", sourceRate = " << sourceRate << ", targetRate = " << targetRate << ", m = " << m << ", latency = " << latency << ", inputPad = " << inputPad << ", m1 = " << m1 << ", n1 = " << n1 << ", n1 - n = " << n1 - n << std::endl; | 
| Chris@138 | 367 | 
| Chris@138 | 368     vector<double> pad(n1 - n, 0.0); | 
| Chris@143 | 369     vector<double> out(m1 + 1, 0.0); | 
| Chris@138 | 370 | 
| Chris@138 | 371     int got = r.process(data, out.data(), n); | 
| Chris@138 | 372     got += r.process(pad.data(), out.data() + got, pad.size()); | 
| Chris@138 | 373 | 
| Chris@141 | 374 #ifdef DEBUG_RESAMPLER | 
| Chris@141 | 375     std::cerr << "resample: " << n << " in, " << got << " out" << std::endl; | 
| Chris@147 | 376     std::cerr << "first 10 in:" << std::endl; | 
| Chris@147 | 377     for (int i = 0; i < 10; ++i) { | 
| Chris@147 | 378         std::cerr << data[i] << " "; | 
| Chris@147 | 379         if (i == 5) std::cerr << std::endl; | 
| Chris@141 | 380     } | 
| Chris@147 | 381     std::cerr << std::endl; | 
| Chris@141 | 382 #endif | 
| Chris@141 | 383 | 
| Chris@143 | 384     int toReturn = got - latency; | 
| Chris@143 | 385     if (toReturn > m) toReturn = m; | 
| Chris@143 | 386 | 
| Chris@147 | 387     vector<double> sliced(out.begin() + latency, | 
| Chris@143 | 388 			  out.begin() + latency + toReturn); | 
| Chris@147 | 389 | 
| Chris@147 | 390 #ifdef DEBUG_RESAMPLER | 
| Chris@147 | 391     std::cerr << "all out (after latency compensation), length " << sliced.size() << ":"; | 
| Chris@147 | 392     for (int i = 0; i < sliced.size(); ++i) { | 
| Chris@147 | 393 	if (i % 5 == 0) std::cerr << std::endl << i << "... "; | 
| Chris@147 | 394 	std::cerr << sliced[i] << " "; | 
| Chris@147 | 395     } | 
| Chris@147 | 396     std::cerr << std::endl; | 
| Chris@147 | 397 #endif | 
| Chris@147 | 398 | 
| Chris@147 | 399     return sliced; | 
| Chris@138 | 400 } | 
| Chris@138 | 401 |