annotate layer/FlexiNoteLayer.cpp @ 1553:76e4302a3fc2

Fix note numbering - ensure stable across whole track (as it used to be, but without scanning all notes in paint in order to do that)
author Chris Cannam
date Fri, 22 Nov 2019 14:12:50 +0000
parents e6362cf5ff1d
children
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
matthiasm@620 16 #include "FlexiNoteLayer.h"
Chris@30 17
Chris@128 18 #include "data/model/Model.h"
gyorgyf@655 19 #include "data/model/SparseTimeValueModel.h"
Chris@30 20 #include "base/RealTime.h"
Chris@30 21 #include "base/Profiler.h"
Chris@30 22 #include "base/Pitch.h"
Chris@197 23 #include "base/LogRange.h"
Chris@439 24 #include "base/RangeMapper.h"
Chris@1078 25
Chris@376 26 #include "ColourDatabase.h"
Chris@1077 27 #include "LayerGeometryProvider.h"
Chris@692 28 #include "PianoScale.h"
Chris@701 29 #include "LinearNumericalScale.h"
Chris@701 30 #include "LogNumericalScale.h"
Chris@1078 31 #include "PaintAssistant.h"
Chris@30 32
Chris@1426 33 #include "data/model/NoteModel.h"
Chris@30 34
Chris@1077 35 #include "view/View.h"
Chris@1077 36
Chris@70 37 #include "widgets/ItemEditDialog.h"
Chris@701 38 #include "widgets/TextAbbrev.h"
Chris@70 39
Chris@30 40 #include <QPainter>
Chris@30 41 #include <QPainterPath>
Chris@30 42 #include <QMouseEvent>
Chris@316 43 #include <QTextStream>
Chris@360 44 #include <QMessageBox>
Chris@30 45
Chris@30 46 #include <iostream>
Chris@30 47 #include <cmath>
Chris@551 48 #include <utility>
gyorgyf@655 49 #include <limits> // GF: included to compile std::numerical_limits on linux
gyorgyf@655 50 #include <vector>
gyorgyf@655 51
Chris@1426 52 #define NOTE_HEIGHT 16
Chris@30 53
matthiasm@620 54 FlexiNoteLayer::FlexiNoteLayer() :
gyorgyf@646 55 SingleColourLayer(),
gyorgyf@628 56 m_editing(false),
Chris@805 57 m_intelligentActions(true),
Chris@844 58 m_dragPointX(0),
Chris@844 59 m_dragPointY(0),
Chris@844 60 m_dragStartX(0),
Chris@844 61 m_dragStartY(0),
gyorgyf@627 62 m_originalPoint(0, 0.0, 0, 1.f, tr("New Point")),
gyorgyf@627 63 m_editingPoint(0, 0.0, 0, 1.f, tr("New Point")),
Chris@844 64 m_greatestLeftNeighbourFrame(0),
Chris@844 65 m_smallestRightNeighbourFrame(0),
Chris@1408 66 m_editingCommand(nullptr),
matthiasm@634 67 m_verticalScale(AutoAlignScale),
Chris@688 68 m_editMode(DragNote),
gyorgyf@628 69 m_scaleMinimum(34),
Chris@805 70 m_scaleMaximum(77)
Chris@30 71 {
Chris@30 72 }
Chris@30 73
Chris@30 74 void
Chris@1469 75 FlexiNoteLayer::setModel(ModelId modelId)
Chris@30 76 {
Chris@1471 77 auto newModel = ModelById::getAs<NoteModel>(modelId);
Chris@1471 78
Chris@1471 79 if (!modelId.isNone() && !newModel) {
Chris@1471 80 throw std::logic_error("Not a NoteModel");
Chris@1471 81 }
Chris@1471 82
Chris@1469 83 if (m_model == modelId) return;
Chris@1469 84 m_model = modelId;
Chris@30 85
Chris@1471 86 if (newModel) {
Chris@1471 87 connectSignals(m_model);
Chris@1471 88 }
Chris@30 89
Chris@30 90 emit modelReplaced();
Chris@30 91 }
Chris@30 92
Chris@30 93 Layer::PropertyList
matthiasm@620 94 FlexiNoteLayer::getProperties() const
Chris@30 95 {
Chris@287 96 PropertyList list = SingleColourLayer::getProperties();
Chris@87 97 list.push_back("Vertical Scale");
Chris@100 98 list.push_back("Scale Units");
Chris@30 99 return list;
Chris@30 100 }
Chris@30 101
Chris@87 102 QString
matthiasm@620 103 FlexiNoteLayer::getPropertyLabel(const PropertyName &name) const
Chris@87 104 {
Chris@87 105 if (name == "Vertical Scale") return tr("Vertical Scale");
Chris@116 106 if (name == "Scale Units") return tr("Scale Units");
Chris@287 107 return SingleColourLayer::getPropertyLabel(name);
Chris@87 108 }
Chris@87 109
Chris@30 110 Layer::PropertyType
matthiasm@620 111 FlexiNoteLayer::getPropertyType(const PropertyName &name) const
Chris@30 112 {
Chris@100 113 if (name == "Scale Units") return UnitsProperty;
Chris@287 114 if (name == "Vertical Scale") return ValueProperty;
Chris@287 115 return SingleColourLayer::getPropertyType(name);
Chris@30 116 }
Chris@30 117
Chris@198 118 QString
matthiasm@620 119 FlexiNoteLayer::getPropertyGroupName(const PropertyName &name) const
Chris@198 120 {
Chris@198 121 if (name == "Vertical Scale" || name == "Scale Units") {
Chris@198 122 return tr("Scale");
Chris@198 123 }
Chris@287 124 return SingleColourLayer::getPropertyGroupName(name);
Chris@198 125 }
Chris@198 126
Chris@701 127 QString
Chris@703 128 FlexiNoteLayer::getScaleUnits() const
Chris@701 129 {
Chris@1469 130 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 131 if (model) return model->getScaleUnits();
Chris@701 132 else return "";
Chris@701 133 }
Chris@701 134
Chris@30 135 int
matthiasm@620 136 FlexiNoteLayer::getPropertyRangeAndValue(const PropertyName &name,
Chris@714 137 int *min, int *max, int *deflt) const
Chris@30 138 {
Chris@216 139 int val = 0;
Chris@30 140
Chris@287 141 if (name == "Vertical Scale") {
gyorgyf@646 142
Chris@714 143 if (min) *min = 0;
Chris@714 144 if (max) *max = 3;
Chris@216 145 if (deflt) *deflt = int(AutoAlignScale);
gyorgyf@646 146
Chris@714 147 val = int(m_verticalScale);
Chris@30 148
Chris@100 149 } else if (name == "Scale Units") {
Chris@100 150
Chris@216 151 if (deflt) *deflt = 0;
Chris@1469 152 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 153 if (model) {
Chris@216 154 val = UnitDatabase::getInstance()->getUnitId
Chris@701 155 (getScaleUnits());
Chris@100 156 }
Chris@100 157
Chris@30 158 } else {
Chris@216 159
Chris@714 160 val = SingleColourLayer::getPropertyRangeAndValue(name, min, max, deflt);
Chris@30 161 }
Chris@30 162
Chris@216 163 return val;
Chris@30 164 }
Chris@30 165
Chris@30 166 QString
matthiasm@620 167 FlexiNoteLayer::getPropertyValueLabel(const PropertyName &name,
Chris@714 168 int value) const
Chris@30 169 {
Chris@287 170 if (name == "Vertical Scale") {
Chris@714 171 switch (value) {
Chris@714 172 default:
Chris@714 173 case 0: return tr("Auto-Align");
Chris@714 174 case 1: return tr("Linear");
Chris@714 175 case 2: return tr("Log");
Chris@714 176 case 3: return tr("MIDI Notes");
Chris@714 177 }
Chris@30 178 }
Chris@287 179 return SingleColourLayer::getPropertyValueLabel(name, value);
Chris@30 180 }
Chris@30 181
Chris@30 182 void
matthiasm@620 183 FlexiNoteLayer::setProperty(const PropertyName &name, int value)
Chris@30 184 {
Chris@287 185 if (name == "Vertical Scale") {
Chris@714 186 setVerticalScale(VerticalScale(value));
Chris@100 187 } else if (name == "Scale Units") {
Chris@1469 188 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 189 if (model) {
Chris@1469 190 model->setScaleUnits
Chris@100 191 (UnitDatabase::getInstance()->getUnitById(value));
Chris@1481 192 emit modelChanged(m_model);
Chris@100 193 }
Chris@287 194 } else {
Chris@287 195 return SingleColourLayer::setProperty(name, value);
Chris@30 196 }
Chris@30 197 }
Chris@30 198
Chris@30 199 void
matthiasm@620 200 FlexiNoteLayer::setVerticalScale(VerticalScale scale)
Chris@30 201 {
Chris@30 202 if (m_verticalScale == scale) return;
Chris@30 203 m_verticalScale = scale;
Chris@30 204 emit layerParametersChanged();
Chris@30 205 }
Chris@30 206
Chris@30 207 bool
Chris@916 208 FlexiNoteLayer::isLayerScrollable(const LayerGeometryProvider *v) const
Chris@30 209 {
Chris@30 210 QPoint discard;
Chris@44 211 return !v->shouldIlluminateLocalFeatures(this, discard);
Chris@30 212 }
Chris@30 213
Chris@79 214 bool
matthiasm@620 215 FlexiNoteLayer::shouldConvertMIDIToHz() const
Chris@101 216 {
Chris@701 217 QString unit = getScaleUnits();
Chris@101 218 return (unit != "Hz");
Chris@101 219 // if (unit == "" ||
Chris@101 220 // unit.startsWith("MIDI") ||
Chris@101 221 // unit.startsWith("midi")) return true;
Chris@101 222 // return false;
Chris@101 223 }
Chris@101 224
Chris@1469 225 int
Chris@1469 226 FlexiNoteLayer::getCompletion(LayerGeometryProvider *) const
Chris@1469 227 {
Chris@1469 228 auto model = ModelById::get(m_model);
Chris@1469 229 if (model) return model->getCompletion();
Chris@1469 230 else return 0;
Chris@1469 231 }
Chris@1469 232
Chris@101 233 bool
Chris@904 234 FlexiNoteLayer::getValueExtents(double &min, double &max,
Chris@714 235 bool &logarithmic, QString &unit) const
Chris@79 236 {
Chris@1469 237 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 238 if (!model) return false;
Chris@1469 239 min = model->getValueMinimum();
Chris@1469 240 max = model->getValueMaximum();
Chris@101 241
Chris@105 242 if (shouldConvertMIDIToHz()) {
Chris@105 243 unit = "Hz";
Chris@904 244 min = Pitch::getFrequencyForPitch(int(lrint(min)));
Chris@904 245 max = Pitch::getFrequencyForPitch(int(lrint(max + 1)));
Chris@701 246 } else unit = getScaleUnits();
Chris@101 247
Chris@101 248 if (m_verticalScale == MIDIRangeScale ||
Chris@101 249 m_verticalScale == LogScale) logarithmic = true;
Chris@101 250
Chris@101 251 return true;
Chris@101 252 }
Chris@101 253
Chris@101 254 bool
Chris@904 255 FlexiNoteLayer::getDisplayExtents(double &min, double &max) const
Chris@101 256 {
Chris@1469 257 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 258 if (!model || shouldAutoAlign()) {
Chris@695 259 // std::cerr << "No model or shouldAutoAlign()" << std::endl;
gyorgyf@646 260 return false;
gyorgyf@646 261 }
Chris@101 262
Chris@101 263 if (m_verticalScale == MIDIRangeScale) {
Chris@101 264 min = Pitch::getFrequencyForPitch(0);
matthiasm@634 265 max = Pitch::getFrequencyForPitch(127);
Chris@101 266 return true;
Chris@101 267 }
Chris@101 268
Chris@439 269 if (m_scaleMinimum == m_scaleMaximum) {
Chris@1469 270 min = model->getValueMinimum();
Chris@1469 271 max = model->getValueMaximum();
Chris@455 272 } else {
Chris@455 273 min = m_scaleMinimum;
Chris@455 274 max = m_scaleMaximum;
Chris@439 275 }
Chris@439 276
Chris@101 277 if (shouldConvertMIDIToHz()) {
Chris@904 278 min = Pitch::getFrequencyForPitch(int(lrint(min)));
Chris@904 279 max = Pitch::getFrequencyForPitch(int(lrint(max + 1)));
Chris@101 280 }
Chris@101 281
Chris@667 282 #ifdef DEBUG_NOTE_LAYER
Chris@682 283 cerr << "NoteLayer::getDisplayExtents: min = " << min << ", max = " << max << " (m_scaleMinimum = " << m_scaleMinimum << ", m_scaleMaximum = " << m_scaleMaximum << ")" << endl;
Chris@667 284 #endif
Chris@667 285
Chris@79 286 return true;
Chris@79 287 }
Chris@79 288
Chris@439 289 bool
Chris@904 290 FlexiNoteLayer::setDisplayExtents(double min, double max)
Chris@439 291 {
Chris@1469 292 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 293 if (!model) return false;
Chris@439 294
Chris@439 295 if (min == max) {
Chris@439 296 if (min == 0.f) {
Chris@439 297 max = 1.f;
Chris@439 298 } else {
Chris@904 299 max = min * 1.0001f;
Chris@439 300 }
Chris@439 301 }
Chris@439 302
Chris@439 303 m_scaleMinimum = min;
Chris@439 304 m_scaleMaximum = max;
Chris@439 305
Chris@667 306 #ifdef DEBUG_NOTE_LAYER
Chris@684 307 cerr << "FlexiNoteLayer::setDisplayExtents: min = " << min << ", max = " << max << endl;
Chris@667 308 #endif
Chris@439 309
Chris@439 310 emit layerParametersChanged();
Chris@439 311 return true;
Chris@439 312 }
Chris@439 313
Chris@439 314 int
matthiasm@620 315 FlexiNoteLayer::getVerticalZoomSteps(int &defaultStep) const
Chris@439 316 {
Chris@439 317 if (shouldAutoAlign()) return 0;
Chris@1469 318 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 319 if (!model) return 0;
Chris@439 320
Chris@439 321 defaultStep = 0;
Chris@439 322 return 100;
Chris@439 323 }
Chris@439 324
Chris@439 325 int
matthiasm@620 326 FlexiNoteLayer::getCurrentVerticalZoomStep() const
Chris@439 327 {
Chris@439 328 if (shouldAutoAlign()) return 0;
Chris@1469 329 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 330 if (!model) return 0;
Chris@439 331
Chris@439 332 RangeMapper *mapper = getNewVerticalZoomRangeMapper();
Chris@439 333 if (!mapper) return 0;
Chris@439 334
Chris@904 335 double dmin, dmax;
Chris@439 336 getDisplayExtents(dmin, dmax);
Chris@439 337
Chris@439 338 int nr = mapper->getPositionForValue(dmax - dmin);
Chris@439 339
Chris@439 340 delete mapper;
Chris@439 341
Chris@439 342 return 100 - nr;
Chris@439 343 }
Chris@439 344
Chris@439 345 //!!! lots of duplication with TimeValueLayer
Chris@439 346
Chris@439 347 void
matthiasm@620 348 FlexiNoteLayer::setVerticalZoomStep(int step)
Chris@439 349 {
Chris@439 350 if (shouldAutoAlign()) return;
Chris@1469 351 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 352 if (!model) return;
Chris@439 353
Chris@439 354 RangeMapper *mapper = getNewVerticalZoomRangeMapper();
Chris@439 355 if (!mapper) return;
Chris@439 356
Chris@904 357 double min, max;
Chris@439 358 bool logarithmic;
Chris@439 359 QString unit;
Chris@439 360 getValueExtents(min, max, logarithmic, unit);
Chris@439 361
Chris@904 362 double dmin, dmax;
Chris@439 363 getDisplayExtents(dmin, dmax);
Chris@439 364
Chris@904 365 double newdist = mapper->getValueForPosition(100 - step);
Chris@439 366
Chris@904 367 double newmin, newmax;
Chris@439 368
Chris@439 369 if (logarithmic) {
Chris@439 370
Chris@439 371 // see SpectrogramLayer::setVerticalZoomStep
Chris@439 372
Chris@904 373 newmax = (newdist + sqrt(newdist*newdist + 4*dmin*dmax)) / 2;
Chris@439 374 newmin = newmax - newdist;
Chris@439 375
Chris@682 376 // cerr << "newmin = " << newmin << ", newmax = " << newmax << endl;
Chris@439 377
Chris@439 378 } else {
Chris@904 379 double dmid = (dmax + dmin) / 2;
Chris@439 380 newmin = dmid - newdist / 2;
Chris@439 381 newmax = dmid + newdist / 2;
Chris@439 382 }
Chris@439 383
Chris@439 384 if (newmin < min) {
Chris@439 385 newmax += (min - newmin);
Chris@439 386 newmin = min;
Chris@439 387 }
Chris@439 388 if (newmax > max) {
Chris@439 389 newmax = max;
Chris@439 390 }
Chris@439 391
Chris@667 392 #ifdef DEBUG_NOTE_LAYER
Chris@684 393 cerr << "FlexiNoteLayer::setVerticalZoomStep: " << step << ": " << newmin << " -> " << newmax << " (range " << newdist << ")" << endl;
Chris@667 394 #endif
Chris@439 395
Chris@439 396 setDisplayExtents(newmin, newmax);
Chris@439 397 }
Chris@439 398
Chris@439 399 RangeMapper *
matthiasm@620 400 FlexiNoteLayer::getNewVerticalZoomRangeMapper() const
Chris@439 401 {
Chris@1469 402 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 403 if (!model) return nullptr;
Chris@439 404
Chris@439 405 RangeMapper *mapper;
Chris@439 406
Chris@904 407 double min, max;
Chris@439 408 bool logarithmic;
Chris@439 409 QString unit;
Chris@439 410 getValueExtents(min, max, logarithmic, unit);
Chris@439 411
Chris@1408 412 if (min == max) return nullptr;
Chris@439 413
Chris@439 414 if (logarithmic) {
Chris@439 415 mapper = new LogRangeMapper(0, 100, min, max, unit);
Chris@439 416 } else {
Chris@439 417 mapper = new LinearRangeMapper(0, 100, min, max, unit);
Chris@439 418 }
Chris@439 419
Chris@439 420 return mapper;
Chris@439 421 }
Chris@439 422
Chris@1426 423 EventVector
Chris@916 424 FlexiNoteLayer::getLocalPoints(LayerGeometryProvider *v, int x) const
Chris@30 425 {
Chris@1469 426 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 427 if (!model) return {};
Chris@1426 428
Chris@904 429 sv_frame_t frame = v->getFrameForX(x);
Chris@30 430
Chris@1469 431 EventVector local = model->getEventsCovering(frame);
Chris@1426 432 if (!local.empty()) return local;
Chris@30 433
Chris@1426 434 int fuzz = ViewManager::scalePixelSize(2);
Chris@1426 435 sv_frame_t start = v->getFrameForX(x - fuzz);
Chris@1426 436 sv_frame_t end = v->getFrameForX(x + fuzz);
Chris@30 437
Chris@1469 438 local = model->getEventsStartingWithin(frame, end - frame);
Chris@1426 439 if (!local.empty()) return local;
Chris@30 440
Chris@1469 441 local = model->getEventsSpanning(start, frame - start);
Chris@1426 442 if (!local.empty()) return local;
Chris@30 443
Chris@1426 444 return {};
Chris@30 445 }
Chris@30 446
Chris@550 447 bool
Chris@1426 448 FlexiNoteLayer::getPointToDrag(LayerGeometryProvider *v, int x, int y, Event &point) const
Chris@550 449 {
Chris@1469 450 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 451 if (!model) return false;
Chris@550 452
Chris@904 453 sv_frame_t frame = v->getFrameForX(x);
Chris@550 454
Chris@1469 455 EventVector onPoints = model->getEventsCovering(frame);
Chris@550 456 if (onPoints.empty()) return false;
Chris@550 457
Chris@550 458 int nearestDistance = -1;
Chris@1426 459 for (const auto &p: onPoints) {
Chris@1426 460 int distance = getYForValue(v, p.getValue()) - y;
Chris@550 461 if (distance < 0) distance = -distance;
Chris@550 462 if (nearestDistance == -1 || distance < nearestDistance) {
Chris@550 463 nearestDistance = distance;
Chris@1426 464 point = p;
Chris@550 465 }
Chris@550 466 }
Chris@550 467
Chris@550 468 return true;
Chris@550 469 }
Chris@550 470
gyorgyf@646 471 bool
Chris@1426 472 FlexiNoteLayer::getNoteToEdit(LayerGeometryProvider *v, int x, int y, Event &point) const
gyorgyf@646 473 {
gyorgyf@647 474 // GF: find the note that is closest to the cursor
Chris@1469 475 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 476 if (!model) return false;
gyorgyf@646 477
Chris@904 478 sv_frame_t frame = v->getFrameForX(x);
gyorgyf@646 479
Chris@1469 480 EventVector onPoints = model->getEventsCovering(frame);
gyorgyf@646 481 if (onPoints.empty()) return false;
gyorgyf@646 482
gyorgyf@646 483 int nearestDistance = -1;
Chris@1426 484 for (const auto &p: onPoints) {
Chris@1426 485 int distance = getYForValue(v, p.getValue()) - y;
gyorgyf@646 486 if (distance < 0) distance = -distance;
gyorgyf@646 487 if (nearestDistance == -1 || distance < nearestDistance) {
gyorgyf@646 488 nearestDistance = distance;
Chris@1426 489 point = p;
gyorgyf@646 490 }
gyorgyf@646 491 }
gyorgyf@646 492
gyorgyf@646 493 return true;
gyorgyf@646 494 }
gyorgyf@646 495
Chris@30 496 QString
Chris@916 497 FlexiNoteLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const
Chris@30 498 {
Chris@30 499 int x = pos.x();
Chris@30 500
Chris@1469 501 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 502 if (!model || !model->getSampleRate()) return "";
Chris@30 503
Chris@1426 504 EventVector points = getLocalPoints(v, x);
Chris@30 505
Chris@30 506 if (points.empty()) {
Chris@1469 507 if (!model->isReady()) {
Chris@714 508 return tr("In progress");
Chris@714 509 } else {
Chris@714 510 return tr("No local points");
Chris@714 511 }
Chris@30 512 }
Chris@30 513
Chris@1426 514 Event note(0);
Chris@1426 515 EventVector::iterator i;
Chris@30 516
Chris@30 517 for (i = points.begin(); i != points.end(); ++i) {
Chris@30 518
Chris@1426 519 int y = getYForValue(v, i->getValue());
Chris@714 520 int h = NOTE_HEIGHT; // GF: larger notes
Chris@30 521
Chris@1469 522 if (model->getValueQuantization() != 0.0) {
Chris@1426 523 h = y - getYForValue
Chris@1469 524 (v, i->getValue() + model->getValueQuantization());
Chris@714 525 if (h < NOTE_HEIGHT) h = NOTE_HEIGHT;
Chris@714 526 }
Chris@30 527
Chris@714 528 // GF: this is not quite correct
Chris@714 529 if (pos.y() >= y - 4 && pos.y() <= y + h) {
Chris@714 530 note = *i;
Chris@714 531 break;
Chris@714 532 }
Chris@30 533 }
Chris@30 534
Chris@30 535 if (i == points.end()) return tr("No local points");
Chris@30 536
Chris@1426 537 RealTime rt = RealTime::frame2RealTime(note.getFrame(),
Chris@1469 538 model->getSampleRate());
Chris@1426 539 RealTime rd = RealTime::frame2RealTime(note.getDuration(),
Chris@1469 540 model->getSampleRate());
Chris@30 541
Chris@101 542 QString pitchText;
Chris@101 543
Chris@101 544 if (shouldConvertMIDIToHz()) {
Chris@101 545
Chris@1426 546 int mnote = int(lrint(note.getValue()));
Chris@1426 547 int cents = int(lrint((note.getValue() - double(mnote)) * 100));
Chris@904 548 double freq = Pitch::getFrequencyForPitch(mnote, cents);
Chris@544 549 pitchText = tr("%1 (%2, %3 Hz)")
Chris@544 550 .arg(Pitch::getPitchLabel(mnote, cents))
Chris@544 551 .arg(mnote)
Chris@544 552 .arg(freq);
Chris@101 553
Chris@701 554 } else if (getScaleUnits() == "Hz") {
Chris@101 555
Chris@544 556 pitchText = tr("%1 Hz (%2, %3)")
Chris@1426 557 .arg(note.getValue())
Chris@1426 558 .arg(Pitch::getPitchLabelForFrequency(note.getValue()))
Chris@1426 559 .arg(Pitch::getPitchForFrequency(note.getValue()));
Chris@101 560
Chris@101 561 } else {
Chris@234 562 pitchText = tr("%1 %2")
Chris@1426 563 .arg(note.getValue()).arg(getScaleUnits());
Chris@101 564 }
Chris@101 565
Chris@30 566 QString text;
Chris@30 567
Chris@1426 568 if (note.getLabel() == "") {
Chris@714 569 text = QString(tr("Time:\t%1\nPitch:\t%2\nDuration:\t%3\nNo label"))
Chris@714 570 .arg(rt.toText(true).c_str())
Chris@714 571 .arg(pitchText)
Chris@714 572 .arg(rd.toText(true).c_str());
Chris@30 573 } else {
Chris@714 574 text = QString(tr("Time:\t%1\nPitch:\t%2\nDuration:\t%3\nLabel:\t%4"))
Chris@714 575 .arg(rt.toText(true).c_str())
Chris@714 576 .arg(pitchText)
Chris@714 577 .arg(rd.toText(true).c_str())
Chris@1426 578 .arg(note.getLabel());
Chris@30 579 }
Chris@30 580
Chris@1426 581 pos = QPoint(v->getXForFrame(note.getFrame()),
Chris@1426 582 getYForValue(v, note.getValue()));
Chris@30 583 return text;
Chris@30 584 }
Chris@30 585
Chris@30 586 bool
Chris@916 587 FlexiNoteLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
Chris@805 588 int &resolution,
Chris@1547 589 SnapType snap, int ycoord) const
Chris@30 590 {
Chris@1469 591 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 592 if (!model) {
Chris@1547 593 return Layer::snapToFeatureFrame(v, frame, resolution, snap, ycoord);
Chris@30 594 }
Chris@30 595
Chris@1469 596 resolution = model->getResolution();
Chris@1426 597 EventVector points;
Chris@30 598
Chris@30 599 if (snap == SnapNeighbouring) {
gyorgyf@646 600
Chris@714 601 points = getLocalPoints(v, v->getXForFrame(frame));
Chris@714 602 if (points.empty()) return false;
Chris@1426 603 frame = points.begin()->getFrame();
Chris@714 604 return true;
Chris@30 605 }
Chris@30 606
Chris@1469 607 points = model->getEventsCovering(frame);
Chris@904 608 sv_frame_t snapped = frame;
Chris@30 609 bool found = false;
Chris@30 610
Chris@1426 611 for (EventVector::const_iterator i = points.begin();
Chris@714 612 i != points.end(); ++i) {
Chris@30 613
Chris@714 614 if (snap == SnapRight) {
Chris@30 615
Chris@1426 616 if (i->getFrame() > frame) {
Chris@1426 617 snapped = i->getFrame();
Chris@714 618 found = true;
Chris@714 619 break;
Chris@1426 620 } else if (i->getFrame() + i->getDuration() >= frame) {
Chris@1426 621 snapped = i->getFrame() + i->getDuration();
Chris@715 622 found = true;
Chris@715 623 break;
Chris@714 624 }
Chris@714 625
Chris@714 626 } else if (snap == SnapLeft) {
Chris@714 627
Chris@1426 628 if (i->getFrame() <= frame) {
Chris@1426 629 snapped = i->getFrame();
Chris@714 630 found = true; // don't break, as the next may be better
Chris@714 631 } else {
Chris@714 632 break;
Chris@714 633 }
Chris@714 634
Chris@714 635 } else { // nearest
Chris@714 636
Chris@1426 637 EventVector::const_iterator j = i;
Chris@714 638 ++j;
Chris@714 639
Chris@714 640 if (j == points.end()) {
Chris@714 641
Chris@1426 642 snapped = i->getFrame();
Chris@714 643 found = true;
Chris@714 644 break;
Chris@714 645
Chris@1426 646 } else if (j->getFrame() >= frame) {
Chris@714 647
Chris@1426 648 if (j->getFrame() - frame < frame - i->getFrame()) {
Chris@1426 649 snapped = j->getFrame();
Chris@714 650 } else {
Chris@1426 651 snapped = i->getFrame();
Chris@714 652 }
Chris@714 653 found = true;
Chris@714 654 break;
Chris@714 655 }
gyorgyf@646 656 }
Chris@30 657 }
Chris@30 658
Chris@715 659 cerr << "snapToFeatureFrame: frame " << frame << " -> snapped " << snapped << ", found = " << found << endl;
Chris@715 660
Chris@30 661 frame = snapped;
Chris@30 662 return found;
Chris@30 663 }
Chris@30 664
Chris@101 665 void
Chris@916 666 FlexiNoteLayer::getScaleExtents(LayerGeometryProvider *v, double &min, double &max, bool &log) const
Chris@30 667 {
Chris@101 668 min = 0.0;
Chris@101 669 max = 0.0;
Chris@101 670 log = false;
Chris@42 671
Chris@101 672 QString queryUnits;
Chris@101 673 if (shouldConvertMIDIToHz()) queryUnits = "Hz";
Chris@701 674 else queryUnits = getScaleUnits();
Chris@30 675
Chris@439 676 if (shouldAutoAlign()) {
Chris@30 677
Chris@1537 678 if (!v->getVisibleExtentsForUnit(queryUnits, min, max, log)) {
Chris@30 679
Chris@1469 680 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 681 min = model->getValueMinimum();
Chris@1469 682 max = model->getValueMaximum();
Chris@42 683
Chris@101 684 if (shouldConvertMIDIToHz()) {
Chris@904 685 min = Pitch::getFrequencyForPitch(int(lrint(min)));
Chris@904 686 max = Pitch::getFrequencyForPitch(int(lrint(max + 1)));
Chris@101 687 }
Chris@42 688
Chris@667 689 #ifdef DEBUG_NOTE_LAYER
Chris@684 690 cerr << "FlexiNoteLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl;
Chris@667 691 #endif
Chris@105 692
Chris@101 693 } else if (log) {
Chris@101 694
Chris@197 695 LogRange::mapRange(min, max);
Chris@105 696
Chris@667 697 #ifdef DEBUG_NOTE_LAYER
Chris@684 698 cerr << "FlexiNoteLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl;
Chris@667 699 #endif
Chris@101 700 }
Chris@101 701
Chris@101 702 } else {
Chris@101 703
Chris@439 704 getDisplayExtents(min, max);
Chris@101 705
Chris@101 706 if (m_verticalScale == MIDIRangeScale) {
Chris@101 707 min = Pitch::getFrequencyForPitch(0);
matthiasm@623 708 max = Pitch::getFrequencyForPitch(70);
Chris@101 709 } else if (shouldConvertMIDIToHz()) {
Chris@904 710 min = Pitch::getFrequencyForPitch(int(lrint(min)));
Chris@904 711 max = Pitch::getFrequencyForPitch(int(lrint(max + 1)));
Chris@101 712 }
Chris@101 713
Chris@101 714 if (m_verticalScale == LogScale || m_verticalScale == MIDIRangeScale) {
Chris@197 715 LogRange::mapRange(min, max);
Chris@101 716 log = true;
Chris@101 717 }
Chris@30 718 }
Chris@30 719
Chris@101 720 if (max == min) max = min + 1.0;
Chris@101 721 }
Chris@30 722
Chris@101 723 int
Chris@916 724 FlexiNoteLayer::getYForValue(LayerGeometryProvider *v, double val) const
Chris@101 725 {
Chris@904 726 double min = 0.0, max = 0.0;
Chris@101 727 bool logarithmic = false;
Chris@916 728 int h = v->getPaintHeight();
Chris@101 729
Chris@101 730 getScaleExtents(v, min, max, logarithmic);
Chris@101 731
Chris@667 732 #ifdef DEBUG_NOTE_LAYER
Chris@684 733 cerr << "FlexiNoteLayer[" << this << "]::getYForValue(" << val << "): min = " << min << ", max = " << max << ", log = " << logarithmic << endl;
Chris@667 734 #endif
Chris@101 735
Chris@101 736 if (shouldConvertMIDIToHz()) {
Chris@904 737 val = Pitch::getFrequencyForPitch(int(lrint(val)),
Chris@904 738 int(lrint((val - floor(val)) * 100.0)));
Chris@667 739 #ifdef DEBUG_NOTE_LAYER
Chris@682 740 cerr << "shouldConvertMIDIToHz true, val now = " << val << endl;
Chris@667 741 #endif
Chris@101 742 }
Chris@101 743
Chris@101 744 if (logarithmic) {
Chris@197 745 val = LogRange::map(val);
Chris@667 746 #ifdef DEBUG_NOTE_LAYER
Chris@682 747 cerr << "logarithmic true, val now = " << val << endl;
Chris@667 748 #endif
Chris@101 749 }
Chris@101 750
Chris@101 751 int y = int(h - ((val - min) * h) / (max - min)) - 1;
Chris@667 752 #ifdef DEBUG_NOTE_LAYER
Chris@682 753 cerr << "y = " << y << endl;
Chris@667 754 #endif
Chris@101 755 return y;
Chris@30 756 }
Chris@30 757
Chris@904 758 double
Chris@916 759 FlexiNoteLayer::getValueForY(LayerGeometryProvider *v, int y) const
Chris@30 760 {
Chris@904 761 double min = 0.0, max = 0.0;
Chris@101 762 bool logarithmic = false;
Chris@916 763 int h = v->getPaintHeight();
Chris@30 764
Chris@101 765 getScaleExtents(v, min, max, logarithmic);
Chris@101 766
Chris@904 767 double val = min + (double(h - y) * double(max - min)) / h;
Chris@101 768
Chris@101 769 if (logarithmic) {
Chris@904 770 val = pow(10.f, val);
Chris@101 771 }
Chris@101 772
Chris@101 773 if (shouldConvertMIDIToHz()) {
Chris@101 774 val = Pitch::getPitchForFrequency(val);
Chris@101 775 }
Chris@101 776
Chris@101 777 return val;
Chris@30 778 }
Chris@30 779
Chris@439 780 bool
matthiasm@620 781 FlexiNoteLayer::shouldAutoAlign() const
Chris@439 782 {
Chris@439 783 return (m_verticalScale == AutoAlignScale);
Chris@439 784 }
Chris@439 785
Chris@30 786 void
Chris@916 787 FlexiNoteLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
Chris@30 788 {
Chris@1469 789 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 790 if (!model || !model->isOK()) return;
Chris@30 791
Chris@1469 792 sv_samplerate_t sampleRate = model->getSampleRate();
Chris@30 793 if (!sampleRate) return;
Chris@30 794
matthiasm@620 795 // Profiler profiler("FlexiNoteLayer::paint", true);
Chris@30 796
Chris@1426 797 int x0 = rect.left(), x1 = rect.right();
Chris@1426 798 sv_frame_t frame0 = v->getFrameForX(x0);
Chris@904 799 sv_frame_t frame1 = v->getFrameForX(x1);
Chris@30 800
Chris@1469 801 EventVector points(model->getEventsSpanning(frame0, frame1 - frame0));
Chris@30 802 if (points.empty()) return;
Chris@30 803
Chris@287 804 paint.setPen(getBaseQColor());
Chris@30 805
Chris@287 806 QColor brushColour(getBaseQColor());
Chris@30 807 brushColour.setAlpha(80);
Chris@30 808
matthiasm@620 809 // SVDEBUG << "FlexiNoteLayer::paint: resolution is "
Chris@1469 810 // << model->getResolution() << " frames" << endl;
Chris@30 811
Chris@1469 812 double min = model->getValueMinimum();
Chris@1469 813 double max = model->getValueMaximum();
Chris@30 814 if (max == min) max = min + 1.0;
Chris@30 815
Chris@30 816 QPoint localPos;
Chris@1426 817 Event illuminatePoint(0);
Chris@551 818 bool shouldIlluminate = false;
Chris@30 819
Chris@44 820 if (v->shouldIlluminateLocalFeatures(this, localPos)) {
Chris@551 821 shouldIlluminate = getPointToDrag(v, localPos.x(), localPos.y(),
Chris@551 822 illuminatePoint);
Chris@30 823 }
Chris@30 824
Chris@30 825 paint.save();
Chris@30 826 paint.setRenderHint(QPainter::Antialiasing, false);
Chris@1553 827
Chris@1553 828 int noteNumber = -1;
matthiasm@819 829
Chris@1426 830 for (EventVector::const_iterator i = points.begin();
Chris@714 831 i != points.end(); ++i) {
Chris@30 832
Chris@1426 833 const Event &p(*i);
Chris@30 834
Chris@1553 835 if (noteNumber < 0) {
Chris@1553 836 noteNumber = model->getIndexForEvent(p);
Chris@1553 837 } else {
Chris@1553 838 noteNumber ++;
Chris@1553 839 }
Chris@1553 840
Chris@1426 841 int x = v->getXForFrame(p.getFrame());
Chris@1426 842 int y = getYForValue(v, p.getValue());
Chris@1426 843 int w = v->getXForFrame(p.getFrame() + p.getDuration()) - x;
Chris@714 844 int h = NOTE_HEIGHT; //GF: larger notes
gyorgyf@646 845
Chris@1469 846 if (model->getValueQuantization() != 0.0) {
Chris@1469 847 h = y - getYForValue(v, p.getValue() + model->getValueQuantization());
Chris@714 848 if (h < NOTE_HEIGHT) h = NOTE_HEIGHT; //GF: larger notes
Chris@714 849 }
Chris@30 850
Chris@714 851 if (w < 1) w = 1;
Chris@714 852 paint.setPen(getBaseQColor());
Chris@714 853 paint.setBrush(brushColour);
Chris@30 854
Chris@1426 855 if (shouldIlluminate && illuminatePoint == p) {
matthiasm@784 856
Chris@1426 857 paint.drawLine(x, -1, x, v->getPaintHeight() + 1);
Chris@1426 858 paint.drawLine(x+w, -1, x+w, v->getPaintHeight() + 1);
matthiasm@784 859
Chris@1426 860 paint.setPen(v->getForeground());
matthiasm@784 861
Chris@1426 862 QString vlabel = tr("freq: %1%2")
Chris@1469 863 .arg(p.getValue()).arg(model->getScaleUnits());
Chris@1426 864 PaintAssistant::drawVisibleText
Chris@1426 865 (v, paint,
Chris@1426 866 x,
Chris@1426 867 y - h/2 - 2 - paint.fontMetrics().height()
Chris@1426 868 - paint.fontMetrics().descent(),
Chris@1426 869 vlabel, PaintAssistant::OutlinedText);
matthiasm@793 870
Chris@1426 871 QString hlabel = tr("dur: %1")
Chris@1426 872 .arg(RealTime::frame2RealTime
Chris@1469 873 (p.getDuration(), model->getSampleRate()).toText(true)
Chris@1426 874 .c_str());
Chris@1426 875 PaintAssistant::drawVisibleText
Chris@1426 876 (v, paint,
Chris@1426 877 x,
Chris@1426 878 y - h/2 - paint.fontMetrics().descent() - 2,
Chris@1426 879 hlabel, PaintAssistant::OutlinedText);
matthiasm@793 880
Chris@1426 881 QString llabel = QString("%1").arg(p.getLabel());
Chris@1426 882 PaintAssistant::drawVisibleText
Chris@1426 883 (v, paint,
Chris@1426 884 x,
Chris@1426 885 y + h + 2 + paint.fontMetrics().descent(),
Chris@1426 886 llabel, PaintAssistant::OutlinedText);
Chris@1426 887
Chris@1426 888 QString nlabel = QString("%1").arg(noteNumber);
Chris@1426 889 PaintAssistant::drawVisibleText
Chris@1426 890 (v, paint,
Chris@1426 891 x + paint.fontMetrics().averageCharWidth() / 2,
Chris@1426 892 y + h/2 - paint.fontMetrics().descent(),
Chris@1426 893 nlabel, PaintAssistant::OutlinedText);
matthiasm@784 894 }
gyorgyf@646 895
Chris@714 896 paint.drawRect(x, y - h/2, w, h);
Chris@30 897 }
Chris@30 898
Chris@30 899 paint.restore();
Chris@30 900 }
Chris@30 901
Chris@692 902 int
Chris@916 903 FlexiNoteLayer::getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &paint) const
Chris@692 904 {
Chris@1469 905 if (shouldAutoAlign()) {
Chris@696 906 return 0;
Chris@701 907 } else {
Chris@701 908 if (m_verticalScale == LogScale || m_verticalScale == MIDIRangeScale) {
Chris@701 909 return LogNumericalScale().getWidth(v, paint) + 10; // for piano
Chris@701 910 } else {
Chris@701 911 return LinearNumericalScale().getWidth(v, paint);
Chris@701 912 }
Chris@696 913 }
Chris@692 914 }
Chris@692 915
Chris@692 916 void
Chris@916 917 FlexiNoteLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect) const
Chris@692 918 {
Chris@1469 919 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 920 if (!model || model->isEmpty()) return;
Chris@701 921
Chris@701 922 QString unit;
Chris@904 923 double min, max;
Chris@701 924 bool logarithmic;
Chris@701 925
Chris@701 926 int w = getVerticalScaleWidth(v, false, paint);
Chris@916 927 int h = v->getPaintHeight();
Chris@701 928
Chris@701 929 getScaleExtents(v, min, max, logarithmic);
Chris@701 930
Chris@701 931 if (logarithmic) {
Chris@701 932 LogNumericalScale().paintVertical(v, this, paint, 0, min, max);
Chris@696 933 } else {
Chris@701 934 LinearNumericalScale().paintVertical(v, this, paint, 0, min, max);
Chris@701 935 }
Chris@701 936
Chris@701 937 if (logarithmic && (getScaleUnits() == "Hz")) {
Chris@696 938 PianoScale().paintPianoVertical
Chris@701 939 (v, paint, QRect(w - 10, 0, 10, h),
Chris@701 940 LogRange::unmap(min),
Chris@701 941 LogRange::unmap(max));
Chris@701 942 paint.drawLine(w, 0, w, h);
Chris@701 943 }
Chris@701 944
Chris@701 945 if (getScaleUnits() != "") {
Chris@701 946 int mw = w - 5;
Chris@701 947 paint.drawText(5,
Chris@701 948 5 + paint.fontMetrics().ascent(),
Chris@701 949 TextAbbrev::abbreviate(getScaleUnits(),
Chris@701 950 paint.fontMetrics(),
Chris@701 951 mw));
Chris@696 952 }
Chris@692 953 }
Chris@692 954
Chris@30 955 void
Chris@916 956 FlexiNoteLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e)
Chris@30 957 {
matthiasm@620 958 // SVDEBUG << "FlexiNoteLayer::drawStart(" << e->x() << "," << e->y() << ")" << endl;
Chris@30 959
Chris@1469 960 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 961 if (!model) return;
Chris@30 962
Chris@904 963 sv_frame_t frame = v->getFrameForX(e->x());
Chris@30 964 if (frame < 0) frame = 0;
Chris@1469 965 frame = frame / model->getResolution() * model->getResolution();
Chris@30 966
Chris@904 967 double value = getValueForY(v, e->y());
Chris@30 968
Chris@1426 969 m_editingPoint = Event(frame, float(value), 0, 0.8f, tr("New Point"));
Chris@30 970 m_originalPoint = m_editingPoint;
Chris@30 971
Chris@376 972 if (m_editingCommand) finish(m_editingCommand);
Chris@1470 973 m_editingCommand = new ChangeEventsCommand(m_model.untyped, tr("Draw Point"));
Chris@1426 974 m_editingCommand->add(m_editingPoint);
Chris@30 975
Chris@30 976 m_editing = true;
Chris@30 977 }
Chris@30 978
Chris@30 979 void
Chris@916 980 FlexiNoteLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e)
Chris@30 981 {
matthiasm@620 982 // SVDEBUG << "FlexiNoteLayer::drawDrag(" << e->x() << "," << e->y() << ")" << endl;
Chris@30 983
Chris@1469 984 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 985 if (!model || !m_editing) return;
Chris@30 986
Chris@904 987 sv_frame_t frame = v->getFrameForX(e->x());
Chris@30 988 if (frame < 0) frame = 0;
Chris@1469 989 frame = frame / model->getResolution() * model->getResolution();
Chris@30 990
Chris@904 991 double newValue = getValueForY(v, e->y());
Chris@101 992
Chris@1426 993 sv_frame_t newFrame = m_editingPoint.getFrame();
Chris@904 994 sv_frame_t newDuration = frame - newFrame;
Chris@101 995 if (newDuration < 0) {
Chris@101 996 newFrame = frame;
Chris@101 997 newDuration = -newDuration;
Chris@101 998 } else if (newDuration == 0) {
Chris@101 999 newDuration = 1;
Chris@101 1000 }
Chris@30 1001
Chris@1426 1002 m_editingCommand->remove(m_editingPoint);
Chris@1426 1003 m_editingPoint = m_editingPoint
Chris@1426 1004 .withFrame(newFrame)
Chris@1426 1005 .withValue(float(newValue))
Chris@1426 1006 .withDuration(newDuration);
Chris@1426 1007 m_editingCommand->add(m_editingPoint);
Chris@30 1008 }
Chris@30 1009
Chris@30 1010 void
Chris@916 1011 FlexiNoteLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *)
Chris@30 1012 {
matthiasm@620 1013 // SVDEBUG << "FlexiNoteLayer::drawEnd(" << e->x() << "," << e->y() << ")" << endl;
Chris@1469 1014 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 1015 if (!model || !m_editing) return;
Chris@376 1016 finish(m_editingCommand);
Chris@1408 1017 m_editingCommand = nullptr;
Chris@30 1018 m_editing = false;
Chris@30 1019 }
Chris@30 1020
Chris@30 1021 void
Chris@916 1022 FlexiNoteLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e)
Chris@335 1023 {
Chris@1469 1024 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 1025 if (!model) return;
Chris@335 1026
Chris@550 1027 if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return;
Chris@335 1028
Chris@335 1029 if (m_editingCommand) {
Chris@714 1030 finish(m_editingCommand);
Chris@1408 1031 m_editingCommand = nullptr;
Chris@335 1032 }
Chris@335 1033
Chris@335 1034 m_editing = true;
Chris@335 1035 }
Chris@335 1036
Chris@335 1037 void
Chris@916 1038 FlexiNoteLayer::eraseDrag(LayerGeometryProvider *, QMouseEvent *)
Chris@335 1039 {
Chris@335 1040 }
Chris@335 1041
Chris@335 1042 void
Chris@916 1043 FlexiNoteLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e)
Chris@335 1044 {
Chris@1469 1045 if (!m_editing) return;
Chris@335 1046 m_editing = false;
Chris@335 1047
Chris@1426 1048 Event p(0);
Chris@550 1049 if (!getPointToDrag(v, e->x(), e->y(), p)) return;
Chris@1469 1050 if (p.getFrame() != m_editingPoint.getFrame() ||
Chris@1469 1051 p.getValue() != m_editingPoint.getValue()) return;
Chris@550 1052
Chris@1470 1053 m_editingCommand = new ChangeEventsCommand(m_model.untyped, tr("Erase Point"));
Chris@1426 1054 m_editingCommand->remove(m_editingPoint);
Chris@376 1055 finish(m_editingCommand);
Chris@1408 1056 m_editingCommand = nullptr;
Chris@335 1057 m_editing = false;
Chris@335 1058 }
Chris@335 1059
Chris@335 1060 void
Chris@916 1061 FlexiNoteLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e)
Chris@30 1062 {
matthiasm@620 1063 // SVDEBUG << "FlexiNoteLayer::editStart(" << e->x() << "," << e->y() << ")" << endl;
gyorgyf@635 1064 std::cerr << "FlexiNoteLayer::editStart(" << e->x() << "," << e->y() << ")" << std::endl;
Chris@30 1065
Chris@1469 1066 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 1067 if (!model) return;
Chris@30 1068
Chris@550 1069 if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return;
Chris@1426 1070 m_originalPoint = m_editingPoint;
gyorgyf@649 1071
matthiasm@651 1072 if (m_editMode == RightBoundary) {
Chris@1426 1073 m_dragPointX = v->getXForFrame
Chris@1426 1074 (m_editingPoint.getFrame() + m_editingPoint.getDuration());
gyorgyf@649 1075 } else {
Chris@1426 1076 m_dragPointX = v->getXForFrame
Chris@1426 1077 (m_editingPoint.getFrame());
gyorgyf@649 1078 }
Chris@1426 1079 m_dragPointY = getYForValue(v, m_editingPoint.getValue());
Chris@551 1080
Chris@30 1081 if (m_editingCommand) {
Chris@714 1082 finish(m_editingCommand);
Chris@1408 1083 m_editingCommand = nullptr;
Chris@30 1084 }
Chris@30 1085
Chris@30 1086 m_editing = true;
Chris@551 1087 m_dragStartX = e->x();
Chris@551 1088 m_dragStartY = e->y();
matthiasm@651 1089
Chris@1426 1090 sv_frame_t onset = m_originalPoint.getFrame();
Chris@1426 1091 sv_frame_t offset =
Chris@1426 1092 m_originalPoint.getFrame() +
Chris@1426 1093 m_originalPoint.getDuration() - 1;
matthiasm@651 1094
matthiasm@651 1095 m_greatestLeftNeighbourFrame = -1;
Chris@806 1096 m_smallestRightNeighbourFrame = std::numeric_limits<int>::max();
Chris@1426 1097
Chris@1469 1098 EventVector allEvents = model->getAllEvents();
matthiasm@651 1099
Chris@1426 1100 for (auto currentNote: allEvents) {
matthiasm@651 1101
matthiasm@651 1102 // left boundary
Chris@1426 1103 if (currentNote.getFrame() + currentNote.getDuration() - 1 < onset) {
Chris@1426 1104 m_greatestLeftNeighbourFrame =
Chris@1426 1105 currentNote.getFrame() + currentNote.getDuration() - 1;
matthiasm@651 1106 }
matthiasm@651 1107
matthiasm@651 1108 // right boundary
Chris@1426 1109 if (currentNote.getFrame() > offset) {
Chris@1426 1110 m_smallestRightNeighbourFrame = currentNote.getFrame();
matthiasm@651 1111 break;
matthiasm@651 1112 }
matthiasm@651 1113 }
Chris@1426 1114
Chris@753 1115 std::cerr << "editStart: mode is " << m_editMode << ", note frame: " << onset << ", left boundary: " << m_greatestLeftNeighbourFrame << ", right boundary: " << m_smallestRightNeighbourFrame << std::endl;
Chris@30 1116 }
Chris@30 1117
Chris@30 1118 void
Chris@916 1119 FlexiNoteLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e)
Chris@30 1120 {
matthiasm@620 1121 // SVDEBUG << "FlexiNoteLayer::editDrag(" << e->x() << "," << e->y() << ")" << endl;
gyorgyf@635 1122 std::cerr << "FlexiNoteLayer::editDrag(" << e->x() << "," << e->y() << ")" << std::endl;
Chris@30 1123
Chris@1469 1124 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 1125 if (!model || !m_editing) return;
Chris@30 1126
Chris@551 1127 int xdist = e->x() - m_dragStartX;
Chris@551 1128 int ydist = e->y() - m_dragStartY;
Chris@551 1129 int newx = m_dragPointX + xdist;
Chris@551 1130 int newy = m_dragPointY + ydist;
Chris@551 1131
Chris@904 1132 sv_frame_t dragFrame = v->getFrameForX(newx);
matthiasm@657 1133 if (dragFrame < 0) dragFrame = 0;
Chris@1469 1134 dragFrame = dragFrame / model->getResolution() * model->getResolution();
matthiasm@651 1135
Chris@904 1136 double value = getValueForY(v, newy);
Chris@30 1137
Chris@30 1138 if (!m_editingCommand) {
Chris@1469 1139 m_editingCommand =
Chris@1470 1140 new ChangeEventsCommand(m_model.untyped, tr("Drag Point"));
Chris@30 1141 }
Chris@1426 1142 m_editingCommand->remove(m_editingPoint);
matthiasm@651 1143
Chris@874 1144 std::cerr << "edit mode: " << m_editMode << " intelligent actions = "
Chris@874 1145 << m_intelligentActions << std::endl;
matthiasm@657 1146
matthiasm@651 1147 switch (m_editMode) {
Chris@1426 1148
Chris@714 1149 case LeftBoundary : {
Chris@714 1150 // left
Chris@1426 1151 if (m_intelligentActions &&
Chris@1426 1152 dragFrame <= m_greatestLeftNeighbourFrame) {
Chris@1426 1153 dragFrame = m_greatestLeftNeighbourFrame + 1;
Chris@1426 1154 }
Chris@714 1155 // right
Chris@1426 1156 if (m_intelligentActions &&
Chris@1426 1157 dragFrame >= m_originalPoint.getFrame() + m_originalPoint.getDuration()) {
Chris@1426 1158 dragFrame = m_originalPoint.getFrame() + m_originalPoint.getDuration() - 1;
matthiasm@651 1159 }
Chris@1426 1160 m_editingPoint = m_editingPoint
Chris@1426 1161 .withFrame(dragFrame)
Chris@1426 1162 .withDuration(m_originalPoint.getFrame() -
Chris@1426 1163 dragFrame + m_originalPoint.getDuration());
Chris@714 1164 break;
Chris@714 1165 }
Chris@1426 1166
Chris@714 1167 case RightBoundary : {
Chris@714 1168 // left
Chris@1426 1169 if (m_intelligentActions &&
Chris@1426 1170 dragFrame <= m_greatestLeftNeighbourFrame) {
Chris@1426 1171 dragFrame = m_greatestLeftNeighbourFrame + 1;
Chris@1426 1172 }
Chris@1426 1173 if (m_intelligentActions &&
Chris@1426 1174 dragFrame >= m_smallestRightNeighbourFrame) {
Chris@1426 1175 dragFrame = m_smallestRightNeighbourFrame - 1;
Chris@1426 1176 }
Chris@1426 1177 m_editingPoint = m_editingPoint
Chris@1426 1178 .withDuration(dragFrame - m_originalPoint.getFrame() + 1);
Chris@714 1179 break;
Chris@714 1180 }
Chris@1426 1181
Chris@714 1182 case DragNote : {
Chris@714 1183 // left
Chris@1426 1184 if (m_intelligentActions &&
Chris@1426 1185 dragFrame <= m_greatestLeftNeighbourFrame) {
Chris@1426 1186 dragFrame = m_greatestLeftNeighbourFrame + 1;
Chris@1426 1187 }
Chris@714 1188 // right
Chris@1426 1189 if (m_intelligentActions &&
Chris@1426 1190 dragFrame + m_originalPoint.getDuration() >= m_smallestRightNeighbourFrame) {
Chris@1426 1191 dragFrame = m_smallestRightNeighbourFrame - m_originalPoint.getDuration();
matthiasm@651 1192 }
Chris@1426 1193
Chris@1426 1194 m_editingPoint = m_editingPoint
Chris@1426 1195 .withFrame(dragFrame)
Chris@1426 1196 .withValue(float(value));
Chris@875 1197
Chris@875 1198 // Re-analyse region within +/- 1 semitone of the dragged value
Chris@875 1199 float cents = 0;
Chris@1426 1200 int midiPitch = Pitch::getPitchForFrequency(m_editingPoint.getValue(), &cents);
Chris@922 1201 double lower = Pitch::getFrequencyForPitch(midiPitch - 1, cents);
Chris@922 1202 double higher = Pitch::getFrequencyForPitch(midiPitch + 1, cents);
Chris@875 1203
Chris@1426 1204 emit reAnalyseRegion(m_editingPoint.getFrame(),
Chris@1426 1205 m_editingPoint.getFrame() +
Chris@1426 1206 m_editingPoint.getDuration(),
Chris@922 1207 float(lower), float(higher));
Chris@714 1208 break;
Chris@714 1209 }
Chris@1426 1210
Chris@805 1211 case SplitNote: // nothing
Chris@805 1212 break;
gyorgyf@649 1213 }
Chris@875 1214
Chris@1426 1215 m_editingCommand->add(m_editingPoint);
Chris@875 1216
Chris@1426 1217 std::cerr << "added new point(" << m_editingPoint.getFrame() << "," << m_editingPoint.getDuration() << ")" << std::endl;
Chris@30 1218 }
Chris@30 1219
Chris@30 1220 void
Chris@948 1221 FlexiNoteLayer::editEnd(LayerGeometryProvider *v, QMouseEvent *e)
Chris@30 1222 {
Chris@1426 1223 std::cerr << "FlexiNoteLayer::editEnd("
Chris@1426 1224 << e->x() << "," << e->y() << ")" << std::endl;
matthiasm@656 1225
Chris@1469 1226 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 1227 if (!model || !m_editing) return;
Chris@30 1228
Chris@30 1229 if (m_editingCommand) {
Chris@30 1230
Chris@714 1231 QString newName = m_editingCommand->getName();
Chris@30 1232
Chris@876 1233 if (m_editMode == DragNote) {
Chris@876 1234 //!!! command nesting is wrong?
Chris@876 1235 emit materialiseReAnalysis();
Chris@876 1236 }
Chris@876 1237
Chris@1426 1238 m_editingCommand->remove(m_editingPoint);
Chris@875 1239 updateNoteValueFromPitchCurve(v, m_editingPoint);
Chris@1426 1240 m_editingCommand->add(m_editingPoint);
Chris@875 1241
Chris@1426 1242 if (m_editingPoint.getFrame() != m_originalPoint.getFrame()) {
Chris@1426 1243 if (m_editingPoint.getValue() != m_originalPoint.getValue()) {
Chris@714 1244 newName = tr("Edit Point");
Chris@714 1245 } else {
Chris@714 1246 newName = tr("Relocate Point");
Chris@714 1247 }
gyorgyf@646 1248 } else {
Chris@714 1249 newName = tr("Change Point Value");
gyorgyf@646 1250 }
Chris@30 1251
Chris@714 1252 m_editingCommand->setName(newName);
Chris@714 1253 finish(m_editingCommand);
Chris@30 1254 }
Chris@30 1255
Chris@1408 1256 m_editingCommand = nullptr;
Chris@30 1257 m_editing = false;
Chris@30 1258 }
Chris@30 1259
gyorgyf@635 1260 void
Chris@916 1261 FlexiNoteLayer::splitStart(LayerGeometryProvider *v, QMouseEvent *e)
gyorgyf@635 1262 {
Chris@1469 1263 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 1264 if (!model) return;
Chris@1469 1265
gyorgyf@646 1266 // GF: note splitting starts (!! remove printing soon)
Chris@874 1267 std::cerr << "splitStart (n.b. editStart will be called later, if the user drags the mouse)" << std::endl;
gyorgyf@635 1268
gyorgyf@635 1269 if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return;
gyorgyf@635 1270 // m_originalPoint = m_editingPoint;
gyorgyf@635 1271 //
Chris@1426 1272 // m_dragPointX = v->getXForFrame(m_editingPoint.getFrame());
Chris@1426 1273 // m_dragPointY = getYForValue(v, m_editingPoint.getValue());
gyorgyf@635 1274
gyorgyf@635 1275 if (m_editingCommand) {
Chris@714 1276 finish(m_editingCommand);
Chris@1408 1277 m_editingCommand = nullptr;
gyorgyf@635 1278 }
gyorgyf@635 1279
gyorgyf@635 1280 m_editing = true;
gyorgyf@635 1281 m_dragStartX = e->x();
gyorgyf@635 1282 m_dragStartY = e->y();
gyorgyf@635 1283 }
gyorgyf@635 1284
gyorgyf@635 1285 void
Chris@916 1286 FlexiNoteLayer::splitEnd(LayerGeometryProvider *v, QMouseEvent *e)
gyorgyf@635 1287 {
Chris@1469 1288 auto model = ModelById::getAs<NoteModel>(m_model);
gyorgyf@646 1289 // GF: note splitting ends. (!! remove printing soon)
gyorgyf@646 1290 std::cerr << "splitEnd" << std::endl;
Chris@1469 1291 if (!model || !m_editing || m_editMode != SplitNote) return;
gyorgyf@635 1292
gyorgyf@635 1293 int xdist = e->x() - m_dragStartX;
gyorgyf@635 1294 int ydist = e->y() - m_dragStartY;
gyorgyf@635 1295 if (xdist != 0 || ydist != 0) {
gyorgyf@646 1296 std::cerr << "mouse moved" << std::endl;
gyorgyf@635 1297 return;
gyorgyf@635 1298 }
gyorgyf@635 1299
Chris@904 1300 sv_frame_t frame = v->getFrameForX(e->x());
gyorgyf@635 1301
Chris@753 1302 splitNotesAt(v, frame, e);
Chris@746 1303 }
Chris@746 1304
Chris@746 1305 void
Chris@916 1306 FlexiNoteLayer::splitNotesAt(LayerGeometryProvider *v, sv_frame_t frame)
Chris@746 1307 {
Chris@1408 1308 splitNotesAt(v, frame, nullptr);
Chris@753 1309 }
Chris@753 1310
Chris@753 1311 void
Chris@916 1312 FlexiNoteLayer::splitNotesAt(LayerGeometryProvider *v, sv_frame_t frame, QMouseEvent *e)
Chris@753 1313 {
Chris@1469 1314 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 1315 if (!model) return;
Chris@1469 1316
Chris@1469 1317 EventVector onPoints = model->getEventsCovering(frame);
Chris@746 1318 if (onPoints.empty()) return;
Chris@746 1319
Chris@1426 1320 Event note(*onPoints.begin());
gyorgyf@635 1321
Chris@1470 1322 auto command = new ChangeEventsCommand(m_model.untyped, tr("Edit Point"));
Chris@1426 1323 command->remove(note);
Chris@753 1324
Chris@753 1325 if (!e || !(e->modifiers() & Qt::ShiftModifier)) {
Chris@753 1326
Chris@753 1327 int gap = 0; // MM: I prefer a gap of 0, but we can decide later
Chris@753 1328
Chris@1426 1329 Event newNote1(note.getFrame(), note.getValue(),
Chris@1426 1330 frame - note.getFrame() - gap,
Chris@1426 1331 note.getLevel(), note.getLabel());
Chris@753 1332
Chris@1426 1333 Event newNote2(frame, note.getValue(),
Chris@1426 1334 note.getDuration() - newNote1.getDuration(),
Chris@1426 1335 note.getLevel(), note.getLabel());
Chris@747 1336
Chris@753 1337 if (m_intelligentActions) {
Chris@875 1338 if (updateNoteValueFromPitchCurve(v, newNote1)) {
Chris@1426 1339 command->add(newNote1);
Chris@753 1340 }
Chris@875 1341 if (updateNoteValueFromPitchCurve(v, newNote2)) {
Chris@1426 1342 command->add(newNote2);
Chris@753 1343 }
Chris@753 1344 } else {
Chris@1426 1345 command->add(newNote1);
Chris@1426 1346 command->add(newNote2);
Chris@747 1347 }
Chris@747 1348 }
Chris@746 1349
gyorgyf@635 1350 finish(command);
gyorgyf@646 1351 }
gyorgyf@646 1352
gyorgyf@655 1353 void
Chris@916 1354 FlexiNoteLayer::addNote(LayerGeometryProvider *v, QMouseEvent *e)
matthiasm@660 1355 {
Chris@1469 1356 auto model = ModelById::getAs<NoteModel>(m_model);
matthiasm@660 1357 std::cerr << "addNote" << std::endl;
Chris@1469 1358 if (!model) return;
matthiasm@660 1359
Chris@904 1360 sv_frame_t duration = 10000;
matthiasm@660 1361
Chris@904 1362 sv_frame_t frame = v->getFrameForX(e->x());
Chris@904 1363 double value = getValueForY(v, e->y());
matthiasm@660 1364
Chris@1469 1365 EventVector noteList = model->getAllEvents();
matthiasm@792 1366
matthiasm@660 1367 if (m_intelligentActions) {
Chris@904 1368 sv_frame_t smallestRightNeighbourFrame = 0;
Chris@1426 1369 for (EventVector::const_iterator i = noteList.begin();
matthiasm@792 1370 i != noteList.end(); ++i) {
Chris@1426 1371 Event currentNote = *i;
Chris@1426 1372 if (currentNote.getFrame() > frame) {
Chris@1426 1373 smallestRightNeighbourFrame = currentNote.getFrame();
matthiasm@660 1374 break;
matthiasm@660 1375 }
matthiasm@660 1376 }
matthiasm@792 1377 if (smallestRightNeighbourFrame > 0) {
matthiasm@792 1378 duration = std::min(smallestRightNeighbourFrame - frame + 1, duration);
matthiasm@792 1379 duration = (duration > 0) ? duration : 0;
matthiasm@792 1380 }
matthiasm@660 1381 }
matthiasm@660 1382
matthiasm@660 1383 if (!m_intelligentActions ||
Chris@1469 1384 (model->getEventsCovering(frame).empty() && duration > 0)) {
Chris@1426 1385 Event newNote(frame, float(value), duration, 100.f, tr("new note"));
Chris@1470 1386 auto command = new ChangeEventsCommand(m_model.untyped, tr("Add Point"));
Chris@1426 1387 command->add(newNote);
matthiasm@660 1388 finish(command);
matthiasm@660 1389 }
matthiasm@660 1390 }
matthiasm@660 1391
Chris@1469 1392 ModelId
Chris@916 1393 FlexiNoteLayer::getAssociatedPitchModel(LayerGeometryProvider *v) const
Chris@745 1394 {
Chris@745 1395 // Better than we used to do, but still not very satisfactory
Chris@745 1396
Chris@874 1397 // cerr << "FlexiNoteLayer::getAssociatedPitchModel()" << endl;
Chris@746 1398
Chris@918 1399 for (int i = 0; i < v->getView()->getLayerCount(); ++i) {
Chris@918 1400 Layer *layer = v->getView()->getLayer(i);
Chris@795 1401 if (layer &&
Chris@748 1402 layer->getLayerPresentationName() != "candidate") {
Chris@874 1403 // cerr << "FlexiNoteLayer::getAssociatedPitchModel: looks like our layer is " << layer << endl;
Chris@1469 1404 auto modelId = layer->getModel();
Chris@1469 1405 auto model = ModelById::getAs<SparseTimeValueModel>(modelId);
Chris@745 1406 if (model && model->getScaleUnits() == "Hz") {
Chris@1469 1407 // cerr << "FlexiNoteLayer::getAssociatedPitchModel: it's good, returning " << model << endl;
Chris@1469 1408 return modelId;
Chris@745 1409 }
Chris@745 1410 }
Chris@745 1411 }
Chris@1469 1412 // cerr << "FlexiNoteLayer::getAssociatedPitchModel: failed to find a model" << endl;
Chris@1469 1413 return {};
Chris@745 1414 }
matthiasm@660 1415
matthiasm@660 1416 void
Chris@916 1417 FlexiNoteLayer::snapSelectedNotesToPitchTrack(LayerGeometryProvider *v, Selection s)
Chris@746 1418 {
Chris@1469 1419 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 1420 if (!model) return;
Chris@746 1421
Chris@1426 1422 EventVector points =
Chris@1469 1423 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
Chris@746 1424
Chris@1470 1425 auto command = new ChangeEventsCommand(m_model.untyped, tr("Snap Notes"));
Chris@746 1426
Chris@746 1427 cerr << "snapSelectedNotesToPitchTrack: selection is from " << s.getStartFrame() << " to " << s.getEndFrame() << endl;
Chris@746 1428
Chris@1426 1429 for (EventVector::iterator i = points.begin();
Chris@746 1430 i != points.end(); ++i) {
Chris@746 1431
Chris@1426 1432 Event note(*i);
Chris@746 1433
Chris@1426 1434 cerr << "snapSelectedNotesToPitchTrack: looking at note from " << note.getFrame() << " to " << note.getFrame() + note.getDuration() << endl;
Chris@746 1435
Chris@1426 1436 if (!s.contains(note.getFrame()) &&
Chris@1426 1437 !s.contains(note.getFrame() + note.getDuration() - 1)) {
Chris@746 1438 continue;
Chris@746 1439 }
Chris@746 1440
matthiasm@773 1441 cerr << "snapSelectedNotesToPitchTrack: making new note" << endl;
Chris@1426 1442 Event newNote(note);
Chris@746 1443
Chris@1426 1444 command->remove(note);
Chris@746 1445
Chris@875 1446 if (updateNoteValueFromPitchCurve(v, newNote)) {
Chris@1426 1447 command->add(newNote);
matthiasm@775 1448 }
Chris@746 1449 }
Chris@747 1450
Chris@746 1451 finish(command);
Chris@746 1452 }
Chris@746 1453
Chris@746 1454 void
Chris@916 1455 FlexiNoteLayer::mergeNotes(LayerGeometryProvider *v, Selection s, bool inclusive)
Chris@747 1456 {
Chris@1469 1457 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 1458 if (!model) return;
Chris@1469 1459
Chris@1426 1460 EventVector points;
Chris@749 1461 if (inclusive) {
Chris@1469 1462 points = model->getEventsSpanning(s.getStartFrame(), s.getDuration());
Chris@749 1463 } else {
Chris@1469 1464 points = model->getEventsWithin(s.getStartFrame(), s.getDuration());
Chris@747 1465 }
Chris@749 1466
Chris@1426 1467 EventVector::iterator i = points.begin();
Chris@747 1468 if (i == points.end()) return;
Chris@747 1469
Chris@1470 1470 auto command = new ChangeEventsCommand(m_model.untyped, tr("Merge Notes"));
Chris@747 1471
Chris@1426 1472 Event newNote(*i);
Chris@747 1473
Chris@747 1474 while (i != points.end()) {
Chris@747 1475
Chris@749 1476 if (inclusive) {
Chris@1426 1477 if (i->getFrame() >= s.getEndFrame()) break;
Chris@749 1478 } else {
Chris@1426 1479 if (i->getFrame() + i->getDuration() > s.getEndFrame()) break;
Chris@749 1480 }
Chris@747 1481
Chris@1426 1482 newNote = newNote.withDuration
Chris@1426 1483 (i->getFrame() + i->getDuration() - newNote.getFrame());
Chris@1426 1484 command->remove(*i);
Chris@747 1485
Chris@747 1486 ++i;
Chris@747 1487 }
Chris@747 1488
Chris@875 1489 updateNoteValueFromPitchCurve(v, newNote);
Chris@1426 1490 command->add(newNote);
Chris@747 1491 finish(command);
Chris@747 1492 }
Chris@747 1493
Chris@747 1494 bool
Chris@1426 1495 FlexiNoteLayer::updateNoteValueFromPitchCurve(LayerGeometryProvider *v, Event &note) const
gyorgyf@655 1496 {
Chris@1469 1497 ModelId modelId = getAssociatedPitchModel(v);
Chris@1469 1498 auto model = ModelById::getAs<SparseTimeValueModel>(modelId);
Chris@747 1499 if (!model) return false;
gyorgyf@655 1500
gyorgyf@655 1501 std::cerr << model->getTypeName() << std::endl;
gyorgyf@655 1502
Chris@1429 1503 EventVector dataPoints =
Chris@1429 1504 model->getEventsWithin(note.getFrame(), note.getDuration());
Chris@746 1505
Chris@1426 1506 std::cerr << "frame " << note.getFrame() << ": " << dataPoints.size() << " candidate points" << std::endl;
Chris@746 1507
Chris@747 1508 if (dataPoints.empty()) return false;
Chris@746 1509
Chris@904 1510 std::vector<double> pitchValues;
gyorgyf@655 1511
Chris@1429 1512 for (EventVector::const_iterator i =
Chris@747 1513 dataPoints.begin(); i != dataPoints.end(); ++i) {
Chris@1429 1514 pitchValues.push_back(i->getValue());
gyorgyf@655 1515 }
Chris@747 1516
Chris@747 1517 if (pitchValues.empty()) return false;
Chris@747 1518
gyorgyf@655 1519 sort(pitchValues.begin(), pitchValues.end());
Chris@904 1520 int size = int(pitchValues.size());
gyorgyf@655 1521 double median;
gyorgyf@655 1522
gyorgyf@655 1523 if (size % 2 == 0) {
gyorgyf@655 1524 median = (pitchValues[size/2 - 1] + pitchValues[size/2]) / 2;
gyorgyf@655 1525 } else {
gyorgyf@655 1526 median = pitchValues[size/2];
gyorgyf@655 1527 }
Chris@875 1528
Chris@1426 1529 std::cerr << "updateNoteValueFromPitchCurve: corrected from " << note.getValue() << " to median " << median << std::endl;
gyorgyf@655 1530
Chris@1426 1531 note = note.withValue(float(median));
Chris@747 1532
Chris@747 1533 return true;
gyorgyf@655 1534 }
gyorgyf@655 1535
gyorgyf@646 1536 void
Chris@916 1537 FlexiNoteLayer::mouseMoveEvent(LayerGeometryProvider *v, QMouseEvent *e)
gyorgyf@646 1538 {
gyorgyf@646 1539 // GF: context sensitive cursors
Chris@918 1540 // v->getView()->setCursor(Qt::ArrowCursor);
Chris@1426 1541 Event note(0);
gyorgyf@646 1542 if (!getNoteToEdit(v, e->x(), e->y(), note)) {
Chris@918 1543 // v->getView()->setCursor(Qt::UpArrowCursor);
gyorgyf@646 1544 return;
gyorgyf@646 1545 }
gyorgyf@646 1546
Chris@874 1547 bool closeToLeft = false, closeToRight = false,
Chris@874 1548 closeToTop = false, closeToBottom = false;
Chris@874 1549 getRelativeMousePosition(v, note, e->x(), e->y(),
Chris@874 1550 closeToLeft, closeToRight,
Chris@874 1551 closeToTop, closeToBottom);
gyorgyf@649 1552
Chris@874 1553 if (closeToLeft) {
Chris@945 1554 v->getView()->setCursor(Qt::SizeHorCursor);
Chris@874 1555 m_editMode = LeftBoundary;
Chris@874 1556 cerr << "edit mode -> LeftBoundary" << endl;
Chris@874 1557 } else if (closeToRight) {
Chris@945 1558 v->getView()->setCursor(Qt::SizeHorCursor);
Chris@874 1559 m_editMode = RightBoundary;
Chris@874 1560 cerr << "edit mode -> RightBoundary" << endl;
Chris@874 1561 } else if (closeToTop) {
Chris@945 1562 v->getView()->setCursor(Qt::CrossCursor);
Chris@874 1563 m_editMode = DragNote;
Chris@874 1564 cerr << "edit mode -> DragNote" << endl;
Chris@874 1565 } else if (closeToBottom) {
Chris@945 1566 v->getView()->setCursor(Qt::UpArrowCursor);
Chris@874 1567 m_editMode = SplitNote;
Chris@874 1568 cerr << "edit mode -> SplitNote" << endl;
Chris@874 1569 } else {
Chris@945 1570 v->getView()->setCursor(Qt::ArrowCursor);
Chris@874 1571 }
gyorgyf@646 1572 }
gyorgyf@646 1573
gyorgyf@646 1574 void
Chris@1426 1575 FlexiNoteLayer::getRelativeMousePosition(LayerGeometryProvider *v, Event &note, int x, int y, bool &closeToLeft, bool &closeToRight, bool &closeToTop, bool &closeToBottom) const
gyorgyf@646 1576 {
Chris@1469 1577 // GF: TODO: consolidate the tolerance values
gyorgyf@646 1578
matthiasm@651 1579 int ctol = 0;
Chris@1426 1580 int noteStartX = v->getXForFrame(note.getFrame());
Chris@1426 1581 int noteEndX = v->getXForFrame(note.getFrame() + note.getDuration());
Chris@1426 1582 int noteValueY = getYForValue(v,note.getValue());
gyorgyf@646 1583 int noteStartY = noteValueY - (NOTE_HEIGHT / 2);
gyorgyf@646 1584 int noteEndY = noteValueY + (NOTE_HEIGHT / 2);
gyorgyf@646 1585
gyorgyf@646 1586 bool closeToNote = false;
gyorgyf@646 1587
gyorgyf@646 1588 if (y >= noteStartY-ctol && y <= noteEndY+ctol && x >= noteStartX-ctol && x <= noteEndX+ctol) closeToNote = true;
gyorgyf@646 1589 if (!closeToNote) return;
gyorgyf@646 1590
matthiasm@651 1591 int tol = NOTE_HEIGHT / 2;
gyorgyf@646 1592
gyorgyf@646 1593 if (x >= noteStartX - tol && x <= noteStartX + tol) closeToLeft = true;
gyorgyf@646 1594 if (x >= noteEndX - tol && x <= noteEndX + tol) closeToRight = true;
gyorgyf@646 1595 if (y >= noteStartY - tol && y <= noteStartY + tol) closeToTop = true;
gyorgyf@646 1596 if (y >= noteEndY - tol && y <= noteEndY + tol) closeToBottom = true;
Chris@688 1597
Chris@688 1598 // cerr << "FlexiNoteLayer::getRelativeMousePosition: close to: left " << closeToLeft << " right " << closeToRight << " top " << closeToTop << " bottom " << closeToBottom << endl;
gyorgyf@635 1599 }
gyorgyf@635 1600
gyorgyf@635 1601
Chris@255 1602 bool
Chris@916 1603 FlexiNoteLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e)
Chris@70 1604 {
gyorgyf@633 1605 std::cerr << "Opening note editor dialog" << std::endl;
Chris@1469 1606 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 1607 if (!model) return false;
Chris@70 1608
Chris@1426 1609 Event note(0);
Chris@550 1610 if (!getPointToDrag(v, e->x(), e->y(), note)) return false;
Chris@550 1611
Chris@70 1612 ItemEditDialog *dialog = new ItemEditDialog
Chris@1469 1613 (model->getSampleRate(),
Chris@70 1614 ItemEditDialog::ShowTime |
Chris@70 1615 ItemEditDialog::ShowDuration |
Chris@70 1616 ItemEditDialog::ShowValue |
Chris@1515 1617 ItemEditDialog::ShowLevel |
Chris@100 1618 ItemEditDialog::ShowText,
Chris@701 1619 getScaleUnits());
Chris@70 1620
Chris@1426 1621 dialog->setFrameTime(note.getFrame());
Chris@1426 1622 dialog->setValue(note.getValue());
Chris@1426 1623 dialog->setFrameDuration(note.getDuration());
Chris@1426 1624 dialog->setText(note.getLabel());
Chris@70 1625
Chris@70 1626 if (dialog->exec() == QDialog::Accepted) {
Chris@70 1627
Chris@1426 1628 Event newNote = note
Chris@1426 1629 .withFrame(dialog->getFrameTime())
Chris@1426 1630 .withValue(dialog->getValue())
Chris@1426 1631 .withDuration(dialog->getFrameDuration())
Chris@1426 1632 .withLabel(dialog->getText());
Chris@70 1633
Chris@1470 1634 auto command = new ChangeEventsCommand(m_model.untyped, tr("Edit Point"));
Chris@1426 1635 command->remove(note);
Chris@1426 1636 command->add(newNote);
Chris@376 1637 finish(command);
Chris@70 1638 }
Chris@70 1639
Chris@70 1640 delete dialog;
Chris@255 1641 return true;
Chris@70 1642 }
Chris@70 1643
Chris@70 1644 void
Chris@905 1645 FlexiNoteLayer::moveSelection(Selection s, sv_frame_t newStartFrame)
Chris@43 1646 {
Chris@1469 1647 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 1648 if (!model) return;
Chris@1469 1649
Chris@1470 1650 auto command = new ChangeEventsCommand(m_model.untyped, tr("Drag Selection"));
Chris@43 1651
Chris@1426 1652 EventVector points =
Chris@1469 1653 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
Chris@43 1654
Chris@1426 1655 for (Event p: points) {
Chris@1426 1656 command->remove(p);
Chris@1426 1657 Event moved = p.withFrame(p.getFrame() +
Chris@1426 1658 newStartFrame - s.getStartFrame());
Chris@1426 1659 command->add(moved);
Chris@43 1660 }
Chris@43 1661
Chris@376 1662 finish(command);
Chris@43 1663 }
Chris@43 1664
Chris@43 1665 void
matthiasm@620 1666 FlexiNoteLayer::resizeSelection(Selection s, Selection newSize)
Chris@43 1667 {
Chris@1469 1668 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 1669 if (!model || !s.getDuration()) return;
Chris@99 1670
Chris@1470 1671 auto command = new ChangeEventsCommand(m_model.untyped, tr("Resize Selection"));
Chris@43 1672
Chris@1426 1673 EventVector points =
Chris@1469 1674 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
Chris@43 1675
Chris@1426 1676 double ratio = double(newSize.getDuration()) / double(s.getDuration());
Chris@1426 1677 double oldStart = double(s.getStartFrame());
Chris@1426 1678 double newStart = double(newSize.getStartFrame());
Chris@1426 1679
Chris@1426 1680 for (Event p: points) {
Chris@43 1681
Chris@1426 1682 double newFrame = (double(p.getFrame()) - oldStart) * ratio + newStart;
Chris@1426 1683 double newDuration = double(p.getDuration()) * ratio;
Chris@43 1684
Chris@1426 1685 Event newPoint = p
Chris@1426 1686 .withFrame(lrint(newFrame))
Chris@1426 1687 .withDuration(lrint(newDuration));
Chris@1426 1688 command->remove(p);
Chris@1426 1689 command->add(newPoint);
Chris@43 1690 }
Chris@43 1691
Chris@376 1692 finish(command);
Chris@43 1693 }
Chris@43 1694
Chris@76 1695 void
matthiasm@620 1696 FlexiNoteLayer::deleteSelection(Selection s)
Chris@76 1697 {
Chris@1469 1698 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 1699 if (!model) return;
Chris@99 1700
Chris@1469 1701 auto command =
Chris@1470 1702 new ChangeEventsCommand(m_model.untyped, tr("Delete Selected Points"));
Chris@76 1703
Chris@1426 1704 EventVector points =
Chris@1469 1705 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
Chris@76 1706
Chris@1426 1707 for (Event p: points) {
Chris@1426 1708 command->remove(p);
Chris@76 1709 }
Chris@76 1710
Chris@376 1711 finish(command);
Chris@76 1712 }
Chris@76 1713
Chris@76 1714 void
matthiasm@784 1715 FlexiNoteLayer::deleteSelectionInclusive(Selection s)
matthiasm@784 1716 {
Chris@1469 1717 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 1718 if (!model) return;
matthiasm@784 1719
Chris@1469 1720 auto command =
Chris@1470 1721 new ChangeEventsCommand(m_model.untyped, tr("Delete Selected Points"));
matthiasm@784 1722
Chris@1426 1723 EventVector points =
Chris@1469 1724 model->getEventsSpanning(s.getStartFrame(), s.getDuration());
matthiasm@784 1725
Chris@1426 1726 for (Event p: points) {
Chris@1426 1727 command->remove(p);
matthiasm@784 1728 }
matthiasm@784 1729
matthiasm@784 1730 finish(command);
matthiasm@784 1731 }
matthiasm@784 1732
matthiasm@784 1733 void
Chris@916 1734 FlexiNoteLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
Chris@76 1735 {
Chris@1469 1736 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 1737 if (!model) return;
Chris@99 1738
Chris@1426 1739 EventVector points =
Chris@1469 1740 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
Chris@76 1741
Chris@1426 1742 for (Event p: points) {
Chris@1426 1743 to.addPoint(p.withReferenceFrame(alignToReference(v, p.getFrame())));
Chris@76 1744 }
Chris@76 1745 }
Chris@76 1746
Chris@125 1747 bool
Chris@1533 1748 FlexiNoteLayer::paste(LayerGeometryProvider *v, const Clipboard &from,
Chris@1533 1749 sv_frame_t /*frameOffset */, bool /* interactive */)
Chris@76 1750 {
Chris@1469 1751 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 1752 if (!model) return false;
Chris@99 1753
Chris@1423 1754 const EventVector &points = from.getPoints();
Chris@76 1755
Chris@360 1756 bool realign = false;
Chris@360 1757
Chris@360 1758 if (clipboardHasDifferentAlignment(v, from)) {
Chris@360 1759
Chris@360 1760 QMessageBox::StandardButton button =
Chris@918 1761 QMessageBox::question(v->getView(), tr("Re-align pasted items?"),
Chris@360 1762 tr("The items you are pasting came from a layer with different source material from this one. Do you want to re-align them in time, to match the source material for this layer?"),
Chris@360 1763 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
Chris@360 1764 QMessageBox::Yes);
Chris@360 1765
Chris@360 1766 if (button == QMessageBox::Cancel) {
Chris@360 1767 return false;
Chris@360 1768 }
Chris@360 1769
Chris@360 1770 if (button == QMessageBox::Yes) {
Chris@360 1771 realign = true;
Chris@360 1772 }
Chris@360 1773 }
Chris@360 1774
Chris@1470 1775 auto command = new ChangeEventsCommand(m_model.untyped, tr("Paste"));
Chris@76 1776
Chris@1423 1777 for (EventVector::const_iterator i = points.begin();
Chris@76 1778 i != points.end(); ++i) {
Chris@76 1779
Chris@904 1780 sv_frame_t frame = 0;
Chris@360 1781
Chris@360 1782 if (!realign) {
Chris@360 1783
Chris@360 1784 frame = i->getFrame();
Chris@360 1785
Chris@360 1786 } else {
Chris@360 1787
Chris@1423 1788 if (i->hasReferenceFrame()) {
Chris@360 1789 frame = i->getReferenceFrame();
Chris@360 1790 frame = alignFromReference(v, frame);
Chris@360 1791 } else {
Chris@360 1792 frame = i->getFrame();
Chris@360 1793 }
Chris@76 1794 }
Chris@360 1795
Chris@1533 1796 Event p = i->withFrame(frame);
Chris@1533 1797
Chris@1426 1798 Event newPoint = p;
Chris@1426 1799 if (!p.hasValue()) {
Chris@1469 1800 newPoint = newPoint.withValue((model->getValueMinimum() +
Chris@1469 1801 model->getValueMaximum()) / 2);
Chris@1426 1802 }
Chris@1426 1803 if (!p.hasDuration()) {
Chris@904 1804 sv_frame_t nextFrame = frame;
Chris@1423 1805 EventVector::const_iterator j = i;
Chris@125 1806 for (; j != points.end(); ++j) {
Chris@125 1807 if (j != i) break;
Chris@125 1808 }
Chris@125 1809 if (j != points.end()) {
Chris@125 1810 nextFrame = j->getFrame();
Chris@125 1811 }
Chris@125 1812 if (nextFrame == frame) {
Chris@1469 1813 newPoint = newPoint.withDuration(model->getResolution());
Chris@125 1814 } else {
Chris@1426 1815 newPoint = newPoint.withDuration(nextFrame - frame);
Chris@125 1816 }
Chris@125 1817 }
Chris@76 1818
Chris@1426 1819 command->add(newPoint);
Chris@76 1820 }
Chris@76 1821
Chris@376 1822 finish(command);
Chris@125 1823 return true;
Chris@76 1824 }
Chris@76 1825
Chris@507 1826 void
Chris@904 1827 FlexiNoteLayer::addNoteOn(sv_frame_t frame, int pitch, int velocity)
Chris@507 1828 {
Chris@1426 1829 m_pendingNoteOns.insert(Event(frame, float(pitch), 0,
Chris@1426 1830 float(velocity / 127.0), ""));
Chris@507 1831 }
Chris@507 1832
Chris@507 1833 void
Chris@904 1834 FlexiNoteLayer::addNoteOff(sv_frame_t frame, int pitch)
Chris@507 1835 {
Chris@1426 1836 for (NoteSet::iterator i = m_pendingNoteOns.begin();
Chris@507 1837 i != m_pendingNoteOns.end(); ++i) {
Chris@1426 1838
Chris@1426 1839 Event p = *i;
Chris@1426 1840
Chris@1426 1841 if (lrintf(p.getValue()) == pitch) {
Chris@507 1842 m_pendingNoteOns.erase(i);
Chris@1426 1843 Event note = p.withDuration(frame - p.getFrame());
Chris@1470 1844 auto c = new ChangeEventsCommand
Chris@1470 1845 (m_model.untyped, tr("Record Note"));
Chris@1469 1846 c->add(note);
Chris@1469 1847 // execute and bundle:
Chris@1469 1848 CommandHistory::getInstance()->addCommand(c, true, true);
Chris@507 1849 break;
Chris@507 1850 }
Chris@507 1851 }
Chris@507 1852 }
Chris@507 1853
Chris@507 1854 void
matthiasm@620 1855 FlexiNoteLayer::abandonNoteOns()
Chris@507 1856 {
Chris@507 1857 m_pendingNoteOns.clear();
Chris@507 1858 }
Chris@507 1859
Chris@287 1860 int
matthiasm@620 1861 FlexiNoteLayer::getDefaultColourHint(bool darkbg, bool &impose)
Chris@287 1862 {
Chris@287 1863 impose = false;
Chris@287 1864 return ColourDatabase::getInstance()->getColourIndex
Chris@287 1865 (QString(darkbg ? "White" : "Black"));
Chris@287 1866 }
Chris@287 1867
Chris@316 1868 void
matthiasm@620 1869 FlexiNoteLayer::toXml(QTextStream &stream,
Chris@714 1870 QString indent, QString extraAttributes) const
Chris@30 1871 {
Chris@316 1872 SingleColourLayer::toXml(stream, indent, extraAttributes +
Chris@445 1873 QString(" verticalScale=\"%1\" scaleMinimum=\"%2\" scaleMaximum=\"%3\" ")
Chris@445 1874 .arg(m_verticalScale)
Chris@445 1875 .arg(m_scaleMinimum)
Chris@445 1876 .arg(m_scaleMaximum));
Chris@30 1877 }
Chris@30 1878
Chris@30 1879 void
matthiasm@620 1880 FlexiNoteLayer::setProperties(const QXmlAttributes &attributes)
Chris@30 1881 {
Chris@287 1882 SingleColourLayer::setProperties(attributes);
Chris@30 1883
Chris@805 1884 bool ok;
Chris@30 1885 VerticalScale scale = (VerticalScale)
Chris@714 1886 attributes.value("verticalScale").toInt(&ok);
Chris@30 1887 if (ok) setVerticalScale(scale);
Chris@30 1888 }
Chris@30 1889
matthiasm@651 1890 void
Chris@916 1891 FlexiNoteLayer::setVerticalRangeToNoteRange(LayerGeometryProvider *v)
matthiasm@651 1892 {
Chris@1469 1893 auto model = ModelById::getAs<NoteModel>(m_model);
Chris@1469 1894 if (!model) return;
Chris@1469 1895
Chris@904 1896 double minf = std::numeric_limits<double>::max();
Chris@904 1897 double maxf = 0;
matthiasm@656 1898 bool hasNotes = 0;
Chris@1469 1899 EventVector allPoints = model->getAllEvents();
Chris@1426 1900 for (EventVector::const_iterator i = allPoints.begin();
Chris@1426 1901 i != allPoints.end(); ++i) {
Chris@714 1902 hasNotes = 1;
Chris@1426 1903 Event note = *i;
Chris@1426 1904 if (note.getValue() < minf) minf = note.getValue();
Chris@1426 1905 if (note.getValue() > maxf) maxf = note.getValue();
matthiasm@651 1906 }
matthiasm@651 1907
matthiasm@656 1908 std::cerr << "min frequency:" << minf << ", max frequency: " << maxf << std::endl;
matthiasm@656 1909
matthiasm@656 1910 if (hasNotes) {
Chris@918 1911 v->getView()->getLayer(1)->setDisplayExtents(minf*0.66,maxf*1.5);
matthiasm@656 1912 // MM: this is a hack because we rely on
matthiasm@656 1913 // * this layer being automatically aligned to layer 1
matthiasm@656 1914 // * layer one is a log frequency layer.
matthiasm@651 1915 }
matthiasm@651 1916 }
Chris@30 1917
matthiasm@651 1918