annotate layer/NoteLayer.cpp @ 101:0f36cdf407a6 sv1-v0.9rc1

* Make vertical scale alignment modes work in note layer as well as time-value layer, and several significant fixes to it * Make it possible to draw notes properly on the note layer * Show units (and frequencies etc in note layer's case) in the time-value and note layer description boxes * Minor fix to item edit dialog layout * Some minor menu rearrangement * Comment out a lot of debug output * Add SV website and reference URLs to Help menu, and add code to (attempt to) open them in the user's preferred browser
author Chris Cannam
date Fri, 12 May 2006 14:40:43 +0000
parents 0db5e7492ce8
children 571805759a66
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@30 18 #include "base/Model.h"
Chris@30 19 #include "base/RealTime.h"
Chris@30 20 #include "base/Profiler.h"
Chris@30 21 #include "base/Pitch.h"
Chris@30 22 #include "base/View.h"
Chris@30 23
Chris@30 24 #include "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@100 83 if (name == "Scale Units") return tr("Pitch 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@101 228 if (shouldConvertMIDIToHz()) unit = "Hz";
Chris@101 229 else unit = m_model->getScaleUnits();
Chris@101 230
Chris@101 231 if (m_verticalScale == MIDIRangeScale ||
Chris@101 232 m_verticalScale == LogScale) logarithmic = true;
Chris@101 233
Chris@101 234 return true;
Chris@101 235 }
Chris@101 236
Chris@101 237 bool
Chris@101 238 NoteLayer::getDisplayExtents(float &min, float &max) const
Chris@101 239 {
Chris@101 240 if (!m_model || m_verticalScale == AutoAlignScale) return false;
Chris@101 241
Chris@101 242 if (m_verticalScale == MIDIRangeScale) {
Chris@101 243 min = Pitch::getFrequencyForPitch(0);
Chris@101 244 max = Pitch::getFrequencyForPitch(127);
Chris@101 245 return true;
Chris@101 246 }
Chris@101 247
Chris@101 248 min = m_model->getValueMinimum();
Chris@101 249 max = m_model->getValueMaximum();
Chris@101 250
Chris@101 251 if (shouldConvertMIDIToHz()) {
Chris@101 252 min = Pitch::getFrequencyForPitch(lrintf(min));
Chris@101 253 max = Pitch::getFrequencyForPitch(lrintf(max + 1));
Chris@101 254 }
Chris@101 255
Chris@79 256 return true;
Chris@79 257 }
Chris@79 258
Chris@30 259 NoteModel::PointList
Chris@44 260 NoteLayer::getLocalPoints(View *v, int x) const
Chris@30 261 {
Chris@30 262 if (!m_model) return NoteModel::PointList();
Chris@30 263
Chris@44 264 long frame = v->getFrameForX(x);
Chris@30 265
Chris@30 266 NoteModel::PointList onPoints =
Chris@30 267 m_model->getPoints(frame);
Chris@30 268
Chris@30 269 if (!onPoints.empty()) {
Chris@30 270 return onPoints;
Chris@30 271 }
Chris@30 272
Chris@30 273 NoteModel::PointList prevPoints =
Chris@30 274 m_model->getPreviousPoints(frame);
Chris@30 275 NoteModel::PointList nextPoints =
Chris@30 276 m_model->getNextPoints(frame);
Chris@30 277
Chris@30 278 NoteModel::PointList usePoints = prevPoints;
Chris@30 279
Chris@30 280 if (prevPoints.empty()) {
Chris@30 281 usePoints = nextPoints;
Chris@44 282 } else if (prevPoints.begin()->frame < v->getStartFrame() &&
Chris@44 283 !(nextPoints.begin()->frame > v->getEndFrame())) {
Chris@30 284 usePoints = nextPoints;
Chris@30 285 } else if (nextPoints.begin()->frame - frame <
Chris@30 286 frame - prevPoints.begin()->frame) {
Chris@30 287 usePoints = nextPoints;
Chris@30 288 }
Chris@30 289
Chris@30 290 if (!usePoints.empty()) {
Chris@30 291 int fuzz = 2;
Chris@44 292 int px = v->getXForFrame(usePoints.begin()->frame);
Chris@30 293 if ((px > x && px - x > fuzz) ||
Chris@30 294 (px < x && x - px > fuzz + 1)) {
Chris@30 295 usePoints.clear();
Chris@30 296 }
Chris@30 297 }
Chris@30 298
Chris@30 299 return usePoints;
Chris@30 300 }
Chris@30 301
Chris@30 302 QString
Chris@44 303 NoteLayer::getFeatureDescription(View *v, QPoint &pos) const
Chris@30 304 {
Chris@30 305 int x = pos.x();
Chris@30 306
Chris@30 307 if (!m_model || !m_model->getSampleRate()) return "";
Chris@30 308
Chris@44 309 NoteModel::PointList points = getLocalPoints(v, x);
Chris@30 310
Chris@30 311 if (points.empty()) {
Chris@30 312 if (!m_model->isReady()) {
Chris@30 313 return tr("In progress");
Chris@30 314 } else {
Chris@30 315 return tr("No local points");
Chris@30 316 }
Chris@30 317 }
Chris@30 318
Chris@30 319 Note note(0);
Chris@30 320 NoteModel::PointList::iterator i;
Chris@30 321
Chris@30 322 for (i = points.begin(); i != points.end(); ++i) {
Chris@30 323
Chris@44 324 int y = getYForValue(v, i->value);
Chris@30 325 int h = 3;
Chris@30 326
Chris@30 327 if (m_model->getValueQuantization() != 0.0) {
Chris@44 328 h = y - getYForValue(v, i->value + m_model->getValueQuantization());
Chris@30 329 if (h < 3) h = 3;
Chris@30 330 }
Chris@30 331
Chris@30 332 if (pos.y() >= y - h && pos.y() <= y) {
Chris@30 333 note = *i;
Chris@30 334 break;
Chris@30 335 }
Chris@30 336 }
Chris@30 337
Chris@30 338 if (i == points.end()) return tr("No local points");
Chris@30 339
Chris@30 340 RealTime rt = RealTime::frame2RealTime(note.frame,
Chris@30 341 m_model->getSampleRate());
Chris@30 342 RealTime rd = RealTime::frame2RealTime(note.duration,
Chris@30 343 m_model->getSampleRate());
Chris@30 344
Chris@101 345 QString pitchText;
Chris@101 346
Chris@101 347 if (shouldConvertMIDIToHz()) {
Chris@101 348
Chris@101 349 int mnote = lrintf(note.value);
Chris@101 350 int cents = lrintf((note.value - mnote) * 100);
Chris@101 351 float freq = Pitch::getFrequencyForPitch(mnote, cents);
Chris@101 352 pitchText = QString("%1 (%2 Hz)")
Chris@101 353 .arg(Pitch::getPitchLabel(mnote, cents)).arg(freq);
Chris@101 354
Chris@101 355 } else if (m_model->getScaleUnits() == "Hz") {
Chris@101 356
Chris@101 357 pitchText = QString("%1 Hz (%2)")
Chris@101 358 .arg(note.value)
Chris@101 359 .arg(Pitch::getPitchLabelForFrequency(note.value));
Chris@101 360
Chris@101 361 } else {
Chris@101 362 pitchText = QString("%1 %2")
Chris@101 363 .arg(note.value).arg(m_model->getScaleUnits());
Chris@101 364 }
Chris@101 365
Chris@30 366 QString text;
Chris@30 367
Chris@30 368 if (note.label == "") {
Chris@30 369 text = QString(tr("Time:\t%1\nPitch:\t%2\nDuration:\t%3\nNo label"))
Chris@30 370 .arg(rt.toText(true).c_str())
Chris@101 371 .arg(pitchText)
Chris@30 372 .arg(rd.toText(true).c_str());
Chris@30 373 } else {
Chris@30 374 text = QString(tr("Time:\t%1\nPitch:\t%2\nDuration:\t%3\nLabel:\t%4"))
Chris@30 375 .arg(rt.toText(true).c_str())
Chris@101 376 .arg(pitchText)
Chris@30 377 .arg(rd.toText(true).c_str())
Chris@30 378 .arg(note.label);
Chris@30 379 }
Chris@30 380
Chris@44 381 pos = QPoint(v->getXForFrame(note.frame),
Chris@44 382 getYForValue(v, note.value));
Chris@30 383 return text;
Chris@30 384 }
Chris@30 385
Chris@30 386 bool
Chris@44 387 NoteLayer::snapToFeatureFrame(View *v, int &frame,
Chris@44 388 size_t &resolution,
Chris@44 389 SnapType snap) const
Chris@30 390 {
Chris@30 391 if (!m_model) {
Chris@44 392 return Layer::snapToFeatureFrame(v, frame, resolution, snap);
Chris@30 393 }
Chris@30 394
Chris@30 395 resolution = m_model->getResolution();
Chris@30 396 NoteModel::PointList points;
Chris@30 397
Chris@30 398 if (snap == SnapNeighbouring) {
Chris@30 399
Chris@44 400 points = getLocalPoints(v, v->getXForFrame(frame));
Chris@30 401 if (points.empty()) return false;
Chris@30 402 frame = points.begin()->frame;
Chris@30 403 return true;
Chris@30 404 }
Chris@30 405
Chris@30 406 points = m_model->getPoints(frame, frame);
Chris@30 407 int snapped = frame;
Chris@30 408 bool found = false;
Chris@30 409
Chris@30 410 for (NoteModel::PointList::const_iterator i = points.begin();
Chris@30 411 i != points.end(); ++i) {
Chris@30 412
Chris@30 413 if (snap == SnapRight) {
Chris@30 414
Chris@30 415 if (i->frame > frame) {
Chris@30 416 snapped = i->frame;
Chris@30 417 found = true;
Chris@30 418 break;
Chris@30 419 }
Chris@30 420
Chris@30 421 } else if (snap == SnapLeft) {
Chris@30 422
Chris@30 423 if (i->frame <= frame) {
Chris@30 424 snapped = i->frame;
Chris@30 425 found = true; // don't break, as the next may be better
Chris@30 426 } else {
Chris@30 427 break;
Chris@30 428 }
Chris@30 429
Chris@30 430 } else { // nearest
Chris@30 431
Chris@30 432 NoteModel::PointList::const_iterator j = i;
Chris@30 433 ++j;
Chris@30 434
Chris@30 435 if (j == points.end()) {
Chris@30 436
Chris@30 437 snapped = i->frame;
Chris@30 438 found = true;
Chris@30 439 break;
Chris@30 440
Chris@30 441 } else if (j->frame >= frame) {
Chris@30 442
Chris@30 443 if (j->frame - frame < frame - i->frame) {
Chris@30 444 snapped = j->frame;
Chris@30 445 } else {
Chris@30 446 snapped = i->frame;
Chris@30 447 }
Chris@30 448 found = true;
Chris@30 449 break;
Chris@30 450 }
Chris@30 451 }
Chris@30 452 }
Chris@30 453
Chris@30 454 frame = snapped;
Chris@30 455 return found;
Chris@30 456 }
Chris@30 457
Chris@101 458 void
Chris@101 459 NoteLayer::getScaleExtents(View *v, float &min, float &max, bool &log) const
Chris@30 460 {
Chris@101 461 min = 0.0;
Chris@101 462 max = 0.0;
Chris@101 463 log = false;
Chris@42 464
Chris@101 465 QString queryUnits;
Chris@101 466 if (shouldConvertMIDIToHz()) queryUnits = "Hz";
Chris@101 467 else queryUnits = m_model->getScaleUnits();
Chris@30 468
Chris@101 469 if (m_verticalScale == AutoAlignScale) {
Chris@30 470
Chris@101 471 if (!v->getValueExtents(queryUnits, min, max, log)) {
Chris@30 472
Chris@101 473 min = m_model->getValueMinimum();
Chris@101 474 max = m_model->getValueMaximum();
Chris@42 475
Chris@101 476 if (shouldConvertMIDIToHz()) {
Chris@101 477 min = Pitch::getFrequencyForPitch(lrintf(min));
Chris@101 478 max = Pitch::getFrequencyForPitch(lrintf(max + 1));
Chris@101 479 }
Chris@42 480
Chris@101 481 } else if (log) {
Chris@101 482
Chris@101 483 // std::cerr << "NoteLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << std::endl;
Chris@101 484
Chris@101 485 min = (min < 0.0) ? -log10(-min) : (min == 0.0) ? 0.0 : log10(min);
Chris@101 486 max = (max < 0.0) ? -log10(-max) : (max == 0.0) ? 0.0 : log10(max);
Chris@101 487 }
Chris@101 488
Chris@101 489 } else {
Chris@101 490
Chris@101 491 min = m_model->getValueMinimum();
Chris@101 492 max = m_model->getValueMaximum();
Chris@101 493
Chris@101 494 if (m_verticalScale == MIDIRangeScale) {
Chris@101 495 min = Pitch::getFrequencyForPitch(0);
Chris@101 496 max = Pitch::getFrequencyForPitch(127);
Chris@101 497 } else if (shouldConvertMIDIToHz()) {
Chris@101 498 min = Pitch::getFrequencyForPitch(lrintf(min));
Chris@101 499 max = Pitch::getFrequencyForPitch(lrintf(max + 1));
Chris@101 500 }
Chris@101 501
Chris@101 502 if (m_verticalScale == LogScale || m_verticalScale == MIDIRangeScale) {
Chris@101 503 min = (min < 0.0) ? -log10(-min) : (min == 0.0) ? 0.0 : log10(min);
Chris@101 504 max = (max < 0.0) ? -log10(-max) : (max == 0.0) ? 0.0 : log10(max);
Chris@101 505 log = true;
Chris@101 506 }
Chris@30 507 }
Chris@30 508
Chris@101 509 if (max == min) max = min + 1.0;
Chris@101 510 }
Chris@30 511
Chris@101 512 int
Chris@101 513 NoteLayer::getYForValue(View *v, float val) const
Chris@101 514 {
Chris@101 515 float min = 0.0, max = 0.0;
Chris@101 516 bool logarithmic = false;
Chris@101 517 int h = v->height();
Chris@101 518
Chris@101 519 getScaleExtents(v, min, max, logarithmic);
Chris@101 520
Chris@101 521 // std::cerr << "NoteLayer[" << this << "]::getYForValue(" << val << "): min = " << min << ", max = " << max << ", log = " << logarithmic << std::endl;
Chris@101 522
Chris@101 523 if (shouldConvertMIDIToHz()) {
Chris@101 524 val = Pitch::getFrequencyForPitch(lrintf(val),
Chris@101 525 lrintf((val - lrintf(val)) * 100));
Chris@101 526 // std::cerr << "shouldConvertMIDIToHz true, val now = " << val << std::endl;
Chris@101 527 }
Chris@101 528
Chris@101 529 if (logarithmic) {
Chris@101 530 val = (val < 0.0) ? -log10(-val) : (val == 0.0) ? 0.0 : log10(val);
Chris@101 531 // std::cerr << "logarithmic true, val now = " << val << std::endl;
Chris@101 532 }
Chris@101 533
Chris@101 534 int y = int(h - ((val - min) * h) / (max - min)) - 1;
Chris@101 535 // std::cerr << "y = " << y << std::endl;
Chris@101 536 return y;
Chris@30 537 }
Chris@30 538
Chris@30 539 float
Chris@44 540 NoteLayer::getValueForY(View *v, int y) const
Chris@30 541 {
Chris@101 542 float min = 0.0, max = 0.0;
Chris@101 543 bool logarithmic = false;
Chris@44 544 int h = v->height();
Chris@30 545
Chris@101 546 getScaleExtents(v, min, max, logarithmic);
Chris@101 547
Chris@101 548 float val = min + (float(h - y) * float(max - min)) / h;
Chris@101 549
Chris@101 550 if (logarithmic) {
Chris@101 551 val = pow(10, val);
Chris@101 552 }
Chris@101 553
Chris@101 554 if (shouldConvertMIDIToHz()) {
Chris@101 555 val = Pitch::getPitchForFrequency(val);
Chris@101 556 }
Chris@101 557
Chris@101 558 return val;
Chris@30 559 }
Chris@30 560
Chris@30 561 void
Chris@44 562 NoteLayer::paint(View *v, QPainter &paint, QRect rect) const
Chris@30 563 {
Chris@30 564 if (!m_model || !m_model->isOK()) return;
Chris@30 565
Chris@30 566 int sampleRate = m_model->getSampleRate();
Chris@30 567 if (!sampleRate) return;
Chris@30 568
Chris@30 569 // Profiler profiler("NoteLayer::paint", true);
Chris@30 570
Chris@30 571 int x0 = rect.left(), x1 = rect.right();
Chris@44 572 long frame0 = v->getFrameForX(x0);
Chris@44 573 long frame1 = v->getFrameForX(x1);
Chris@30 574
Chris@30 575 NoteModel::PointList points(m_model->getPoints(frame0, frame1));
Chris@30 576 if (points.empty()) return;
Chris@30 577
Chris@30 578 paint.setPen(m_colour);
Chris@30 579
Chris@30 580 QColor brushColour(m_colour);
Chris@30 581 brushColour.setAlpha(80);
Chris@30 582
Chris@30 583 // std::cerr << "NoteLayer::paint: resolution is "
Chris@30 584 // << m_model->getResolution() << " frames" << std::endl;
Chris@30 585
Chris@30 586 float min = m_model->getValueMinimum();
Chris@30 587 float max = m_model->getValueMaximum();
Chris@30 588 if (max == min) max = min + 1.0;
Chris@30 589
Chris@44 590 int origin = int(nearbyint(v->height() -
Chris@44 591 (-min * v->height()) / (max - min)));
Chris@30 592
Chris@30 593 QPoint localPos;
Chris@30 594 long illuminateFrame = -1;
Chris@30 595
Chris@44 596 if (v->shouldIlluminateLocalFeatures(this, localPos)) {
Chris@30 597 NoteModel::PointList localPoints =
Chris@44 598 getLocalPoints(v, localPos.x());
Chris@30 599 if (!localPoints.empty()) illuminateFrame = localPoints.begin()->frame;
Chris@30 600 }
Chris@30 601
Chris@30 602 paint.save();
Chris@30 603 paint.setRenderHint(QPainter::Antialiasing, false);
Chris@30 604
Chris@30 605 for (NoteModel::PointList::const_iterator i = points.begin();
Chris@30 606 i != points.end(); ++i) {
Chris@30 607
Chris@30 608 const NoteModel::Point &p(*i);
Chris@30 609
Chris@44 610 int x = v->getXForFrame(p.frame);
Chris@44 611 int y = getYForValue(v, p.value);
Chris@44 612 int w = v->getXForFrame(p.frame + p.duration) - x;
Chris@30 613 int h = 3;
Chris@30 614
Chris@30 615 if (m_model->getValueQuantization() != 0.0) {
Chris@44 616 h = y - getYForValue(v, p.value + m_model->getValueQuantization());
Chris@30 617 if (h < 3) h = 3;
Chris@30 618 }
Chris@30 619
Chris@30 620 if (w < 1) w = 1;
Chris@30 621 paint.setPen(m_colour);
Chris@30 622 paint.setBrush(brushColour);
Chris@30 623
Chris@30 624 if (illuminateFrame == p.frame) {
Chris@30 625 if (localPos.y() >= y - h && localPos.y() < y) {
Chris@30 626 paint.setPen(Qt::black);//!!!
Chris@30 627 paint.setBrush(Qt::black);//!!!
Chris@30 628 }
Chris@30 629 }
Chris@30 630
Chris@30 631 paint.drawRect(x, y - h, w, h);
Chris@30 632
Chris@30 633 /// if (p.label != "") {
Chris@30 634 /// paint.drawText(x + 5, y - paint.fontMetrics().height() + paint.fontMetrics().ascent(), p.label);
Chris@30 635 /// }
Chris@30 636 }
Chris@30 637
Chris@30 638 paint.restore();
Chris@30 639 }
Chris@30 640
Chris@30 641 void
Chris@44 642 NoteLayer::drawStart(View *v, QMouseEvent *e)
Chris@30 643 {
Chris@101 644 // std::cerr << "NoteLayer::drawStart(" << e->x() << "," << e->y() << ")" << std::endl;
Chris@30 645
Chris@30 646 if (!m_model) return;
Chris@30 647
Chris@44 648 long frame = v->getFrameForX(e->x());
Chris@30 649 if (frame < 0) frame = 0;
Chris@30 650 frame = frame / m_model->getResolution() * m_model->getResolution();
Chris@30 651
Chris@44 652 float value = getValueForY(v, e->y());
Chris@30 653
Chris@30 654 m_editingPoint = NoteModel::Point(frame, value, 0, tr("New Point"));
Chris@30 655 m_originalPoint = m_editingPoint;
Chris@30 656
Chris@30 657 if (m_editingCommand) m_editingCommand->finish();
Chris@30 658 m_editingCommand = new NoteModel::EditCommand(m_model,
Chris@30 659 tr("Draw Point"));
Chris@30 660 m_editingCommand->addPoint(m_editingPoint);
Chris@30 661
Chris@30 662 m_editing = true;
Chris@30 663 }
Chris@30 664
Chris@30 665 void
Chris@44 666 NoteLayer::drawDrag(View *v, QMouseEvent *e)
Chris@30 667 {
Chris@101 668 // std::cerr << "NoteLayer::drawDrag(" << e->x() << "," << e->y() << ")" << std::endl;
Chris@30 669
Chris@30 670 if (!m_model || !m_editing) return;
Chris@30 671
Chris@44 672 long frame = v->getFrameForX(e->x());
Chris@30 673 if (frame < 0) frame = 0;
Chris@30 674 frame = frame / m_model->getResolution() * m_model->getResolution();
Chris@30 675
Chris@101 676 float newValue = getValueForY(v, e->y());
Chris@101 677
Chris@101 678 long newFrame = m_editingPoint.frame;
Chris@101 679 long newDuration = frame - newFrame;
Chris@101 680 if (newDuration < 0) {
Chris@101 681 newFrame = frame;
Chris@101 682 newDuration = -newDuration;
Chris@101 683 } else if (newDuration == 0) {
Chris@101 684 newDuration = 1;
Chris@101 685 }
Chris@30 686
Chris@30 687 m_editingCommand->deletePoint(m_editingPoint);
Chris@101 688 m_editingPoint.frame = newFrame;
Chris@101 689 m_editingPoint.value = newValue;
Chris@101 690 m_editingPoint.duration = newDuration;
Chris@30 691 m_editingCommand->addPoint(m_editingPoint);
Chris@30 692 }
Chris@30 693
Chris@30 694 void
Chris@44 695 NoteLayer::drawEnd(View *v, QMouseEvent *e)
Chris@30 696 {
Chris@101 697 // std::cerr << "NoteLayer::drawEnd(" << e->x() << "," << e->y() << ")" << std::endl;
Chris@30 698 if (!m_model || !m_editing) return;
Chris@30 699 m_editingCommand->finish();
Chris@30 700 m_editingCommand = 0;
Chris@30 701 m_editing = false;
Chris@30 702 }
Chris@30 703
Chris@30 704 void
Chris@44 705 NoteLayer::editStart(View *v, QMouseEvent *e)
Chris@30 706 {
Chris@101 707 // std::cerr << "NoteLayer::editStart(" << e->x() << "," << e->y() << ")" << std::endl;
Chris@30 708
Chris@30 709 if (!m_model) return;
Chris@30 710
Chris@44 711 NoteModel::PointList points = getLocalPoints(v, e->x());
Chris@30 712 if (points.empty()) return;
Chris@30 713
Chris@30 714 m_editingPoint = *points.begin();
Chris@30 715 m_originalPoint = m_editingPoint;
Chris@30 716
Chris@30 717 if (m_editingCommand) {
Chris@30 718 m_editingCommand->finish();
Chris@30 719 m_editingCommand = 0;
Chris@30 720 }
Chris@30 721
Chris@30 722 m_editing = true;
Chris@30 723 }
Chris@30 724
Chris@30 725 void
Chris@44 726 NoteLayer::editDrag(View *v, QMouseEvent *e)
Chris@30 727 {
Chris@101 728 // std::cerr << "NoteLayer::editDrag(" << e->x() << "," << e->y() << ")" << std::endl;
Chris@30 729
Chris@30 730 if (!m_model || !m_editing) return;
Chris@30 731
Chris@44 732 long frame = v->getFrameForX(e->x());
Chris@30 733 if (frame < 0) frame = 0;
Chris@30 734 frame = frame / m_model->getResolution() * m_model->getResolution();
Chris@30 735
Chris@44 736 float value = getValueForY(v, e->y());
Chris@30 737
Chris@30 738 if (!m_editingCommand) {
Chris@30 739 m_editingCommand = new NoteModel::EditCommand(m_model,
Chris@30 740 tr("Drag Point"));
Chris@30 741 }
Chris@30 742
Chris@30 743 m_editingCommand->deletePoint(m_editingPoint);
Chris@30 744 m_editingPoint.frame = frame;
Chris@30 745 m_editingPoint.value = value;
Chris@30 746 m_editingCommand->addPoint(m_editingPoint);
Chris@30 747 }
Chris@30 748
Chris@30 749 void
Chris@44 750 NoteLayer::editEnd(View *v, QMouseEvent *e)
Chris@30 751 {
Chris@101 752 // std::cerr << "NoteLayer::editEnd(" << e->x() << "," << e->y() << ")" << std::endl;
Chris@30 753 if (!m_model || !m_editing) return;
Chris@30 754
Chris@30 755 if (m_editingCommand) {
Chris@30 756
Chris@30 757 QString newName = m_editingCommand->getName();
Chris@30 758
Chris@30 759 if (m_editingPoint.frame != m_originalPoint.frame) {
Chris@30 760 if (m_editingPoint.value != m_originalPoint.value) {
Chris@30 761 newName = tr("Edit Point");
Chris@30 762 } else {
Chris@30 763 newName = tr("Relocate Point");
Chris@30 764 }
Chris@30 765 } else {
Chris@30 766 newName = tr("Change Point Value");
Chris@30 767 }
Chris@30 768
Chris@30 769 m_editingCommand->setName(newName);
Chris@30 770 m_editingCommand->finish();
Chris@30 771 }
Chris@30 772
Chris@30 773 m_editingCommand = 0;
Chris@30 774 m_editing = false;
Chris@30 775 }
Chris@30 776
Chris@43 777 void
Chris@70 778 NoteLayer::editOpen(View *v, QMouseEvent *e)
Chris@70 779 {
Chris@70 780 if (!m_model) return;
Chris@70 781
Chris@70 782 NoteModel::PointList points = getLocalPoints(v, e->x());
Chris@70 783 if (points.empty()) return;
Chris@70 784
Chris@70 785 NoteModel::Point note = *points.begin();
Chris@70 786
Chris@70 787 ItemEditDialog *dialog = new ItemEditDialog
Chris@70 788 (m_model->getSampleRate(),
Chris@70 789 ItemEditDialog::ShowTime |
Chris@70 790 ItemEditDialog::ShowDuration |
Chris@70 791 ItemEditDialog::ShowValue |
Chris@100 792 ItemEditDialog::ShowText,
Chris@100 793 m_model->getScaleUnits());
Chris@70 794
Chris@70 795 dialog->setFrameTime(note.frame);
Chris@70 796 dialog->setValue(note.value);
Chris@70 797 dialog->setFrameDuration(note.duration);
Chris@70 798 dialog->setText(note.label);
Chris@70 799
Chris@70 800 if (dialog->exec() == QDialog::Accepted) {
Chris@70 801
Chris@70 802 NoteModel::Point newNote = note;
Chris@70 803 newNote.frame = dialog->getFrameTime();
Chris@70 804 newNote.value = dialog->getValue();
Chris@70 805 newNote.duration = dialog->getFrameDuration();
Chris@70 806 newNote.label = dialog->getText();
Chris@70 807
Chris@70 808 NoteModel::EditCommand *command = new NoteModel::EditCommand
Chris@70 809 (m_model, tr("Edit Point"));
Chris@70 810 command->deletePoint(note);
Chris@70 811 command->addPoint(newNote);
Chris@70 812 command->finish();
Chris@70 813 }
Chris@70 814
Chris@70 815 delete dialog;
Chris@70 816 }
Chris@70 817
Chris@70 818 void
Chris@43 819 NoteLayer::moveSelection(Selection s, size_t newStartFrame)
Chris@43 820 {
Chris@99 821 if (!m_model) return;
Chris@99 822
Chris@43 823 NoteModel::EditCommand *command =
Chris@43 824 new NoteModel::EditCommand(m_model, tr("Drag Selection"));
Chris@43 825
Chris@43 826 NoteModel::PointList points =
Chris@43 827 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@43 828
Chris@43 829 for (NoteModel::PointList::iterator i = points.begin();
Chris@43 830 i != points.end(); ++i) {
Chris@43 831
Chris@43 832 if (s.contains(i->frame)) {
Chris@43 833 NoteModel::Point newPoint(*i);
Chris@43 834 newPoint.frame = i->frame + newStartFrame - s.getStartFrame();
Chris@43 835 command->deletePoint(*i);
Chris@43 836 command->addPoint(newPoint);
Chris@43 837 }
Chris@43 838 }
Chris@43 839
Chris@43 840 command->finish();
Chris@43 841 }
Chris@43 842
Chris@43 843 void
Chris@43 844 NoteLayer::resizeSelection(Selection s, Selection newSize)
Chris@43 845 {
Chris@99 846 if (!m_model) return;
Chris@99 847
Chris@43 848 NoteModel::EditCommand *command =
Chris@43 849 new NoteModel::EditCommand(m_model, tr("Resize Selection"));
Chris@43 850
Chris@43 851 NoteModel::PointList points =
Chris@43 852 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@43 853
Chris@43 854 double ratio =
Chris@43 855 double(newSize.getEndFrame() - newSize.getStartFrame()) /
Chris@43 856 double(s.getEndFrame() - s.getStartFrame());
Chris@43 857
Chris@43 858 for (NoteModel::PointList::iterator i = points.begin();
Chris@43 859 i != points.end(); ++i) {
Chris@43 860
Chris@43 861 if (s.contains(i->frame)) {
Chris@43 862
Chris@43 863 double targetStart = i->frame;
Chris@43 864 targetStart = newSize.getStartFrame() +
Chris@43 865 double(targetStart - s.getStartFrame()) * ratio;
Chris@43 866
Chris@43 867 double targetEnd = i->frame + i->duration;
Chris@43 868 targetEnd = newSize.getStartFrame() +
Chris@43 869 double(targetEnd - s.getStartFrame()) * ratio;
Chris@43 870
Chris@43 871 NoteModel::Point newPoint(*i);
Chris@43 872 newPoint.frame = lrint(targetStart);
Chris@43 873 newPoint.duration = lrint(targetEnd - targetStart);
Chris@43 874 command->deletePoint(*i);
Chris@43 875 command->addPoint(newPoint);
Chris@43 876 }
Chris@43 877 }
Chris@43 878
Chris@43 879 command->finish();
Chris@43 880 }
Chris@43 881
Chris@76 882 void
Chris@76 883 NoteLayer::deleteSelection(Selection s)
Chris@76 884 {
Chris@99 885 if (!m_model) return;
Chris@99 886
Chris@76 887 NoteModel::EditCommand *command =
Chris@76 888 new NoteModel::EditCommand(m_model, tr("Delete Selected Points"));
Chris@76 889
Chris@76 890 NoteModel::PointList points =
Chris@76 891 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@76 892
Chris@76 893 for (NoteModel::PointList::iterator i = points.begin();
Chris@76 894 i != points.end(); ++i) {
Chris@76 895
Chris@76 896 if (s.contains(i->frame)) {
Chris@76 897 command->deletePoint(*i);
Chris@76 898 }
Chris@76 899 }
Chris@76 900
Chris@76 901 command->finish();
Chris@76 902 }
Chris@76 903
Chris@76 904 void
Chris@76 905 NoteLayer::copy(Selection s, Clipboard &to)
Chris@76 906 {
Chris@99 907 if (!m_model) return;
Chris@99 908
Chris@76 909 NoteModel::PointList points =
Chris@76 910 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@76 911
Chris@76 912 for (NoteModel::PointList::iterator i = points.begin();
Chris@76 913 i != points.end(); ++i) {
Chris@76 914 if (s.contains(i->frame)) {
Chris@83 915 Clipboard::Point point(i->frame, i->value, i->duration, i->label);
Chris@76 916 to.addPoint(point);
Chris@76 917 }
Chris@76 918 }
Chris@76 919 }
Chris@76 920
Chris@76 921 void
Chris@76 922 NoteLayer::paste(const Clipboard &from, int frameOffset)
Chris@76 923 {
Chris@99 924 if (!m_model) return;
Chris@99 925
Chris@76 926 const Clipboard::PointList &points = from.getPoints();
Chris@76 927
Chris@76 928 NoteModel::EditCommand *command =
Chris@76 929 new NoteModel::EditCommand(m_model, tr("Paste"));
Chris@76 930
Chris@76 931 for (Clipboard::PointList::const_iterator i = points.begin();
Chris@76 932 i != points.end(); ++i) {
Chris@76 933
Chris@76 934 if (!i->haveFrame()) continue;
Chris@76 935 size_t frame = 0;
Chris@76 936 if (frameOffset > 0 || -frameOffset < i->getFrame()) {
Chris@76 937 frame = i->getFrame() + frameOffset;
Chris@76 938 }
Chris@76 939 NoteModel::Point newPoint(frame);
Chris@76 940
Chris@76 941 if (i->haveLabel()) newPoint.label = i->getLabel();
Chris@76 942 if (i->haveValue()) newPoint.value = i->getValue();
Chris@76 943 else newPoint.value = (m_model->getValueMinimum() +
Chris@76 944 m_model->getValueMaximum()) / 2;
Chris@76 945 if (i->haveDuration()) newPoint.duration = i->getDuration();
Chris@76 946 else newPoint.duration = m_model->getResolution(); //!!!
Chris@76 947
Chris@76 948 command->addPoint(newPoint);
Chris@76 949 }
Chris@76 950
Chris@76 951 command->finish();
Chris@76 952 }
Chris@76 953
Chris@30 954 QString
Chris@30 955 NoteLayer::toXmlString(QString indent, QString extraAttributes) const
Chris@30 956 {
Chris@30 957 return Layer::toXmlString(indent, extraAttributes +
Chris@30 958 QString(" colour=\"%1\" verticalScale=\"%2\"")
Chris@30 959 .arg(encodeColour(m_colour)).arg(m_verticalScale));
Chris@30 960 }
Chris@30 961
Chris@30 962 void
Chris@30 963 NoteLayer::setProperties(const QXmlAttributes &attributes)
Chris@30 964 {
Chris@30 965 QString colourSpec = attributes.value("colour");
Chris@30 966 if (colourSpec != "") {
Chris@30 967 QColor colour(colourSpec);
Chris@30 968 if (colour.isValid()) {
Chris@30 969 setBaseColour(QColor(colourSpec));
Chris@30 970 }
Chris@30 971 }
Chris@30 972
Chris@30 973 bool ok;
Chris@30 974 VerticalScale scale = (VerticalScale)
Chris@30 975 attributes.value("verticalScale").toInt(&ok);
Chris@30 976 if (ok) setVerticalScale(scale);
Chris@30 977 }
Chris@30 978
Chris@30 979