annotate layer/SliceLayer.cpp @ 1551:e79731086b0f

Fixes to NoteLayer, particularly to calculation of vertical scale when model unit is not Hz. To avoid inconsistency we now behave as if the unit is always Hz from the point of view of the external API and display, converting at the point where we obtain values from the events themselves. Also various fixes to editing.
author Chris Cannam
date Thu, 21 Nov 2019 14:02:57 +0000
parents 0e971e3d93e2
children
rev   line source
Chris@133 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@133 2
Chris@133 3 /*
Chris@133 4 Sonic Visualiser
Chris@133 5 An audio file viewer and annotation editor.
Chris@133 6 Centre for Digital Music, Queen Mary, University of London.
Chris@195 7 This file copyright 2006-2007 QMUL.
Chris@133 8
Chris@133 9 This program is free software; you can redistribute it and/or
Chris@133 10 modify it under the terms of the GNU General Public License as
Chris@133 11 published by the Free Software Foundation; either version 2 of the
Chris@133 12 License, or (at your option) any later version. See the file
Chris@133 13 COPYING included with this distribution for more information.
Chris@133 14 */
Chris@133 15
Chris@193 16 #include "SliceLayer.h"
Chris@133 17
Chris@133 18 #include "view/View.h"
Chris@153 19 #include "base/AudioLevel.h"
Chris@167 20 #include "base/RangeMapper.h"
Chris@195 21 #include "base/RealTime.h"
Chris@376 22 #include "ColourMapper.h"
Chris@376 23 #include "ColourDatabase.h"
Chris@195 24
Chris@195 25 #include "PaintAssistant.h"
Chris@133 26
Chris@1383 27 #include "base/Profiler.h"
Chris@1383 28
Chris@133 29 #include <QPainter>
Chris@133 30 #include <QPainterPath>
Chris@316 31 #include <QTextStream>
Chris@316 32
Chris@133 33
Chris@193 34 SliceLayer::SliceLayer() :
Chris@1400 35 m_binAlignment(BinsSpanScalePoints),
Chris@1281 36 m_colourMap(int(ColourMapper::Ice)),
Chris@1362 37 m_colourInverted(false),
Chris@153 38 m_energyScale(dBScale),
Chris@198 39 m_samplingMode(SampleMean),
Chris@1233 40 m_plotStyle(PlotLines),
Chris@193 41 m_binScale(LinearBins),
Chris@153 42 m_normalize(false),
Chris@284 43 m_threshold(0.0),
Chris@284 44 m_initialThreshold(0.0),
Chris@198 45 m_gain(1.0),
Chris@1238 46 m_minbin(0),
Chris@1238 47 m_maxbin(0),
Chris@198 48 m_currentf0(0),
Chris@198 49 m_currentf1(0)
Chris@133 50 {
Chris@133 51 }
Chris@133 52
Chris@193 53 SliceLayer::~SliceLayer()
Chris@133 54 {
Chris@193 55
Chris@133 56 }
Chris@133 57
Chris@133 58 void
Chris@1472 59 SliceLayer::setSliceableModel(ModelId modelId)
Chris@133 60 {
Chris@1472 61 auto newModel = ModelById::getAs<DenseThreeDimensionalModel>(modelId);
Chris@1472 62
Chris@1472 63 if (!modelId.isNone() && !newModel) {
Chris@1472 64 throw std::logic_error("Not a DenseThreeDimensionalModel");
Chris@193 65 }
Chris@193 66
Chris@1472 67 if (m_sliceableModel == modelId) return;
Chris@1472 68 m_sliceableModel = modelId;
Chris@193 69
Chris@1472 70 if (newModel) {
Chris@1472 71 connectSignals(m_sliceableModel);
Chris@193 72
Chris@1472 73 if (m_minbin == 0 && m_maxbin == 0) {
Chris@1472 74 m_minbin = 0;
Chris@1472 75 m_maxbin = newModel->getHeight();
Chris@1472 76 }
Chris@1389 77 }
Chris@1238 78
Chris@193 79 emit modelReplaced();
Chris@1238 80 emit layerParametersChanged();
Chris@153 81 }
Chris@153 82
Chris@153 83 void
Chris@1472 84 SliceLayer::sliceableModelReplaced(ModelId orig, ModelId replacement)
Chris@153 85 {
Chris@587 86 SVDEBUG << "SliceLayer::sliceableModelReplaced(" << orig << ", " << replacement << ")" << endl;
Chris@153 87
Chris@193 88 if (orig == m_sliceableModel) {
Chris@1472 89 setSliceableModel(replacement);
Chris@153 90 }
Chris@153 91 }
Chris@153 92
Chris@198 93 QString
Chris@918 94 SliceLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &p) const
Chris@198 95 {
Chris@199 96 int minbin, maxbin, range;
Chris@805 97 return getFeatureDescriptionAux(v, p, true, minbin, maxbin, range);
Chris@199 98 }
Chris@199 99
Chris@199 100 QString
Chris@918 101 SliceLayer::getFeatureDescriptionAux(LayerGeometryProvider *v, QPoint &p,
Chris@805 102 bool includeBinDescription,
Chris@805 103 int &minbin, int &maxbin, int &range) const
Chris@199 104 {
Chris@199 105 minbin = 0;
Chris@199 106 maxbin = 0;
Chris@1472 107
Chris@1472 108 auto sliceableModel =
Chris@1472 109 ModelById::getAs<DenseThreeDimensionalModel>(m_sliceableModel);
Chris@1472 110 if (!sliceableModel) return "";
Chris@198 111
Chris@1400 112 if (m_binAlignment == BinsSpanScalePoints) {
Chris@1400 113 minbin = int(getBinForX(v, p.x()));
Chris@1400 114 maxbin = int(getBinForX(v, p.x() + 1));
Chris@1400 115 } else {
Chris@1400 116 minbin = int(getBinForX(v, p.x()) + 0.5);
Chris@1400 117 maxbin = int(getBinForX(v, p.x() + 1) + 0.5);
Chris@1400 118 }
Chris@1238 119
Chris@1472 120 int mh = sliceableModel->getHeight();
Chris@199 121 if (minbin >= mh) minbin = mh - 1;
Chris@199 122 if (maxbin >= mh) maxbin = mh - 1;
Chris@199 123 if (minbin < 0) minbin = 0;
Chris@199 124 if (maxbin < 0) maxbin = 0;
Chris@198 125
Chris@1472 126 sv_samplerate_t sampleRate = sliceableModel->getSampleRate();
Chris@198 127
Chris@906 128 sv_frame_t f0 = m_currentf0;
Chris@906 129 sv_frame_t f1 = m_currentf1;
Chris@198 130
Chris@198 131 RealTime rt0 = RealTime::frame2RealTime(f0, sampleRate);
Chris@198 132 RealTime rt1 = RealTime::frame2RealTime(f1, sampleRate);
Chris@198 133
Chris@906 134 range = int(f1 - f0 + 1);
Chris@198 135
Chris@280 136 QString rtrangestr = QString("%1 s").arg((rt1 - rt0).toText().c_str());
Chris@280 137
Chris@199 138 if (includeBinDescription) {
Chris@198 139
Chris@1238 140 int i0 = minbin - m_minbin;
Chris@1238 141 int i1 = maxbin - m_minbin;
Chris@1238 142
Chris@906 143 float minvalue = 0.0;
Chris@1238 144 if (in_range_for(m_values, i0)) minvalue = m_values[i0];
Chris@198 145
Chris@199 146 float maxvalue = minvalue;
Chris@1238 147 if (in_range_for(m_values, i1)) maxvalue = m_values[i1];
Chris@1238 148
Chris@199 149 if (minvalue > maxvalue) std::swap(minvalue, maxvalue);
Chris@199 150
Chris@199 151 QString binstr;
Chris@199 152 if (maxbin != minbin) {
Chris@199 153 binstr = tr("%1 - %2").arg(minbin+1).arg(maxbin+1);
Chris@199 154 } else {
Chris@199 155 binstr = QString("%1").arg(minbin+1);
Chris@199 156 }
Chris@199 157
Chris@199 158 QString valuestr;
Chris@199 159 if (maxvalue != minvalue) {
Chris@199 160 valuestr = tr("%1 - %2").arg(minvalue).arg(maxvalue);
Chris@199 161 } else {
Chris@199 162 valuestr = QString("%1").arg(minvalue);
Chris@199 163 }
Chris@199 164
Chris@280 165 QString description = tr("Time:\t%1 - %2\nRange:\t%3 samples (%4)\nBin:\t%5\n%6 value:\t%7")
Chris@199 166 .arg(QString::fromStdString(rt0.toText(true)))
Chris@199 167 .arg(QString::fromStdString(rt1.toText(true)))
Chris@199 168 .arg(range)
Chris@280 169 .arg(rtrangestr)
Chris@199 170 .arg(binstr)
Chris@199 171 .arg(m_samplingMode == NearestSample ? tr("First") :
Chris@199 172 m_samplingMode == SampleMean ? tr("Mean") : tr("Peak"))
Chris@199 173 .arg(valuestr);
Chris@199 174
Chris@199 175 return description;
Chris@199 176
Chris@199 177 } else {
Chris@199 178
Chris@280 179 QString description = tr("Time:\t%1 - %2\nRange:\t%3 samples (%4)")
Chris@199 180 .arg(QString::fromStdString(rt0.toText(true)))
Chris@199 181 .arg(QString::fromStdString(rt1.toText(true)))
Chris@280 182 .arg(range)
Chris@280 183 .arg(rtrangestr);
Chris@199 184
Chris@199 185 return description;
Chris@199 186 }
Chris@198 187 }
Chris@198 188
Chris@906 189 double
Chris@1238 190 SliceLayer::getXForBin(const LayerGeometryProvider *v, double bin) const
Chris@198 191 {
Chris@1386 192 return getXForScalePoint(v, bin, m_minbin, m_maxbin);
Chris@1386 193 }
Chris@1386 194
Chris@1386 195 double
Chris@1386 196 SliceLayer::getXForScalePoint(const LayerGeometryProvider *v,
Chris@1386 197 double p, double pmin, double pmax) const
Chris@1386 198 {
Chris@906 199 double x = 0;
Chris@198 200
Chris@1238 201 int pw = v->getPaintWidth();
Chris@1238 202 int origin = m_xorigins[v->getId()];
Chris@1238 203 int w = pw - origin;
Chris@1238 204 if (w < 1) w = 1;
Chris@1238 205
Chris@1399 206 if (pmax <= pmin) {
Chris@1399 207 pmax = pmin + 1.0;
Chris@1399 208 }
Chris@1399 209
Chris@1394 210 if (p < pmin) p = pmin;
Chris@1394 211 if (p > pmax) p = pmax;
Chris@1399 212
Chris@1394 213 if (m_binScale == LinearBins) {
Chris@1394 214 x = (w * (p - pmin)) / (pmax - pmin);
Chris@1394 215 } else {
Chris@198 216
Chris@1394 217 if (m_binScale == InvertedLogBins) {
Chris@1394 218 // stoopid
Chris@1394 219 p = pmax - p;
Chris@1394 220 }
Chris@198 221
Chris@1281 222 // The 0.8 here is an awkward compromise. Our x-coord is
Chris@1281 223 // proportional to log of bin number, with the x-coord "of a
Chris@1281 224 // bin" being that of the left edge of the bin range. We can't
Chris@1281 225 // start counting bins from 0, as that would give us x = -Inf
Chris@1281 226 // and hide the first bin entirely. But if we start from 1, we
Chris@1281 227 // are giving a lot of space to the first bin, which in most
Chris@1281 228 // display modes won't be used because the "point" location
Chris@1281 229 // for that bin is in the middle of it. Yet in some modes
Chris@1281 230 // we'll still want it. A compromise is to count our first bin
Chris@1281 231 // as "a bit less than 1", so that most of it is visible but a
Chris@1281 232 // bit is tactfully cropped at the left edge so it doesn't
Chris@1281 233 // take up so much space.
Chris@1394 234 const double origin = 0.8;
Chris@198 235
Chris@1394 236 // sometimes we are called with a pmin/pmax range that begins
Chris@1394 237 // before 0: in that situation, we shift everything along by
Chris@1394 238 // the difference between 0 and pmin before doing any other
Chris@1394 239 // calculations
Chris@1394 240 double reqdshift = 0.0;
Chris@1394 241 if (pmin < 0) reqdshift = -pmin;
Chris@1394 242
Chris@1394 243 double pminlog = log10(pmin + reqdshift + origin);
Chris@1394 244 double pmaxlog = log10(pmax + reqdshift + origin);
Chris@1394 245 double plog = log10(p + reqdshift + origin);
Chris@1394 246 x = (w * (plog - pminlog)) / (pmaxlog - pminlog);
Chris@1399 247 /*
Chris@1394 248 cerr << "getXForScalePoint(" << p << "): pmin = " << pmin
Chris@1394 249 << ", pmax = " << pmax << ", w = " << w
Chris@1394 250 << ", reqdshift = " << reqdshift
Chris@1394 251 << ", pminlog = " << pminlog << ", pmaxlog = " << pmaxlog
Chris@1394 252 << ", plog = " << plog
Chris@1394 253 << " -> x = " << x << endl;
Chris@1399 254 */
Chris@1394 255 if (m_binScale == InvertedLogBins) {
Chris@1394 256 // still stoopid
Chris@1394 257 x = w - x;
Chris@1394 258 }
Chris@198 259 }
Chris@1238 260
Chris@1238 261 return x + origin;
Chris@198 262 }
Chris@198 263
Chris@1238 264 double
Chris@1238 265 SliceLayer::getBinForX(const LayerGeometryProvider *v, double x) const
Chris@198 266 {
Chris@1386 267 return getScalePointForX(v, x, m_minbin, m_maxbin);
Chris@1386 268 }
Chris@198 269
Chris@1386 270 double
Chris@1386 271 SliceLayer::getScalePointForX(const LayerGeometryProvider *v,
Chris@1386 272 double x, double pmin, double pmax) const
Chris@1386 273 {
Chris@1386 274 double p = 0;
Chris@1386 275
Chris@1238 276 int pw = v->getPaintWidth();
Chris@1238 277 int origin = m_xorigins[v->getId()];
Chris@1238 278
Chris@1238 279 int w = pw - origin;
Chris@1238 280 if (w < 1) w = 1;
Chris@1238 281
Chris@1238 282 x = x - origin;
Chris@1238 283 if (x < 0) x = 0;
Chris@1256 284
Chris@1256 285 double eps = 1e-10;
Chris@198 286
Chris@1399 287 if (pmax <= pmin) {
Chris@1399 288 pmax = pmin + 1.0;
Chris@1399 289 }
Chris@1399 290
Chris@1394 291 if (m_binScale == LinearBins) {
Chris@1394 292 p = pmin + eps + (x * (pmax - pmin)) / w;
Chris@1394 293 } else {
Chris@198 294
Chris@1394 295 if (m_binScale == InvertedLogBins) {
Chris@1394 296 x = w - x;
Chris@1394 297 }
Chris@1394 298
Chris@1394 299 // See comments in getXForScalePoint
Chris@1394 300
Chris@1394 301 const double origin = 0.8;
Chris@1394 302 double reqdshift = 0.0;
Chris@1394 303 if (pmin < 0) reqdshift = -pmin;
Chris@1394 304
Chris@1394 305 double pminlog = log10(pmin + reqdshift + origin);
Chris@1394 306 double pmaxlog = log10(pmax + reqdshift + origin);
Chris@1394 307
Chris@1394 308 double plog = pminlog + eps + (x * (pmaxlog - pminlog)) / w;
Chris@1394 309 p = pow(10.0, plog) - reqdshift - origin;
Chris@1394 310
Chris@1394 311 if (m_binScale == InvertedLogBins) {
Chris@1394 312 p = pmax - p;
Chris@1394 313 }
Chris@198 314 }
Chris@198 315
Chris@1394 316 return p;
Chris@198 317 }
Chris@198 318
Chris@906 319 double
Chris@1238 320 SliceLayer::getYForValue(const LayerGeometryProvider *v, double value, double &norm) const
Chris@274 321 {
Chris@906 322 norm = 0.0;
Chris@274 323
Chris@1238 324 if (m_yorigins.find(v->getId()) == m_yorigins.end()) return 0;
Chris@274 325
Chris@274 326 value *= m_gain;
Chris@274 327
Chris@1238 328 int yorigin = m_yorigins[v->getId()];
Chris@1238 329 int h = m_heights[v->getId()];
Chris@906 330 double thresh = getThresholdDb();
Chris@274 331
Chris@906 332 double y = 0.0;
Chris@274 333
Chris@274 334 if (h <= 0) return y;
Chris@274 335
Chris@274 336 switch (m_energyScale) {
Chris@274 337
Chris@274 338 case dBScale:
Chris@274 339 {
Chris@906 340 double db = thresh;
Chris@906 341 if (value > 0.0) db = 10.0 * log10(fabs(value));
Chris@274 342 if (db < thresh) db = thresh;
Chris@274 343 norm = (db - thresh) / -thresh;
Chris@906 344 y = yorigin - (double(h) * norm);
Chris@274 345 break;
Chris@274 346 }
Chris@274 347
Chris@274 348 case MeterScale:
Chris@274 349 y = AudioLevel::multiplier_to_preview(value, h);
Chris@906 350 norm = double(y) / double(h);
Chris@274 351 y = yorigin - y;
Chris@274 352 break;
Chris@274 353
Chris@538 354 case AbsoluteScale:
Chris@906 355 value = fabs(value);
Chris@1264 356 #if (__GNUC__ >= 7)
Chris@1263 357 __attribute__ ((fallthrough));
Chris@1263 358 #endif
Chris@538 359
Chris@805 360 case LinearScale:
Chris@274 361 default:
Chris@538 362 norm = (value - m_threshold);
Chris@284 363 if (norm < 0) norm = 0;
Chris@906 364 y = yorigin - (double(h) * norm);
Chris@274 365 break;
Chris@274 366 }
Chris@274 367
Chris@274 368 return y;
Chris@274 369 }
Chris@274 370
Chris@906 371 double
Chris@1238 372 SliceLayer::getValueForY(const LayerGeometryProvider *v, double y) const
Chris@274 373 {
Chris@906 374 double value = 0.0;
Chris@274 375
Chris@1238 376 if (m_yorigins.find(v->getId()) == m_yorigins.end()) return value;
Chris@274 377
Chris@1238 378 int yorigin = m_yorigins[v->getId()];
Chris@1238 379 int h = m_heights[v->getId()];
Chris@906 380 double thresh = getThresholdDb();
Chris@274 381
Chris@274 382 if (h <= 0) return value;
Chris@274 383
Chris@274 384 y = yorigin - y;
Chris@274 385
Chris@274 386 switch (m_energyScale) {
Chris@274 387
Chris@274 388 case dBScale:
Chris@274 389 {
Chris@906 390 double db = ((y / h) * -thresh) + thresh;
Chris@906 391 value = pow(10.0, db/10.0);
Chris@274 392 break;
Chris@274 393 }
Chris@274 394
Chris@274 395 case MeterScale:
Chris@906 396 value = AudioLevel::preview_to_multiplier(int(lrint(y)), h);
Chris@274 397 break;
Chris@805 398
Chris@805 399 case LinearScale:
Chris@805 400 case AbsoluteScale:
Chris@274 401 default:
Chris@284 402 value = y / h + m_threshold;
Chris@274 403 }
Chris@274 404
Chris@274 405 return value / m_gain;
Chris@274 406 }
Chris@274 407
Chris@133 408 void
Chris@916 409 SliceLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
Chris@133 410 {
Chris@1472 411 auto sliceableModel =
Chris@1472 412 ModelById::getAs<DenseThreeDimensionalModel>(m_sliceableModel);
Chris@1472 413 if (!sliceableModel ||
Chris@1472 414 !sliceableModel->isOK() ||
Chris@1472 415 !sliceableModel->isReady()) return;
Chris@133 416
Chris@1383 417 Profiler profiler("SliceLayer::paint()");
Chris@1383 418
Chris@195 419 paint.save();
Chris@1383 420 paint.setRenderHint(QPainter::Antialiasing, true);
Chris@538 421 paint.setBrush(Qt::NoBrush);
Chris@195 422
Chris@195 423 if (v->getViewManager() && v->getViewManager()->shouldShowScaleGuides()) {
Chris@195 424 if (!m_scalePoints.empty()) {
Chris@195 425 paint.setPen(QColor(240, 240, 240)); //!!! and dark background?
Chris@1284 426 int ratio = int(round(double(v->getPaintHeight()) /
Chris@1284 427 m_scalePaintHeight));
Chris@805 428 for (int i = 0; i < (int)m_scalePoints.size(); ++i) {
Chris@1284 429 paint.drawLine(0, m_scalePoints[i] * ratio,
Chris@1284 430 rect.width(), m_scalePoints[i] * ratio);
Chris@195 431 }
Chris@195 432 }
Chris@195 433 }
Chris@195 434
Chris@1472 435 int mh = sliceableModel->getHeight();
Chris@1389 436 int bin0 = 0;
Chris@1389 437 if (m_maxbin > m_minbin) {
Chris@1389 438 mh = m_maxbin - m_minbin;
Chris@1389 439 bin0 = m_minbin;
Chris@1389 440 }
Chris@1387 441
Chris@1233 442 if (m_plotStyle == PlotBlocks) {
Chris@1233 443 // Must use actual zero-width pen, too slow otherwise
Chris@1233 444 paint.setPen(QPen(getBaseQColor(), 0));
Chris@1233 445 } else {
Chris@1389 446 // Similarly, if there are very many bins here, we use a
Chris@1389 447 // thinner pen
Chris@1400 448 QPen pen;
Chris@1389 449 if (mh < 10000) {
Chris@1401 450 pen = v->scalePen(QPen(getBaseQColor(), 0.8));
Chris@1400 451 } else {
Chris@1400 452 pen = QPen(getBaseQColor(), 1);
Chris@1387 453 }
Chris@1389 454 paint.setPen(pen);
Chris@1233 455 }
Chris@195 456
Chris@607 457 int xorigin = getVerticalScaleWidth(v, true, paint) + 1;
Chris@1238 458 m_xorigins[v->getId()] = xorigin; // for use in getFeatureDescription
Chris@133 459
Chris@1383 460 int yorigin = v->getPaintHeight() - getHorizontalScaleHeight(v, paint) -
Chris@1383 461 paint.fontMetrics().height();
Chris@195 462 int h = yorigin - paint.fontMetrics().height() - 8;
Chris@133 463
Chris@1238 464 m_yorigins[v->getId()] = yorigin; // for getYForValue etc
Chris@1238 465 m_heights[v->getId()] = h;
Chris@274 466
Chris@274 467 if (h <= 0) return;
Chris@274 468
Chris@133 469 QPainterPath path;
Chris@1238 470
Chris@193 471 int divisor = 0;
Chris@193 472
Chris@198 473 m_values.clear();
Chris@805 474 for (int bin = 0; bin < mh; ++bin) {
Chris@906 475 m_values.push_back(0.0);
Chris@193 476 }
Chris@193 477
Chris@906 478 sv_frame_t f0 = v->getCentreFrame();
Chris@193 479 int f0x = v->getXForFrame(f0);
Chris@195 480 f0 = v->getFrameForX(f0x);
Chris@906 481 sv_frame_t f1 = v->getFrameForX(f0x + 1);
Chris@195 482 if (f1 > f0) --f1;
Chris@193 483
Chris@682 484 // cerr << "centre frame " << v->getCentreFrame() << ", x " << f0x << ", f0 " << f0 << ", f1 " << f1 << endl;
Chris@274 485
Chris@1472 486 int res = sliceableModel->getResolution();
Chris@906 487 int col0 = int(f0 / res);
Chris@805 488 int col1 = col0;
Chris@906 489 if (m_samplingMode != NearestSample) col1 = int(f1 / res);
Chris@274 490 f0 = col0 * res;
Chris@274 491 f1 = (col1 + 1) * res - 1;
Chris@274 492
Chris@682 493 // cerr << "resolution " << res << ", col0 " << col0 << ", col1 " << col1 << ", f0 " << f0 << ", f1 " << f1 << endl;
Chris@1383 494 // cerr << "mh = " << mh << endl;
Chris@193 495
Chris@198 496 m_currentf0 = f0;
Chris@198 497 m_currentf1 = f1;
Chris@198 498
Chris@254 499 BiasCurve curve;
Chris@254 500 getBiasCurve(curve);
Chris@906 501 int cs = int(curve.size());
Chris@254 502
Chris@805 503 for (int col = col0; col <= col1; ++col) {
Chris@1383 504 DenseThreeDimensionalModel::Column column =
Chris@1472 505 sliceableModel->getColumn(col);
Chris@805 506 for (int bin = 0; bin < mh; ++bin) {
Chris@1383 507 float value = column[bin0 + bin];
Chris@254 508 if (bin < cs) value *= curve[bin];
Chris@193 509 if (m_samplingMode == SamplePeak) {
Chris@198 510 if (value > m_values[bin]) m_values[bin] = value;
Chris@193 511 } else {
Chris@198 512 m_values[bin] += value;
Chris@193 513 }
Chris@153 514 }
Chris@193 515 ++divisor;
Chris@193 516 }
Chris@193 517
Chris@906 518 float max = 0.0;
Chris@805 519 for (int bin = 0; bin < mh; ++bin) {
Chris@847 520 if (m_samplingMode == SampleMean && divisor > 0) {
Chris@906 521 m_values[bin] /= float(divisor);
Chris@847 522 }
Chris@198 523 if (m_values[bin] > max) max = m_values[bin];
Chris@193 524 }
Chris@906 525 if (max != 0.0 && m_normalize) {
Chris@805 526 for (int bin = 0; bin < mh; ++bin) {
Chris@198 527 m_values[bin] /= max;
Chris@193 528 }
Chris@193 529 }
Chris@193 530
Chris@1362 531 ColourMapper mapper(m_colourMap, m_colourInverted, 0, 1);
Chris@197 532
Chris@1383 533 double ytop = 0, ybottom = 0;
Chris@1383 534 bool firstBinOfPixel = true;
Chris@1384 535
Chris@1384 536 QColor prevColour = v->getBackground();
Chris@1384 537 double prevYtop = 0;
Chris@1383 538
Chris@1400 539 double xleft = -1, xmiddle = -1, xright = -1;
Chris@1400 540 double prevXmiddle = 0;
Chris@1400 541
Chris@805 542 for (int bin = 0; bin < mh; ++bin) {
Chris@193 543
Chris@1400 544 if (m_binAlignment == BinsSpanScalePoints) {
Chris@1400 545 if (xright >= 0) xleft = xright; // previous value of
Chris@1400 546 else xleft = getXForBin(v, bin0 + bin);
Chris@1400 547 xmiddle = getXForBin(v, bin0 + bin + 0.5);
Chris@1400 548 xright = getXForBin(v, bin0 + bin + 1);
Chris@1400 549 } else {
Chris@1400 550 if (xright >= 0) xleft = xright; // previous value of
Chris@1400 551 else xleft = getXForBin(v, bin0 + bin - 0.5);
Chris@1400 552 xmiddle = getXForBin(v, bin0 + bin);
Chris@1400 553 xright = getXForBin(v, bin0 + bin + 0.5);
Chris@1400 554 }
Chris@193 555
Chris@906 556 double value = m_values[bin];
Chris@906 557 double norm = 0.0;
Chris@1238 558 double y = getYForValue(v, value, norm);
Chris@133 559
Chris@1383 560 if (y < ytop || firstBinOfPixel) {
Chris@1383 561 ytop = y;
Chris@1383 562 }
Chris@1383 563 if (y > ybottom || firstBinOfPixel) {
Chris@1383 564 ybottom = y;
Chris@1383 565 }
Chris@193 566
Chris@1400 567 if (int(xright) != int(xleft) || bin+1 == mh) {
Chris@1383 568
Chris@1383 569 if (m_plotStyle == PlotLines) {
Chris@1383 570
Chris@1383 571 if (bin == 0) {
Chris@1400 572 path.moveTo(xmiddle, y);
Chris@1383 573 } else {
Chris@1383 574 if (ytop != ybottom) {
Chris@1400 575 path.lineTo(xmiddle, ybottom);
Chris@1400 576 path.lineTo(xmiddle, ytop);
Chris@1400 577 path.moveTo(xmiddle, ybottom);
Chris@1383 578 } else {
Chris@1400 579 path.lineTo(xmiddle, ytop);
Chris@1383 580 }
Chris@1383 581 }
Chris@1383 582
Chris@1383 583 } else if (m_plotStyle == PlotSteps) {
Chris@1383 584
Chris@1383 585 if (bin == 0) {
Chris@1400 586 path.moveTo(xleft, y);
Chris@1383 587 } else {
Chris@1400 588 path.lineTo(xleft, ytop);
Chris@1383 589 }
Chris@1400 590 path.lineTo(xright, ytop);
Chris@1383 591
Chris@1383 592 } else if (m_plotStyle == PlotBlocks) {
Chris@1383 593
Chris@1384 594 // work in pixel coords here, as we don't want the
Chris@1384 595 // vertical edges to be antialiased
Chris@1384 596
Chris@1400 597 path.moveTo(QPoint(int(xleft), int(yorigin)));
Chris@1400 598 path.lineTo(QPoint(int(xleft), int(ytop)));
Chris@1400 599 path.lineTo(QPoint(int(xright), int(ytop)));
Chris@1400 600 path.lineTo(QPoint(int(xright), int(yorigin)));
Chris@1400 601 path.lineTo(QPoint(int(xleft), int(yorigin)));
Chris@1383 602
Chris@1383 603 } else if (m_plotStyle == PlotFilledBlocks) {
Chris@1383 604
Chris@1384 605 QColor c = mapper.map(norm);
Chris@1384 606 paint.setPen(Qt::NoPen);
Chris@1384 607
Chris@1384 608 // work in pixel coords here, as we don't want the
Chris@1384 609 // vertical edges to be antialiased
Chris@1384 610
Chris@1400 611 if (xright > xleft + 1) {
Chris@1384 612
Chris@1384 613 QVector<QPoint> pp;
Chris@1384 614
Chris@1384 615 if (bin > 0) {
Chris@1384 616 paint.setBrush(prevColour);
Chris@1384 617 pp.clear();
Chris@1400 618 pp << QPoint(int(prevXmiddle), int(yorigin));
Chris@1400 619 pp << QPoint(int(prevXmiddle), int(prevYtop));
Chris@1400 620 pp << QPoint(int((xmiddle + prevXmiddle) / 2),
Chris@1384 621 int((ytop + prevYtop) / 2));
Chris@1400 622 pp << QPoint(int((xmiddle + prevXmiddle) / 2),
Chris@1384 623 int(yorigin));
Chris@1384 624 paint.drawConvexPolygon(QPolygon(pp));
Chris@1384 625
Chris@1384 626 paint.setBrush(c);
Chris@1384 627 pp.clear();
Chris@1400 628 pp << QPoint(int((xmiddle + prevXmiddle) / 2),
Chris@1384 629 int(yorigin));
Chris@1400 630 pp << QPoint(int((xmiddle + prevXmiddle) / 2),
Chris@1384 631 int((ytop + prevYtop) / 2));
Chris@1400 632 pp << QPoint(int(xmiddle), int(ytop));
Chris@1400 633 pp << QPoint(int(xmiddle), int(yorigin));
Chris@1384 634 paint.drawConvexPolygon(QPolygon(pp));
Chris@1384 635 }
Chris@1384 636
Chris@1384 637 prevColour = c;
Chris@1384 638 prevYtop = ytop;
Chris@1384 639
Chris@1384 640 } else {
Chris@1384 641
Chris@1400 642 paint.fillRect(QRect(int(xleft), int(ytop),
Chris@1400 643 int(xright) - int(xleft),
Chris@1384 644 int(yorigin) - int(ytop)),
Chris@1384 645 c);
Chris@1384 646 }
Chris@1400 647
Chris@1400 648 prevXmiddle = xmiddle;
Chris@193 649 }
Chris@193 650
Chris@1383 651 firstBinOfPixel = true;
Chris@193 652
Chris@1383 653 } else {
Chris@1383 654 firstBinOfPixel = false;
Chris@133 655 }
Chris@133 656 }
Chris@133 657
Chris@197 658 if (m_plotStyle != PlotFilledBlocks) {
Chris@197 659 paint.drawPath(path);
Chris@197 660 }
Chris@133 661 paint.restore();
Chris@195 662 }
Chris@195 663
Chris@195 664 int
Chris@918 665 SliceLayer::getVerticalScaleWidth(LayerGeometryProvider *, bool, QPainter &paint) const
Chris@195 666 {
Chris@1472 667 // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
Chris@1472 668 // replacement (horizontalAdvance) was only added in Qt 5.11
Chris@1472 669 // which is too new for us
Chris@1472 670 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
Chris@1472 671
Chris@1238 672 int width;
Chris@538 673 if (m_energyScale == LinearScale || m_energyScale == AbsoluteScale) {
Chris@1266 674 width = std::max(paint.fontMetrics().width("0.0") + 13,
Chris@1238 675 paint.fontMetrics().width("x10-10"));
Chris@195 676 } else {
Chris@1238 677 width = std::max(paint.fontMetrics().width(tr("0dB")),
Chris@1238 678 paint.fontMetrics().width(tr("-Inf"))) + 13;
Chris@195 679 }
Chris@1238 680 return width;
Chris@195 681 }
Chris@195 682
Chris@195 683 void
Chris@918 684 SliceLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const
Chris@195 685 {
Chris@906 686 double thresh = m_threshold;
Chris@538 687 if (m_energyScale != LinearScale && m_energyScale != AbsoluteScale) {
Chris@284 688 thresh = AudioLevel::dB_to_multiplier(getThresholdDb());
Chris@195 689 }
Chris@195 690
Chris@195 691 // int h = (rect.height() * 3) / 4;
Chris@195 692 // int y = (rect.height() / 2) - (h / 2);
Chris@195 693
Chris@1383 694 int yorigin = v->getPaintHeight() - getHorizontalScaleHeight(v, paint) -
Chris@1383 695 paint.fontMetrics().height();
Chris@195 696 int h = yorigin - paint.fontMetrics().height() - 8;
Chris@195 697 if (h < 0) return;
Chris@195 698
Chris@195 699 QRect actual(rect.x(), rect.y() + yorigin - h, rect.width(), h);
Chris@195 700
Chris@220 701 int mult = 1;
Chris@220 702
Chris@195 703 PaintAssistant::paintVerticalLevelScale
Chris@195 704 (paint, actual, thresh, 1.0 / m_gain,
Chris@195 705 PaintAssistant::Scale(m_energyScale),
Chris@220 706 mult,
Chris@195 707 const_cast<std::vector<int> *>(&m_scalePoints));
Chris@220 708
Chris@1284 709 // Ugly hack (but then everything about this scale drawing is a
Chris@1284 710 // bit ugly). In pixel-doubling hi-dpi scenarios, the scale is
Chris@1284 711 // painted at pixel-doubled resolution but we do explicit
Chris@1284 712 // pixel-doubling ourselves when painting the layer content. We
Chris@1284 713 // make a note of this here so that we can compare with the
Chris@1284 714 // equivalent dimension in the paint method when deciding where to
Chris@1284 715 // place scale continuation lines.
Chris@1284 716 m_scalePaintHeight = v->getPaintHeight();
Chris@1284 717
Chris@220 718 if (mult != 1 && mult != 0) {
Chris@906 719 int log = int(lrint(log10(mult)));
Chris@220 720 QString a = tr("x10");
Chris@220 721 QString b = QString("%1").arg(-log);
Chris@220 722 paint.drawText(3, 8 + paint.fontMetrics().ascent(), a);
Chris@220 723 paint.drawText(3 + paint.fontMetrics().width(a),
Chris@220 724 3 + paint.fontMetrics().ascent(), b);
Chris@220 725 }
Chris@133 726 }
Chris@133 727
Chris@1281 728 bool
Chris@1281 729 SliceLayer::hasLightBackground() const
Chris@1281 730 {
Chris@1281 731 if (usesSolidColour()) {
Chris@1362 732 ColourMapper mapper(m_colourMap, m_colourInverted, 0, 1);
Chris@1281 733 return mapper.hasLightBackground();
Chris@1281 734 } else {
Chris@1281 735 return SingleColourLayer::hasLightBackground();
Chris@1281 736 }
Chris@1281 737 }
Chris@1281 738
Chris@153 739 Layer::PropertyList
Chris@193 740 SliceLayer::getProperties() const
Chris@153 741 {
Chris@287 742 PropertyList list = SingleColourLayer::getProperties();
Chris@538 743 list.push_back("Bin Scale");
Chris@193 744 list.push_back("Plot Type");
Chris@153 745 list.push_back("Scale");
Chris@153 746 list.push_back("Normalize");
Chris@284 747 list.push_back("Threshold");
Chris@153 748 list.push_back("Gain");
Chris@153 749
Chris@153 750 return list;
Chris@153 751 }
Chris@153 752
Chris@153 753 QString
Chris@193 754 SliceLayer::getPropertyLabel(const PropertyName &name) const
Chris@153 755 {
Chris@193 756 if (name == "Plot Type") return tr("Plot Type");
Chris@290 757 if (name == "Scale") return tr("Scale");
Chris@153 758 if (name == "Normalize") return tr("Normalize");
Chris@284 759 if (name == "Threshold") return tr("Threshold");
Chris@153 760 if (name == "Gain") return tr("Gain");
Chris@193 761 if (name == "Sampling Mode") return tr("Sampling Mode");
Chris@538 762 if (name == "Bin Scale") return tr("Bin Scale");
Chris@287 763 return SingleColourLayer::getPropertyLabel(name);
Chris@153 764 }
Chris@153 765
Chris@335 766 QString
Chris@335 767 SliceLayer::getPropertyIconName(const PropertyName &name) const
Chris@335 768 {
Chris@335 769 if (name == "Normalize") return "normalise";
Chris@335 770 return "";
Chris@335 771 }
Chris@335 772
Chris@153 773 Layer::PropertyType
Chris@193 774 SliceLayer::getPropertyType(const PropertyName &name) const
Chris@153 775 {
Chris@153 776 if (name == "Gain") return RangeProperty;
Chris@153 777 if (name == "Normalize") return ToggleProperty;
Chris@284 778 if (name == "Threshold") return RangeProperty;
Chris@287 779 if (name == "Plot Type") return ValueProperty;
Chris@290 780 if (name == "Scale") return ValueProperty;
Chris@287 781 if (name == "Sampling Mode") return ValueProperty;
Chris@287 782 if (name == "Bin Scale") return ValueProperty;
Chris@1281 783 if (name == "Colour" && usesSolidColour()) return ColourMapProperty;
Chris@287 784 return SingleColourLayer::getPropertyType(name);
Chris@153 785 }
Chris@153 786
Chris@153 787 QString
Chris@193 788 SliceLayer::getPropertyGroupName(const PropertyName &name) const
Chris@153 789 {
Chris@153 790 if (name == "Scale" ||
Chris@153 791 name == "Normalize" ||
Chris@193 792 name == "Sampling Mode" ||
Chris@284 793 name == "Threshold" ||
Chris@193 794 name == "Gain") return tr("Scale");
Chris@193 795 if (name == "Plot Type" ||
Chris@538 796 name == "Bin Scale") return tr("Bins");
Chris@287 797 return SingleColourLayer::getPropertyGroupName(name);
Chris@153 798 }
Chris@153 799
Chris@153 800 int
Chris@193 801 SliceLayer::getPropertyRangeAndValue(const PropertyName &name,
Chris@216 802 int *min, int *max, int *deflt) const
Chris@153 803 {
Chris@216 804 int val = 0;
Chris@153 805
Chris@216 806 int garbage0, garbage1, garbage2;
Chris@153 807 if (!min) min = &garbage0;
Chris@153 808 if (!max) max = &garbage1;
Chris@248 809 if (!deflt) deflt = &garbage2;
Chris@153 810
Chris@153 811 if (name == "Gain") {
Chris@153 812
Chris@1266 813 *min = -50;
Chris@1266 814 *max = 50;
Chris@216 815 *deflt = 0;
Chris@153 816
Chris@1238 817 // cerr << "gain is " << m_gain << ", mode is " << m_samplingMode << endl;
Chris@193 818
Chris@1266 819 val = int(lrint(log10(m_gain) * 20.0));
Chris@1266 820 if (val < *min) val = *min;
Chris@1266 821 if (val > *max) val = *max;
Chris@153 822
Chris@284 823 } else if (name == "Threshold") {
Chris@284 824
Chris@1266 825 *min = -80;
Chris@1266 826 *max = 0;
Chris@284 827
Chris@906 828 *deflt = int(lrint(AudioLevel::multiplier_to_dB(m_initialThreshold)));
Chris@1266 829 if (*deflt < *min) *deflt = *min;
Chris@1266 830 if (*deflt > *max) *deflt = *max;
Chris@284 831
Chris@1266 832 val = int(lrint(AudioLevel::multiplier_to_dB(m_threshold)));
Chris@1266 833 if (val < *min) val = *min;
Chris@1266 834 if (val > *max) val = *max;
Chris@284 835
Chris@153 836 } else if (name == "Normalize") {
Chris@1266 837
Chris@1266 838 val = (m_normalize ? 1 : 0);
Chris@216 839 *deflt = 0;
Chris@153 840
Chris@1281 841 } else if (name == "Colour" && usesSolidColour()) {
Chris@197 842
Chris@287 843 *min = 0;
Chris@287 844 *max = ColourMapper::getColourMapCount() - 1;
Chris@1281 845 *deflt = int(ColourMapper::Ice);
Chris@287 846
Chris@287 847 val = m_colourMap;
Chris@153 848
Chris@153 849 } else if (name == "Scale") {
Chris@153 850
Chris@1266 851 *min = 0;
Chris@1266 852 *max = 3;
Chris@216 853 *deflt = (int)dBScale;
Chris@153 854
Chris@1266 855 val = (int)m_energyScale;
Chris@153 856
Chris@193 857 } else if (name == "Sampling Mode") {
Chris@153 858
Chris@1266 859 *min = 0;
Chris@1266 860 *max = 2;
Chris@216 861 *deflt = (int)SampleMean;
Chris@193 862
Chris@1266 863 val = (int)m_samplingMode;
Chris@153 864
Chris@193 865 } else if (name == "Plot Type") {
Chris@193 866
Chris@193 867 *min = 0;
Chris@197 868 *max = 3;
Chris@216 869 *deflt = (int)PlotSteps;
Chris@193 870
Chris@216 871 val = (int)m_plotStyle;
Chris@193 872
Chris@193 873 } else if (name == "Bin Scale") {
Chris@193 874
Chris@193 875 *min = 0;
Chris@198 876 *max = 2;
Chris@216 877 *deflt = (int)LinearBins;
Chris@198 878 // *max = 1; // I don't think we really do want to offer inverted log
Chris@193 879
Chris@216 880 val = (int)m_binScale;
Chris@193 881
Chris@153 882 } else {
Chris@1266 883 val = SingleColourLayer::getPropertyRangeAndValue(name, min, max, deflt);
Chris@153 884 }
Chris@153 885
Chris@216 886 return val;
Chris@153 887 }
Chris@153 888
Chris@153 889 QString
Chris@193 890 SliceLayer::getPropertyValueLabel(const PropertyName &name,
Chris@1281 891 int value) const
Chris@153 892 {
Chris@1281 893 if (name == "Colour" && usesSolidColour()) {
Chris@1362 894 return ColourMapper::getColourMapLabel(value);
Chris@153 895 }
Chris@153 896 if (name == "Scale") {
Chris@1266 897 switch (value) {
Chris@1266 898 default:
Chris@1266 899 case 0: return tr("Linear");
Chris@1266 900 case 1: return tr("Meter");
Chris@1266 901 case 2: return tr("Log");
Chris@1266 902 case 3: return tr("Absolute");
Chris@1266 903 }
Chris@153 904 }
Chris@193 905 if (name == "Sampling Mode") {
Chris@1266 906 switch (value) {
Chris@1266 907 default:
Chris@1266 908 case 0: return tr("Any");
Chris@1266 909 case 1: return tr("Mean");
Chris@1266 910 case 2: return tr("Peak");
Chris@1266 911 }
Chris@193 912 }
Chris@193 913 if (name == "Plot Type") {
Chris@1266 914 switch (value) {
Chris@1266 915 default:
Chris@1266 916 case 0: return tr("Lines");
Chris@1266 917 case 1: return tr("Steps");
Chris@1266 918 case 2: return tr("Blocks");
Chris@1266 919 case 3: return tr("Colours");
Chris@1266 920 }
Chris@193 921 }
Chris@193 922 if (name == "Bin Scale") {
Chris@1266 923 switch (value) {
Chris@1266 924 default:
Chris@1266 925 case 0: return tr("Linear");
Chris@1266 926 case 1: return tr("Log");
Chris@1266 927 case 2: return tr("Rev Log");
Chris@1266 928 }
Chris@153 929 }
Chris@287 930 return SingleColourLayer::getPropertyValueLabel(name, value);
Chris@153 931 }
Chris@153 932
Chris@167 933 RangeMapper *
Chris@193 934 SliceLayer::getNewPropertyRangeMapper(const PropertyName &name) const
Chris@167 935 {
Chris@167 936 if (name == "Gain") {
Chris@167 937 return new LinearRangeMapper(-50, 50, -25, 25, tr("dB"));
Chris@167 938 }
Chris@284 939 if (name == "Threshold") {
Chris@284 940 return new LinearRangeMapper(-80, 0, -80, 0, tr("dB"));
Chris@284 941 }
Chris@287 942 return SingleColourLayer::getNewPropertyRangeMapper(name);
Chris@167 943 }
Chris@167 944
Chris@133 945 void
Chris@193 946 SliceLayer::setProperty(const PropertyName &name, int value)
Chris@133 947 {
Chris@153 948 if (name == "Gain") {
Chris@1266 949 setGain(powf(10, float(value)/20.0f));
Chris@284 950 } else if (name == "Threshold") {
Chris@1266 951 if (value == -80) setThreshold(0.0f);
Chris@1266 952 else setThreshold(float(AudioLevel::dB_to_multiplier(value)));
Chris@1281 953 } else if (name == "Colour" && usesSolidColour()) {
Chris@287 954 setFillColourMap(value);
Chris@153 955 } else if (name == "Scale") {
Chris@1266 956 switch (value) {
Chris@1266 957 default:
Chris@1266 958 case 0: setEnergyScale(LinearScale); break;
Chris@1266 959 case 1: setEnergyScale(MeterScale); break;
Chris@1266 960 case 2: setEnergyScale(dBScale); break;
Chris@1266 961 case 3: setEnergyScale(AbsoluteScale); break;
Chris@1266 962 }
Chris@193 963 } else if (name == "Plot Type") {
Chris@1266 964 setPlotStyle(PlotStyle(value));
Chris@193 965 } else if (name == "Sampling Mode") {
Chris@1266 966 switch (value) {
Chris@1266 967 default:
Chris@1266 968 case 0: setSamplingMode(NearestSample); break;
Chris@1266 969 case 1: setSamplingMode(SampleMean); break;
Chris@1266 970 case 2: setSamplingMode(SamplePeak); break;
Chris@1266 971 }
Chris@193 972 } else if (name == "Bin Scale") {
Chris@1266 973 switch (value) {
Chris@1266 974 default:
Chris@1266 975 case 0: setBinScale(LinearBins); break;
Chris@1266 976 case 1: setBinScale(LogBins); break;
Chris@1266 977 case 2: setBinScale(InvertedLogBins); break;
Chris@1266 978 }
Chris@153 979 } else if (name == "Normalize") {
Chris@1266 980 setNormalize(value ? true : false);
Chris@287 981 } else {
Chris@287 982 SingleColourLayer::setProperty(name, value);
Chris@153 983 }
Chris@153 984 }
Chris@153 985
Chris@153 986 void
Chris@197 987 SliceLayer::setFillColourMap(int map)
Chris@197 988 {
Chris@197 989 if (m_colourMap == map) return;
Chris@197 990 m_colourMap = map;
Chris@197 991 emit layerParametersChanged();
Chris@197 992 }
Chris@197 993
Chris@197 994 void
Chris@193 995 SliceLayer::setEnergyScale(EnergyScale scale)
Chris@153 996 {
Chris@153 997 if (m_energyScale == scale) return;
Chris@153 998 m_energyScale = scale;
Chris@153 999 emit layerParametersChanged();
Chris@153 1000 }
Chris@153 1001
Chris@153 1002 void
Chris@193 1003 SliceLayer::setSamplingMode(SamplingMode mode)
Chris@153 1004 {
Chris@193 1005 if (m_samplingMode == mode) return;
Chris@193 1006 m_samplingMode = mode;
Chris@153 1007 emit layerParametersChanged();
Chris@153 1008 }
Chris@153 1009
Chris@153 1010 void
Chris@193 1011 SliceLayer::setPlotStyle(PlotStyle style)
Chris@153 1012 {
Chris@193 1013 if (m_plotStyle == style) return;
Chris@197 1014 bool colourTypeChanged = (style == PlotFilledBlocks ||
Chris@197 1015 m_plotStyle == PlotFilledBlocks);
Chris@193 1016 m_plotStyle = style;
Chris@197 1017 if (colourTypeChanged) {
Chris@197 1018 emit layerParameterRangesChanged();
Chris@197 1019 }
Chris@153 1020 emit layerParametersChanged();
Chris@153 1021 }
Chris@153 1022
Chris@153 1023 void
Chris@193 1024 SliceLayer::setBinScale(BinScale scale)
Chris@153 1025 {
Chris@193 1026 if (m_binScale == scale) return;
Chris@193 1027 m_binScale = scale;
Chris@153 1028 emit layerParametersChanged();
Chris@153 1029 }
Chris@153 1030
Chris@153 1031 void
Chris@193 1032 SliceLayer::setNormalize(bool n)
Chris@153 1033 {
Chris@153 1034 if (m_normalize == n) return;
Chris@153 1035 m_normalize = n;
Chris@153 1036 emit layerParametersChanged();
Chris@153 1037 }
Chris@153 1038
Chris@153 1039 void
Chris@284 1040 SliceLayer::setThreshold(float thresh)
Chris@284 1041 {
Chris@284 1042 if (m_threshold == thresh) return;
Chris@284 1043 m_threshold = thresh;
Chris@284 1044 emit layerParametersChanged();
Chris@284 1045 }
Chris@284 1046
Chris@284 1047 void
Chris@193 1048 SliceLayer::setGain(float gain)
Chris@153 1049 {
Chris@153 1050 if (m_gain == gain) return;
Chris@153 1051 m_gain = gain;
Chris@153 1052 emit layerParametersChanged();
Chris@153 1053 }
Chris@153 1054
Chris@284 1055 float
Chris@284 1056 SliceLayer::getThresholdDb() const
Chris@284 1057 {
Chris@284 1058 if (m_threshold == 0.0) return -80.f;
Chris@906 1059 float db = float(AudioLevel::multiplier_to_dB(m_threshold));
Chris@284 1060 return db;
Chris@284 1061 }
Chris@284 1062
Chris@287 1063 int
Chris@287 1064 SliceLayer::getDefaultColourHint(bool darkbg, bool &impose)
Chris@287 1065 {
Chris@287 1066 impose = false;
Chris@287 1067 return ColourDatabase::getInstance()->getColourIndex
Chris@287 1068 (QString(darkbg ? "Bright Blue" : "Blue"));
Chris@287 1069 }
Chris@287 1070
Chris@316 1071 void
Chris@316 1072 SliceLayer::toXml(QTextStream &stream,
Chris@316 1073 QString indent, QString extraAttributes) const
Chris@153 1074 {
Chris@153 1075 QString s;
Chris@153 1076
Chris@1362 1077 s += QString("energyScale=\"%1\" "
Chris@1362 1078 "samplingMode=\"%2\" "
Chris@1362 1079 "plotStyle=\"%3\" "
Chris@1362 1080 "binScale=\"%4\" "
Chris@1362 1081 "gain=\"%5\" "
Chris@1362 1082 "threshold=\"%6\" "
Chris@1362 1083 "normalize=\"%7\" %8 ")
Chris@1266 1084 .arg(m_energyScale)
Chris@193 1085 .arg(m_samplingMode)
Chris@598 1086 .arg(m_plotStyle)
Chris@598 1087 .arg(m_binScale)
Chris@153 1088 .arg(m_gain)
Chris@598 1089 .arg(m_threshold)
Chris@1238 1090 .arg(m_normalize ? "true" : "false")
Chris@1238 1091 .arg(QString("minbin=\"%1\" "
Chris@1238 1092 "maxbin=\"%2\"")
Chris@1238 1093 .arg(m_minbin)
Chris@1238 1094 .arg(m_maxbin));
Chris@153 1095
Chris@1362 1096 // New-style colour map attribute, by string id rather than by
Chris@1362 1097 // number
Chris@1362 1098
Chris@1362 1099 s += QString("fillColourMap=\"%1\" ")
Chris@1362 1100 .arg(ColourMapper::getColourMapId(m_colourMap));
Chris@1362 1101
Chris@1362 1102 // Old-style colour map attribute
Chris@1362 1103
Chris@1362 1104 s += QString("colourScheme=\"%1\" ")
Chris@1362 1105 .arg(ColourMapper::getBackwardCompatibilityColourMap(m_colourMap));
Chris@1362 1106
Chris@316 1107 SingleColourLayer::toXml(stream, indent, extraAttributes + " " + s);
Chris@153 1108 }
Chris@153 1109
Chris@153 1110 void
Chris@193 1111 SliceLayer::setProperties(const QXmlAttributes &attributes)
Chris@153 1112 {
Chris@153 1113 bool ok = false;
Chris@153 1114
Chris@287 1115 SingleColourLayer::setProperties(attributes);
Chris@153 1116
Chris@153 1117 EnergyScale scale = (EnergyScale)
Chris@1266 1118 attributes.value("energyScale").toInt(&ok);
Chris@153 1119 if (ok) setEnergyScale(scale);
Chris@153 1120
Chris@193 1121 SamplingMode mode = (SamplingMode)
Chris@1266 1122 attributes.value("samplingMode").toInt(&ok);
Chris@193 1123 if (ok) setSamplingMode(mode);
Chris@153 1124
Chris@1362 1125 QString colourMapId = attributes.value("fillColourMap");
Chris@1362 1126 int colourMap = ColourMapper::getColourMapById(colourMapId);
Chris@1362 1127 if (colourMap >= 0) {
Chris@1362 1128 setFillColourMap(colourMap);
Chris@1362 1129 } else {
Chris@1362 1130 colourMap = attributes.value("colourScheme").toInt(&ok);
Chris@1362 1131 if (ok && colourMap < ColourMapper::getColourMapCount()) {
Chris@1362 1132 setFillColourMap(colourMap);
Chris@1362 1133 }
Chris@1362 1134 }
Chris@197 1135
Chris@598 1136 PlotStyle s = (PlotStyle)
Chris@1266 1137 attributes.value("plotStyle").toInt(&ok);
Chris@598 1138 if (ok) setPlotStyle(s);
Chris@598 1139
Chris@598 1140 BinScale b = (BinScale)
Chris@1266 1141 attributes.value("binScale").toInt(&ok);
Chris@598 1142 if (ok) setBinScale(b);
Chris@598 1143
Chris@153 1144 float gain = attributes.value("gain").toFloat(&ok);
Chris@153 1145 if (ok) setGain(gain);
Chris@153 1146
Chris@598 1147 float threshold = attributes.value("threshold").toFloat(&ok);
Chris@598 1148 if (ok) setThreshold(threshold);
Chris@598 1149
Chris@153 1150 bool normalize = (attributes.value("normalize").trimmed() == "true");
Chris@153 1151 setNormalize(normalize);
Chris@1238 1152
Chris@1238 1153 bool alsoOk = false;
Chris@1238 1154
Chris@1238 1155 float min = attributes.value("minbin").toFloat(&ok);
Chris@1238 1156 float max = attributes.value("maxbin").toFloat(&alsoOk);
Chris@1238 1157 if (ok && alsoOk) setDisplayExtents(min, max);
Chris@133 1158 }
Chris@133 1159
Chris@133 1160 bool
Chris@1238 1161 SliceLayer::getValueExtents(double &min, double &max, bool &logarithmic,
Chris@1238 1162 QString &unit) const
Chris@133 1163 {
Chris@1472 1164 auto sliceableModel =
Chris@1472 1165 ModelById::getAs<DenseThreeDimensionalModel>(m_sliceableModel);
Chris@1472 1166 if (!sliceableModel) return false;
Chris@1238 1167
Chris@1238 1168 min = 0;
Chris@1472 1169 max = double(sliceableModel->getHeight());
Chris@1238 1170
Chris@1238 1171 logarithmic = (m_binScale == BinScale::LogBins);
Chris@1238 1172 unit = "";
Chris@1238 1173
Chris@1238 1174 return true;
Chris@133 1175 }
Chris@133 1176
Chris@1238 1177 bool
Chris@1238 1178 SliceLayer::getDisplayExtents(double &min, double &max) const
Chris@1238 1179 {
Chris@1472 1180 auto sliceableModel =
Chris@1472 1181 ModelById::getAs<DenseThreeDimensionalModel>(m_sliceableModel);
Chris@1472 1182 if (!sliceableModel) return false;
Chris@1238 1183
Chris@1472 1184 double hmax = double(sliceableModel->getHeight());
Chris@1238 1185
Chris@1238 1186 min = m_minbin;
Chris@1238 1187 max = m_maxbin;
Chris@1238 1188 if (max <= min) {
Chris@1238 1189 min = 0;
Chris@1238 1190 max = hmax;
Chris@1238 1191 }
Chris@1238 1192 if (min < 0) min = 0;
Chris@1238 1193 if (max > hmax) max = hmax;
Chris@1238 1194
Chris@1238 1195 return true;
Chris@1238 1196 }
Chris@1238 1197
Chris@1238 1198 bool
Chris@1238 1199 SliceLayer::setDisplayExtents(double min, double max)
Chris@1238 1200 {
Chris@1472 1201 auto sliceableModel =
Chris@1472 1202 ModelById::getAs<DenseThreeDimensionalModel>(m_sliceableModel);
Chris@1472 1203 if (!sliceableModel) return false;
Chris@1238 1204
Chris@1238 1205 m_minbin = int(lrint(min));
Chris@1238 1206 m_maxbin = int(lrint(max));
Chris@1403 1207
Chris@1403 1208 if (m_minbin < 0) {
Chris@1403 1209 m_minbin = 0;
Chris@1403 1210 }
Chris@1403 1211 if (m_maxbin < 0) {
Chris@1403 1212 m_maxbin = 0;
Chris@1403 1213 }
Chris@1472 1214 if (m_minbin > sliceableModel->getHeight()) {
Chris@1472 1215 m_minbin = sliceableModel->getHeight();
Chris@1403 1216 }
Chris@1472 1217 if (m_maxbin > sliceableModel->getHeight()) {
Chris@1472 1218 m_maxbin = sliceableModel->getHeight();
Chris@1403 1219 }
Chris@1403 1220 if (m_maxbin < m_minbin) {
Chris@1403 1221 m_maxbin = m_minbin;
Chris@1403 1222 }
Chris@1403 1223
Chris@1238 1224 emit layerParametersChanged();
Chris@1238 1225 return true;
Chris@1238 1226 }
Chris@1238 1227
Chris@1238 1228 int
Chris@1238 1229 SliceLayer::getVerticalZoomSteps(int &defaultStep) const
Chris@1238 1230 {
Chris@1472 1231 auto sliceableModel =
Chris@1472 1232 ModelById::getAs<DenseThreeDimensionalModel>(m_sliceableModel);
Chris@1472 1233 if (!sliceableModel) return 0;
Chris@1238 1234
Chris@1238 1235 defaultStep = 0;
Chris@1472 1236 int h = sliceableModel->getHeight();
Chris@1238 1237 return h;
Chris@1238 1238 }
Chris@1238 1239
Chris@1238 1240 int
Chris@1238 1241 SliceLayer::getCurrentVerticalZoomStep() const
Chris@1238 1242 {
Chris@1472 1243 auto sliceableModel =
Chris@1472 1244 ModelById::getAs<DenseThreeDimensionalModel>(m_sliceableModel);
Chris@1472 1245 if (!sliceableModel) return 0;
Chris@1238 1246
Chris@1238 1247 double min, max;
Chris@1238 1248 getDisplayExtents(min, max);
Chris@1472 1249 return sliceableModel->getHeight() - int(lrint(max - min));
Chris@1238 1250 }
Chris@1238 1251
Chris@1238 1252 void
Chris@1238 1253 SliceLayer::setVerticalZoomStep(int step)
Chris@1238 1254 {
Chris@1472 1255 auto sliceableModel =
Chris@1472 1256 ModelById::getAs<DenseThreeDimensionalModel>(m_sliceableModel);
Chris@1472 1257 if (!sliceableModel) return;
Chris@1238 1258
Chris@1238 1259 // SVDEBUG << "SliceLayer::setVerticalZoomStep(" <<step <<"): before: minbin = " << m_minbin << ", maxbin = " << m_maxbin << endl;
Chris@1238 1260
Chris@1472 1261 int dist = sliceableModel->getHeight() - step;
Chris@1238 1262 if (dist < 1) dist = 1;
Chris@1238 1263 double centre = m_minbin + (m_maxbin - m_minbin) / 2.0;
Chris@1403 1264 int minbin = int(lrint(centre - dist/2.0));
Chris@1403 1265 int maxbin = minbin + dist;
Chris@1403 1266 setDisplayExtents(minbin, maxbin);
Chris@1238 1267 }
Chris@1238 1268
Chris@1238 1269 RangeMapper *
Chris@1238 1270 SliceLayer::getNewVerticalZoomRangeMapper() const
Chris@1238 1271 {
Chris@1472 1272 auto sliceableModel =
Chris@1472 1273 ModelById::getAs<DenseThreeDimensionalModel>(m_sliceableModel);
Chris@1472 1274 if (!sliceableModel) return nullptr;
Chris@1238 1275
Chris@1472 1276 return new LinearRangeMapper(0, sliceableModel->getHeight(),
Chris@1472 1277 0, sliceableModel->getHeight(), "");
Chris@1238 1278 }
Chris@1389 1279
Chris@1389 1280 void
Chris@1389 1281 SliceLayer::zoomToRegion(const LayerGeometryProvider *v, QRect rect)
Chris@1389 1282 {
Chris@1389 1283 double bin0 = getBinForX(v, rect.x());
Chris@1389 1284 double bin1 = getBinForX(v, rect.x() + rect.width());
Chris@1389 1285
Chris@1389 1286 // ignore y for now...
Chris@1389 1287
Chris@1389 1288 SVDEBUG << "SliceLayer::zoomToRegion: zooming to bin range "
Chris@1389 1289 << bin0 << " -> " << bin1 << endl;
Chris@1389 1290
Chris@1389 1291 setDisplayExtents(floor(bin0), ceil(bin1));
Chris@1389 1292 }
Chris@1389 1293