annotate data/model/WaveFileModel.cpp @ 167:665342c6ec57

* Add a bit of resistance to pane dragging so as to make it harder to inadvertently drag in the other axis from the one you intended
author Chris Cannam
date Fri, 22 Sep 2006 16:46:10 +0000
parents 4148ad087959
children b0f4555b625e
rev   line source
Chris@147 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@147 2
Chris@147 3 /*
Chris@147 4 Sonic Visualiser
Chris@147 5 An audio file viewer and annotation editor.
Chris@147 6 Centre for Digital Music, Queen Mary, University of London.
Chris@147 7 This file copyright 2006 Chris Cannam.
Chris@147 8
Chris@147 9 This program is free software; you can redistribute it and/or
Chris@147 10 modify it under the terms of the GNU General Public License as
Chris@147 11 published by the Free Software Foundation; either version 2 of the
Chris@147 12 License, or (at your option) any later version. See the file
Chris@147 13 COPYING included with this distribution for more information.
Chris@147 14 */
Chris@147 15
Chris@150 16 #include "WaveFileModel.h"
Chris@147 17
Chris@147 18 #include "fileio/AudioFileReader.h"
Chris@147 19 #include "fileio/AudioFileReaderFactory.h"
Chris@147 20
Chris@150 21 #include "system/System.h"
Chris@147 22
Chris@147 23 #include <QMessageBox>
Chris@147 24 #include <QFileInfo>
Chris@147 25
Chris@147 26 #include <iostream>
Chris@147 27 #include <unistd.h>
Chris@147 28 #include <math.h>
Chris@147 29 #include <sndfile.h>
Chris@147 30
Chris@147 31 #include <cassert>
Chris@147 32
Chris@147 33 using std::cerr;
Chris@147 34 using std::endl;
Chris@147 35
Chris@147 36 WaveFileModel::WaveFileModel(QString path) :
Chris@147 37 m_path(path),
Chris@147 38 m_fillThread(0),
Chris@147 39 m_updateTimer(0),
Chris@147 40 m_lastFillExtent(0),
Chris@147 41 m_exiting(false)
Chris@147 42 {
Chris@147 43 m_reader = AudioFileReaderFactory::createReader(path);
Chris@147 44 setObjectName(QFileInfo(path).fileName());
Chris@147 45 if (isOK()) fillCache();
Chris@147 46 }
Chris@147 47
Chris@147 48 WaveFileModel::~WaveFileModel()
Chris@147 49 {
Chris@147 50 m_exiting = true;
Chris@147 51 if (m_fillThread) m_fillThread->wait();
Chris@147 52 delete m_reader;
Chris@147 53 m_reader = 0;
Chris@147 54 }
Chris@147 55
Chris@147 56 bool
Chris@147 57 WaveFileModel::isOK() const
Chris@147 58 {
Chris@147 59 return m_reader && m_reader->isOK();
Chris@147 60 }
Chris@147 61
Chris@147 62 bool
Chris@147 63 WaveFileModel::isReady(int *completion) const
Chris@147 64 {
Chris@147 65 bool ready = (isOK() && (m_fillThread == 0));
Chris@147 66 double c = double(m_lastFillExtent) / double(getEndFrame() - getStartFrame());
Chris@147 67 if (completion) *completion = int(c * 100.0 + 0.01);
Chris@147 68 return ready;
Chris@147 69 }
Chris@147 70
Chris@147 71 Model *
Chris@147 72 WaveFileModel::clone() const
Chris@147 73 {
Chris@147 74 WaveFileModel *model = new WaveFileModel(m_path);
Chris@147 75 return model;
Chris@147 76 }
Chris@147 77
Chris@147 78 size_t
Chris@147 79 WaveFileModel::getFrameCount() const
Chris@147 80 {
Chris@147 81 if (!m_reader) return 0;
Chris@147 82 return m_reader->getFrameCount();
Chris@147 83 }
Chris@147 84
Chris@147 85 size_t
Chris@147 86 WaveFileModel::getChannelCount() const
Chris@147 87 {
Chris@147 88 if (!m_reader) return 0;
Chris@147 89 return m_reader->getChannelCount();
Chris@147 90 }
Chris@147 91
Chris@147 92 size_t
Chris@147 93 WaveFileModel::getSampleRate() const
Chris@147 94 {
Chris@147 95 if (!m_reader) return 0;
Chris@147 96 return m_reader->getSampleRate();
Chris@147 97 }
Chris@147 98
Chris@147 99 size_t
Chris@147 100 WaveFileModel::getValues(int channel, size_t start, size_t end,
Chris@147 101 float *buffer) const
Chris@147 102 {
Chris@147 103 // Always read these directly from the file.
Chris@147 104 // This is used for e.g. audio playback.
Chris@147 105 // Could be much more efficient (although compiler optimisation will help)
Chris@147 106
Chris@147 107 if (end < start) {
Chris@147 108 std::cerr << "ERROR: WaveFileModel::getValues[float]: end < start ("
Chris@147 109 << end << " < " << start << ")" << std::endl;
Chris@147 110 assert(end >= start);
Chris@147 111 }
Chris@147 112
Chris@147 113 if (!m_reader || !m_reader->isOK()) return 0;
Chris@147 114
Chris@147 115 SampleBlock frames;
Chris@147 116 m_reader->getInterleavedFrames(start, end - start, frames);
Chris@147 117
Chris@147 118 size_t i = 0;
Chris@147 119
Chris@147 120 int ch0 = channel, ch1 = channel, channels = getChannelCount();
Chris@147 121 if (channel == -1) {
Chris@147 122 ch0 = 0;
Chris@147 123 ch1 = channels - 1;
Chris@147 124 }
Chris@147 125
Chris@147 126 while (i < end - start) {
Chris@147 127
Chris@147 128 buffer[i] = 0.0;
Chris@147 129
Chris@147 130 for (int ch = ch0; ch <= ch1; ++ch) {
Chris@147 131
Chris@147 132 size_t index = i * channels + ch;
Chris@147 133 if (index >= frames.size()) break;
Chris@147 134
Chris@147 135 float sample = frames[index];
Chris@147 136 buffer[i] += sample;
Chris@147 137 }
Chris@147 138
Chris@147 139 ++i;
Chris@147 140 }
Chris@147 141
Chris@147 142 return i;
Chris@147 143 }
Chris@147 144
Chris@147 145 size_t
Chris@147 146 WaveFileModel::getValues(int channel, size_t start, size_t end,
Chris@147 147 double *buffer) const
Chris@147 148 {
Chris@147 149 if (end < start) {
Chris@147 150 std::cerr << "ERROR: WaveFileModel::getValues[double]: end < start ("
Chris@147 151 << end << " < " << start << ")" << std::endl;
Chris@147 152 assert(end >= start);
Chris@147 153 }
Chris@147 154
Chris@147 155 if (!m_reader || !m_reader->isOK()) return 0;
Chris@147 156
Chris@147 157 SampleBlock frames;
Chris@147 158 m_reader->getInterleavedFrames(start, end - start, frames);
Chris@147 159
Chris@147 160 size_t i = 0;
Chris@147 161
Chris@147 162 int ch0 = channel, ch1 = channel, channels = getChannelCount();
Chris@147 163 if (channel == -1) {
Chris@147 164 ch0 = 0;
Chris@147 165 ch1 = channels - 1;
Chris@147 166 }
Chris@147 167
Chris@147 168 while (i < end - start) {
Chris@147 169
Chris@147 170 buffer[i] = 0.0;
Chris@147 171
Chris@147 172 for (int ch = ch0; ch <= ch1; ++ch) {
Chris@147 173
Chris@147 174 size_t index = i * channels + ch;
Chris@147 175 if (index >= frames.size()) break;
Chris@147 176
Chris@147 177 float sample = frames[index];
Chris@147 178 buffer[i] += sample;
Chris@147 179 }
Chris@147 180
Chris@147 181 ++i;
Chris@147 182 }
Chris@147 183
Chris@147 184 return i;
Chris@147 185 }
Chris@147 186
Chris@147 187 WaveFileModel::RangeBlock
Chris@147 188 WaveFileModel::getRanges(size_t channel, size_t start, size_t end,
Chris@147 189 size_t &blockSize) const
Chris@147 190 {
Chris@147 191 RangeBlock ranges;
Chris@147 192 if (!isOK()) return ranges;
Chris@147 193
Chris@147 194 if (end <= start) {
Chris@147 195 std::cerr << "WARNING: Internal error: end <= start in WaveFileModel::getRanges (end = " << end << ", start = " << start << ", blocksize = " << blockSize << ")" << std::endl;
Chris@147 196 return ranges;
Chris@147 197 }
Chris@147 198
Chris@147 199 int cacheType = 0;
Chris@147 200 int power = getMinCachePower();
Chris@147 201 blockSize = getNearestBlockSize(blockSize, cacheType, power,
Chris@147 202 ZoomConstraint::RoundUp);
Chris@147 203
Chris@147 204 size_t channels = getChannelCount();
Chris@147 205
Chris@147 206 if (cacheType != 0 && cacheType != 1) {
Chris@147 207
Chris@147 208 // We need to read directly from the file. We haven't got
Chris@147 209 // this cached. Hope the requested area is small. This is
Chris@147 210 // not optimal -- we'll end up reading the same frames twice
Chris@147 211 // for stereo files, in two separate calls to this method.
Chris@147 212 // We could fairly trivially handle this for most cases that
Chris@147 213 // matter by putting a single cache in getInterleavedFrames
Chris@147 214 // for short queries.
Chris@147 215
Chris@147 216 SampleBlock frames;
Chris@147 217 m_reader->getInterleavedFrames(start, end - start, frames);
Chris@147 218 float max = 0.0, min = 0.0, total = 0.0;
Chris@147 219 size_t i = 0, count = 0;
Chris@147 220
Chris@147 221 while (i < end - start) {
Chris@147 222
Chris@147 223 size_t index = i * channels + channel;
Chris@147 224 if (index >= frames.size()) break;
Chris@147 225
Chris@147 226 float sample = frames[index];
Chris@147 227 if (sample > max || count == 0) max = sample;
Chris@147 228 if (sample < min || count == 0) min = sample;
Chris@147 229 total += fabsf(sample);
Chris@147 230
Chris@147 231 ++i;
Chris@147 232 ++count;
Chris@147 233
Chris@147 234 if (count == blockSize) {
Chris@147 235 ranges.push_back(Range(min, max, total / count));
Chris@147 236 min = max = total = 0.0f;
Chris@147 237 count = 0;
Chris@147 238 }
Chris@147 239 }
Chris@147 240
Chris@147 241 if (count > 0) {
Chris@147 242 ranges.push_back(Range(min, max, total / count));
Chris@147 243 }
Chris@147 244
Chris@147 245 return ranges;
Chris@147 246
Chris@147 247 } else {
Chris@147 248
Chris@147 249 QMutexLocker locker(&m_mutex);
Chris@147 250
Chris@147 251 const RangeBlock &cache = m_cache[cacheType];
Chris@147 252
Chris@147 253 size_t cacheBlock, div;
Chris@147 254
Chris@147 255 if (cacheType == 0) {
Chris@147 256 cacheBlock = (1 << getMinCachePower());
Chris@147 257 div = (1 << power) / cacheBlock;
Chris@147 258 } else {
Chris@147 259 cacheBlock = ((unsigned int)((1 << getMinCachePower()) * sqrt(2) + 0.01));
Chris@147 260 div = ((unsigned int)((1 << power) * sqrt(2) + 0.01)) / cacheBlock;
Chris@147 261 }
Chris@147 262
Chris@147 263 size_t startIndex = start / cacheBlock;
Chris@147 264 size_t endIndex = end / cacheBlock;
Chris@147 265
Chris@147 266 float max = 0.0, min = 0.0, total = 0.0;
Chris@147 267 size_t i = 0, count = 0;
Chris@147 268
Chris@147 269 //cerr << "blockSize is " << blockSize << ", cacheBlock " << cacheBlock << ", start " << start << ", end " << end << ", power is " << power << ", div is " << div << ", startIndex " << startIndex << ", endIndex " << endIndex << endl;
Chris@147 270
Chris@147 271 for (i = 0; i < endIndex - startIndex; ) {
Chris@147 272
Chris@147 273 size_t index = (i + startIndex) * channels + channel;
Chris@147 274 if (index >= cache.size()) break;
Chris@147 275
Chris@147 276 const Range &range = cache[index];
Chris@147 277 if (range.max > max || count == 0) max = range.max;
Chris@147 278 if (range.min < min || count == 0) min = range.min;
Chris@147 279 total += range.absmean;
Chris@147 280
Chris@147 281 ++i;
Chris@147 282 ++count;
Chris@147 283
Chris@147 284 if (count == div) {
Chris@147 285 ranges.push_back(Range(min, max, total / count));
Chris@147 286 min = max = total = 0.0f;
Chris@147 287 count = 0;
Chris@147 288 }
Chris@147 289 }
Chris@147 290
Chris@147 291 if (count > 0) {
Chris@147 292 ranges.push_back(Range(min, max, total / count));
Chris@147 293 }
Chris@147 294 }
Chris@147 295
Chris@147 296 //cerr << "returning " << ranges.size() << " ranges" << endl;
Chris@147 297 return ranges;
Chris@147 298 }
Chris@147 299
Chris@147 300 WaveFileModel::Range
Chris@147 301 WaveFileModel::getRange(size_t channel, size_t start, size_t end) const
Chris@147 302 {
Chris@147 303 Range range;
Chris@147 304 if (!isOK()) return range;
Chris@147 305
Chris@147 306 if (end <= start) {
Chris@147 307 std::cerr << "WARNING: Internal error: end <= start in WaveFileModel::getRange (end = " << end << ", start = " << start << ")" << std::endl;
Chris@147 308 return range;
Chris@147 309 }
Chris@147 310
Chris@147 311 size_t blockSize;
Chris@147 312 for (blockSize = 1; blockSize <= end - start; blockSize *= 2);
Chris@147 313 blockSize /= 2;
Chris@147 314
Chris@147 315 bool first = false;
Chris@147 316
Chris@147 317 size_t blockStart = (start / blockSize) * blockSize;
Chris@147 318 size_t blockEnd = (end / blockSize) * blockSize;
Chris@147 319
Chris@147 320 if (blockStart < start) blockStart += blockSize;
Chris@147 321
Chris@147 322 if (blockEnd > blockStart) {
Chris@147 323 RangeBlock ranges = getRanges(channel, blockStart, blockEnd, blockSize);
Chris@147 324 for (size_t i = 0; i < ranges.size(); ++i) {
Chris@147 325 if (first || ranges[i].min < range.min) range.min = ranges[i].min;
Chris@147 326 if (first || ranges[i].max > range.max) range.max = ranges[i].max;
Chris@147 327 if (first || ranges[i].absmean < range.absmean) range.absmean = ranges[i].absmean;
Chris@147 328 first = false;
Chris@147 329 }
Chris@147 330 }
Chris@147 331
Chris@147 332 if (blockStart > start) {
Chris@147 333 Range startRange = getRange(channel, start, blockStart);
Chris@147 334 range.min = std::min(range.min, startRange.min);
Chris@147 335 range.max = std::max(range.max, startRange.max);
Chris@147 336 range.absmean = std::min(range.absmean, startRange.absmean);
Chris@147 337 }
Chris@147 338
Chris@147 339 if (blockEnd < end) {
Chris@147 340 Range endRange = getRange(channel, blockEnd, end);
Chris@147 341 range.min = std::min(range.min, endRange.min);
Chris@147 342 range.max = std::max(range.max, endRange.max);
Chris@147 343 range.absmean = std::min(range.absmean, endRange.absmean);
Chris@147 344 }
Chris@147 345
Chris@147 346 return range;
Chris@147 347 }
Chris@147 348
Chris@147 349 void
Chris@147 350 WaveFileModel::fillCache()
Chris@147 351 {
Chris@147 352 m_mutex.lock();
Chris@147 353 m_updateTimer = new QTimer(this);
Chris@147 354 connect(m_updateTimer, SIGNAL(timeout()), this, SLOT(fillTimerTimedOut()));
Chris@147 355 m_updateTimer->start(100);
Chris@147 356 m_fillThread = new RangeCacheFillThread(*this);
Chris@147 357 connect(m_fillThread, SIGNAL(finished()), this, SLOT(cacheFilled()));
Chris@147 358 m_mutex.unlock();
Chris@147 359 m_fillThread->start();
Chris@147 360 }
Chris@147 361
Chris@147 362 void
Chris@147 363 WaveFileModel::fillTimerTimedOut()
Chris@147 364 {
Chris@147 365 if (m_fillThread) {
Chris@147 366 size_t fillExtent = m_fillThread->getFillExtent();
Chris@147 367 if (fillExtent > m_lastFillExtent) {
Chris@147 368 emit modelChanged(m_lastFillExtent, fillExtent);
Chris@147 369 m_lastFillExtent = fillExtent;
Chris@147 370 }
Chris@147 371 } else {
Chris@147 372 emit modelChanged();
Chris@147 373 }
Chris@147 374 }
Chris@147 375
Chris@147 376 void
Chris@147 377 WaveFileModel::cacheFilled()
Chris@147 378 {
Chris@147 379 m_mutex.lock();
Chris@147 380 delete m_fillThread;
Chris@147 381 m_fillThread = 0;
Chris@147 382 delete m_updateTimer;
Chris@147 383 m_updateTimer = 0;
Chris@147 384 m_mutex.unlock();
Chris@147 385 emit modelChanged();
Chris@147 386 // cerr << "WaveFileModel::cacheFilled" << endl;
Chris@147 387 }
Chris@147 388
Chris@147 389 void
Chris@147 390 WaveFileModel::RangeCacheFillThread::run()
Chris@147 391 {
Chris@147 392 size_t cacheBlockSize[2];
Chris@147 393 cacheBlockSize[0] = (1 << m_model.getMinCachePower());
Chris@147 394 cacheBlockSize[1] = ((unsigned int)((1 << m_model.getMinCachePower()) *
Chris@147 395 sqrt(2) + 0.01));
Chris@147 396
Chris@147 397 size_t frame = 0;
Chris@147 398 size_t readBlockSize = 16384;
Chris@147 399 SampleBlock block;
Chris@147 400
Chris@147 401 if (!m_model.isOK()) return;
Chris@147 402
Chris@147 403 size_t channels = m_model.getChannelCount();
Chris@147 404 size_t frames = m_model.getFrameCount();
Chris@147 405
Chris@147 406 Range *range = new Range[2 * channels];
Chris@147 407 size_t count[2];
Chris@147 408 count[0] = count[1] = 0;
Chris@147 409
Chris@147 410 while (frame < frames) {
Chris@147 411
Chris@147 412 m_model.m_reader->getInterleavedFrames(frame, readBlockSize, block);
Chris@147 413
Chris@147 414 for (size_t i = 0; i < readBlockSize; ++i) {
Chris@147 415
Chris@147 416 for (size_t ch = 0; ch < size_t(channels); ++ch) {
Chris@147 417
Chris@147 418 size_t index = channels * i + ch;
Chris@147 419 if (index >= block.size()) continue;
Chris@147 420 float sample = block[index];
Chris@147 421
Chris@147 422 for (size_t ct = 0; ct < 2; ++ct) {
Chris@147 423
Chris@147 424 size_t rangeIndex = ch * 2 + ct;
Chris@147 425
Chris@147 426 if (sample > range[rangeIndex].max || count[ct] == 0) {
Chris@147 427 range[rangeIndex].max = sample;
Chris@147 428 }
Chris@147 429 if (sample < range[rangeIndex].min || count[ct] == 0) {
Chris@147 430 range[rangeIndex].min = sample;
Chris@147 431 }
Chris@147 432 range[rangeIndex].absmean += fabsf(sample);
Chris@147 433 }
Chris@147 434 }
Chris@147 435
Chris@147 436 QMutexLocker locker(&m_model.m_mutex);
Chris@147 437 for (size_t ct = 0; ct < 2; ++ct) {
Chris@147 438 if (++count[ct] == cacheBlockSize[ct]) {
Chris@147 439 for (size_t ch = 0; ch < size_t(channels); ++ch) {
Chris@147 440 size_t rangeIndex = ch * 2 + ct;
Chris@147 441 range[rangeIndex].absmean /= count[ct];
Chris@147 442 m_model.m_cache[ct].push_back(range[rangeIndex]);
Chris@147 443 range[rangeIndex] = Range();
Chris@147 444 }
Chris@147 445 count[ct] = 0;
Chris@147 446 }
Chris@147 447 }
Chris@147 448
Chris@147 449 ++frame;
Chris@147 450 }
Chris@147 451
Chris@147 452 if (m_model.m_exiting) break;
Chris@147 453
Chris@147 454 m_fillExtent = frame;
Chris@147 455 }
Chris@147 456
Chris@147 457 QMutexLocker locker(&m_model.m_mutex);
Chris@147 458 for (size_t ct = 0; ct < 2; ++ct) {
Chris@147 459 if (count[ct] > 0) {
Chris@147 460 for (size_t ch = 0; ch < size_t(channels); ++ch) {
Chris@147 461 size_t rangeIndex = ch * 2 + ct;
Chris@147 462 range[rangeIndex].absmean /= count[ct];
Chris@147 463 m_model.m_cache[ct].push_back(range[rangeIndex]);
Chris@147 464 range[rangeIndex] = Range();
Chris@147 465 }
Chris@147 466 count[ct] = 0;
Chris@147 467 }
Chris@147 468
Chris@147 469 const Range &rr = *m_model.m_cache[ct].begin();
Chris@147 470 MUNLOCK(&rr, m_model.m_cache[ct].capacity() * sizeof(Range));
Chris@147 471 }
Chris@147 472
Chris@147 473 delete[] range;
Chris@147 474
Chris@147 475 m_fillExtent = frames;
Chris@147 476
Chris@147 477 // for (size_t ct = 0; ct < 2; ++ct) {
Chris@147 478 // cerr << "Cache type " << ct << " now contains " << m_model.m_cache[ct].size() << " ranges" << endl;
Chris@147 479 // }
Chris@147 480 }
Chris@147 481
Chris@163 482 void
Chris@163 483 WaveFileModel::toXml(QTextStream &out,
Chris@163 484 QString indent,
Chris@163 485 QString extraAttributes) const
Chris@163 486 {
Chris@163 487 Model::toXml(out, indent,
Chris@163 488 QString("type=\"wavefile\" file=\"%1\" %2")
Chris@163 489 .arg(m_path).arg(extraAttributes));
Chris@163 490 }
Chris@163 491
Chris@147 492 QString
Chris@147 493 WaveFileModel::toXmlString(QString indent,
Chris@147 494 QString extraAttributes) const
Chris@147 495 {
Chris@147 496 return Model::toXmlString(indent,
Chris@147 497 QString("type=\"wavefile\" file=\"%1\" %2")
Chris@147 498 .arg(m_path).arg(extraAttributes));
Chris@147 499 }
Chris@147 500
Chris@147 501
Chris@147 502 #ifdef INCLUDE_MOCFILES
Chris@147 503 #ifdef INCLUDE_MOCFILES
Chris@147 504 #include "WaveFileModel.moc.cpp"
Chris@147 505 #endif
Chris@147 506 #endif
Chris@147 507