annotate data/model/EditableDenseThreeDimensionalModel.cpp @ 537:3cc4b7cd2aa5

* Merge from one-fftdataserver-per-fftmodel branch. This bit of reworking (which is not described very accurately by the title of the branch) turns the MatrixFile object into something that either reads or writes, but not both, and separates the FFT file cache reader and writer implementations separately. This allows the FFT data server to have a single thread owning writers and one reader per "customer" thread, and for all locking to be vastly simplified and concentrated in the data server alone (because none of the classes it makes use of is used in more than one thread at a time). The result is faster and more trustworthy code.
author Chris Cannam
date Tue, 27 Jan 2009 13:25:10 +0000
parents beb51f558e9c
children 4f3e3fb07e0b
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@152 30 EditableDenseThreeDimensionalModel::EditableDenseThreeDimensionalModel(size_t sampleRate,
Chris@152 31 size_t resolution,
Chris@152 32 size_t yBinCount,
Chris@535 33 CompressionType compression,
Chris@152 34 bool notifyOnAdd) :
Chris@152 35 m_sampleRate(sampleRate),
Chris@152 36 m_resolution(resolution),
Chris@152 37 m_yBinCount(yBinCount),
Chris@535 38 m_compression(compression),
Chris@152 39 m_minimum(0.0),
Chris@152 40 m_maximum(0.0),
Chris@256 41 m_haveExtents(false),
Chris@152 42 m_notifyOnAdd(notifyOnAdd),
Chris@152 43 m_sinceLastNotifyMin(-1),
Chris@152 44 m_sinceLastNotifyMax(-1),
Chris@152 45 m_completion(100)
Chris@152 46 {
Chris@152 47 }
Chris@152 48
Chris@152 49 bool
Chris@152 50 EditableDenseThreeDimensionalModel::isOK() const
Chris@152 51 {
Chris@152 52 return true;
Chris@152 53 }
Chris@152 54
Chris@152 55 size_t
Chris@152 56 EditableDenseThreeDimensionalModel::getSampleRate() const
Chris@152 57 {
Chris@152 58 return m_sampleRate;
Chris@152 59 }
Chris@152 60
Chris@152 61 size_t
Chris@152 62 EditableDenseThreeDimensionalModel::getStartFrame() const
Chris@152 63 {
Chris@152 64 return 0;
Chris@152 65 }
Chris@152 66
Chris@152 67 size_t
Chris@152 68 EditableDenseThreeDimensionalModel::getEndFrame() const
Chris@152 69 {
Chris@152 70 return m_resolution * m_data.size() + (m_resolution - 1);
Chris@152 71 }
Chris@152 72
Chris@152 73 Model *
Chris@152 74 EditableDenseThreeDimensionalModel::clone() const
Chris@152 75 {
Chris@536 76 QReadLocker locker(&m_lock);
Chris@534 77
Chris@152 78 EditableDenseThreeDimensionalModel *model =
Chris@152 79 new EditableDenseThreeDimensionalModel
Chris@535 80 (m_sampleRate, m_resolution, m_yBinCount, m_compression);
Chris@152 81
Chris@152 82 model->m_minimum = m_minimum;
Chris@152 83 model->m_maximum = m_maximum;
Chris@256 84 model->m_haveExtents = m_haveExtents;
Chris@152 85
Chris@152 86 for (size_t i = 0; i < m_data.size(); ++i) {
Chris@533 87 model->setColumn(i, m_data.at(i));
Chris@152 88 }
Chris@152 89
Chris@152 90 return model;
Chris@152 91 }
Chris@152 92
Chris@152 93 size_t
Chris@152 94 EditableDenseThreeDimensionalModel::getResolution() const
Chris@152 95 {
Chris@152 96 return m_resolution;
Chris@152 97 }
Chris@152 98
Chris@152 99 void
Chris@152 100 EditableDenseThreeDimensionalModel::setResolution(size_t sz)
Chris@152 101 {
Chris@152 102 m_resolution = sz;
Chris@152 103 }
Chris@152 104
Chris@152 105 size_t
Chris@182 106 EditableDenseThreeDimensionalModel::getWidth() const
Chris@182 107 {
Chris@182 108 return m_data.size();
Chris@182 109 }
Chris@182 110
Chris@182 111 size_t
Chris@182 112 EditableDenseThreeDimensionalModel::getHeight() const
Chris@152 113 {
Chris@152 114 return m_yBinCount;
Chris@152 115 }
Chris@152 116
Chris@152 117 void
Chris@182 118 EditableDenseThreeDimensionalModel::setHeight(size_t sz)
Chris@152 119 {
Chris@152 120 m_yBinCount = sz;
Chris@152 121 }
Chris@152 122
Chris@152 123 float
Chris@152 124 EditableDenseThreeDimensionalModel::getMinimumLevel() const
Chris@152 125 {
Chris@152 126 return m_minimum;
Chris@152 127 }
Chris@152 128
Chris@152 129 void
Chris@152 130 EditableDenseThreeDimensionalModel::setMinimumLevel(float level)
Chris@152 131 {
Chris@152 132 m_minimum = level;
Chris@152 133 }
Chris@152 134
Chris@152 135 float
Chris@152 136 EditableDenseThreeDimensionalModel::getMaximumLevel() const
Chris@152 137 {
Chris@152 138 return m_maximum;
Chris@152 139 }
Chris@152 140
Chris@152 141 void
Chris@152 142 EditableDenseThreeDimensionalModel::setMaximumLevel(float level)
Chris@152 143 {
Chris@152 144 m_maximum = level;
Chris@152 145 }
Chris@152 146
Chris@533 147 EditableDenseThreeDimensionalModel::Column
Chris@533 148 EditableDenseThreeDimensionalModel::getColumn(size_t index) const
Chris@152 149 {
Chris@536 150 QReadLocker locker(&m_lock);
Chris@534 151 if (index >= m_data.size()) return Column();
Chris@534 152 return expandAndRetrieve(index);
Chris@152 153 }
Chris@152 154
Chris@152 155 float
Chris@182 156 EditableDenseThreeDimensionalModel::getValueAt(size_t index, size_t n) const
Chris@152 157 {
Chris@534 158 Column c = getColumn(index);
Chris@535 159 if (n < c.size()) return c.at(n);
Chris@534 160 return m_minimum;
Chris@534 161 }
Chris@152 162
Chris@535 163 //static int given = 0, stored = 0;
Chris@534 164
Chris@534 165 void
Chris@534 166 EditableDenseThreeDimensionalModel::truncateAndStore(size_t index,
Chris@534 167 const Column &values)
Chris@534 168 {
Chris@534 169 assert(index < m_data.size());
Chris@534 170
Chris@534 171 //std::cout << "truncateAndStore(" << index << ", " << values.size() << ")" << std::endl;
Chris@534 172
Chris@535 173 // The default case is to store the entire column at m_data[index]
Chris@535 174 // and place 0 at m_trunc[index] to indicate that it has not been
Chris@535 175 // truncated. We only do clever stuff if one of the clever-stuff
Chris@535 176 // tests works out.
Chris@535 177
Chris@534 178 m_trunc[index] = 0;
Chris@535 179 if (index == 0 ||
Chris@535 180 m_compression == NoCompression ||
Chris@535 181 values.size() != m_yBinCount) {
Chris@535 182 // given += values.size();
Chris@535 183 // stored += values.size();
Chris@534 184 m_data[index] = values;
Chris@534 185 return;
Chris@152 186 }
Chris@152 187
Chris@535 188 // Maximum distance between a column and the one we refer to as
Chris@535 189 // the source of its truncated values. Limited by having to fit
Chris@535 190 // in a signed char, but in any case small values are usually
Chris@535 191 // better
Chris@535 192 static int maxdist = 6;
Chris@534 193
Chris@535 194 bool known = false; // do we know whether to truncate at top or bottom?
Chris@535 195 bool top = false; // if we do know, will we truncate at top?
Chris@534 196
Chris@535 197 // If the previous column is not truncated, then it is the only
Chris@535 198 // candidate for comparison. If it is truncated, then the column
Chris@535 199 // that it refers to is the only candidate. Either way, we only
Chris@535 200 // have one possible column to compare against here, and we are
Chris@535 201 // being careful to ensure it is not a truncated one (to avoid
Chris@535 202 // doing more work recursively when uncompressing).
Chris@534 203 int tdist = 1;
Chris@534 204 int ptrunc = m_trunc[index-1];
Chris@534 205 if (ptrunc < 0) {
Chris@534 206 top = false;
Chris@534 207 known = true;
Chris@534 208 tdist = -ptrunc + 1;
Chris@534 209 } else if (ptrunc > 0) {
Chris@534 210 top = true;
Chris@534 211 known = true;
Chris@534 212 tdist = ptrunc + 1;
Chris@534 213 }
Chris@534 214
Chris@534 215 Column p = expandAndRetrieve(index - tdist);
Chris@534 216 int h = m_yBinCount;
Chris@534 217
Chris@534 218 if (p.size() == h && tdist <= maxdist) {
Chris@534 219
Chris@534 220 int bcount = 0, tcount = 0;
Chris@534 221 if (!known || !top) {
Chris@535 222 // count how many identical values there are at the bottom
Chris@534 223 for (int i = 0; i < h; ++i) {
Chris@534 224 if (values.at(i) == p.at(i)) ++bcount;
Chris@534 225 else break;
Chris@534 226 }
Chris@534 227 }
Chris@534 228 if (!known || top) {
Chris@535 229 // count how many identical values there are at the top
Chris@534 230 for (int i = h; i > 0; --i) {
Chris@534 231 if (values.at(i-1) == p.at(i-1)) ++tcount;
Chris@534 232 else break;
Chris@534 233 }
Chris@534 234 }
Chris@534 235 if (!known) top = (tcount > bcount);
Chris@534 236
Chris@535 237 int limit = h / 4; // don't bother unless we have at least this many
Chris@534 238 if ((top ? tcount : bcount) > limit) {
Chris@534 239
Chris@534 240 if (!top) {
Chris@535 241 // create a new column with h - bcount values from bcount up
Chris@534 242 Column tcol(h - bcount);
Chris@535 243 // given += values.size();
Chris@535 244 // stored += h - bcount;
Chris@534 245 for (int i = bcount; i < h; ++i) {
Chris@534 246 tcol[i - bcount] = values.at(i);
Chris@534 247 }
Chris@534 248 m_data[index] = tcol;
Chris@534 249 m_trunc[index] = -tdist;
Chris@534 250 return;
Chris@534 251 } else {
Chris@535 252 // create a new column with h - tcount values from 0 up
Chris@534 253 Column tcol(h - tcount);
Chris@535 254 // given += values.size();
Chris@535 255 // stored += h - tcount;
Chris@534 256 for (int i = 0; i < h - tcount; ++i) {
Chris@534 257 tcol[i] = values.at(i);
Chris@534 258 }
Chris@534 259 m_data[index] = tcol;
Chris@534 260 m_trunc[index] = tdist;
Chris@534 261 return;
Chris@534 262 }
Chris@534 263 }
Chris@534 264 }
Chris@534 265
Chris@535 266 // given += values.size();
Chris@535 267 // stored += values.size();
Chris@534 268 // std::cout << "given: " << given << ", stored: " << stored << " ("
Chris@534 269 // << ((float(stored) / float(given)) * 100.f) << "%)" << std::endl;
Chris@534 270
Chris@535 271 // default case if nothing wacky worked out
Chris@534 272 m_data[index] = values;
Chris@534 273 return;
Chris@534 274 }
Chris@534 275
Chris@534 276 EditableDenseThreeDimensionalModel::Column
Chris@534 277 EditableDenseThreeDimensionalModel::expandAndRetrieve(size_t index) const
Chris@534 278 {
Chris@535 279 // See comment above m_trunc declaration in header
Chris@535 280
Chris@534 281 assert(index < m_data.size());
Chris@534 282 Column c = m_data.at(index);
Chris@534 283 if (index == 0) {
Chris@534 284 return c;
Chris@534 285 }
Chris@534 286 int trunc = (int)m_trunc[index];
Chris@534 287 if (trunc == 0) {
Chris@534 288 return c;
Chris@534 289 }
Chris@534 290 bool top = true;
Chris@534 291 int tdist = trunc;
Chris@534 292 if (trunc < 0) { top = false; tdist = -trunc; }
Chris@534 293 Column p = expandAndRetrieve(index - tdist);
Chris@537 294 int psize = p.size(), csize = c.size();
Chris@537 295 if (psize != m_yBinCount) {
Chris@534 296 std::cerr << "WARNING: EditableDenseThreeDimensionalModel::expandAndRetrieve: Trying to expand from incorrectly sized column" << std::endl;
Chris@534 297 }
Chris@534 298 if (top) {
Chris@537 299 for (int i = csize; i < psize; ++i) {
Chris@534 300 c.push_back(p.at(i));
Chris@534 301 }
Chris@534 302 } else {
Chris@537 303 for (int i = psize - csize - 1; i >= 0; --i) {
Chris@534 304 c.push_front(p.at(i));
Chris@534 305 }
Chris@534 306 }
Chris@534 307 return c;
Chris@152 308 }
Chris@152 309
Chris@152 310 void
Chris@182 311 EditableDenseThreeDimensionalModel::setColumn(size_t index,
Chris@182 312 const Column &values)
Chris@152 313 {
Chris@536 314 QWriteLocker locker(&m_lock);
Chris@152 315
Chris@182 316 while (index >= m_data.size()) {
Chris@182 317 m_data.push_back(Column());
Chris@534 318 m_trunc.push_back(0);
Chris@152 319 }
Chris@152 320
Chris@152 321 bool allChange = false;
Chris@152 322
Chris@534 323 // if (values.size() > m_yBinCount) m_yBinCount = values.size();
Chris@439 324
Chris@152 325 for (size_t i = 0; i < values.size(); ++i) {
Chris@256 326 float value = values[i];
Chris@257 327 if (std::isnan(value) || std::isinf(value)) {
Chris@256 328 continue;
Chris@256 329 }
Chris@256 330 if (!m_haveExtents || value < m_minimum) {
Chris@256 331 m_minimum = value;
Chris@152 332 allChange = true;
Chris@152 333 }
Chris@256 334 if (!m_haveExtents || value > m_maximum) {
Chris@256 335 m_maximum = value;
Chris@152 336 allChange = true;
Chris@152 337 }
Chris@256 338 m_haveExtents = true;
Chris@152 339 }
Chris@152 340
Chris@534 341 truncateAndStore(index, values);
Chris@534 342
Chris@534 343 assert(values == expandAndRetrieve(index));
Chris@152 344
Chris@182 345 long windowStart = index;
Chris@182 346 windowStart *= m_resolution;
Chris@182 347
Chris@152 348 if (m_notifyOnAdd) {
Chris@152 349 if (allChange) {
Chris@152 350 emit modelChanged();
Chris@152 351 } else {
Chris@152 352 emit modelChanged(windowStart, windowStart + m_resolution);
Chris@152 353 }
Chris@152 354 } else {
Chris@152 355 if (allChange) {
Chris@152 356 m_sinceLastNotifyMin = -1;
Chris@152 357 m_sinceLastNotifyMax = -1;
Chris@152 358 emit modelChanged();
Chris@152 359 } else {
Chris@152 360 if (m_sinceLastNotifyMin == -1 ||
Chris@152 361 windowStart < m_sinceLastNotifyMin) {
Chris@152 362 m_sinceLastNotifyMin = windowStart;
Chris@152 363 }
Chris@152 364 if (m_sinceLastNotifyMax == -1 ||
Chris@152 365 windowStart > m_sinceLastNotifyMax) {
Chris@152 366 m_sinceLastNotifyMax = windowStart;
Chris@152 367 }
Chris@152 368 }
Chris@152 369 }
Chris@152 370 }
Chris@152 371
Chris@152 372 QString
Chris@152 373 EditableDenseThreeDimensionalModel::getBinName(size_t n) const
Chris@152 374 {
Chris@152 375 if (m_binNames.size() > n) return m_binNames[n];
Chris@152 376 else return "";
Chris@152 377 }
Chris@152 378
Chris@152 379 void
Chris@152 380 EditableDenseThreeDimensionalModel::setBinName(size_t n, QString name)
Chris@152 381 {
Chris@152 382 while (m_binNames.size() <= n) m_binNames.push_back("");
Chris@152 383 m_binNames[n] = name;
Chris@152 384 emit modelChanged();
Chris@152 385 }
Chris@152 386
Chris@152 387 void
Chris@152 388 EditableDenseThreeDimensionalModel::setBinNames(std::vector<QString> names)
Chris@152 389 {
Chris@152 390 m_binNames = names;
Chris@152 391 emit modelChanged();
Chris@152 392 }
Chris@152 393
Chris@478 394 bool
Chris@478 395 EditableDenseThreeDimensionalModel::shouldUseLogValueScale() const
Chris@478 396 {
Chris@536 397 QReadLocker locker(&m_lock);
Chris@534 398
Chris@533 399 QVector<float> sample;
Chris@533 400 QVector<int> n;
Chris@478 401
Chris@478 402 for (int i = 0; i < 10; ++i) {
Chris@478 403 size_t index = i * 10;
Chris@478 404 if (index < m_data.size()) {
Chris@533 405 const Column &c = m_data.at(index);
Chris@478 406 while (c.size() > sample.size()) {
Chris@478 407 sample.push_back(0.f);
Chris@478 408 n.push_back(0);
Chris@478 409 }
Chris@478 410 for (int j = 0; j < c.size(); ++j) {
Chris@533 411 sample[j] += c.at(j);
Chris@478 412 ++n[j];
Chris@478 413 }
Chris@478 414 }
Chris@478 415 }
Chris@478 416
Chris@478 417 if (sample.empty()) return false;
Chris@478 418 for (int j = 0; j < sample.size(); ++j) {
Chris@478 419 if (n[j]) sample[j] /= n[j];
Chris@478 420 }
Chris@478 421
Chris@533 422 return LogRange::useLogScale(sample.toStdVector());
Chris@478 423 }
Chris@478 424
Chris@152 425 void
Chris@333 426 EditableDenseThreeDimensionalModel::setCompletion(int completion, bool update)
Chris@152 427 {
Chris@152 428 if (m_completion != completion) {
Chris@152 429 m_completion = completion;
Chris@152 430
Chris@152 431 if (completion == 100) {
Chris@152 432
Chris@152 433 m_notifyOnAdd = true; // henceforth
Chris@152 434 emit modelChanged();
Chris@152 435
Chris@152 436 } else if (!m_notifyOnAdd) {
Chris@152 437
Chris@333 438 if (update &&
Chris@333 439 m_sinceLastNotifyMin >= 0 &&
Chris@152 440 m_sinceLastNotifyMax >= 0) {
Chris@152 441 emit modelChanged(m_sinceLastNotifyMin,
Chris@152 442 m_sinceLastNotifyMax + m_resolution);
Chris@152 443 m_sinceLastNotifyMin = m_sinceLastNotifyMax = -1;
Chris@152 444 } else {
Chris@152 445 emit completionChanged();
Chris@152 446 }
Chris@152 447 } else {
Chris@152 448 emit completionChanged();
Chris@152 449 }
Chris@152 450 }
Chris@152 451 }
Chris@152 452
Chris@318 453 QString
Chris@318 454 EditableDenseThreeDimensionalModel::toDelimitedDataString(QString delimiter) const
Chris@318 455 {
Chris@536 456 QReadLocker locker(&m_lock);
Chris@318 457 QString s;
Chris@318 458 for (size_t i = 0; i < m_data.size(); ++i) {
Chris@318 459 QStringList list;
Chris@533 460 for (size_t j = 0; j < m_data.at(i).size(); ++j) {
Chris@533 461 list << QString("%1").arg(m_data.at(i).at(j));
Chris@318 462 }
Chris@318 463 s += list.join(delimiter) + "\n";
Chris@318 464 }
Chris@318 465 return s;
Chris@318 466 }
Chris@318 467
Chris@152 468 void
Chris@152 469 EditableDenseThreeDimensionalModel::toXml(QTextStream &out,
Chris@314 470 QString indent,
Chris@314 471 QString extraAttributes) const
Chris@152 472 {
Chris@536 473 QReadLocker locker(&m_lock);
Chris@534 474
Chris@152 475 // For historical reasons we read and write "resolution" as "windowSize"
Chris@152 476
Chris@318 477 std::cerr << "EditableDenseThreeDimensionalModel::toXml" << std::endl;
Chris@318 478
Chris@314 479 Model::toXml
Chris@314 480 (out, indent,
Chris@314 481 QString("type=\"dense\" dimensions=\"3\" windowSize=\"%1\" yBinCount=\"%2\" minimum=\"%3\" maximum=\"%4\" dataset=\"%5\" %6")
Chris@152 482 .arg(m_resolution)
Chris@152 483 .arg(m_yBinCount)
Chris@152 484 .arg(m_minimum)
Chris@152 485 .arg(m_maximum)
Chris@152 486 .arg(getObjectExportId(&m_data))
Chris@152 487 .arg(extraAttributes));
Chris@152 488
Chris@152 489 out << indent;
Chris@152 490 out << QString("<dataset id=\"%1\" dimensions=\"3\" separator=\" \">\n")
Chris@152 491 .arg(getObjectExportId(&m_data));
Chris@152 492
Chris@152 493 for (size_t i = 0; i < m_binNames.size(); ++i) {
Chris@152 494 if (m_binNames[i] != "") {
Chris@152 495 out << indent + " ";
Chris@152 496 out << QString("<bin number=\"%1\" name=\"%2\"/>\n")
Chris@152 497 .arg(i).arg(m_binNames[i]);
Chris@152 498 }
Chris@152 499 }
Chris@152 500
Chris@152 501 for (size_t i = 0; i < m_data.size(); ++i) {
Chris@152 502 out << indent + " ";
Chris@152 503 out << QString("<row n=\"%1\">").arg(i);
Chris@533 504 for (size_t j = 0; j < m_data.at(i).size(); ++j) {
Chris@152 505 if (j > 0) out << " ";
Chris@533 506 out << m_data.at(i).at(j);
Chris@152 507 }
Chris@152 508 out << QString("</row>\n");
Chris@318 509 out.flush();
Chris@152 510 }
Chris@152 511
Chris@152 512 out << indent + "</dataset>\n";
Chris@152 513 }
Chris@152 514
Chris@152 515