annotate data/fileio/CodedAudioFileReader.cpp @ 1248:58dd6a6fe414 piper

Update to use listargs variant of Piper stuff (so that the plugin winnowing feature from the penultimate commit actually works)
author Chris Cannam
date Thu, 03 Nov 2016 15:38:17 +0000
parents 6877f4200912
children 0a9193dc136b
rev   line source
Chris@148 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@148 2
Chris@148 3 /*
Chris@148 4 Sonic Visualiser
Chris@148 5 An audio file viewer and annotation editor.
Chris@148 6 Centre for Digital Music, Queen Mary, University of London.
Chris@297 7 This file copyright 2006-2007 Chris Cannam and QMUL.
Chris@148 8
Chris@148 9 This program is free software; you can redistribute it and/or
Chris@148 10 modify it under the terms of the GNU General Public License as
Chris@148 11 published by the Free Software Foundation; either version 2 of the
Chris@148 12 License, or (at your option) any later version. See the file
Chris@148 13 COPYING included with this distribution for more information.
Chris@148 14 */
Chris@148 15
Chris@148 16 #include "CodedAudioFileReader.h"
Chris@148 17
Chris@148 18 #include "WavFileReader.h"
Chris@148 19 #include "base/TempDirectory.h"
Chris@148 20 #include "base/Exceptions.h"
Chris@192 21 #include "base/Profiler.h"
Chris@297 22 #include "base/Serialiser.h"
Chris@297 23 #include "base/Resampler.h"
Chris@1098 24 #include "base/StorageAdviser.h"
Chris@148 25
Chris@723 26 #include <stdint.h>
Chris@148 27 #include <iostream>
Chris@148 28 #include <QDir>
Chris@263 29 #include <QMutexLocker>
Chris@148 30
Chris@1096 31 using namespace std;
Chris@1096 32
Chris@297 33 CodedAudioFileReader::CodedAudioFileReader(CacheMode cacheMode,
Chris@1040 34 sv_samplerate_t targetRate,
Chris@920 35 bool normalised) :
Chris@148 36 m_cacheMode(cacheMode),
Chris@148 37 m_initialised(false),
Chris@297 38 m_serialiser(0),
Chris@297 39 m_fileRate(0),
Chris@148 40 m_cacheFileWritePtr(0),
Chris@148 41 m_cacheFileReader(0),
Chris@148 42 m_cacheWriteBuffer(0),
Chris@148 43 m_cacheWriteBufferIndex(0),
Chris@297 44 m_cacheWriteBufferSize(16384),
Chris@297 45 m_resampler(0),
Chris@757 46 m_resampleBuffer(0),
Chris@920 47 m_fileFrameCount(0),
Chris@920 48 m_normalised(normalised),
Chris@920 49 m_max(0.f),
Chris@920 50 m_gain(1.f)
Chris@148 51 {
Chris@922 52 SVDEBUG << "CodedAudioFileReader::CodedAudioFileReader: rate " << targetRate << ", normalised = " << normalised << endl;
Chris@297 53
Chris@297 54 m_frameCount = 0;
Chris@297 55 m_sampleRate = targetRate;
Chris@148 56 }
Chris@148 57
Chris@148 58 CodedAudioFileReader::~CodedAudioFileReader()
Chris@148 59 {
Chris@263 60 QMutexLocker locker(&m_cacheMutex);
Chris@263 61
Chris@297 62 endSerialised();
Chris@1098 63
Chris@148 64 if (m_cacheFileWritePtr) sf_close(m_cacheFileWritePtr);
Chris@297 65
Chris@742 66 SVDEBUG << "CodedAudioFileReader::~CodedAudioFileReader: deleting cache file reader" << endl;
Chris@532 67
Chris@297 68 delete m_cacheFileReader;
Chris@297 69 delete[] m_cacheWriteBuffer;
Chris@148 70
Chris@148 71 if (m_cacheFileName != "") {
Chris@290 72 if (!QFile(m_cacheFileName).remove()) {
Chris@843 73 cerr << "WARNING: CodedAudioFileReader::~CodedAudioFileReader: Failed to delete cache file \"" << m_cacheFileName << "\"" << endl;
Chris@148 74 }
Chris@148 75 }
Chris@297 76
Chris@297 77 delete m_resampler;
Chris@297 78 delete[] m_resampleBuffer;
Chris@1098 79
Chris@1098 80 if (!m_data.empty()) {
Chris@1098 81 StorageAdviser::notifyDoneAllocation
Chris@1098 82 (StorageAdviser::MemoryAllocation,
Chris@1098 83 (m_data.size() * sizeof(float)) / 1024);
Chris@1098 84 }
Chris@297 85 }
Chris@297 86
Chris@297 87 void
Chris@297 88 CodedAudioFileReader::startSerialised(QString id)
Chris@297 89 {
Chris@742 90 SVDEBUG << "CodedAudioFileReader::startSerialised(" << id << ")" << endl;
Chris@297 91
Chris@297 92 delete m_serialiser;
Chris@297 93 m_serialiser = new Serialiser(id);
Chris@297 94 }
Chris@297 95
Chris@297 96 void
Chris@297 97 CodedAudioFileReader::endSerialised()
Chris@297 98 {
Chris@844 99 SVDEBUG << "CodedAudioFileReader(" << this << ")::endSerialised: id = " << (m_serialiser ? m_serialiser->getId() : "(none)") << endl;
Chris@297 100
Chris@297 101 delete m_serialiser;
Chris@297 102 m_serialiser = 0;
Chris@148 103 }
Chris@148 104
Chris@148 105 void
Chris@148 106 CodedAudioFileReader::initialiseDecodeCache()
Chris@148 107 {
Chris@263 108 QMutexLocker locker(&m_cacheMutex);
Chris@263 109
Chris@742 110 SVDEBUG << "CodedAudioFileReader::initialiseDecodeCache: file rate = " << m_fileRate << endl;
Chris@297 111
Chris@297 112 if (m_fileRate == 0) {
Chris@843 113 cerr << "CodedAudioFileReader::initialiseDecodeCache: ERROR: File sample rate unknown (bug in subclass implementation?)" << endl;
Chris@754 114 throw FileOperationFailed("(coded file)", "File sample rate unknown (bug in subclass implementation?)");
Chris@297 115 }
Chris@297 116 if (m_sampleRate == 0) {
Chris@297 117 m_sampleRate = m_fileRate;
Chris@690 118 SVDEBUG << "CodedAudioFileReader::initialiseDecodeCache: rate (from file) = " << m_fileRate << endl;
Chris@297 119 }
Chris@297 120 if (m_fileRate != m_sampleRate) {
Chris@757 121 SVDEBUG << "CodedAudioFileReader: resampling " << m_fileRate << " -> " << m_sampleRate << endl;
Chris@297 122 m_resampler = new Resampler(Resampler::FastestTolerable,
Chris@297 123 m_channelCount,
Chris@297 124 m_cacheWriteBufferSize);
Chris@1040 125 double ratio = m_sampleRate / m_fileRate;
Chris@297 126 m_resampleBuffer = new float
Chris@1038 127 [lrint(ceil(double(m_cacheWriteBufferSize) * m_channelCount * ratio + 1))];
Chris@297 128 }
Chris@297 129
Chris@297 130 m_cacheWriteBuffer = new float[m_cacheWriteBufferSize * m_channelCount];
Chris@297 131 m_cacheWriteBufferIndex = 0;
Chris@297 132
Chris@148 133 if (m_cacheMode == CacheInTemporaryFile) {
Chris@148 134
Chris@148 135 try {
Chris@148 136 QDir dir(TempDirectory::getInstance()->getPath());
Chris@148 137 m_cacheFileName = dir.filePath(QString("decoded_%1.wav")
Chris@290 138 .arg((intptr_t)this));
Chris@148 139
Chris@148 140 SF_INFO fileInfo;
Chris@1040 141 int fileRate = int(round(m_sampleRate));
Chris@1040 142 if (m_sampleRate != sv_samplerate_t(fileRate)) {
Chris@1040 143 cerr << "CodedAudioFileReader: WARNING: Non-integer sample rate "
Chris@1040 144 << m_sampleRate << " presented for writing, rounding to " << fileRate
Chris@1040 145 << endl;
Chris@1040 146 }
Chris@1040 147 fileInfo.samplerate = fileRate;
Chris@148 148 fileInfo.channels = m_channelCount;
Chris@1161 149
Chris@1161 150 // Previously we were writing SF_FORMAT_PCM_16 and in a
Chris@1161 151 // comment I wrote: "No point in writing 24-bit or float;
Chris@1161 152 // generally this class is used for decoding files that
Chris@1161 153 // have come from a 16 bit source or that decode to only
Chris@1161 154 // 16 bits anyway." That was naive -- we want to preserve
Chris@1161 155 // the original values to the same float precision that we
Chris@1161 156 // use internally. Saving PCM_16 obviously doesn't
Chris@1161 157 // preserve values for sources at bit depths greater than
Chris@1161 158 // 16, but it also doesn't always do so for sources at bit
Chris@1161 159 // depths less than 16.
Chris@1161 160 //
Chris@1161 161 // (This came to light with a bug in libsndfile 1.0.26,
Chris@1161 162 // which always reports every file as non-seekable, so
Chris@1161 163 // that coded readers were being used even for WAV
Chris@1161 164 // files. This changed the values that came from PCM_8 WAV
Chris@1161 165 // sources, breaking Sonic Annotator's output comparison
Chris@1161 166 // tests.)
Chris@1161 167 //
Chris@1161 168 // So: now we write floats.
Chris@1161 169 fileInfo.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT;
Chris@148 170
Chris@290 171 m_cacheFileWritePtr = sf_open(m_cacheFileName.toLocal8Bit(),
Chris@148 172 SFM_WRITE, &fileInfo);
Chris@148 173
Chris@265 174 if (m_cacheFileWritePtr) {
Chris@265 175
Chris@297 176 // Ideally we would do this now only if we were in a
Chris@297 177 // threaded mode -- creating the reader later if we're
Chris@297 178 // not threaded -- but we don't have access to that
Chris@297 179 // information here
Chris@265 180
Chris@265 181 m_cacheFileReader = new WavFileReader(m_cacheFileName);
Chris@265 182
Chris@265 183 if (!m_cacheFileReader->isOK()) {
Chris@843 184 cerr << "ERROR: CodedAudioFileReader::initialiseDecodeCache: Failed to construct WAV file reader for temporary file: " << m_cacheFileReader->getError() << endl;
Chris@265 185 delete m_cacheFileReader;
Chris@265 186 m_cacheFileReader = 0;
Chris@265 187 m_cacheMode = CacheInMemory;
Chris@265 188 sf_close(m_cacheFileWritePtr);
Chris@265 189 }
Chris@297 190
Chris@265 191 } else {
Chris@843 192 cerr << "CodedAudioFileReader::initialiseDecodeCache: failed to open cache file \"" << m_cacheFileName << "\" (" << m_channelCount << " channels, sample rate " << m_sampleRate << " for writing, falling back to in-memory cache" << endl;
Chris@148 193 m_cacheMode = CacheInMemory;
Chris@148 194 }
Chris@265 195
Chris@148 196 } catch (DirectoryCreationFailed f) {
Chris@843 197 cerr << "CodedAudioFileReader::initialiseDecodeCache: failed to create temporary directory! Falling back to in-memory cache" << endl;
Chris@148 198 m_cacheMode = CacheInMemory;
Chris@148 199 }
Chris@148 200 }
Chris@148 201
Chris@148 202 if (m_cacheMode == CacheInMemory) {
Chris@148 203 m_data.clear();
Chris@148 204 }
Chris@148 205
Chris@148 206 m_initialised = true;
Chris@148 207 }
Chris@148 208
Chris@148 209 void
Chris@1038 210 CodedAudioFileReader::addSamplesToDecodeCache(float **samples, sv_frame_t nframes)
Chris@148 211 {
Chris@263 212 QMutexLocker locker(&m_cacheMutex);
Chris@263 213
Chris@148 214 if (!m_initialised) return;
Chris@148 215
Chris@1038 216 for (sv_frame_t i = 0; i < nframes; ++i) {
Chris@297 217
Chris@929 218 for (int c = 0; c < m_channelCount; ++c) {
Chris@148 219
Chris@297 220 float sample = samples[c][i];
Chris@297 221
Chris@297 222 m_cacheWriteBuffer[m_cacheWriteBufferIndex++] = sample;
Chris@148 223
Chris@297 224 if (m_cacheWriteBufferIndex ==
Chris@297 225 m_cacheWriteBufferSize * m_channelCount) {
Chris@297 226
Chris@297 227 pushBuffer(m_cacheWriteBuffer, m_cacheWriteBufferSize, false);
Chris@297 228 m_cacheWriteBufferIndex = 0;
Chris@297 229 }
Chris@297 230
Chris@297 231 if (m_cacheWriteBufferIndex % 10240 == 0 &&
Chris@297 232 m_cacheFileReader) {
Chris@297 233 m_cacheFileReader->updateFrameCount();
Chris@297 234 }
Chris@297 235 }
Chris@297 236 }
Chris@297 237 }
Chris@297 238
Chris@297 239 void
Chris@1038 240 CodedAudioFileReader::addSamplesToDecodeCache(float *samples, sv_frame_t nframes)
Chris@297 241 {
Chris@297 242 QMutexLocker locker(&m_cacheMutex);
Chris@297 243
Chris@297 244 if (!m_initialised) return;
Chris@297 245
Chris@1038 246 for (sv_frame_t i = 0; i < nframes; ++i) {
Chris@297 247
Chris@929 248 for (int c = 0; c < m_channelCount; ++c) {
Chris@297 249
Chris@297 250 float sample = samples[i * m_channelCount + c];
Chris@297 251
Chris@297 252 m_cacheWriteBuffer[m_cacheWriteBufferIndex++] = sample;
Chris@297 253
Chris@297 254 if (m_cacheWriteBufferIndex ==
Chris@297 255 m_cacheWriteBufferSize * m_channelCount) {
Chris@297 256
Chris@297 257 pushBuffer(m_cacheWriteBuffer, m_cacheWriteBufferSize, false);
Chris@297 258 m_cacheWriteBufferIndex = 0;
Chris@297 259 }
Chris@297 260
Chris@297 261 if (m_cacheWriteBufferIndex % 10240 == 0 &&
Chris@297 262 m_cacheFileReader) {
Chris@297 263 m_cacheFileReader->updateFrameCount();
Chris@297 264 }
Chris@297 265 }
Chris@297 266 }
Chris@297 267 }
Chris@297 268
Chris@297 269 void
Chris@1096 270 CodedAudioFileReader::addSamplesToDecodeCache(const vector<float> &samples)
Chris@297 271 {
Chris@297 272 QMutexLocker locker(&m_cacheMutex);
Chris@297 273
Chris@297 274 if (!m_initialised) return;
Chris@297 275
Chris@1038 276 for (float sample: samples) {
Chris@297 277
Chris@148 278 m_cacheWriteBuffer[m_cacheWriteBufferIndex++] = sample;
Chris@148 279
Chris@148 280 if (m_cacheWriteBufferIndex ==
Chris@148 281 m_cacheWriteBufferSize * m_channelCount) {
Chris@148 282
Chris@297 283 pushBuffer(m_cacheWriteBuffer, m_cacheWriteBufferSize, false);
Chris@148 284 m_cacheWriteBufferIndex = 0;
Chris@266 285 }
Chris@265 286
Chris@266 287 if (m_cacheWriteBufferIndex % 10240 == 0 &&
Chris@266 288 m_cacheFileReader) {
Chris@266 289 m_cacheFileReader->updateFrameCount();
Chris@148 290 }
Chris@148 291 }
Chris@148 292 }
Chris@148 293
Chris@148 294 void
Chris@148 295 CodedAudioFileReader::finishDecodeCache()
Chris@148 296 {
Chris@263 297 QMutexLocker locker(&m_cacheMutex);
Chris@263 298
Chris@192 299 Profiler profiler("CodedAudioFileReader::finishDecodeCache", true);
Chris@192 300
Chris@148 301 if (!m_initialised) {
Chris@843 302 cerr << "WARNING: CodedAudioFileReader::finishDecodeCache: Cache was never initialised!" << endl;
Chris@148 303 return;
Chris@148 304 }
Chris@148 305
Chris@920 306 pushBuffer(m_cacheWriteBuffer,
Chris@920 307 m_cacheWriteBufferIndex / m_channelCount,
Chris@920 308 true);
Chris@297 309
Chris@297 310 delete[] m_cacheWriteBuffer;
Chris@297 311 m_cacheWriteBuffer = 0;
Chris@297 312
Chris@297 313 delete[] m_resampleBuffer;
Chris@297 314 m_resampleBuffer = 0;
Chris@297 315
Chris@297 316 delete m_resampler;
Chris@297 317 m_resampler = 0;
Chris@297 318
Chris@297 319 if (m_cacheMode == CacheInTemporaryFile) {
Chris@1098 320
Chris@297 321 sf_close(m_cacheFileWritePtr);
Chris@297 322 m_cacheFileWritePtr = 0;
Chris@297 323 if (m_cacheFileReader) m_cacheFileReader->updateFrameCount();
Chris@1098 324
Chris@1098 325 } else {
Chris@1098 326 // I know, I know, we already allocated it...
Chris@1098 327 StorageAdviser::notifyPlannedAllocation
Chris@1098 328 (StorageAdviser::MemoryAllocation,
Chris@1098 329 (m_data.size() * sizeof(float)) / 1024);
Chris@297 330 }
Chris@297 331 }
Chris@297 332
Chris@297 333 void
Chris@1038 334 CodedAudioFileReader::pushBuffer(float *buffer, sv_frame_t sz, bool final)
Chris@297 335 {
Chris@757 336 m_fileFrameCount += sz;
Chris@757 337
Chris@1040 338 double ratio = 1.0;
Chris@758 339 if (m_resampler && m_fileRate != 0) {
Chris@1040 340 ratio = m_sampleRate / m_fileRate;
Chris@758 341 }
Chris@758 342
Chris@1040 343 if (ratio != 1.0) {
Chris@758 344 pushBufferResampling(buffer, sz, ratio, final);
Chris@758 345 } else {
Chris@758 346 pushBufferNonResampling(buffer, sz);
Chris@758 347 }
Chris@758 348 }
Chris@757 349
Chris@758 350 void
Chris@1038 351 CodedAudioFileReader::pushBufferNonResampling(float *buffer, sv_frame_t sz)
Chris@758 352 {
Chris@920 353 float clip = 1.0;
Chris@1038 354 sv_frame_t count = sz * m_channelCount;
Chris@318 355
Chris@920 356 if (m_normalised) {
Chris@1038 357 for (sv_frame_t i = 0; i < count; ++i) {
Chris@920 358 float v = fabsf(buffer[i]);
Chris@920 359 if (v > m_max) {
Chris@920 360 m_max = v;
Chris@920 361 m_gain = 1.f / m_max;
Chris@920 362 }
Chris@920 363 }
Chris@920 364 } else {
Chris@1038 365 for (sv_frame_t i = 0; i < count; ++i) {
Chris@920 366 if (buffer[i] > clip) buffer[i] = clip;
Chris@920 367 }
Chris@1038 368 for (sv_frame_t i = 0; i < count; ++i) {
Chris@920 369 if (buffer[i] < -clip) buffer[i] = -clip;
Chris@920 370 }
Chris@297 371 }
Chris@297 372
Chris@297 373 m_frameCount += sz;
Chris@297 374
Chris@148 375 switch (m_cacheMode) {
Chris@148 376
Chris@148 377 case CacheInTemporaryFile:
Chris@1038 378 if (sf_writef_float(m_cacheFileWritePtr, buffer, sz) < sz) {
Chris@544 379 sf_close(m_cacheFileWritePtr);
Chris@544 380 m_cacheFileWritePtr = 0;
Chris@544 381 throw InsufficientDiscSpace(TempDirectory::getInstance()->getPath());
Chris@544 382 }
Chris@148 383 break;
Chris@148 384
Chris@148 385 case CacheInMemory:
Chris@1100 386 m_dataLock.lock();
Chris@1096 387 m_data.insert(m_data.end(), buffer, buffer + count);
Chris@543 388 m_dataLock.unlock();
Chris@148 389 break;
Chris@148 390 }
Chris@758 391 }
Chris@757 392
Chris@758 393 void
Chris@1038 394 CodedAudioFileReader::pushBufferResampling(float *buffer, sv_frame_t sz,
Chris@1038 395 double ratio, bool final)
Chris@758 396 {
Chris@759 397 SVDEBUG << "pushBufferResampling: ratio = " << ratio << ", sz = " << sz << ", final = " << final << endl;
Chris@757 398
Chris@759 399 if (sz > 0) {
Chris@759 400
Chris@1038 401 sv_frame_t out = m_resampler->resampleInterleaved
Chris@759 402 (buffer,
Chris@759 403 m_resampleBuffer,
Chris@759 404 sz,
Chris@759 405 ratio,
Chris@759 406 false);
Chris@759 407
Chris@759 408 pushBufferNonResampling(m_resampleBuffer, out);
Chris@759 409 }
Chris@757 410
Chris@758 411 if (final) {
Chris@758 412
Chris@1038 413 sv_frame_t padFrames = 1;
Chris@1038 414 if (double(m_frameCount) / ratio < double(m_fileFrameCount)) {
Chris@1038 415 padFrames = m_fileFrameCount - sv_frame_t(double(m_frameCount) / ratio) + 1;
Chris@757 416 }
Chris@758 417
Chris@1038 418 sv_frame_t padSamples = padFrames * m_channelCount;
Chris@758 419
Chris@1038 420 SVDEBUG << "frameCount = " << m_frameCount << ", equivFileFrames = " << double(m_frameCount) / ratio << ", m_fileFrameCount = " << m_fileFrameCount << ", padFrames= " << padFrames << ", padSamples = " << padSamples << endl;
Chris@758 421
Chris@758 422 float *padding = new float[padSamples];
Chris@1038 423 for (sv_frame_t i = 0; i < padSamples; ++i) padding[i] = 0.f;
Chris@758 424
Chris@1038 425 sv_frame_t out = m_resampler->resampleInterleaved
Chris@758 426 (padding,
Chris@758 427 m_resampleBuffer,
Chris@758 428 padFrames,
Chris@758 429 ratio,
Chris@758 430 true);
Chris@758 431
Chris@1038 432 if (m_frameCount + out > sv_frame_t(double(m_fileFrameCount) * ratio)) {
Chris@1038 433 out = sv_frame_t(double(m_fileFrameCount) * ratio) - m_frameCount;
Chris@759 434 }
Chris@759 435
Chris@758 436 pushBufferNonResampling(m_resampleBuffer, out);
Chris@758 437 delete[] padding;
Chris@757 438 }
Chris@148 439 }
Chris@148 440
Chris@1096 441 vector<float>
Chris@1041 442 CodedAudioFileReader::getInterleavedFrames(sv_frame_t start, sv_frame_t count) const
Chris@148 443 {
Chris@543 444 // Lock is only required in CacheInMemory mode (the cache file
Chris@543 445 // reader is expected to be thread safe and manage its own
Chris@543 446 // locking)
Chris@263 447
Chris@265 448 if (!m_initialised) {
Chris@690 449 SVDEBUG << "CodedAudioFileReader::getInterleavedFrames: not initialised" << endl;
Chris@1096 450 return {};
Chris@265 451 }
Chris@148 452
Chris@1096 453 vector<float> frames;
Chris@1041 454
Chris@148 455 switch (m_cacheMode) {
Chris@148 456
Chris@148 457 case CacheInTemporaryFile:
Chris@148 458 if (m_cacheFileReader) {
Chris@1041 459 frames = m_cacheFileReader->getInterleavedFrames(start, count);
Chris@148 460 }
Chris@148 461 break;
Chris@148 462
Chris@148 463 case CacheInMemory:
Chris@148 464 {
Chris@1096 465 if (!isOK()) return {};
Chris@1096 466 if (count == 0) return {};
Chris@148 467
Chris@1100 468 sv_frame_t ix0 = start * m_channelCount;
Chris@1100 469 sv_frame_t ix1 = ix0 + (count * m_channelCount);
Chris@148 470
Chris@1052 471
Chris@1100 472 // This lock used to be a QReadWriteLock, but it appears that
Chris@1100 473 // its lock mechanism is significantly slower than QMutex so
Chris@1100 474 // it's not a good idea in cases like this where we don't
Chris@1100 475 // really have threads taking a long time to read concurrently
Chris@1100 476 m_dataLock.lock();
Chris@1100 477 sv_frame_t n = sv_frame_t(m_data.size());
Chris@1100 478 if (ix1 > n) ix1 = n;
Chris@1100 479 frames = vector<float>(m_data.begin() + ix0, m_data.begin() + ix1);
Chris@543 480 m_dataLock.unlock();
Chris@148 481 }
Chris@148 482 }
Chris@920 483
Chris@920 484 if (m_normalised) {
Chris@1052 485 for (auto &f: frames) f *= m_gain;
Chris@920 486 }
Chris@1041 487
Chris@1041 488 return frames;
Chris@148 489 }
Chris@148 490