annotate layer/NoteLayer.cpp @ 162:f32212631b9c

* Handle generator transforms (plugins whose channel count isn't dependent on number of audio inputs, as they have none) * Be less keen to suspend writing FFT data in spectrogram repaint -- only do it if we find we actually need to query the FFT data (i.e. we aren't repainting an area that hasn't been generated at all yet)
author Chris Cannam
date Tue, 10 Oct 2006 19:04:57 +0000
parents 33929e0c3c6b
children 6b023411087b
rev   line source
Chris@58 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@30 2
Chris@30 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@30 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@30 14 */
Chris@30 15
Chris@30 16 #include "NoteLayer.h"
Chris@30 17
Chris@128 18 #include "data/model/Model.h"
Chris@30 19 #include "base/RealTime.h"
Chris@30 20 #include "base/Profiler.h"
Chris@30 21 #include "base/Pitch.h"
Chris@128 22 #include "view/View.h"
Chris@30 23
Chris@128 24 #include "data/model/NoteModel.h"
Chris@30 25
Chris@70 26 #include "widgets/ItemEditDialog.h"
Chris@70 27
Chris@42 28 #include "SpectrogramLayer.h" // for optional frequency alignment
Chris@42 29
Chris@30 30 #include <QPainter>
Chris@30 31 #include <QPainterPath>
Chris@30 32 #include <QMouseEvent>
Chris@30 33
Chris@30 34 #include <iostream>
Chris@30 35 #include <cmath>
Chris@30 36
Chris@44 37 NoteLayer::NoteLayer() :
Chris@44 38 Layer(),
Chris@30 39 m_model(0),
Chris@30 40 m_editing(false),
Chris@30 41 m_originalPoint(0, 0.0, 0, tr("New Point")),
Chris@30 42 m_editingPoint(0, 0.0, 0, tr("New Point")),
Chris@30 43 m_editingCommand(0),
Chris@30 44 m_colour(Qt::black),
Chris@101 45 m_verticalScale(AutoAlignScale)
Chris@30 46 {
Chris@44 47
Chris@30 48 }
Chris@30 49
Chris@30 50 void
Chris@30 51 NoteLayer::setModel(NoteModel *model)
Chris@30 52 {
Chris@30 53 if (m_model == model) return;
Chris@30 54 m_model = model;
Chris@30 55
Chris@30 56 connect(m_model, SIGNAL(modelChanged()), this, SIGNAL(modelChanged()));
Chris@30 57 connect(m_model, SIGNAL(modelChanged(size_t, size_t)),
Chris@30 58 this, SIGNAL(modelChanged(size_t, size_t)));
Chris@30 59
Chris@30 60 connect(m_model, SIGNAL(completionChanged()),
Chris@30 61 this, SIGNAL(modelCompletionChanged()));
Chris@30 62
Chris@101 63 // std::cerr << "NoteLayer::setModel(" << model << ")" << std::endl;
Chris@30 64
Chris@30 65 emit modelReplaced();
Chris@30 66 }
Chris@30 67
Chris@30 68 Layer::PropertyList
Chris@30 69 NoteLayer::getProperties() const
Chris@30 70 {
Chris@30 71 PropertyList list;
Chris@87 72 list.push_back("Colour");
Chris@87 73 list.push_back("Vertical Scale");
Chris@100 74 list.push_back("Scale Units");
Chris@30 75 return list;
Chris@30 76 }
Chris@30 77
Chris@87 78 QString
Chris@87 79 NoteLayer::getPropertyLabel(const PropertyName &name) const
Chris@87 80 {
Chris@87 81 if (name == "Colour") return tr("Colour");
Chris@87 82 if (name == "Vertical Scale") return tr("Vertical Scale");
Chris@116 83 if (name == "Scale Units") return tr("Scale Units");
Chris@87 84 return "";
Chris@87 85 }
Chris@87 86
Chris@30 87 Layer::PropertyType
Chris@100 88 NoteLayer::getPropertyType(const PropertyName &name) const
Chris@30 89 {
Chris@100 90 if (name == "Scale Units") return UnitsProperty;
Chris@30 91 return ValueProperty;
Chris@30 92 }
Chris@30 93
Chris@30 94 int
Chris@30 95 NoteLayer::getPropertyRangeAndValue(const PropertyName &name,
Chris@30 96 int *min, int *max) const
Chris@30 97 {
Chris@30 98 //!!! factor this colour handling stuff out into a colour manager class
Chris@30 99
Chris@30 100 int deft = 0;
Chris@30 101
Chris@87 102 if (name == "Colour") {
Chris@30 103
Chris@30 104 if (min) *min = 0;
Chris@30 105 if (max) *max = 5;
Chris@30 106
Chris@30 107 if (m_colour == Qt::black) deft = 0;
Chris@30 108 else if (m_colour == Qt::darkRed) deft = 1;
Chris@30 109 else if (m_colour == Qt::darkBlue) deft = 2;
Chris@30 110 else if (m_colour == Qt::darkGreen) deft = 3;
Chris@30 111 else if (m_colour == QColor(200, 50, 255)) deft = 4;
Chris@30 112 else if (m_colour == QColor(255, 150, 50)) deft = 5;
Chris@30 113
Chris@87 114 } else if (name == "Vertical Scale") {
Chris@30 115
Chris@30 116 if (min) *min = 0;
Chris@101 117 if (max) *max = 3;
Chris@30 118
Chris@30 119 deft = int(m_verticalScale);
Chris@30 120
Chris@100 121 } else if (name == "Scale Units") {
Chris@100 122
Chris@100 123 if (m_model) {
Chris@100 124 deft = UnitDatabase::getInstance()->getUnitId
Chris@100 125 (m_model->getScaleUnits());
Chris@100 126 }
Chris@100 127
Chris@30 128 } else {
Chris@30 129
Chris@30 130 deft = Layer::getPropertyRangeAndValue(name, min, max);
Chris@30 131 }
Chris@30 132
Chris@30 133 return deft;
Chris@30 134 }
Chris@30 135
Chris@30 136 QString
Chris@30 137 NoteLayer::getPropertyValueLabel(const PropertyName &name,
Chris@30 138 int value) const
Chris@30 139 {
Chris@87 140 if (name == "Colour") {
Chris@30 141 switch (value) {
Chris@30 142 default:
Chris@30 143 case 0: return tr("Black");
Chris@30 144 case 1: return tr("Red");
Chris@30 145 case 2: return tr("Blue");
Chris@30 146 case 3: return tr("Green");
Chris@30 147 case 4: return tr("Purple");
Chris@30 148 case 5: return tr("Orange");
Chris@30 149 }
Chris@87 150 } else if (name == "Vertical Scale") {
Chris@30 151 switch (value) {
Chris@30 152 default:
Chris@101 153 case 0: return tr("Auto-Align");
Chris@101 154 case 1: return tr("Linear Scale");
Chris@101 155 case 2: return tr("Log Scale");
Chris@101 156 case 3: return tr("MIDI Note Range");
Chris@30 157 }
Chris@30 158 }
Chris@30 159 return tr("<unknown>");
Chris@30 160 }
Chris@30 161
Chris@30 162 void
Chris@30 163 NoteLayer::setProperty(const PropertyName &name, int value)
Chris@30 164 {
Chris@87 165 if (name == "Colour") {
Chris@30 166 switch (value) {
Chris@30 167 default:
Chris@30 168 case 0: setBaseColour(Qt::black); break;
Chris@30 169 case 1: setBaseColour(Qt::darkRed); break;
Chris@30 170 case 2: setBaseColour(Qt::darkBlue); break;
Chris@30 171 case 3: setBaseColour(Qt::darkGreen); break;
Chris@30 172 case 4: setBaseColour(QColor(200, 50, 255)); break;
Chris@30 173 case 5: setBaseColour(QColor(255, 150, 50)); break;
Chris@30 174 }
Chris@87 175 } else if (name == "Vertical Scale") {
Chris@30 176 setVerticalScale(VerticalScale(value));
Chris@100 177 } else if (name == "Scale Units") {
Chris@100 178 if (m_model) {
Chris@100 179 m_model->setScaleUnits
Chris@100 180 (UnitDatabase::getInstance()->getUnitById(value));
Chris@100 181 emit modelChanged();
Chris@100 182 }
Chris@30 183 }
Chris@30 184 }
Chris@30 185
Chris@30 186 void
Chris@30 187 NoteLayer::setBaseColour(QColor colour)
Chris@30 188 {
Chris@30 189 if (m_colour == colour) return;
Chris@30 190 m_colour = colour;
Chris@30 191 emit layerParametersChanged();
Chris@30 192 }
Chris@30 193
Chris@30 194 void
Chris@30 195 NoteLayer::setVerticalScale(VerticalScale scale)
Chris@30 196 {
Chris@30 197 if (m_verticalScale == scale) return;
Chris@30 198 m_verticalScale = scale;
Chris@30 199 emit layerParametersChanged();
Chris@30 200 }
Chris@30 201
Chris@30 202 bool
Chris@44 203 NoteLayer::isLayerScrollable(const View *v) const
Chris@30 204 {
Chris@30 205 QPoint discard;
Chris@44 206 return !v->shouldIlluminateLocalFeatures(this, discard);
Chris@30 207 }
Chris@30 208
Chris@79 209 bool
Chris@101 210 NoteLayer::shouldConvertMIDIToHz() const
Chris@101 211 {
Chris@101 212 QString unit = m_model->getScaleUnits();
Chris@101 213 return (unit != "Hz");
Chris@101 214 // if (unit == "" ||
Chris@101 215 // unit.startsWith("MIDI") ||
Chris@101 216 // unit.startsWith("midi")) return true;
Chris@101 217 // return false;
Chris@101 218 }
Chris@101 219
Chris@101 220 bool
Chris@101 221 NoteLayer::getValueExtents(float &min, float &max,
Chris@101 222 bool &logarithmic, QString &unit) const
Chris@79 223 {
Chris@79 224 if (!m_model) return false;
Chris@79 225 min = m_model->getValueMinimum();
Chris@79 226 max = m_model->getValueMaximum();
Chris@101 227
Chris@105 228 if (shouldConvertMIDIToHz()) {
Chris@105 229 unit = "Hz";
Chris@105 230 min = Pitch::getFrequencyForPitch(lrintf(min));
Chris@105 231 max = Pitch::getFrequencyForPitch(lrintf(max + 1));
Chris@105 232 } else unit = m_model->getScaleUnits();
Chris@101 233
Chris@101 234 if (m_verticalScale == MIDIRangeScale ||
Chris@101 235 m_verticalScale == LogScale) logarithmic = true;
Chris@101 236
Chris@101 237 return true;
Chris@101 238 }
Chris@101 239
Chris@101 240 bool
Chris@101 241 NoteLayer::getDisplayExtents(float &min, float &max) const
Chris@101 242 {
Chris@101 243 if (!m_model || m_verticalScale == AutoAlignScale) return false;
Chris@101 244
Chris@101 245 if (m_verticalScale == MIDIRangeScale) {
Chris@101 246 min = Pitch::getFrequencyForPitch(0);
Chris@101 247 max = Pitch::getFrequencyForPitch(127);
Chris@101 248 return true;
Chris@101 249 }
Chris@101 250
Chris@101 251 min = m_model->getValueMinimum();
Chris@101 252 max = m_model->getValueMaximum();
Chris@101 253
Chris@101 254 if (shouldConvertMIDIToHz()) {
Chris@101 255 min = Pitch::getFrequencyForPitch(lrintf(min));
Chris@101 256 max = Pitch::getFrequencyForPitch(lrintf(max + 1));
Chris@101 257 }
Chris@101 258
Chris@79 259 return true;
Chris@79 260 }
Chris@79 261
Chris@30 262 NoteModel::PointList
Chris@44 263 NoteLayer::getLocalPoints(View *v, int x) const
Chris@30 264 {
Chris@30 265 if (!m_model) return NoteModel::PointList();
Chris@30 266
Chris@44 267 long frame = v->getFrameForX(x);
Chris@30 268
Chris@30 269 NoteModel::PointList onPoints =
Chris@30 270 m_model->getPoints(frame);
Chris@30 271
Chris@30 272 if (!onPoints.empty()) {
Chris@30 273 return onPoints;
Chris@30 274 }
Chris@30 275
Chris@30 276 NoteModel::PointList prevPoints =
Chris@30 277 m_model->getPreviousPoints(frame);
Chris@30 278 NoteModel::PointList nextPoints =
Chris@30 279 m_model->getNextPoints(frame);
Chris@30 280
Chris@30 281 NoteModel::PointList usePoints = prevPoints;
Chris@30 282
Chris@30 283 if (prevPoints.empty()) {
Chris@30 284 usePoints = nextPoints;
Chris@44 285 } else if (prevPoints.begin()->frame < v->getStartFrame() &&
Chris@44 286 !(nextPoints.begin()->frame > v->getEndFrame())) {
Chris@30 287 usePoints = nextPoints;
Chris@30 288 } else if (nextPoints.begin()->frame - frame <
Chris@30 289 frame - prevPoints.begin()->frame) {
Chris@30 290 usePoints = nextPoints;
Chris@30 291 }
Chris@30 292
Chris@30 293 if (!usePoints.empty()) {
Chris@30 294 int fuzz = 2;
Chris@44 295 int px = v->getXForFrame(usePoints.begin()->frame);
Chris@30 296 if ((px > x && px - x > fuzz) ||
Chris@30 297 (px < x && x - px > fuzz + 1)) {
Chris@30 298 usePoints.clear();
Chris@30 299 }
Chris@30 300 }
Chris@30 301
Chris@30 302 return usePoints;
Chris@30 303 }
Chris@30 304
Chris@30 305 QString
Chris@44 306 NoteLayer::getFeatureDescription(View *v, QPoint &pos) const
Chris@30 307 {
Chris@30 308 int x = pos.x();
Chris@30 309
Chris@30 310 if (!m_model || !m_model->getSampleRate()) return "";
Chris@30 311
Chris@44 312 NoteModel::PointList points = getLocalPoints(v, x);
Chris@30 313
Chris@30 314 if (points.empty()) {
Chris@30 315 if (!m_model->isReady()) {
Chris@30 316 return tr("In progress");
Chris@30 317 } else {
Chris@30 318 return tr("No local points");
Chris@30 319 }
Chris@30 320 }
Chris@30 321
Chris@30 322 Note note(0);
Chris@30 323 NoteModel::PointList::iterator i;
Chris@30 324
Chris@30 325 for (i = points.begin(); i != points.end(); ++i) {
Chris@30 326
Chris@44 327 int y = getYForValue(v, i->value);
Chris@30 328 int h = 3;
Chris@30 329
Chris@30 330 if (m_model->getValueQuantization() != 0.0) {
Chris@44 331 h = y - getYForValue(v, i->value + m_model->getValueQuantization());
Chris@30 332 if (h < 3) h = 3;
Chris@30 333 }
Chris@30 334
Chris@30 335 if (pos.y() >= y - h && pos.y() <= y) {
Chris@30 336 note = *i;
Chris@30 337 break;
Chris@30 338 }
Chris@30 339 }
Chris@30 340
Chris@30 341 if (i == points.end()) return tr("No local points");
Chris@30 342
Chris@30 343 RealTime rt = RealTime::frame2RealTime(note.frame,
Chris@30 344 m_model->getSampleRate());
Chris@30 345 RealTime rd = RealTime::frame2RealTime(note.duration,
Chris@30 346 m_model->getSampleRate());
Chris@30 347
Chris@101 348 QString pitchText;
Chris@101 349
Chris@101 350 if (shouldConvertMIDIToHz()) {
Chris@101 351
Chris@101 352 int mnote = lrintf(note.value);
Chris@101 353 int cents = lrintf((note.value - mnote) * 100);
Chris@101 354 float freq = Pitch::getFrequencyForPitch(mnote, cents);
Chris@101 355 pitchText = QString("%1 (%2 Hz)")
Chris@101 356 .arg(Pitch::getPitchLabel(mnote, cents)).arg(freq);
Chris@101 357
Chris@101 358 } else if (m_model->getScaleUnits() == "Hz") {
Chris@101 359
Chris@101 360 pitchText = QString("%1 Hz (%2)")
Chris@101 361 .arg(note.value)
Chris@101 362 .arg(Pitch::getPitchLabelForFrequency(note.value));
Chris@101 363
Chris@101 364 } else {
Chris@101 365 pitchText = QString("%1 %2")
Chris@101 366 .arg(note.value).arg(m_model->getScaleUnits());
Chris@101 367 }
Chris@101 368
Chris@30 369 QString text;
Chris@30 370
Chris@30 371 if (note.label == "") {
Chris@30 372 text = QString(tr("Time:\t%1\nPitch:\t%2\nDuration:\t%3\nNo label"))
Chris@30 373 .arg(rt.toText(true).c_str())
Chris@101 374 .arg(pitchText)
Chris@30 375 .arg(rd.toText(true).c_str());
Chris@30 376 } else {
Chris@30 377 text = QString(tr("Time:\t%1\nPitch:\t%2\nDuration:\t%3\nLabel:\t%4"))
Chris@30 378 .arg(rt.toText(true).c_str())
Chris@101 379 .arg(pitchText)
Chris@30 380 .arg(rd.toText(true).c_str())
Chris@30 381 .arg(note.label);
Chris@30 382 }
Chris@30 383
Chris@44 384 pos = QPoint(v->getXForFrame(note.frame),
Chris@44 385 getYForValue(v, note.value));
Chris@30 386 return text;
Chris@30 387 }
Chris@30 388
Chris@30 389 bool
Chris@44 390 NoteLayer::snapToFeatureFrame(View *v, int &frame,
Chris@44 391 size_t &resolution,
Chris@44 392 SnapType snap) const
Chris@30 393 {
Chris@30 394 if (!m_model) {
Chris@44 395 return Layer::snapToFeatureFrame(v, frame, resolution, snap);
Chris@30 396 }
Chris@30 397
Chris@30 398 resolution = m_model->getResolution();
Chris@30 399 NoteModel::PointList points;
Chris@30 400
Chris@30 401 if (snap == SnapNeighbouring) {
Chris@30 402
Chris@44 403 points = getLocalPoints(v, v->getXForFrame(frame));
Chris@30 404 if (points.empty()) return false;
Chris@30 405 frame = points.begin()->frame;
Chris@30 406 return true;
Chris@30 407 }
Chris@30 408
Chris@30 409 points = m_model->getPoints(frame, frame);
Chris@30 410 int snapped = frame;
Chris@30 411 bool found = false;
Chris@30 412
Chris@30 413 for (NoteModel::PointList::const_iterator i = points.begin();
Chris@30 414 i != points.end(); ++i) {
Chris@30 415
Chris@30 416 if (snap == SnapRight) {
Chris@30 417
Chris@30 418 if (i->frame > frame) {
Chris@30 419 snapped = i->frame;
Chris@30 420 found = true;
Chris@30 421 break;
Chris@30 422 }
Chris@30 423
Chris@30 424 } else if (snap == SnapLeft) {
Chris@30 425
Chris@30 426 if (i->frame <= frame) {
Chris@30 427 snapped = i->frame;
Chris@30 428 found = true; // don't break, as the next may be better
Chris@30 429 } else {
Chris@30 430 break;
Chris@30 431 }
Chris@30 432
Chris@30 433 } else { // nearest
Chris@30 434
Chris@30 435 NoteModel::PointList::const_iterator j = i;
Chris@30 436 ++j;
Chris@30 437
Chris@30 438 if (j == points.end()) {
Chris@30 439
Chris@30 440 snapped = i->frame;
Chris@30 441 found = true;
Chris@30 442 break;
Chris@30 443
Chris@30 444 } else if (j->frame >= frame) {
Chris@30 445
Chris@30 446 if (j->frame - frame < frame - i->frame) {
Chris@30 447 snapped = j->frame;
Chris@30 448 } else {
Chris@30 449 snapped = i->frame;
Chris@30 450 }
Chris@30 451 found = true;
Chris@30 452 break;
Chris@30 453 }
Chris@30 454 }
Chris@30 455 }
Chris@30 456
Chris@30 457 frame = snapped;
Chris@30 458 return found;
Chris@30 459 }
Chris@30 460
Chris@101 461 void
Chris@101 462 NoteLayer::getScaleExtents(View *v, float &min, float &max, bool &log) const
Chris@30 463 {
Chris@101 464 min = 0.0;
Chris@101 465 max = 0.0;
Chris@101 466 log = false;
Chris@42 467
Chris@101 468 QString queryUnits;
Chris@101 469 if (shouldConvertMIDIToHz()) queryUnits = "Hz";
Chris@101 470 else queryUnits = m_model->getScaleUnits();
Chris@30 471
Chris@101 472 if (m_verticalScale == AutoAlignScale) {
Chris@30 473
Chris@101 474 if (!v->getValueExtents(queryUnits, min, max, log)) {
Chris@30 475
Chris@101 476 min = m_model->getValueMinimum();
Chris@101 477 max = m_model->getValueMaximum();
Chris@42 478
Chris@101 479 if (shouldConvertMIDIToHz()) {
Chris@101 480 min = Pitch::getFrequencyForPitch(lrintf(min));
Chris@101 481 max = Pitch::getFrequencyForPitch(lrintf(max + 1));
Chris@101 482 }
Chris@42 483
Chris@105 484 std::cerr << "NoteLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << std::endl;
Chris@105 485
Chris@101 486 } else if (log) {
Chris@101 487
Chris@101 488 min = (min < 0.0) ? -log10(-min) : (min == 0.0) ? 0.0 : log10(min);
Chris@101 489 max = (max < 0.0) ? -log10(-max) : (max == 0.0) ? 0.0 : log10(max);
Chris@105 490
Chris@105 491 std::cerr << "NoteLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << std::endl;
Chris@105 492
Chris@101 493 }
Chris@101 494
Chris@101 495 } else {
Chris@101 496
Chris@101 497 min = m_model->getValueMinimum();
Chris@101 498 max = m_model->getValueMaximum();
Chris@101 499
Chris@101 500 if (m_verticalScale == MIDIRangeScale) {
Chris@101 501 min = Pitch::getFrequencyForPitch(0);
Chris@101 502 max = Pitch::getFrequencyForPitch(127);
Chris@101 503 } else if (shouldConvertMIDIToHz()) {
Chris@101 504 min = Pitch::getFrequencyForPitch(lrintf(min));
Chris@101 505 max = Pitch::getFrequencyForPitch(lrintf(max + 1));
Chris@101 506 }
Chris@101 507
Chris@101 508 if (m_verticalScale == LogScale || m_verticalScale == MIDIRangeScale) {
Chris@101 509 min = (min < 0.0) ? -log10(-min) : (min == 0.0) ? 0.0 : log10(min);
Chris@101 510 max = (max < 0.0) ? -log10(-max) : (max == 0.0) ? 0.0 : log10(max);
Chris@101 511 log = true;
Chris@101 512 }
Chris@30 513 }
Chris@30 514
Chris@101 515 if (max == min) max = min + 1.0;
Chris@101 516 }
Chris@30 517
Chris@101 518 int
Chris@101 519 NoteLayer::getYForValue(View *v, float val) const
Chris@101 520 {
Chris@101 521 float min = 0.0, max = 0.0;
Chris@101 522 bool logarithmic = false;
Chris@101 523 int h = v->height();
Chris@101 524
Chris@101 525 getScaleExtents(v, min, max, logarithmic);
Chris@101 526
Chris@101 527 // std::cerr << "NoteLayer[" << this << "]::getYForValue(" << val << "): min = " << min << ", max = " << max << ", log = " << logarithmic << std::endl;
Chris@101 528
Chris@101 529 if (shouldConvertMIDIToHz()) {
Chris@101 530 val = Pitch::getFrequencyForPitch(lrintf(val),
Chris@101 531 lrintf((val - lrintf(val)) * 100));
Chris@101 532 // std::cerr << "shouldConvertMIDIToHz true, val now = " << val << std::endl;
Chris@101 533 }
Chris@101 534
Chris@101 535 if (logarithmic) {
Chris@101 536 val = (val < 0.0) ? -log10(-val) : (val == 0.0) ? 0.0 : log10(val);
Chris@101 537 // std::cerr << "logarithmic true, val now = " << val << std::endl;
Chris@101 538 }
Chris@101 539
Chris@101 540 int y = int(h - ((val - min) * h) / (max - min)) - 1;
Chris@101 541 // std::cerr << "y = " << y << std::endl;
Chris@101 542 return y;
Chris@30 543 }
Chris@30 544
Chris@30 545 float
Chris@44 546 NoteLayer::getValueForY(View *v, int y) const
Chris@30 547 {
Chris@101 548 float min = 0.0, max = 0.0;
Chris@101 549 bool logarithmic = false;
Chris@44 550 int h = v->height();
Chris@30 551
Chris@101 552 getScaleExtents(v, min, max, logarithmic);
Chris@101 553
Chris@101 554 float val = min + (float(h - y) * float(max - min)) / h;
Chris@101 555
Chris@101 556 if (logarithmic) {
Chris@101 557 val = pow(10, val);
Chris@101 558 }
Chris@101 559
Chris@101 560 if (shouldConvertMIDIToHz()) {
Chris@101 561 val = Pitch::getPitchForFrequency(val);
Chris@101 562 }
Chris@101 563
Chris@101 564 return val;
Chris@30 565 }
Chris@30 566
Chris@30 567 void
Chris@44 568 NoteLayer::paint(View *v, QPainter &paint, QRect rect) const
Chris@30 569 {
Chris@30 570 if (!m_model || !m_model->isOK()) return;
Chris@30 571
Chris@30 572 int sampleRate = m_model->getSampleRate();
Chris@30 573 if (!sampleRate) return;
Chris@30 574
Chris@30 575 // Profiler profiler("NoteLayer::paint", true);
Chris@30 576
Chris@30 577 int x0 = rect.left(), x1 = rect.right();
Chris@44 578 long frame0 = v->getFrameForX(x0);
Chris@44 579 long frame1 = v->getFrameForX(x1);
Chris@30 580
Chris@30 581 NoteModel::PointList points(m_model->getPoints(frame0, frame1));
Chris@30 582 if (points.empty()) return;
Chris@30 583
Chris@30 584 paint.setPen(m_colour);
Chris@30 585
Chris@30 586 QColor brushColour(m_colour);
Chris@30 587 brushColour.setAlpha(80);
Chris@30 588
Chris@30 589 // std::cerr << "NoteLayer::paint: resolution is "
Chris@30 590 // << m_model->getResolution() << " frames" << std::endl;
Chris@30 591
Chris@30 592 float min = m_model->getValueMinimum();
Chris@30 593 float max = m_model->getValueMaximum();
Chris@30 594 if (max == min) max = min + 1.0;
Chris@30 595
Chris@44 596 int origin = int(nearbyint(v->height() -
Chris@44 597 (-min * v->height()) / (max - min)));
Chris@30 598
Chris@30 599 QPoint localPos;
Chris@30 600 long illuminateFrame = -1;
Chris@30 601
Chris@44 602 if (v->shouldIlluminateLocalFeatures(this, localPos)) {
Chris@30 603 NoteModel::PointList localPoints =
Chris@44 604 getLocalPoints(v, localPos.x());
Chris@30 605 if (!localPoints.empty()) illuminateFrame = localPoints.begin()->frame;
Chris@30 606 }
Chris@30 607
Chris@30 608 paint.save();
Chris@30 609 paint.setRenderHint(QPainter::Antialiasing, false);
Chris@30 610
Chris@30 611 for (NoteModel::PointList::const_iterator i = points.begin();
Chris@30 612 i != points.end(); ++i) {
Chris@30 613
Chris@30 614 const NoteModel::Point &p(*i);
Chris@30 615
Chris@44 616 int x = v->getXForFrame(p.frame);
Chris@44 617 int y = getYForValue(v, p.value);
Chris@44 618 int w = v->getXForFrame(p.frame + p.duration) - x;
Chris@30 619 int h = 3;
Chris@30 620
Chris@30 621 if (m_model->getValueQuantization() != 0.0) {
Chris@44 622 h = y - getYForValue(v, p.value + m_model->getValueQuantization());
Chris@30 623 if (h < 3) h = 3;
Chris@30 624 }
Chris@30 625
Chris@30 626 if (w < 1) w = 1;
Chris@30 627 paint.setPen(m_colour);
Chris@30 628 paint.setBrush(brushColour);
Chris@30 629
Chris@30 630 if (illuminateFrame == p.frame) {
Chris@30 631 if (localPos.y() >= y - h && localPos.y() < y) {
Chris@30 632 paint.setPen(Qt::black);//!!!
Chris@30 633 paint.setBrush(Qt::black);//!!!
Chris@30 634 }
Chris@30 635 }
Chris@30 636
Chris@124 637 paint.drawRect(x, y - h/2, w, h);
Chris@30 638
Chris@30 639 /// if (p.label != "") {
Chris@30 640 /// paint.drawText(x + 5, y - paint.fontMetrics().height() + paint.fontMetrics().ascent(), p.label);
Chris@30 641 /// }
Chris@30 642 }
Chris@30 643
Chris@30 644 paint.restore();
Chris@30 645 }
Chris@30 646
Chris@30 647 void
Chris@44 648 NoteLayer::drawStart(View *v, QMouseEvent *e)
Chris@30 649 {
Chris@101 650 // std::cerr << "NoteLayer::drawStart(" << e->x() << "," << e->y() << ")" << std::endl;
Chris@30 651
Chris@30 652 if (!m_model) return;
Chris@30 653
Chris@44 654 long frame = v->getFrameForX(e->x());
Chris@30 655 if (frame < 0) frame = 0;
Chris@30 656 frame = frame / m_model->getResolution() * m_model->getResolution();
Chris@30 657
Chris@44 658 float value = getValueForY(v, e->y());
Chris@30 659
Chris@30 660 m_editingPoint = NoteModel::Point(frame, value, 0, tr("New Point"));
Chris@30 661 m_originalPoint = m_editingPoint;
Chris@30 662
Chris@30 663 if (m_editingCommand) m_editingCommand->finish();
Chris@30 664 m_editingCommand = new NoteModel::EditCommand(m_model,
Chris@30 665 tr("Draw Point"));
Chris@30 666 m_editingCommand->addPoint(m_editingPoint);
Chris@30 667
Chris@30 668 m_editing = true;
Chris@30 669 }
Chris@30 670
Chris@30 671 void
Chris@44 672 NoteLayer::drawDrag(View *v, QMouseEvent *e)
Chris@30 673 {
Chris@101 674 // std::cerr << "NoteLayer::drawDrag(" << e->x() << "," << e->y() << ")" << std::endl;
Chris@30 675
Chris@30 676 if (!m_model || !m_editing) return;
Chris@30 677
Chris@44 678 long frame = v->getFrameForX(e->x());
Chris@30 679 if (frame < 0) frame = 0;
Chris@30 680 frame = frame / m_model->getResolution() * m_model->getResolution();
Chris@30 681
Chris@101 682 float newValue = getValueForY(v, e->y());
Chris@101 683
Chris@101 684 long newFrame = m_editingPoint.frame;
Chris@101 685 long newDuration = frame - newFrame;
Chris@101 686 if (newDuration < 0) {
Chris@101 687 newFrame = frame;
Chris@101 688 newDuration = -newDuration;
Chris@101 689 } else if (newDuration == 0) {
Chris@101 690 newDuration = 1;
Chris@101 691 }
Chris@30 692
Chris@30 693 m_editingCommand->deletePoint(m_editingPoint);
Chris@101 694 m_editingPoint.frame = newFrame;
Chris@101 695 m_editingPoint.value = newValue;
Chris@101 696 m_editingPoint.duration = newDuration;
Chris@30 697 m_editingCommand->addPoint(m_editingPoint);
Chris@30 698 }
Chris@30 699
Chris@30 700 void
Chris@44 701 NoteLayer::drawEnd(View *v, QMouseEvent *e)
Chris@30 702 {
Chris@101 703 // std::cerr << "NoteLayer::drawEnd(" << e->x() << "," << e->y() << ")" << std::endl;
Chris@30 704 if (!m_model || !m_editing) return;
Chris@30 705 m_editingCommand->finish();
Chris@30 706 m_editingCommand = 0;
Chris@30 707 m_editing = false;
Chris@30 708 }
Chris@30 709
Chris@30 710 void
Chris@44 711 NoteLayer::editStart(View *v, QMouseEvent *e)
Chris@30 712 {
Chris@101 713 // std::cerr << "NoteLayer::editStart(" << e->x() << "," << e->y() << ")" << std::endl;
Chris@30 714
Chris@30 715 if (!m_model) return;
Chris@30 716
Chris@44 717 NoteModel::PointList points = getLocalPoints(v, e->x());
Chris@30 718 if (points.empty()) return;
Chris@30 719
Chris@30 720 m_editingPoint = *points.begin();
Chris@30 721 m_originalPoint = m_editingPoint;
Chris@30 722
Chris@30 723 if (m_editingCommand) {
Chris@30 724 m_editingCommand->finish();
Chris@30 725 m_editingCommand = 0;
Chris@30 726 }
Chris@30 727
Chris@30 728 m_editing = true;
Chris@30 729 }
Chris@30 730
Chris@30 731 void
Chris@44 732 NoteLayer::editDrag(View *v, QMouseEvent *e)
Chris@30 733 {
Chris@101 734 // std::cerr << "NoteLayer::editDrag(" << e->x() << "," << e->y() << ")" << std::endl;
Chris@30 735
Chris@30 736 if (!m_model || !m_editing) return;
Chris@30 737
Chris@44 738 long frame = v->getFrameForX(e->x());
Chris@30 739 if (frame < 0) frame = 0;
Chris@30 740 frame = frame / m_model->getResolution() * m_model->getResolution();
Chris@30 741
Chris@44 742 float value = getValueForY(v, e->y());
Chris@30 743
Chris@30 744 if (!m_editingCommand) {
Chris@30 745 m_editingCommand = new NoteModel::EditCommand(m_model,
Chris@30 746 tr("Drag Point"));
Chris@30 747 }
Chris@30 748
Chris@30 749 m_editingCommand->deletePoint(m_editingPoint);
Chris@30 750 m_editingPoint.frame = frame;
Chris@30 751 m_editingPoint.value = value;
Chris@30 752 m_editingCommand->addPoint(m_editingPoint);
Chris@30 753 }
Chris@30 754
Chris@30 755 void
Chris@44 756 NoteLayer::editEnd(View *v, QMouseEvent *e)
Chris@30 757 {
Chris@101 758 // std::cerr << "NoteLayer::editEnd(" << e->x() << "," << e->y() << ")" << std::endl;
Chris@30 759 if (!m_model || !m_editing) return;
Chris@30 760
Chris@30 761 if (m_editingCommand) {
Chris@30 762
Chris@30 763 QString newName = m_editingCommand->getName();
Chris@30 764
Chris@30 765 if (m_editingPoint.frame != m_originalPoint.frame) {
Chris@30 766 if (m_editingPoint.value != m_originalPoint.value) {
Chris@30 767 newName = tr("Edit Point");
Chris@30 768 } else {
Chris@30 769 newName = tr("Relocate Point");
Chris@30 770 }
Chris@30 771 } else {
Chris@30 772 newName = tr("Change Point Value");
Chris@30 773 }
Chris@30 774
Chris@30 775 m_editingCommand->setName(newName);
Chris@30 776 m_editingCommand->finish();
Chris@30 777 }
Chris@30 778
Chris@30 779 m_editingCommand = 0;
Chris@30 780 m_editing = false;
Chris@30 781 }
Chris@30 782
Chris@43 783 void
Chris@70 784 NoteLayer::editOpen(View *v, QMouseEvent *e)
Chris@70 785 {
Chris@70 786 if (!m_model) return;
Chris@70 787
Chris@70 788 NoteModel::PointList points = getLocalPoints(v, e->x());
Chris@70 789 if (points.empty()) return;
Chris@70 790
Chris@70 791 NoteModel::Point note = *points.begin();
Chris@70 792
Chris@70 793 ItemEditDialog *dialog = new ItemEditDialog
Chris@70 794 (m_model->getSampleRate(),
Chris@70 795 ItemEditDialog::ShowTime |
Chris@70 796 ItemEditDialog::ShowDuration |
Chris@70 797 ItemEditDialog::ShowValue |
Chris@100 798 ItemEditDialog::ShowText,
Chris@100 799 m_model->getScaleUnits());
Chris@70 800
Chris@70 801 dialog->setFrameTime(note.frame);
Chris@70 802 dialog->setValue(note.value);
Chris@70 803 dialog->setFrameDuration(note.duration);
Chris@70 804 dialog->setText(note.label);
Chris@70 805
Chris@70 806 if (dialog->exec() == QDialog::Accepted) {
Chris@70 807
Chris@70 808 NoteModel::Point newNote = note;
Chris@70 809 newNote.frame = dialog->getFrameTime();
Chris@70 810 newNote.value = dialog->getValue();
Chris@70 811 newNote.duration = dialog->getFrameDuration();
Chris@70 812 newNote.label = dialog->getText();
Chris@70 813
Chris@70 814 NoteModel::EditCommand *command = new NoteModel::EditCommand
Chris@70 815 (m_model, tr("Edit Point"));
Chris@70 816 command->deletePoint(note);
Chris@70 817 command->addPoint(newNote);
Chris@70 818 command->finish();
Chris@70 819 }
Chris@70 820
Chris@70 821 delete dialog;
Chris@70 822 }
Chris@70 823
Chris@70 824 void
Chris@43 825 NoteLayer::moveSelection(Selection s, size_t newStartFrame)
Chris@43 826 {
Chris@99 827 if (!m_model) return;
Chris@99 828
Chris@43 829 NoteModel::EditCommand *command =
Chris@43 830 new NoteModel::EditCommand(m_model, tr("Drag Selection"));
Chris@43 831
Chris@43 832 NoteModel::PointList points =
Chris@43 833 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@43 834
Chris@43 835 for (NoteModel::PointList::iterator i = points.begin();
Chris@43 836 i != points.end(); ++i) {
Chris@43 837
Chris@43 838 if (s.contains(i->frame)) {
Chris@43 839 NoteModel::Point newPoint(*i);
Chris@43 840 newPoint.frame = i->frame + newStartFrame - s.getStartFrame();
Chris@43 841 command->deletePoint(*i);
Chris@43 842 command->addPoint(newPoint);
Chris@43 843 }
Chris@43 844 }
Chris@43 845
Chris@43 846 command->finish();
Chris@43 847 }
Chris@43 848
Chris@43 849 void
Chris@43 850 NoteLayer::resizeSelection(Selection s, Selection newSize)
Chris@43 851 {
Chris@99 852 if (!m_model) return;
Chris@99 853
Chris@43 854 NoteModel::EditCommand *command =
Chris@43 855 new NoteModel::EditCommand(m_model, tr("Resize Selection"));
Chris@43 856
Chris@43 857 NoteModel::PointList points =
Chris@43 858 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@43 859
Chris@43 860 double ratio =
Chris@43 861 double(newSize.getEndFrame() - newSize.getStartFrame()) /
Chris@43 862 double(s.getEndFrame() - s.getStartFrame());
Chris@43 863
Chris@43 864 for (NoteModel::PointList::iterator i = points.begin();
Chris@43 865 i != points.end(); ++i) {
Chris@43 866
Chris@43 867 if (s.contains(i->frame)) {
Chris@43 868
Chris@43 869 double targetStart = i->frame;
Chris@43 870 targetStart = newSize.getStartFrame() +
Chris@43 871 double(targetStart - s.getStartFrame()) * ratio;
Chris@43 872
Chris@43 873 double targetEnd = i->frame + i->duration;
Chris@43 874 targetEnd = newSize.getStartFrame() +
Chris@43 875 double(targetEnd - s.getStartFrame()) * ratio;
Chris@43 876
Chris@43 877 NoteModel::Point newPoint(*i);
Chris@43 878 newPoint.frame = lrint(targetStart);
Chris@43 879 newPoint.duration = lrint(targetEnd - targetStart);
Chris@43 880 command->deletePoint(*i);
Chris@43 881 command->addPoint(newPoint);
Chris@43 882 }
Chris@43 883 }
Chris@43 884
Chris@43 885 command->finish();
Chris@43 886 }
Chris@43 887
Chris@76 888 void
Chris@76 889 NoteLayer::deleteSelection(Selection s)
Chris@76 890 {
Chris@99 891 if (!m_model) return;
Chris@99 892
Chris@76 893 NoteModel::EditCommand *command =
Chris@76 894 new NoteModel::EditCommand(m_model, tr("Delete Selected Points"));
Chris@76 895
Chris@76 896 NoteModel::PointList points =
Chris@76 897 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@76 898
Chris@76 899 for (NoteModel::PointList::iterator i = points.begin();
Chris@76 900 i != points.end(); ++i) {
Chris@76 901
Chris@76 902 if (s.contains(i->frame)) {
Chris@76 903 command->deletePoint(*i);
Chris@76 904 }
Chris@76 905 }
Chris@76 906
Chris@76 907 command->finish();
Chris@76 908 }
Chris@76 909
Chris@76 910 void
Chris@76 911 NoteLayer::copy(Selection s, Clipboard &to)
Chris@76 912 {
Chris@99 913 if (!m_model) return;
Chris@99 914
Chris@76 915 NoteModel::PointList points =
Chris@76 916 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@76 917
Chris@76 918 for (NoteModel::PointList::iterator i = points.begin();
Chris@76 919 i != points.end(); ++i) {
Chris@76 920 if (s.contains(i->frame)) {
Chris@83 921 Clipboard::Point point(i->frame, i->value, i->duration, i->label);
Chris@76 922 to.addPoint(point);
Chris@76 923 }
Chris@76 924 }
Chris@76 925 }
Chris@76 926
Chris@125 927 bool
Chris@125 928 NoteLayer::paste(const Clipboard &from, int frameOffset, bool interactive)
Chris@76 929 {
Chris@125 930 if (!m_model) return false;
Chris@99 931
Chris@76 932 const Clipboard::PointList &points = from.getPoints();
Chris@76 933
Chris@76 934 NoteModel::EditCommand *command =
Chris@76 935 new NoteModel::EditCommand(m_model, tr("Paste"));
Chris@76 936
Chris@76 937 for (Clipboard::PointList::const_iterator i = points.begin();
Chris@76 938 i != points.end(); ++i) {
Chris@76 939
Chris@76 940 if (!i->haveFrame()) continue;
Chris@76 941 size_t frame = 0;
Chris@76 942 if (frameOffset > 0 || -frameOffset < i->getFrame()) {
Chris@76 943 frame = i->getFrame() + frameOffset;
Chris@76 944 }
Chris@76 945 NoteModel::Point newPoint(frame);
Chris@76 946
Chris@76 947 if (i->haveLabel()) newPoint.label = i->getLabel();
Chris@76 948 if (i->haveValue()) newPoint.value = i->getValue();
Chris@76 949 else newPoint.value = (m_model->getValueMinimum() +
Chris@76 950 m_model->getValueMaximum()) / 2;
Chris@76 951 if (i->haveDuration()) newPoint.duration = i->getDuration();
Chris@125 952 else {
Chris@125 953 size_t nextFrame = frame;
Chris@125 954 Clipboard::PointList::const_iterator j = i;
Chris@125 955 for (; j != points.end(); ++j) {
Chris@125 956 if (!j->haveFrame()) continue;
Chris@125 957 if (j != i) break;
Chris@125 958 }
Chris@125 959 if (j != points.end()) {
Chris@125 960 nextFrame = j->getFrame();
Chris@125 961 }
Chris@125 962 if (nextFrame == frame) {
Chris@125 963 newPoint.duration = m_model->getResolution();
Chris@125 964 } else {
Chris@125 965 newPoint.duration = nextFrame - frame;
Chris@125 966 }
Chris@125 967 }
Chris@76 968
Chris@76 969 command->addPoint(newPoint);
Chris@76 970 }
Chris@76 971
Chris@76 972 command->finish();
Chris@125 973 return true;
Chris@76 974 }
Chris@76 975
Chris@30 976 QString
Chris@30 977 NoteLayer::toXmlString(QString indent, QString extraAttributes) const
Chris@30 978 {
Chris@30 979 return Layer::toXmlString(indent, extraAttributes +
Chris@30 980 QString(" colour=\"%1\" verticalScale=\"%2\"")
Chris@30 981 .arg(encodeColour(m_colour)).arg(m_verticalScale));
Chris@30 982 }
Chris@30 983
Chris@30 984 void
Chris@30 985 NoteLayer::setProperties(const QXmlAttributes &attributes)
Chris@30 986 {
Chris@30 987 QString colourSpec = attributes.value("colour");
Chris@30 988 if (colourSpec != "") {
Chris@30 989 QColor colour(colourSpec);
Chris@30 990 if (colour.isValid()) {
Chris@30 991 setBaseColour(QColor(colourSpec));
Chris@30 992 }
Chris@30 993 }
Chris@30 994
Chris@30 995 bool ok;
Chris@30 996 VerticalScale scale = (VerticalScale)
Chris@30 997 attributes.value("verticalScale").toInt(&ok);
Chris@30 998 if (ok) setVerticalScale(scale);
Chris@30 999 }
Chris@30 1000
Chris@30 1001