annotate data/model/EditableDenseThreeDimensionalModel.cpp @ 823:f0558e69a074

Rename Resampling- to DecodingWavFileReader, and use it whenever we have an audio file that is not quickly seekable using libsndfile. Avoids very slow performance when analysing ogg files.
author Chris Cannam
date Wed, 17 Jul 2013 15:40:01 +0100
parents 1424aa29ae95
children 33fca917c800
rev   line source
Chris@152 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@152 2
Chris@152 3 /*
Chris@152 4 Sonic Visualiser
Chris@152 5 An audio file viewer and annotation editor.
Chris@152 6 Centre for Digital Music, Queen Mary, University of London.
Chris@202 7 This file copyright 2006 Chris Cannam and QMUL.
Chris@152 8
Chris@152 9 This program is free software; you can redistribute it and/or
Chris@152 10 modify it under the terms of the GNU General Public License as
Chris@152 11 published by the Free Software Foundation; either version 2 of the
Chris@152 12 License, or (at your option) any later version. See the file
Chris@152 13 COPYING included with this distribution for more information.
Chris@152 14 */
Chris@152 15
Chris@152 16 #include "EditableDenseThreeDimensionalModel.h"
Chris@152 17
Chris@478 18 #include "base/LogRange.h"
Chris@478 19
Chris@152 20 #include <QTextStream>
Chris@387 21 #include <QStringList>
Chris@536 22 #include <QReadLocker>
Chris@536 23 #include <QWriteLocker>
Chris@387 24
Chris@181 25 #include <iostream>
Chris@181 26
Chris@256 27 #include <cmath>
Chris@534 28 #include <cassert>
Chris@256 29
Chris@607 30 #include "system/System.h"
Chris@607 31
Chris@152 32 EditableDenseThreeDimensionalModel::EditableDenseThreeDimensionalModel(size_t sampleRate,
Chris@152 33 size_t resolution,
Chris@152 34 size_t yBinCount,
Chris@535 35 CompressionType compression,
Chris@152 36 bool notifyOnAdd) :
Chris@611 37 m_startFrame(0),
Chris@152 38 m_sampleRate(sampleRate),
Chris@152 39 m_resolution(resolution),
Chris@152 40 m_yBinCount(yBinCount),
Chris@535 41 m_compression(compression),
Chris@152 42 m_minimum(0.0),
Chris@152 43 m_maximum(0.0),
Chris@256 44 m_haveExtents(false),
Chris@152 45 m_notifyOnAdd(notifyOnAdd),
Chris@152 46 m_sinceLastNotifyMin(-1),
Chris@152 47 m_sinceLastNotifyMax(-1),
Chris@152 48 m_completion(100)
Chris@152 49 {
Chris@152 50 }
Chris@152 51
Chris@152 52 bool
Chris@152 53 EditableDenseThreeDimensionalModel::isOK() const
Chris@152 54 {
Chris@152 55 return true;
Chris@152 56 }
Chris@152 57
Chris@152 58 size_t
Chris@152 59 EditableDenseThreeDimensionalModel::getSampleRate() const
Chris@152 60 {
Chris@152 61 return m_sampleRate;
Chris@152 62 }
Chris@152 63
Chris@152 64 size_t
Chris@152 65 EditableDenseThreeDimensionalModel::getStartFrame() const
Chris@152 66 {
Chris@611 67 return m_startFrame;
Chris@611 68 }
Chris@611 69
Chris@611 70 void
Chris@611 71 EditableDenseThreeDimensionalModel::setStartFrame(size_t f)
Chris@611 72 {
Chris@611 73 m_startFrame = f;
Chris@152 74 }
Chris@152 75
Chris@152 76 size_t
Chris@152 77 EditableDenseThreeDimensionalModel::getEndFrame() const
Chris@152 78 {
Chris@152 79 return m_resolution * m_data.size() + (m_resolution - 1);
Chris@152 80 }
Chris@152 81
Chris@152 82 Model *
Chris@152 83 EditableDenseThreeDimensionalModel::clone() const
Chris@152 84 {
Chris@536 85 QReadLocker locker(&m_lock);
Chris@534 86
Chris@152 87 EditableDenseThreeDimensionalModel *model =
Chris@152 88 new EditableDenseThreeDimensionalModel
Chris@535 89 (m_sampleRate, m_resolution, m_yBinCount, m_compression);
Chris@152 90
Chris@152 91 model->m_minimum = m_minimum;
Chris@152 92 model->m_maximum = m_maximum;
Chris@256 93 model->m_haveExtents = m_haveExtents;
Chris@152 94
Chris@152 95 for (size_t i = 0; i < m_data.size(); ++i) {
Chris@533 96 model->setColumn(i, m_data.at(i));
Chris@152 97 }
Chris@152 98
Chris@152 99 return model;
Chris@152 100 }
Chris@152 101
Chris@152 102 size_t
Chris@152 103 EditableDenseThreeDimensionalModel::getResolution() const
Chris@152 104 {
Chris@152 105 return m_resolution;
Chris@152 106 }
Chris@152 107
Chris@152 108 void
Chris@152 109 EditableDenseThreeDimensionalModel::setResolution(size_t sz)
Chris@152 110 {
Chris@152 111 m_resolution = sz;
Chris@152 112 }
Chris@152 113
Chris@152 114 size_t
Chris@182 115 EditableDenseThreeDimensionalModel::getWidth() const
Chris@182 116 {
Chris@182 117 return m_data.size();
Chris@182 118 }
Chris@182 119
Chris@182 120 size_t
Chris@182 121 EditableDenseThreeDimensionalModel::getHeight() const
Chris@152 122 {
Chris@152 123 return m_yBinCount;
Chris@152 124 }
Chris@152 125
Chris@152 126 void
Chris@182 127 EditableDenseThreeDimensionalModel::setHeight(size_t sz)
Chris@152 128 {
Chris@152 129 m_yBinCount = sz;
Chris@152 130 }
Chris@152 131
Chris@152 132 float
Chris@152 133 EditableDenseThreeDimensionalModel::getMinimumLevel() const
Chris@152 134 {
Chris@152 135 return m_minimum;
Chris@152 136 }
Chris@152 137
Chris@152 138 void
Chris@152 139 EditableDenseThreeDimensionalModel::setMinimumLevel(float level)
Chris@152 140 {
Chris@152 141 m_minimum = level;
Chris@152 142 }
Chris@152 143
Chris@152 144 float
Chris@152 145 EditableDenseThreeDimensionalModel::getMaximumLevel() const
Chris@152 146 {
Chris@152 147 return m_maximum;
Chris@152 148 }
Chris@152 149
Chris@152 150 void
Chris@152 151 EditableDenseThreeDimensionalModel::setMaximumLevel(float level)
Chris@152 152 {
Chris@152 153 m_maximum = level;
Chris@152 154 }
Chris@152 155
Chris@533 156 EditableDenseThreeDimensionalModel::Column
Chris@533 157 EditableDenseThreeDimensionalModel::getColumn(size_t index) const
Chris@152 158 {
Chris@536 159 QReadLocker locker(&m_lock);
Chris@534 160 if (index >= m_data.size()) return Column();
Chris@534 161 return expandAndRetrieve(index);
Chris@152 162 }
Chris@152 163
Chris@152 164 float
Chris@182 165 EditableDenseThreeDimensionalModel::getValueAt(size_t index, size_t n) const
Chris@152 166 {
Chris@534 167 Column c = getColumn(index);
Chris@535 168 if (n < c.size()) return c.at(n);
Chris@534 169 return m_minimum;
Chris@534 170 }
Chris@152 171
Chris@535 172 //static int given = 0, stored = 0;
Chris@534 173
Chris@534 174 void
Chris@534 175 EditableDenseThreeDimensionalModel::truncateAndStore(size_t index,
Chris@534 176 const Column &values)
Chris@534 177 {
Chris@534 178 assert(index < m_data.size());
Chris@534 179
Chris@534 180 //std::cout << "truncateAndStore(" << index << ", " << values.size() << ")" << std::endl;
Chris@534 181
Chris@535 182 // The default case is to store the entire column at m_data[index]
Chris@535 183 // and place 0 at m_trunc[index] to indicate that it has not been
Chris@535 184 // truncated. We only do clever stuff if one of the clever-stuff
Chris@535 185 // tests works out.
Chris@535 186
Chris@534 187 m_trunc[index] = 0;
Chris@535 188 if (index == 0 ||
Chris@535 189 m_compression == NoCompression ||
Chris@535 190 values.size() != m_yBinCount) {
Chris@535 191 // given += values.size();
Chris@535 192 // stored += values.size();
Chris@534 193 m_data[index] = values;
Chris@534 194 return;
Chris@152 195 }
Chris@152 196
Chris@535 197 // Maximum distance between a column and the one we refer to as
Chris@535 198 // the source of its truncated values. Limited by having to fit
Chris@535 199 // in a signed char, but in any case small values are usually
Chris@535 200 // better
Chris@535 201 static int maxdist = 6;
Chris@534 202
Chris@535 203 bool known = false; // do we know whether to truncate at top or bottom?
Chris@535 204 bool top = false; // if we do know, will we truncate at top?
Chris@534 205
Chris@535 206 // If the previous column is not truncated, then it is the only
Chris@535 207 // candidate for comparison. If it is truncated, then the column
Chris@535 208 // that it refers to is the only candidate. Either way, we only
Chris@535 209 // have one possible column to compare against here, and we are
Chris@535 210 // being careful to ensure it is not a truncated one (to avoid
Chris@535 211 // doing more work recursively when uncompressing).
Chris@534 212 int tdist = 1;
Chris@534 213 int ptrunc = m_trunc[index-1];
Chris@534 214 if (ptrunc < 0) {
Chris@534 215 top = false;
Chris@534 216 known = true;
Chris@534 217 tdist = -ptrunc + 1;
Chris@534 218 } else if (ptrunc > 0) {
Chris@534 219 top = true;
Chris@534 220 known = true;
Chris@534 221 tdist = ptrunc + 1;
Chris@534 222 }
Chris@534 223
Chris@534 224 Column p = expandAndRetrieve(index - tdist);
Chris@534 225 int h = m_yBinCount;
Chris@534 226
Chris@534 227 if (p.size() == h && tdist <= maxdist) {
Chris@534 228
Chris@534 229 int bcount = 0, tcount = 0;
Chris@534 230 if (!known || !top) {
Chris@535 231 // count how many identical values there are at the bottom
Chris@534 232 for (int i = 0; i < h; ++i) {
Chris@534 233 if (values.at(i) == p.at(i)) ++bcount;
Chris@534 234 else break;
Chris@534 235 }
Chris@534 236 }
Chris@534 237 if (!known || top) {
Chris@535 238 // count how many identical values there are at the top
Chris@534 239 for (int i = h; i > 0; --i) {
Chris@534 240 if (values.at(i-1) == p.at(i-1)) ++tcount;
Chris@534 241 else break;
Chris@534 242 }
Chris@534 243 }
Chris@534 244 if (!known) top = (tcount > bcount);
Chris@534 245
Chris@535 246 int limit = h / 4; // don't bother unless we have at least this many
Chris@534 247 if ((top ? tcount : bcount) > limit) {
Chris@534 248
Chris@534 249 if (!top) {
Chris@535 250 // create a new column with h - bcount values from bcount up
Chris@534 251 Column tcol(h - bcount);
Chris@535 252 // given += values.size();
Chris@535 253 // stored += h - bcount;
Chris@534 254 for (int i = bcount; i < h; ++i) {
Chris@534 255 tcol[i - bcount] = values.at(i);
Chris@534 256 }
Chris@534 257 m_data[index] = tcol;
Chris@534 258 m_trunc[index] = -tdist;
Chris@534 259 return;
Chris@534 260 } else {
Chris@535 261 // create a new column with h - tcount values from 0 up
Chris@534 262 Column tcol(h - tcount);
Chris@535 263 // given += values.size();
Chris@535 264 // stored += h - tcount;
Chris@534 265 for (int i = 0; i < h - tcount; ++i) {
Chris@534 266 tcol[i] = values.at(i);
Chris@534 267 }
Chris@534 268 m_data[index] = tcol;
Chris@534 269 m_trunc[index] = tdist;
Chris@534 270 return;
Chris@534 271 }
Chris@534 272 }
Chris@534 273 }
Chris@534 274
Chris@535 275 // given += values.size();
Chris@535 276 // stored += values.size();
Chris@534 277 // std::cout << "given: " << given << ", stored: " << stored << " ("
Chris@534 278 // << ((float(stored) / float(given)) * 100.f) << "%)" << std::endl;
Chris@534 279
Chris@535 280 // default case if nothing wacky worked out
Chris@534 281 m_data[index] = values;
Chris@534 282 return;
Chris@534 283 }
Chris@534 284
Chris@534 285 EditableDenseThreeDimensionalModel::Column
Chris@534 286 EditableDenseThreeDimensionalModel::expandAndRetrieve(size_t index) const
Chris@534 287 {
Chris@535 288 // See comment above m_trunc declaration in header
Chris@535 289
Chris@534 290 assert(index < m_data.size());
Chris@534 291 Column c = m_data.at(index);
Chris@534 292 if (index == 0) {
Chris@534 293 return c;
Chris@534 294 }
Chris@534 295 int trunc = (int)m_trunc[index];
Chris@534 296 if (trunc == 0) {
Chris@534 297 return c;
Chris@534 298 }
Chris@534 299 bool top = true;
Chris@534 300 int tdist = trunc;
Chris@534 301 if (trunc < 0) { top = false; tdist = -trunc; }
Chris@534 302 Column p = expandAndRetrieve(index - tdist);
Chris@537 303 int psize = p.size(), csize = c.size();
Chris@537 304 if (psize != m_yBinCount) {
Chris@534 305 std::cerr << "WARNING: EditableDenseThreeDimensionalModel::expandAndRetrieve: Trying to expand from incorrectly sized column" << std::endl;
Chris@534 306 }
Chris@534 307 if (top) {
Chris@537 308 for (int i = csize; i < psize; ++i) {
Chris@534 309 c.push_back(p.at(i));
Chris@534 310 }
Chris@534 311 } else {
Chris@593 312 // push_front is very slow on QVector -- but not enough to
Chris@593 313 // make it desirable to choose a different container, since
Chris@593 314 // QVector has all the other advantages for us. easier to
Chris@593 315 // write the whole array out to a new vector
Chris@593 316 Column cc(psize);
Chris@593 317 for (int i = 0; i < psize - csize; ++i) {
Chris@593 318 cc[i] = p.at(i);
Chris@534 319 }
Chris@593 320 for (int i = 0; i < csize; ++i) {
Chris@593 321 cc[i + (psize - csize)] = c.at(i);
Chris@593 322 }
Chris@593 323 return cc;
Chris@534 324 }
Chris@534 325 return c;
Chris@152 326 }
Chris@152 327
Chris@152 328 void
Chris@182 329 EditableDenseThreeDimensionalModel::setColumn(size_t index,
Chris@182 330 const Column &values)
Chris@152 331 {
Chris@536 332 QWriteLocker locker(&m_lock);
Chris@152 333
Chris@182 334 while (index >= m_data.size()) {
Chris@182 335 m_data.push_back(Column());
Chris@534 336 m_trunc.push_back(0);
Chris@152 337 }
Chris@152 338
Chris@152 339 bool allChange = false;
Chris@152 340
Chris@534 341 // if (values.size() > m_yBinCount) m_yBinCount = values.size();
Chris@439 342
Chris@152 343 for (size_t i = 0; i < values.size(); ++i) {
Chris@256 344 float value = values[i];
Chris@606 345 if (ISNAN(value) || ISINF(value)) {
Chris@256 346 continue;
Chris@256 347 }
Chris@256 348 if (!m_haveExtents || value < m_minimum) {
Chris@256 349 m_minimum = value;
Chris@152 350 allChange = true;
Chris@152 351 }
Chris@256 352 if (!m_haveExtents || value > m_maximum) {
Chris@256 353 m_maximum = value;
Chris@152 354 allChange = true;
Chris@152 355 }
Chris@256 356 m_haveExtents = true;
Chris@152 357 }
Chris@152 358
Chris@534 359 truncateAndStore(index, values);
Chris@534 360
Chris@593 361 // assert(values == expandAndRetrieve(index));
Chris@152 362
Chris@182 363 long windowStart = index;
Chris@182 364 windowStart *= m_resolution;
Chris@182 365
Chris@152 366 if (m_notifyOnAdd) {
Chris@152 367 if (allChange) {
Chris@152 368 emit modelChanged();
Chris@152 369 } else {
Chris@152 370 emit modelChanged(windowStart, windowStart + m_resolution);
Chris@152 371 }
Chris@152 372 } else {
Chris@152 373 if (allChange) {
Chris@152 374 m_sinceLastNotifyMin = -1;
Chris@152 375 m_sinceLastNotifyMax = -1;
Chris@152 376 emit modelChanged();
Chris@152 377 } else {
Chris@152 378 if (m_sinceLastNotifyMin == -1 ||
Chris@152 379 windowStart < m_sinceLastNotifyMin) {
Chris@152 380 m_sinceLastNotifyMin = windowStart;
Chris@152 381 }
Chris@152 382 if (m_sinceLastNotifyMax == -1 ||
Chris@152 383 windowStart > m_sinceLastNotifyMax) {
Chris@152 384 m_sinceLastNotifyMax = windowStart;
Chris@152 385 }
Chris@152 386 }
Chris@152 387 }
Chris@152 388 }
Chris@152 389
Chris@152 390 QString
Chris@152 391 EditableDenseThreeDimensionalModel::getBinName(size_t n) const
Chris@152 392 {
Chris@152 393 if (m_binNames.size() > n) return m_binNames[n];
Chris@152 394 else return "";
Chris@152 395 }
Chris@152 396
Chris@152 397 void
Chris@152 398 EditableDenseThreeDimensionalModel::setBinName(size_t n, QString name)
Chris@152 399 {
Chris@152 400 while (m_binNames.size() <= n) m_binNames.push_back("");
Chris@152 401 m_binNames[n] = name;
Chris@152 402 emit modelChanged();
Chris@152 403 }
Chris@152 404
Chris@152 405 void
Chris@152 406 EditableDenseThreeDimensionalModel::setBinNames(std::vector<QString> names)
Chris@152 407 {
Chris@152 408 m_binNames = names;
Chris@152 409 emit modelChanged();
Chris@152 410 }
Chris@152 411
Chris@478 412 bool
Chris@478 413 EditableDenseThreeDimensionalModel::shouldUseLogValueScale() const
Chris@478 414 {
Chris@536 415 QReadLocker locker(&m_lock);
Chris@534 416
Chris@533 417 QVector<float> sample;
Chris@533 418 QVector<int> n;
Chris@478 419
Chris@478 420 for (int i = 0; i < 10; ++i) {
Chris@478 421 size_t index = i * 10;
Chris@478 422 if (index < m_data.size()) {
Chris@533 423 const Column &c = m_data.at(index);
Chris@478 424 while (c.size() > sample.size()) {
Chris@478 425 sample.push_back(0.f);
Chris@478 426 n.push_back(0);
Chris@478 427 }
Chris@478 428 for (int j = 0; j < c.size(); ++j) {
Chris@533 429 sample[j] += c.at(j);
Chris@478 430 ++n[j];
Chris@478 431 }
Chris@478 432 }
Chris@478 433 }
Chris@478 434
Chris@478 435 if (sample.empty()) return false;
Chris@478 436 for (int j = 0; j < sample.size(); ++j) {
Chris@478 437 if (n[j]) sample[j] /= n[j];
Chris@478 438 }
Chris@478 439
Chris@533 440 return LogRange::useLogScale(sample.toStdVector());
Chris@478 441 }
Chris@478 442
Chris@152 443 void
Chris@333 444 EditableDenseThreeDimensionalModel::setCompletion(int completion, bool update)
Chris@152 445 {
Chris@152 446 if (m_completion != completion) {
Chris@152 447 m_completion = completion;
Chris@152 448
Chris@152 449 if (completion == 100) {
Chris@152 450
Chris@152 451 m_notifyOnAdd = true; // henceforth
Chris@152 452 emit modelChanged();
Chris@152 453
Chris@152 454 } else if (!m_notifyOnAdd) {
Chris@152 455
Chris@333 456 if (update &&
Chris@333 457 m_sinceLastNotifyMin >= 0 &&
Chris@152 458 m_sinceLastNotifyMax >= 0) {
Chris@152 459 emit modelChanged(m_sinceLastNotifyMin,
Chris@152 460 m_sinceLastNotifyMax + m_resolution);
Chris@152 461 m_sinceLastNotifyMin = m_sinceLastNotifyMax = -1;
Chris@152 462 } else {
Chris@152 463 emit completionChanged();
Chris@152 464 }
Chris@152 465 } else {
Chris@152 466 emit completionChanged();
Chris@152 467 }
Chris@152 468 }
Chris@152 469 }
Chris@152 470
Chris@318 471 QString
Chris@318 472 EditableDenseThreeDimensionalModel::toDelimitedDataString(QString delimiter) const
Chris@318 473 {
Chris@536 474 QReadLocker locker(&m_lock);
Chris@318 475 QString s;
Chris@318 476 for (size_t i = 0; i < m_data.size(); ++i) {
Chris@318 477 QStringList list;
Chris@533 478 for (size_t j = 0; j < m_data.at(i).size(); ++j) {
Chris@533 479 list << QString("%1").arg(m_data.at(i).at(j));
Chris@318 480 }
Chris@318 481 s += list.join(delimiter) + "\n";
Chris@318 482 }
Chris@318 483 return s;
Chris@318 484 }
Chris@318 485
Chris@152 486 void
Chris@152 487 EditableDenseThreeDimensionalModel::toXml(QTextStream &out,
Chris@314 488 QString indent,
Chris@314 489 QString extraAttributes) const
Chris@152 490 {
Chris@536 491 QReadLocker locker(&m_lock);
Chris@534 492
Chris@152 493 // For historical reasons we read and write "resolution" as "windowSize"
Chris@152 494
Chris@690 495 SVDEBUG << "EditableDenseThreeDimensionalModel::toXml" << endl;
Chris@318 496
Chris@314 497 Model::toXml
Chris@314 498 (out, indent,
Chris@611 499 QString("type=\"dense\" dimensions=\"3\" windowSize=\"%1\" yBinCount=\"%2\" minimum=\"%3\" maximum=\"%4\" dataset=\"%5\" startFrame=\"%6\" %7")
Chris@152 500 .arg(m_resolution)
Chris@152 501 .arg(m_yBinCount)
Chris@152 502 .arg(m_minimum)
Chris@152 503 .arg(m_maximum)
Chris@152 504 .arg(getObjectExportId(&m_data))
Chris@611 505 .arg(m_startFrame)
Chris@152 506 .arg(extraAttributes));
Chris@152 507
Chris@152 508 out << indent;
Chris@152 509 out << QString("<dataset id=\"%1\" dimensions=\"3\" separator=\" \">\n")
Chris@152 510 .arg(getObjectExportId(&m_data));
Chris@152 511
Chris@152 512 for (size_t i = 0; i < m_binNames.size(); ++i) {
Chris@152 513 if (m_binNames[i] != "") {
Chris@152 514 out << indent + " ";
Chris@152 515 out << QString("<bin number=\"%1\" name=\"%2\"/>\n")
Chris@152 516 .arg(i).arg(m_binNames[i]);
Chris@152 517 }
Chris@152 518 }
Chris@152 519
Chris@152 520 for (size_t i = 0; i < m_data.size(); ++i) {
Chris@152 521 out << indent + " ";
Chris@152 522 out << QString("<row n=\"%1\">").arg(i);
Chris@533 523 for (size_t j = 0; j < m_data.at(i).size(); ++j) {
Chris@152 524 if (j > 0) out << " ";
Chris@533 525 out << m_data.at(i).at(j);
Chris@152 526 }
Chris@152 527 out << QString("</row>\n");
Chris@318 528 out.flush();
Chris@152 529 }
Chris@152 530
Chris@152 531 out << indent + "</dataset>\n";
Chris@152 532 }
Chris@152 533
Chris@152 534