annotate layer/TimeRulerLayer.cpp @ 1386:fc3d89f88690 spectrogramparam

Use log-frequency rather than log-bin for calculating x coord in spectrum. This has the advantage that frequency positions don't move when we change the window size or oversampling ratio, but it does give us an unhelpfully large amount of space for very low frequencies - to be considered
author Chris Cannam
date Mon, 12 Nov 2018 11:34:34 +0000
parents dddfd28e4f02
children c39f2d439d59
rev   line source
Chris@58 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@0 2
Chris@0 3 /*
Chris@59 4 Sonic Visualiser
Chris@59 5 An audio file viewer and annotation editor.
Chris@59 6 Centre for Digital Music, Queen Mary, University of London.
Chris@59 7 This file copyright 2006 Chris Cannam.
Chris@0 8
Chris@59 9 This program is free software; you can redistribute it and/or
Chris@59 10 modify it under the terms of the GNU General Public License as
Chris@59 11 published by the Free Software Foundation; either version 2 of the
Chris@59 12 License, or (at your option) any later version. See the file
Chris@59 13 COPYING included with this distribution for more information.
Chris@0 14 */
Chris@0 15
Chris@0 16 #include "TimeRulerLayer.h"
Chris@0 17
Chris@335 18 #include "LayerFactory.h"
Chris@335 19
Chris@128 20 #include "data/model/Model.h"
Chris@0 21 #include "base/RealTime.h"
Chris@1345 22 #include "base/Preferences.h"
Chris@1078 23 #include "view/View.h"
Chris@1078 24
Chris@376 25 #include "ColourDatabase.h"
Chris@1078 26 #include "PaintAssistant.h"
Chris@0 27
Chris@0 28 #include <QPainter>
Chris@0 29
Chris@0 30 #include <iostream>
Chris@271 31 #include <cmath>
Chris@1021 32 #include <stdexcept>
Chris@0 33
Chris@1346 34 //#define DEBUG_TIME_RULER_LAYER 1
Chris@370 35
Chris@682 36
Chris@44 37 TimeRulerLayer::TimeRulerLayer() :
Chris@287 38 SingleColourLayer(),
Chris@0 39 m_model(0),
Chris@0 40 m_labelHeight(LabelTop)
Chris@0 41 {
Chris@44 42
Chris@0 43 }
Chris@0 44
Chris@0 45 void
Chris@0 46 TimeRulerLayer::setModel(Model *model)
Chris@0 47 {
Chris@0 48 if (m_model == model) return;
Chris@0 49 m_model = model;
Chris@0 50 emit modelReplaced();
Chris@0 51 }
Chris@0 52
Chris@271 53 bool
Chris@918 54 TimeRulerLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
Chris@805 55 int &resolution, SnapType snap) const
Chris@271 56 {
Chris@271 57 if (!m_model) {
Chris@271 58 resolution = 1;
Chris@271 59 return false;
Chris@271 60 }
Chris@271 61
Chris@271 62 bool q;
Chris@1346 63 int64_t tickUSec = getMajorTickUSec(v, q);
Chris@1346 64 RealTime rtick = RealTime::fromMicroseconds(tickUSec);
Chris@908 65 sv_samplerate_t rate = m_model->getSampleRate();
Chris@271 66
Chris@271 67 RealTime rt = RealTime::frame2RealTime(frame, rate);
Chris@271 68 double ratio = rt / rtick;
Chris@271 69
Chris@272 70 int rounded = int(ratio);
Chris@271 71 RealTime rdrt = rtick * rounded;
Chris@271 72
Chris@908 73 sv_frame_t left = RealTime::realTime2Frame(rdrt, rate);
Chris@908 74 resolution = int(RealTime::realTime2Frame(rtick, rate));
Chris@908 75 sv_frame_t right = left + resolution;
Chris@271 76
Chris@587 77 // SVDEBUG << "TimeRulerLayer::snapToFeatureFrame: type "
Chris@272 78 // << int(snap) << ", frame " << frame << " (time "
Chris@272 79 // << rt << ", tick " << rtick << ", rounded " << rdrt << ") ";
Chris@272 80
Chris@271 81 switch (snap) {
Chris@271 82
Chris@271 83 case SnapLeft:
Chris@271 84 frame = left;
Chris@271 85 break;
Chris@271 86
Chris@271 87 case SnapRight:
Chris@271 88 frame = right;
Chris@271 89 break;
Chris@271 90
Chris@271 91 case SnapNearest:
Chris@271 92 {
Chris@989 93 if (llabs(frame - left) > llabs(right - frame)) {
Chris@271 94 frame = right;
Chris@271 95 } else {
Chris@271 96 frame = left;
Chris@271 97 }
Chris@271 98 break;
Chris@271 99 }
Chris@271 100
Chris@271 101 case SnapNeighbouring:
Chris@271 102 {
Chris@271 103 int dl = -1, dr = -1;
Chris@271 104 int x = v->getXForFrame(frame);
Chris@271 105
Chris@271 106 if (left > v->getStartFrame() &&
Chris@271 107 left < v->getEndFrame()) {
Chris@271 108 dl = abs(v->getXForFrame(left) - x);
Chris@271 109 }
Chris@271 110
Chris@271 111 if (right > v->getStartFrame() &&
Chris@271 112 right < v->getEndFrame()) {
Chris@271 113 dr = abs(v->getXForFrame(right) - x);
Chris@271 114 }
Chris@271 115
Chris@271 116 int fuzz = 2;
Chris@271 117
Chris@271 118 if (dl >= 0 && dr >= 0) {
Chris@271 119 if (dl < dr) {
Chris@271 120 if (dl <= fuzz) {
Chris@271 121 frame = left;
Chris@271 122 }
Chris@271 123 } else {
Chris@271 124 if (dr < fuzz) {
Chris@271 125 frame = right;
Chris@271 126 }
Chris@271 127 }
Chris@271 128 } else if (dl >= 0) {
Chris@271 129 if (dl <= fuzz) {
Chris@271 130 frame = left;
Chris@271 131 }
Chris@271 132 } else if (dr >= 0) {
Chris@271 133 if (dr <= fuzz) {
Chris@271 134 frame = right;
Chris@271 135 }
Chris@271 136 }
Chris@271 137 }
Chris@271 138 }
Chris@271 139
Chris@587 140 // SVDEBUG << " -> " << frame << " (resolution = " << resolution << ")" << endl;
Chris@272 141
Chris@271 142 return true;
Chris@271 143 }
Chris@271 144
Chris@1346 145 int64_t
Chris@1341 146 TimeRulerLayer::getMajorTickUSec(LayerGeometryProvider *v,
Chris@1341 147 bool &quarterTicks) const
Chris@271 148 {
Chris@1341 149 // return value is in microseconds
Chris@1341 150 if (!m_model || !v) return 1000 * 1000;
Chris@271 151
Chris@908 152 sv_samplerate_t sampleRate = m_model->getSampleRate();
Chris@1341 153 if (!sampleRate) return 1000 * 1000;
Chris@271 154
Chris@908 155 sv_frame_t startFrame = v->getStartFrame();
Chris@908 156 sv_frame_t endFrame = v->getEndFrame();
Chris@1356 157 if (endFrame == startFrame) {
Chris@1356 158 endFrame = startFrame + 1;
Chris@1356 159 }
Chris@271 160
Chris@1356 161 int exampleWidth = QFontMetrics(QFont()).width("10:42.987654");
Chris@1356 162 int minPixelSpacing = v->getXForViewX(exampleWidth);
Chris@271 163
Chris@271 164 RealTime rtStart = RealTime::frame2RealTime(startFrame, sampleRate);
Chris@271 165 RealTime rtEnd = RealTime::frame2RealTime(endFrame, sampleRate);
Chris@271 166
Chris@918 167 int count = v->getPaintWidth() / minPixelSpacing;
Chris@271 168 if (count < 1) count = 1;
Chris@271 169 RealTime rtGap = (rtEnd - rtStart) / count;
Chris@271 170
Chris@1356 171 #ifdef DEBUG_TIME_RULER_LAYER
Chris@1356 172 SVCERR << "zoomLevel = " << v->getZoomLevel()
Chris@1356 173 << ", startFrame = " << startFrame << ", endFrame = " << endFrame
Chris@1356 174 << ", rtStart = " << rtStart << ", rtEnd = " << rtEnd
Chris@1356 175 << ", paint width = " << v->getPaintWidth()
Chris@1356 176 << ", minPixelSpacing = " << minPixelSpacing
Chris@1356 177 << ", count = " << count << ", rtGap = " << rtGap << endl;
Chris@1356 178 #endif
Chris@1356 179
Chris@1346 180 int64_t incus;
Chris@271 181 quarterTicks = false;
Chris@271 182
Chris@271 183 if (rtGap.sec > 0) {
Chris@1341 184 incus = 1000 * 1000;
Chris@1266 185 int s = rtGap.sec;
Chris@1341 186 if (s > 0) { incus *= 5; s /= 5; }
Chris@1341 187 if (s > 0) { incus *= 2; s /= 2; }
Chris@1341 188 if (s > 0) { incus *= 6; s /= 6; quarterTicks = true; }
Chris@1341 189 if (s > 0) { incus *= 5; s /= 5; quarterTicks = false; }
Chris@1341 190 if (s > 0) { incus *= 2; s /= 2; }
Chris@1341 191 if (s > 0) { incus *= 6; s /= 6; quarterTicks = true; }
Chris@1266 192 while (s > 0) {
Chris@1341 193 incus *= 10;
Chris@1266 194 s /= 10;
Chris@1266 195 quarterTicks = false;
Chris@1266 196 }
Chris@1341 197 } else if (rtGap.msec() > 0) {
Chris@1341 198 incus = 1000;
Chris@1341 199 int ms = rtGap.msec();
Chris@1341 200 if (ms > 0) { incus *= 10; ms /= 10; }
Chris@1341 201 if (ms > 0) { incus *= 10; ms /= 10; }
Chris@1341 202 if (ms > 0) { incus *= 5; ms /= 5; }
Chris@1341 203 if (ms > 0) { incus *= 2; ms /= 2; }
Chris@271 204 } else {
Chris@1341 205 incus = 1;
Chris@1341 206 int us = rtGap.usec();
Chris@1341 207 if (us > 0) { incus *= 10; us /= 10; }
Chris@1341 208 if (us > 0) { incus *= 10; us /= 10; }
Chris@1341 209 if (us > 0) { incus *= 5; us /= 5; }
Chris@1341 210 if (us > 0) { incus *= 2; us /= 2; }
Chris@271 211 }
Chris@271 212
Chris@1356 213 #ifdef DEBUG_TIME_RULER_LAYER
Chris@1356 214 SVCERR << "getMajorTickUSec: returning incus = " << incus << endl;
Chris@1356 215 #endif
Chris@1356 216
Chris@1341 217 return incus;
Chris@1341 218 }
Chris@1341 219
Chris@1341 220 int
Chris@1341 221 TimeRulerLayer::getXForUSec(LayerGeometryProvider *v, double us) const
Chris@1341 222 {
Chris@1341 223 sv_samplerate_t sampleRate = m_model->getSampleRate();
Chris@1341 224 double dframe = (us * sampleRate) / 1000000.0;
Chris@1341 225 double eps = 1e-7;
Chris@1341 226 sv_frame_t frame = sv_frame_t(floor(dframe + eps));
Chris@1341 227 int x;
Chris@1341 228
Chris@1341 229 ZoomLevel zoom = v->getZoomLevel();
Chris@1341 230
Chris@1341 231 if (zoom.zone == ZoomLevel::FramesPerPixel) {
Chris@1341 232
Chris@1341 233 frame /= zoom.level;
Chris@1341 234 frame *= zoom.level; // so frame corresponds to an exact pixel
Chris@1341 235
Chris@1341 236 x = v->getXForFrame(frame);
Chris@1341 237
Chris@1341 238 } else {
Chris@1341 239
Chris@1341 240 double off = dframe - double(frame);
Chris@1341 241 int x0 = v->getXForFrame(frame);
Chris@1341 242 int x1 = v->getXForFrame(frame + 1);
Chris@1341 243
Chris@1341 244 x = int(x0 + off * (x1 - x0));
Chris@1341 245 }
Chris@1341 246
Chris@1341 247 #ifdef DEBUG_TIME_RULER_LAYER
Chris@1341 248 cerr << "Considering frame = " << frame << ", x = " << x << endl;
Chris@1341 249 #endif
Chris@1341 250
Chris@1341 251 return x;
Chris@271 252 }
Chris@271 253
Chris@0 254 void
Chris@916 255 TimeRulerLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
Chris@0 256 {
Chris@370 257 #ifdef DEBUG_TIME_RULER_LAYER
Chris@1346 258 SVCERR << "TimeRulerLayer::paint (" << rect.x() << "," << rect.y()
Chris@1346 259 << ") [" << rect.width() << "x" << rect.height() << "]" << endl;
Chris@370 260 #endif
Chris@0 261
Chris@0 262 if (!m_model || !m_model->isOK()) return;
Chris@0 263
Chris@908 264 sv_samplerate_t sampleRate = m_model->getSampleRate();
Chris@0 265 if (!sampleRate) return;
Chris@0 266
Chris@908 267 sv_frame_t startFrame = v->getFrameForX(rect.x() - 50);
Chris@0 268
Chris@370 269 #ifdef DEBUG_TIME_RULER_LAYER
Chris@1346 270 SVCERR << "start frame = " << startFrame << endl;
Chris@370 271 #endif
Chris@0 272
Chris@0 273 bool quarter = false;
Chris@1346 274 int64_t incus = getMajorTickUSec(v, quarter);
Chris@1346 275 int64_t us = int64_t(floor(1000.0 * 1000.0 * (double(startFrame) /
Chris@1346 276 double(sampleRate))));
Chris@1341 277 us = (us / incus) * incus - incus;
Chris@0 278
Chris@370 279 #ifdef DEBUG_TIME_RULER_LAYER
Chris@1346 280 SVCERR << "start us = " << us << " at step " << incus << endl;
Chris@370 281 #endif
Chris@370 282
Chris@1345 283 Preferences *prefs = Preferences::getInstance();
Chris@1345 284 auto origTimeTextMode = prefs->getTimeToTextMode();
Chris@1345 285 if (incus < 1000) {
Chris@1345 286 // Temporarily switch to usec display mode (if we aren't using
Chris@1345 287 // it already)
Chris@1345 288 prefs->blockSignals(true);
Chris@1345 289 prefs->setTimeToTextMode(Preferences::TimeToTextUs);
Chris@1345 290 }
Chris@1345 291
Chris@370 292 // Calculate the number of ticks per increment -- approximate
Chris@370 293 // values for x and frame counts here will do, no rounding issue.
Chris@1341 294 // We always use the exact incus in our calculations for where to
Chris@370 295 // draw the actual ticks or lines.
Chris@370 296
Chris@1356 297 int minPixelSpacing = v->getXForViewX(50);
Chris@1346 298 sv_frame_t incFrame = lrint((double(incus) * sampleRate) / 1000000);
Chris@1326 299 int incX = int(round(v->getZoomLevel().framesToPixels(double(incFrame))));
Chris@0 300 int ticks = 10;
Chris@0 301 if (incX < minPixelSpacing * 2) {
Chris@1266 302 ticks = quarter ? 4 : 5;
Chris@0 303 }
Chris@0 304
Chris@370 305 QColor greyColour = getPartialShades(v)[1];
Chris@0 306
Chris@370 307 paint.save();
Chris@0 308
Chris@832 309 // Do not label time zero - we now overlay an opaque area over
Chris@832 310 // time < 0 which would cut it in half
Chris@1341 311 int minlabel = 1; // us
Chris@1021 312
Chris@0 313 while (1) {
Chris@0 314
Chris@370 315 // frame is used to determine where to draw the lines, so it
Chris@370 316 // needs to correspond to an exact pixel (so that we don't get
Chris@370 317 // a different pixel when scrolling a small amount and
Chris@370 318 // re-drawing with a different start frame).
Chris@370 319
Chris@1346 320 double dus = double(us);
Chris@370 321
Chris@1341 322 int x = getXForUSec(v, dus);
Chris@0 323
Chris@370 324 if (x >= rect.x() + rect.width() + 50) {
Chris@370 325 #ifdef DEBUG_TIME_RULER_LAYER
Chris@1346 326 SVCERR << "X well out of range, ending here" << endl;
Chris@370 327 #endif
Chris@370 328 break;
Chris@370 329 }
Chris@0 330
Chris@1341 331 if (x >= rect.x() - 50 && us >= minlabel) {
Chris@0 332
Chris@1342 333 RealTime rt = RealTime::fromMicroseconds(us);
Chris@375 334
Chris@370 335 #ifdef DEBUG_TIME_RULER_LAYER
Chris@1346 336 SVCERR << "X in range, drawing line here for time " << rt.toText() << " (usec = " << us << ")" << endl;
Chris@370 337 #endif
Chris@0 338
Chris@370 339 QString text(QString::fromStdString(rt.toText()));
Chris@1345 340
Chris@370 341 QFontMetrics metrics = paint.fontMetrics();
Chris@370 342 int tw = metrics.width(text);
Chris@0 343
Chris@370 344 if (tw < 50 &&
Chris@370 345 (x < rect.x() - tw/2 ||
Chris@370 346 x >= rect.x() + rect.width() + tw/2)) {
Chris@370 347 #ifdef DEBUG_TIME_RULER_LAYER
Chris@1346 348 SVCERR << "hm, maybe X isn't in range after all (x = " << x << ", tw = " << tw << ", rect.x() = " << rect.x() << ", rect.width() = " << rect.width() << ")" << endl;
Chris@370 349 #endif
Chris@370 350 }
Chris@70 351
Chris@370 352 paint.setPen(greyColour);
Chris@918 353 paint.drawLine(x, 0, x, v->getPaintHeight());
Chris@370 354
Chris@370 355 paint.setPen(getBaseQColor());
Chris@370 356 paint.drawLine(x, 0, x, 5);
Chris@918 357 paint.drawLine(x, v->getPaintHeight() - 6, x, v->getPaintHeight() - 1);
Chris@370 358
Chris@370 359 int y;
Chris@370 360 switch (m_labelHeight) {
Chris@370 361 default:
Chris@370 362 case LabelTop:
Chris@370 363 y = 6 + metrics.ascent();
Chris@370 364 break;
Chris@370 365 case LabelMiddle:
Chris@918 366 y = v->getPaintHeight() / 2 - metrics.height() / 2 + metrics.ascent();
Chris@370 367 break;
Chris@370 368 case LabelBottom:
Chris@918 369 y = v->getPaintHeight() - metrics.height() + metrics.ascent() - 6;
Chris@370 370 }
Chris@370 371
Chris@370 372 if (v->getViewManager() && v->getViewManager()->getOverlayMode() !=
Chris@370 373 ViewManager::NoOverlays) {
Chris@370 374
Chris@919 375 if (v->getView()->getLayer(0) == this) {
Chris@370 376 // backmost layer, don't worry about outlining the text
Chris@370 377 paint.drawText(x+2 - tw/2, y, text);
Chris@370 378 } else {
Chris@1078 379 PaintAssistant::drawVisibleText(v, paint, x+2 - tw/2, y, text, PaintAssistant::OutlinedText);
Chris@370 380 }
Chris@70 381 }
Chris@70 382 }
Chris@0 383
Chris@1266 384 paint.setPen(greyColour);
Chris@0 385
Chris@1266 386 for (int i = 1; i < ticks; ++i) {
Chris@370 387
Chris@1346 388 dus = double(us) + (i * double(incus)) / ticks;
Chris@370 389
Chris@1341 390 x = getXForUSec(v, dus);
Chris@370 391
Chris@370 392 if (x < rect.x() || x >= rect.x() + rect.width()) {
Chris@370 393 #ifdef DEBUG_TIME_RULER_LAYER
Chris@1346 394 // SVCERR << "tick " << i << ": X out of range, going on to next tick" << endl;
Chris@370 395 #endif
Chris@370 396 continue;
Chris@370 397 }
Chris@370 398
Chris@370 399 #ifdef DEBUG_TIME_RULER_LAYER
Chris@1346 400 SVCERR << "tick " << i << " in range, drawing at " << x << endl;
Chris@370 401 #endif
Chris@370 402
Chris@1266 403 int sz = 5;
Chris@1266 404 if (ticks == 10) {
Chris@1266 405 if ((i % 2) == 1) {
Chris@1266 406 if (i == 5) {
Chris@1266 407 paint.drawLine(x, 0, x, v->getPaintHeight());
Chris@1266 408 } else sz = 3;
Chris@1266 409 } else {
Chris@1266 410 sz = 7;
Chris@1266 411 }
Chris@1266 412 }
Chris@1266 413 paint.drawLine(x, 0, x, sz);
Chris@1266 414 paint.drawLine(x, v->getPaintHeight() - sz - 1, x, v->getPaintHeight() - 1);
Chris@1266 415 }
Chris@375 416
Chris@1341 417 us += incus;
Chris@0 418 }
Chris@1345 419
Chris@1345 420 prefs->setTimeToTextMode(origTimeTextMode);
Chris@1345 421 prefs->blockSignals(false);
Chris@0 422
Chris@0 423 paint.restore();
Chris@0 424 }
Chris@287 425
Chris@287 426 int
Chris@287 427 TimeRulerLayer::getDefaultColourHint(bool darkbg, bool &impose)
Chris@287 428 {
Chris@287 429 impose = true;
Chris@287 430 return ColourDatabase::getInstance()->getColourIndex
Chris@287 431 (QString(darkbg ? "White" : "Black"));
Chris@287 432 }
Chris@287 433
Chris@335 434 QString TimeRulerLayer::getLayerPresentationName() const
Chris@335 435 {
Chris@335 436 LayerFactory *factory = LayerFactory::getInstance();
Chris@335 437 QString layerName = factory->getLayerPresentationName
Chris@335 438 (factory->getLayerType(this));
Chris@335 439 return layerName;
Chris@335 440 }
Chris@335 441
Chris@316 442 void
Chris@316 443 TimeRulerLayer::toXml(QTextStream &stream,
Chris@316 444 QString indent, QString extraAttributes) const
Chris@6 445 {
Chris@316 446 SingleColourLayer::toXml(stream, indent, extraAttributes);
Chris@6 447 }
Chris@6 448
Chris@11 449 void
Chris@11 450 TimeRulerLayer::setProperties(const QXmlAttributes &attributes)
Chris@11 451 {
Chris@287 452 SingleColourLayer::setProperties(attributes);
Chris@11 453 }
Chris@11 454