svgui
1.9
|
00001 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ 00002 00003 /* 00004 Sonic Visualiser 00005 An audio file viewer and annotation editor. 00006 Centre for Digital Music, Queen Mary, University of London. 00007 This file copyright 2006 Chris Cannam. 00008 00009 This program is free software; you can redistribute it and/or 00010 modify it under the terms of the GNU General Public License as 00011 published by the Free Software Foundation; either version 2 of the 00012 License, or (at your option) any later version. See the file 00013 COPYING included with this distribution for more information. 00014 */ 00015 00016 #include "NoteLayer.h" 00017 00018 #include "data/model/Model.h" 00019 #include "base/RealTime.h" 00020 #include "base/Profiler.h" 00021 #include "base/Pitch.h" 00022 #include "base/LogRange.h" 00023 #include "base/RangeMapper.h" 00024 #include "ColourDatabase.h" 00025 #include "view/View.h" 00026 00027 #include "PianoScale.h" 00028 #include "LinearNumericalScale.h" 00029 #include "LogNumericalScale.h" 00030 00031 #include "data/model/NoteModel.h" 00032 00033 #include "widgets/ItemEditDialog.h" 00034 #include "widgets/TextAbbrev.h" 00035 00036 #include <QPainter> 00037 #include <QPainterPath> 00038 #include <QMouseEvent> 00039 #include <QTextStream> 00040 #include <QMessageBox> 00041 00042 #include <iostream> 00043 #include <cmath> 00044 #include <utility> 00045 00046 //#define DEBUG_NOTE_LAYER 1 00047 00048 NoteLayer::NoteLayer() : 00049 SingleColourLayer(), 00050 m_model(0), 00051 m_editing(false), 00052 m_dragPointX(0), 00053 m_dragPointY(0), 00054 m_dragStartX(0), 00055 m_dragStartY(0), 00056 m_originalPoint(0, 0.0, 0, 1.f, tr("New Point")), 00057 m_editingPoint(0, 0.0, 0, 1.f, tr("New Point")), 00058 m_editingCommand(0), 00059 m_verticalScale(AutoAlignScale), 00060 m_scaleMinimum(0), 00061 m_scaleMaximum(0) 00062 { 00063 SVDEBUG << "constructed NoteLayer" << endl; 00064 } 00065 00066 void 00067 NoteLayer::setModel(NoteModel *model) 00068 { 00069 if (m_model == model) return; 00070 m_model = model; 00071 00072 connectSignals(m_model); 00073 00074 // SVDEBUG << "NoteLayer::setModel(" << model << ")" << endl; 00075 00076 m_scaleMinimum = 0; 00077 m_scaleMaximum = 0; 00078 00079 emit modelReplaced(); 00080 } 00081 00082 Layer::PropertyList 00083 NoteLayer::getProperties() const 00084 { 00085 PropertyList list = SingleColourLayer::getProperties(); 00086 list.push_back("Vertical Scale"); 00087 list.push_back("Scale Units"); 00088 return list; 00089 } 00090 00091 QString 00092 NoteLayer::getPropertyLabel(const PropertyName &name) const 00093 { 00094 if (name == "Vertical Scale") return tr("Vertical Scale"); 00095 if (name == "Scale Units") return tr("Scale Units"); 00096 return SingleColourLayer::getPropertyLabel(name); 00097 } 00098 00099 Layer::PropertyType 00100 NoteLayer::getPropertyType(const PropertyName &name) const 00101 { 00102 if (name == "Scale Units") return UnitsProperty; 00103 if (name == "Vertical Scale") return ValueProperty; 00104 return SingleColourLayer::getPropertyType(name); 00105 } 00106 00107 QString 00108 NoteLayer::getPropertyGroupName(const PropertyName &name) const 00109 { 00110 if (name == "Vertical Scale" || name == "Scale Units") { 00111 return tr("Scale"); 00112 } 00113 return SingleColourLayer::getPropertyGroupName(name); 00114 } 00115 00116 QString 00117 NoteLayer::getScaleUnits() const 00118 { 00119 if (m_model) return m_model->getScaleUnits(); 00120 else return ""; 00121 } 00122 00123 int 00124 NoteLayer::getPropertyRangeAndValue(const PropertyName &name, 00125 int *min, int *max, int *deflt) const 00126 { 00127 int val = 0; 00128 00129 if (name == "Vertical Scale") { 00130 00131 if (min) *min = 0; 00132 if (max) *max = 3; 00133 if (deflt) *deflt = int(AutoAlignScale); 00134 00135 val = int(m_verticalScale); 00136 00137 } else if (name == "Scale Units") { 00138 00139 if (deflt) *deflt = 0; 00140 if (m_model) { 00141 val = UnitDatabase::getInstance()->getUnitId 00142 (getScaleUnits()); 00143 } 00144 00145 } else { 00146 00147 val = SingleColourLayer::getPropertyRangeAndValue(name, min, max, deflt); 00148 } 00149 00150 return val; 00151 } 00152 00153 QString 00154 NoteLayer::getPropertyValueLabel(const PropertyName &name, 00155 int value) const 00156 { 00157 if (name == "Vertical Scale") { 00158 switch (value) { 00159 default: 00160 case 0: return tr("Auto-Align"); 00161 case 1: return tr("Linear"); 00162 case 2: return tr("Log"); 00163 case 3: return tr("MIDI Notes"); 00164 } 00165 } 00166 return SingleColourLayer::getPropertyValueLabel(name, value); 00167 } 00168 00169 void 00170 NoteLayer::setProperty(const PropertyName &name, int value) 00171 { 00172 if (name == "Vertical Scale") { 00173 setVerticalScale(VerticalScale(value)); 00174 } else if (name == "Scale Units") { 00175 if (m_model) { 00176 m_model->setScaleUnits 00177 (UnitDatabase::getInstance()->getUnitById(value)); 00178 emit modelChanged(); 00179 } 00180 } else { 00181 return SingleColourLayer::setProperty(name, value); 00182 } 00183 } 00184 00185 void 00186 NoteLayer::setVerticalScale(VerticalScale scale) 00187 { 00188 if (m_verticalScale == scale) return; 00189 m_verticalScale = scale; 00190 emit layerParametersChanged(); 00191 } 00192 00193 bool 00194 NoteLayer::isLayerScrollable(const View *v) const 00195 { 00196 QPoint discard; 00197 return !v->shouldIlluminateLocalFeatures(this, discard); 00198 } 00199 00200 bool 00201 NoteLayer::shouldConvertMIDIToHz() const 00202 { 00203 QString unit = getScaleUnits(); 00204 return (unit != "Hz"); 00205 // if (unit == "" || 00206 // unit.startsWith("MIDI") || 00207 // unit.startsWith("midi")) return true; 00208 // return false; 00209 } 00210 00211 bool 00212 NoteLayer::getValueExtents(float &min, float &max, 00213 bool &logarithmic, QString &unit) const 00214 { 00215 if (!m_model) return false; 00216 min = m_model->getValueMinimum(); 00217 max = m_model->getValueMaximum(); 00218 00219 if (shouldConvertMIDIToHz()) { 00220 unit = "Hz"; 00221 min = Pitch::getFrequencyForPitch(lrintf(min)); 00222 max = Pitch::getFrequencyForPitch(lrintf(max + 1)); 00223 } else unit = getScaleUnits(); 00224 00225 if (m_verticalScale == MIDIRangeScale || 00226 m_verticalScale == LogScale) logarithmic = true; 00227 00228 return true; 00229 } 00230 00231 bool 00232 NoteLayer::getDisplayExtents(float &min, float &max) const 00233 { 00234 if (!m_model || shouldAutoAlign()) return false; 00235 00236 if (m_verticalScale == MIDIRangeScale) { 00237 min = Pitch::getFrequencyForPitch(0); 00238 max = Pitch::getFrequencyForPitch(127); 00239 return true; 00240 } 00241 00242 if (m_scaleMinimum == m_scaleMaximum) { 00243 min = m_model->getValueMinimum(); 00244 max = m_model->getValueMaximum(); 00245 } else { 00246 min = m_scaleMinimum; 00247 max = m_scaleMaximum; 00248 } 00249 00250 if (shouldConvertMIDIToHz()) { 00251 min = Pitch::getFrequencyForPitch(lrintf(min)); 00252 max = Pitch::getFrequencyForPitch(lrintf(max + 1)); 00253 } 00254 00255 #ifdef DEBUG_NOTE_LAYER 00256 cerr << "NoteLayer::getDisplayExtents: min = " << min << ", max = " << max << " (m_scaleMinimum = " << m_scaleMinimum << ", m_scaleMaximum = " << m_scaleMaximum << ")" << endl; 00257 #endif 00258 00259 return true; 00260 } 00261 00262 bool 00263 NoteLayer::setDisplayExtents(float min, float max) 00264 { 00265 if (!m_model) return false; 00266 00267 if (min == max) { 00268 if (min == 0.f) { 00269 max = 1.f; 00270 } else { 00271 max = min * 1.0001; 00272 } 00273 } 00274 00275 m_scaleMinimum = min; 00276 m_scaleMaximum = max; 00277 00278 #ifdef DEBUG_NOTE_LAYER 00279 cerr << "NoteLayer::setDisplayExtents: min = " << min << ", max = " << max << endl; 00280 #endif 00281 00282 emit layerParametersChanged(); 00283 return true; 00284 } 00285 00286 int 00287 NoteLayer::getVerticalZoomSteps(int &defaultStep) const 00288 { 00289 if (shouldAutoAlign()) return 0; 00290 if (!m_model) return 0; 00291 00292 defaultStep = 0; 00293 return 100; 00294 } 00295 00296 int 00297 NoteLayer::getCurrentVerticalZoomStep() const 00298 { 00299 if (shouldAutoAlign()) return 0; 00300 if (!m_model) return 0; 00301 00302 RangeMapper *mapper = getNewVerticalZoomRangeMapper(); 00303 if (!mapper) return 0; 00304 00305 float dmin, dmax; 00306 getDisplayExtents(dmin, dmax); 00307 00308 int nr = mapper->getPositionForValue(dmax - dmin); 00309 00310 delete mapper; 00311 00312 return 100 - nr; 00313 } 00314 00316 00317 void 00318 NoteLayer::setVerticalZoomStep(int step) 00319 { 00320 if (shouldAutoAlign()) return; 00321 if (!m_model) return; 00322 00323 RangeMapper *mapper = getNewVerticalZoomRangeMapper(); 00324 if (!mapper) return; 00325 00326 float min, max; 00327 bool logarithmic; 00328 QString unit; 00329 getValueExtents(min, max, logarithmic, unit); 00330 00331 float dmin, dmax; 00332 getDisplayExtents(dmin, dmax); 00333 00334 float newdist = mapper->getValueForPosition(100 - step); 00335 00336 float newmin, newmax; 00337 00338 if (logarithmic) { 00339 00340 // see SpectrogramLayer::setVerticalZoomStep 00341 00342 newmax = (newdist + sqrtf(newdist*newdist + 4*dmin*dmax)) / 2; 00343 newmin = newmax - newdist; 00344 00345 // cerr << "newmin = " << newmin << ", newmax = " << newmax << endl; 00346 00347 } else { 00348 float dmid = (dmax + dmin) / 2; 00349 newmin = dmid - newdist / 2; 00350 newmax = dmid + newdist / 2; 00351 } 00352 00353 if (newmin < min) { 00354 newmax += (min - newmin); 00355 newmin = min; 00356 } 00357 if (newmax > max) { 00358 newmax = max; 00359 } 00360 00361 #ifdef DEBUG_NOTE_LAYER 00362 cerr << "NoteLayer::setVerticalZoomStep: " << step << ": " << newmin << " -> " << newmax << " (range " << newdist << ")" << endl; 00363 #endif 00364 00365 setDisplayExtents(newmin, newmax); 00366 } 00367 00368 RangeMapper * 00369 NoteLayer::getNewVerticalZoomRangeMapper() const 00370 { 00371 if (!m_model) return 0; 00372 00373 RangeMapper *mapper; 00374 00375 float min, max; 00376 bool logarithmic; 00377 QString unit; 00378 getValueExtents(min, max, logarithmic, unit); 00379 00380 if (min == max) return 0; 00381 00382 if (logarithmic) { 00383 mapper = new LogRangeMapper(0, 100, min, max, unit); 00384 } else { 00385 mapper = new LinearRangeMapper(0, 100, min, max, unit); 00386 } 00387 00388 return mapper; 00389 } 00390 00391 NoteModel::PointList 00392 NoteLayer::getLocalPoints(View *v, int x) const 00393 { 00394 if (!m_model) return NoteModel::PointList(); 00395 00396 int frame = v->getFrameForX(x); 00397 00398 NoteModel::PointList onPoints = 00399 m_model->getPoints(frame); 00400 00401 if (!onPoints.empty()) { 00402 return onPoints; 00403 } 00404 00405 NoteModel::PointList prevPoints = 00406 m_model->getPreviousPoints(frame); 00407 NoteModel::PointList nextPoints = 00408 m_model->getNextPoints(frame); 00409 00410 NoteModel::PointList usePoints = prevPoints; 00411 00412 if (prevPoints.empty()) { 00413 usePoints = nextPoints; 00414 } else if (int(prevPoints.begin()->frame) < v->getStartFrame() && 00415 !(nextPoints.begin()->frame > v->getEndFrame())) { 00416 usePoints = nextPoints; 00417 } else if (int(nextPoints.begin()->frame) - frame < 00418 frame - int(prevPoints.begin()->frame)) { 00419 usePoints = nextPoints; 00420 } 00421 00422 if (!usePoints.empty()) { 00423 int fuzz = 2; 00424 int px = v->getXForFrame(usePoints.begin()->frame); 00425 if ((px > x && px - x > fuzz) || 00426 (px < x && x - px > fuzz + 1)) { 00427 usePoints.clear(); 00428 } 00429 } 00430 00431 return usePoints; 00432 } 00433 00434 bool 00435 NoteLayer::getPointToDrag(View *v, int x, int y, NoteModel::Point &p) const 00436 { 00437 if (!m_model) return false; 00438 00439 int frame = v->getFrameForX(x); 00440 00441 NoteModel::PointList onPoints = m_model->getPoints(frame); 00442 if (onPoints.empty()) return false; 00443 00444 // cerr << "frame " << frame << ": " << onPoints.size() << " candidate points" << endl; 00445 00446 int nearestDistance = -1; 00447 00448 for (NoteModel::PointList::const_iterator i = onPoints.begin(); 00449 i != onPoints.end(); ++i) { 00450 00451 int distance = getYForValue(v, (*i).value) - y; 00452 if (distance < 0) distance = -distance; 00453 if (nearestDistance == -1 || distance < nearestDistance) { 00454 nearestDistance = distance; 00455 p = *i; 00456 } 00457 } 00458 00459 return true; 00460 } 00461 00462 QString 00463 NoteLayer::getFeatureDescription(View *v, QPoint &pos) const 00464 { 00465 int x = pos.x(); 00466 00467 if (!m_model || !m_model->getSampleRate()) return ""; 00468 00469 NoteModel::PointList points = getLocalPoints(v, x); 00470 00471 if (points.empty()) { 00472 if (!m_model->isReady()) { 00473 return tr("In progress"); 00474 } else { 00475 return tr("No local points"); 00476 } 00477 } 00478 00479 Note note(0); 00480 NoteModel::PointList::iterator i; 00481 00482 for (i = points.begin(); i != points.end(); ++i) { 00483 00484 int y = getYForValue(v, i->value); 00485 int h = 3; 00486 00487 if (m_model->getValueQuantization() != 0.0) { 00488 h = y - getYForValue(v, i->value + m_model->getValueQuantization()); 00489 if (h < 3) h = 3; 00490 } 00491 00492 if (pos.y() >= y - h && pos.y() <= y) { 00493 note = *i; 00494 break; 00495 } 00496 } 00497 00498 if (i == points.end()) return tr("No local points"); 00499 00500 RealTime rt = RealTime::frame2RealTime(note.frame, 00501 m_model->getSampleRate()); 00502 RealTime rd = RealTime::frame2RealTime(note.duration, 00503 m_model->getSampleRate()); 00504 00505 QString pitchText; 00506 00507 if (shouldConvertMIDIToHz()) { 00508 00509 int mnote = lrintf(note.value); 00510 int cents = lrintf((note.value - mnote) * 100); 00511 float freq = Pitch::getFrequencyForPitch(mnote, cents); 00512 pitchText = tr("%1 (%2, %3 Hz)") 00513 .arg(Pitch::getPitchLabel(mnote, cents)) 00514 .arg(mnote) 00515 .arg(freq); 00516 00517 } else if (getScaleUnits() == "Hz") { 00518 00519 pitchText = tr("%1 Hz (%2, %3)") 00520 .arg(note.value) 00521 .arg(Pitch::getPitchLabelForFrequency(note.value)) 00522 .arg(Pitch::getPitchForFrequency(note.value)); 00523 00524 } else { 00525 pitchText = tr("%1 %2") 00526 .arg(note.value).arg(getScaleUnits()); 00527 } 00528 00529 QString text; 00530 00531 if (note.label == "") { 00532 text = QString(tr("Time:\t%1\nPitch:\t%2\nDuration:\t%3\nNo label")) 00533 .arg(rt.toText(true).c_str()) 00534 .arg(pitchText) 00535 .arg(rd.toText(true).c_str()); 00536 } else { 00537 text = QString(tr("Time:\t%1\nPitch:\t%2\nDuration:\t%3\nLabel:\t%4")) 00538 .arg(rt.toText(true).c_str()) 00539 .arg(pitchText) 00540 .arg(rd.toText(true).c_str()) 00541 .arg(note.label); 00542 } 00543 00544 pos = QPoint(v->getXForFrame(note.frame), 00545 getYForValue(v, note.value)); 00546 return text; 00547 } 00548 00549 bool 00550 NoteLayer::snapToFeatureFrame(View *v, int &frame, 00551 int &resolution, 00552 SnapType snap) const 00553 { 00554 if (!m_model) { 00555 return Layer::snapToFeatureFrame(v, frame, resolution, snap); 00556 } 00557 00558 resolution = m_model->getResolution(); 00559 NoteModel::PointList points; 00560 00561 if (snap == SnapNeighbouring) { 00562 00563 points = getLocalPoints(v, v->getXForFrame(frame)); 00564 if (points.empty()) return false; 00565 frame = points.begin()->frame; 00566 return true; 00567 } 00568 00569 points = m_model->getPoints(frame, frame); 00570 int snapped = frame; 00571 bool found = false; 00572 00573 for (NoteModel::PointList::const_iterator i = points.begin(); 00574 i != points.end(); ++i) { 00575 00576 if (snap == SnapRight) { 00577 00578 if (i->frame > frame) { 00579 snapped = i->frame; 00580 found = true; 00581 break; 00582 } 00583 00584 } else if (snap == SnapLeft) { 00585 00586 if (i->frame <= frame) { 00587 snapped = i->frame; 00588 found = true; // don't break, as the next may be better 00589 } else { 00590 break; 00591 } 00592 00593 } else { // nearest 00594 00595 NoteModel::PointList::const_iterator j = i; 00596 ++j; 00597 00598 if (j == points.end()) { 00599 00600 snapped = i->frame; 00601 found = true; 00602 break; 00603 00604 } else if (j->frame >= frame) { 00605 00606 if (j->frame - frame < frame - i->frame) { 00607 snapped = j->frame; 00608 } else { 00609 snapped = i->frame; 00610 } 00611 found = true; 00612 break; 00613 } 00614 } 00615 } 00616 00617 frame = snapped; 00618 return found; 00619 } 00620 00621 void 00622 NoteLayer::getScaleExtents(View *v, float &min, float &max, bool &log) const 00623 { 00624 min = 0.0; 00625 max = 0.0; 00626 log = false; 00627 00628 QString queryUnits; 00629 if (shouldConvertMIDIToHz()) queryUnits = "Hz"; 00630 else queryUnits = getScaleUnits(); 00631 00632 if (shouldAutoAlign()) { 00633 00634 if (!v->getValueExtents(queryUnits, min, max, log)) { 00635 00636 min = m_model->getValueMinimum(); 00637 max = m_model->getValueMaximum(); 00638 00639 if (shouldConvertMIDIToHz()) { 00640 min = Pitch::getFrequencyForPitch(lrintf(min)); 00641 max = Pitch::getFrequencyForPitch(lrintf(max + 1)); 00642 } 00643 00644 #ifdef DEBUG_NOTE_LAYER 00645 cerr << "NoteLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl; 00646 #endif 00647 00648 } else if (log) { 00649 00650 LogRange::mapRange(min, max); 00651 00652 #ifdef DEBUG_NOTE_LAYER 00653 cerr << "NoteLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl; 00654 #endif 00655 00656 } 00657 00658 } else { 00659 00660 getDisplayExtents(min, max); 00661 00662 if (m_verticalScale == MIDIRangeScale) { 00663 min = Pitch::getFrequencyForPitch(0); 00664 max = Pitch::getFrequencyForPitch(127); 00665 } else if (shouldConvertMIDIToHz()) { 00666 min = Pitch::getFrequencyForPitch(lrintf(min)); 00667 max = Pitch::getFrequencyForPitch(lrintf(max + 1)); 00668 } 00669 00670 if (m_verticalScale == LogScale || m_verticalScale == MIDIRangeScale) { 00671 LogRange::mapRange(min, max); 00672 log = true; 00673 } 00674 } 00675 00676 if (max == min) max = min + 1.0; 00677 } 00678 00679 int 00680 NoteLayer::getYForValue(View *v, float val) const 00681 { 00682 float min = 0.0, max = 0.0; 00683 bool logarithmic = false; 00684 int h = v->height(); 00685 00686 getScaleExtents(v, min, max, logarithmic); 00687 00688 #ifdef DEBUG_NOTE_LAYER 00689 cerr << "NoteLayer[" << this << "]::getYForValue(" << val << "): min = " << min << ", max = " << max << ", log = " << logarithmic << endl; 00690 #endif 00691 00692 if (shouldConvertMIDIToHz()) { 00693 val = Pitch::getFrequencyForPitch(lrintf(val), 00694 lrintf((val - lrintf(val)) * 100)); 00695 #ifdef DEBUG_NOTE_LAYER 00696 cerr << "shouldConvertMIDIToHz true, val now = " << val << endl; 00697 #endif 00698 } 00699 00700 if (logarithmic) { 00701 val = LogRange::map(val); 00702 #ifdef DEBUG_NOTE_LAYER 00703 cerr << "logarithmic true, val now = " << val << endl; 00704 #endif 00705 } 00706 00707 int y = int(h - ((val - min) * h) / (max - min)) - 1; 00708 #ifdef DEBUG_NOTE_LAYER 00709 cerr << "y = " << y << endl; 00710 #endif 00711 return y; 00712 } 00713 00714 float 00715 NoteLayer::getValueForY(View *v, int y) const 00716 { 00717 float min = 0.0, max = 0.0; 00718 bool logarithmic = false; 00719 int h = v->height(); 00720 00721 getScaleExtents(v, min, max, logarithmic); 00722 00723 float val = min + (float(h - y) * float(max - min)) / h; 00724 00725 if (logarithmic) { 00726 val = powf(10.f, val); 00727 } 00728 00729 if (shouldConvertMIDIToHz()) { 00730 val = Pitch::getPitchForFrequency(val); 00731 } 00732 00733 return val; 00734 } 00735 00736 bool 00737 NoteLayer::shouldAutoAlign() const 00738 { 00739 if (!m_model) return false; 00740 return (m_verticalScale == AutoAlignScale); 00741 } 00742 00743 void 00744 NoteLayer::paint(View *v, QPainter &paint, QRect rect) const 00745 { 00746 if (!m_model || !m_model->isOK()) return; 00747 00748 int sampleRate = m_model->getSampleRate(); 00749 if (!sampleRate) return; 00750 00751 // Profiler profiler("NoteLayer::paint", true); 00752 00753 int x0 = rect.left(), x1 = rect.right(); 00754 int frame0 = v->getFrameForX(x0); 00755 int frame1 = v->getFrameForX(x1); 00756 00757 NoteModel::PointList points(m_model->getPoints(frame0, frame1)); 00758 if (points.empty()) return; 00759 00760 paint.setPen(getBaseQColor()); 00761 00762 QColor brushColour(getBaseQColor()); 00763 brushColour.setAlpha(80); 00764 00765 // SVDEBUG << "NoteLayer::paint: resolution is " 00766 // << m_model->getResolution() << " frames" << endl; 00767 00768 float min = m_model->getValueMinimum(); 00769 float max = m_model->getValueMaximum(); 00770 if (max == min) max = min + 1.0; 00771 00772 QPoint localPos; 00773 NoteModel::Point illuminatePoint(0); 00774 bool shouldIlluminate = false; 00775 00776 if (v->shouldIlluminateLocalFeatures(this, localPos)) { 00777 shouldIlluminate = getPointToDrag(v, localPos.x(), localPos.y(), 00778 illuminatePoint); 00779 } 00780 00781 paint.save(); 00782 paint.setRenderHint(QPainter::Antialiasing, false); 00783 00784 for (NoteModel::PointList::const_iterator i = points.begin(); 00785 i != points.end(); ++i) { 00786 00787 const NoteModel::Point &p(*i); 00788 00789 int x = v->getXForFrame(p.frame); 00790 int y = getYForValue(v, p.value); 00791 int w = v->getXForFrame(p.frame + p.duration) - x; 00792 int h = 3; 00793 00794 if (m_model->getValueQuantization() != 0.0) { 00795 h = y - getYForValue(v, p.value + m_model->getValueQuantization()); 00796 if (h < 3) h = 3; 00797 } 00798 00799 if (w < 1) w = 1; 00800 paint.setPen(getBaseQColor()); 00801 paint.setBrush(brushColour); 00802 00803 if (shouldIlluminate && 00804 // "illuminatePoint == p" 00805 !NoteModel::Point::Comparator()(illuminatePoint, p) && 00806 !NoteModel::Point::Comparator()(p, illuminatePoint)) { 00807 00808 paint.setPen(v->getForeground()); 00809 paint.setBrush(v->getForeground()); 00810 00811 QString vlabel = QString("%1%2").arg(p.value).arg(getScaleUnits()); 00812 v->drawVisibleText(paint, 00813 x - paint.fontMetrics().width(vlabel) - 2, 00814 y + paint.fontMetrics().height()/2 00815 - paint.fontMetrics().descent(), 00816 vlabel, View::OutlinedText); 00817 00818 QString hlabel = RealTime::frame2RealTime 00819 (p.frame, m_model->getSampleRate()).toText(true).c_str(); 00820 v->drawVisibleText(paint, 00821 x, 00822 y - h/2 - paint.fontMetrics().descent() - 2, 00823 hlabel, View::OutlinedText); 00824 } 00825 00826 paint.drawRect(x, y - h/2, w, h); 00827 } 00828 00829 paint.restore(); 00830 } 00831 00832 int 00833 NoteLayer::getVerticalScaleWidth(View *v, bool, QPainter &paint) const 00834 { 00835 if (!m_model || shouldAutoAlign()) { 00836 return 0; 00837 } else { 00838 if (m_verticalScale == LogScale || m_verticalScale == MIDIRangeScale) { 00839 return LogNumericalScale().getWidth(v, paint) + 10; // for piano 00840 } else { 00841 return LinearNumericalScale().getWidth(v, paint); 00842 } 00843 } 00844 } 00845 00846 void 00847 NoteLayer::paintVerticalScale(View *v, bool, QPainter &paint, QRect) const 00848 { 00849 if (!m_model || m_model->getPoints().empty()) return; 00850 00851 QString unit; 00852 float min, max; 00853 bool logarithmic; 00854 00855 int w = getVerticalScaleWidth(v, false, paint); 00856 int h = v->height(); 00857 00858 getScaleExtents(v, min, max, logarithmic); 00859 00860 if (logarithmic) { 00861 LogNumericalScale().paintVertical(v, this, paint, 0, min, max); 00862 } else { 00863 LinearNumericalScale().paintVertical(v, this, paint, 0, min, max); 00864 } 00865 00866 if (logarithmic && (getScaleUnits() == "Hz")) { 00867 PianoScale().paintPianoVertical 00868 (v, paint, QRect(w - 10, 0, 10, h), 00869 LogRange::unmap(min), 00870 LogRange::unmap(max)); 00871 paint.drawLine(w, 0, w, h); 00872 } 00873 00874 if (getScaleUnits() != "") { 00875 int mw = w - 5; 00876 paint.drawText(5, 00877 5 + paint.fontMetrics().ascent(), 00878 TextAbbrev::abbreviate(getScaleUnits(), 00879 paint.fontMetrics(), 00880 mw)); 00881 } 00882 } 00883 00884 void 00885 NoteLayer::drawStart(View *v, QMouseEvent *e) 00886 { 00887 // SVDEBUG << "NoteLayer::drawStart(" << e->x() << "," << e->y() << ")" << endl; 00888 00889 if (!m_model) return; 00890 00891 int frame = v->getFrameForX(e->x()); 00892 if (frame < 0) frame = 0; 00893 frame = frame / m_model->getResolution() * m_model->getResolution(); 00894 00895 float value = getValueForY(v, e->y()); 00896 00897 m_editingPoint = NoteModel::Point(frame, value, 0, 0.8, tr("New Point")); 00898 m_originalPoint = m_editingPoint; 00899 00900 if (m_editingCommand) finish(m_editingCommand); 00901 m_editingCommand = new NoteModel::EditCommand(m_model, 00902 tr("Draw Point")); 00903 m_editingCommand->addPoint(m_editingPoint); 00904 00905 m_editing = true; 00906 } 00907 00908 void 00909 NoteLayer::drawDrag(View *v, QMouseEvent *e) 00910 { 00911 // SVDEBUG << "NoteLayer::drawDrag(" << e->x() << "," << e->y() << ")" << endl; 00912 00913 if (!m_model || !m_editing) return; 00914 00915 int frame = v->getFrameForX(e->x()); 00916 if (frame < 0) frame = 0; 00917 frame = frame / m_model->getResolution() * m_model->getResolution(); 00918 00919 float newValue = getValueForY(v, e->y()); 00920 00921 int newFrame = m_editingPoint.frame; 00922 int newDuration = frame - newFrame; 00923 if (newDuration < 0) { 00924 newFrame = frame; 00925 newDuration = -newDuration; 00926 } else if (newDuration == 0) { 00927 newDuration = 1; 00928 } 00929 00930 m_editingCommand->deletePoint(m_editingPoint); 00931 m_editingPoint.frame = newFrame; 00932 m_editingPoint.value = newValue; 00933 m_editingPoint.duration = newDuration; 00934 m_editingCommand->addPoint(m_editingPoint); 00935 } 00936 00937 void 00938 NoteLayer::drawEnd(View *, QMouseEvent *) 00939 { 00940 // SVDEBUG << "NoteLayer::drawEnd(" << e->x() << "," << e->y() << ")" << endl; 00941 if (!m_model || !m_editing) return; 00942 finish(m_editingCommand); 00943 m_editingCommand = 0; 00944 m_editing = false; 00945 } 00946 00947 void 00948 NoteLayer::eraseStart(View *v, QMouseEvent *e) 00949 { 00950 if (!m_model) return; 00951 00952 if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return; 00953 00954 if (m_editingCommand) { 00955 finish(m_editingCommand); 00956 m_editingCommand = 0; 00957 } 00958 00959 m_editing = true; 00960 } 00961 00962 void 00963 NoteLayer::eraseDrag(View *, QMouseEvent *) 00964 { 00965 } 00966 00967 void 00968 NoteLayer::eraseEnd(View *v, QMouseEvent *e) 00969 { 00970 if (!m_model || !m_editing) return; 00971 00972 m_editing = false; 00973 00974 NoteModel::Point p(0); 00975 if (!getPointToDrag(v, e->x(), e->y(), p)) return; 00976 if (p.frame != m_editingPoint.frame || p.value != m_editingPoint.value) return; 00977 00978 m_editingCommand = new NoteModel::EditCommand(m_model, tr("Erase Point")); 00979 00980 m_editingCommand->deletePoint(m_editingPoint); 00981 00982 finish(m_editingCommand); 00983 m_editingCommand = 0; 00984 m_editing = false; 00985 } 00986 00987 void 00988 NoteLayer::editStart(View *v, QMouseEvent *e) 00989 { 00990 // SVDEBUG << "NoteLayer::editStart(" << e->x() << "," << e->y() << ")" << endl; 00991 00992 if (!m_model) return; 00993 00994 if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return; 00995 m_originalPoint = m_editingPoint; 00996 00997 m_dragPointX = v->getXForFrame(m_editingPoint.frame); 00998 m_dragPointY = getYForValue(v, m_editingPoint.value); 00999 01000 if (m_editingCommand) { 01001 finish(m_editingCommand); 01002 m_editingCommand = 0; 01003 } 01004 01005 m_editing = true; 01006 m_dragStartX = e->x(); 01007 m_dragStartY = e->y(); 01008 } 01009 01010 void 01011 NoteLayer::editDrag(View *v, QMouseEvent *e) 01012 { 01013 // SVDEBUG << "NoteLayer::editDrag(" << e->x() << "," << e->y() << ")" << endl; 01014 01015 if (!m_model || !m_editing) return; 01016 01017 int xdist = e->x() - m_dragStartX; 01018 int ydist = e->y() - m_dragStartY; 01019 int newx = m_dragPointX + xdist; 01020 int newy = m_dragPointY + ydist; 01021 01022 int frame = v->getFrameForX(newx); 01023 if (frame < 0) frame = 0; 01024 frame = frame / m_model->getResolution() * m_model->getResolution(); 01025 01026 float value = getValueForY(v, newy); 01027 01028 if (!m_editingCommand) { 01029 m_editingCommand = new NoteModel::EditCommand(m_model, 01030 tr("Drag Point")); 01031 } 01032 01033 m_editingCommand->deletePoint(m_editingPoint); 01034 m_editingPoint.frame = frame; 01035 m_editingPoint.value = value; 01036 m_editingCommand->addPoint(m_editingPoint); 01037 } 01038 01039 void 01040 NoteLayer::editEnd(View *, QMouseEvent *) 01041 { 01042 // SVDEBUG << "NoteLayer::editEnd(" << e->x() << "," << e->y() << ")" << endl; 01043 if (!m_model || !m_editing) return; 01044 01045 if (m_editingCommand) { 01046 01047 QString newName = m_editingCommand->getName(); 01048 01049 if (m_editingPoint.frame != m_originalPoint.frame) { 01050 if (m_editingPoint.value != m_originalPoint.value) { 01051 newName = tr("Edit Point"); 01052 } else { 01053 newName = tr("Relocate Point"); 01054 } 01055 } else { 01056 newName = tr("Change Point Value"); 01057 } 01058 01059 m_editingCommand->setName(newName); 01060 finish(m_editingCommand); 01061 } 01062 01063 m_editingCommand = 0; 01064 m_editing = false; 01065 } 01066 01067 bool 01068 NoteLayer::editOpen(View *v, QMouseEvent *e) 01069 { 01070 if (!m_model) return false; 01071 01072 NoteModel::Point note(0); 01073 if (!getPointToDrag(v, e->x(), e->y(), note)) return false; 01074 01075 // NoteModel::Point note = *points.begin(); 01076 01077 ItemEditDialog *dialog = new ItemEditDialog 01078 (m_model->getSampleRate(), 01079 ItemEditDialog::ShowTime | 01080 ItemEditDialog::ShowDuration | 01081 ItemEditDialog::ShowValue | 01082 ItemEditDialog::ShowText, 01083 getScaleUnits()); 01084 01085 dialog->setFrameTime(note.frame); 01086 dialog->setValue(note.value); 01087 dialog->setFrameDuration(note.duration); 01088 dialog->setText(note.label); 01089 01090 if (dialog->exec() == QDialog::Accepted) { 01091 01092 NoteModel::Point newNote = note; 01093 newNote.frame = dialog->getFrameTime(); 01094 newNote.value = dialog->getValue(); 01095 newNote.duration = dialog->getFrameDuration(); 01096 newNote.label = dialog->getText(); 01097 01098 NoteModel::EditCommand *command = new NoteModel::EditCommand 01099 (m_model, tr("Edit Point")); 01100 command->deletePoint(note); 01101 command->addPoint(newNote); 01102 finish(command); 01103 } 01104 01105 delete dialog; 01106 return true; 01107 } 01108 01109 void 01110 NoteLayer::moveSelection(Selection s, int newStartFrame) 01111 { 01112 if (!m_model) return; 01113 01114 NoteModel::EditCommand *command = 01115 new NoteModel::EditCommand(m_model, tr("Drag Selection")); 01116 01117 NoteModel::PointList points = 01118 m_model->getPoints(s.getStartFrame(), s.getEndFrame()); 01119 01120 for (NoteModel::PointList::iterator i = points.begin(); 01121 i != points.end(); ++i) { 01122 01123 if (s.contains(i->frame)) { 01124 NoteModel::Point newPoint(*i); 01125 newPoint.frame = i->frame + newStartFrame - s.getStartFrame(); 01126 command->deletePoint(*i); 01127 command->addPoint(newPoint); 01128 } 01129 } 01130 01131 finish(command); 01132 } 01133 01134 void 01135 NoteLayer::resizeSelection(Selection s, Selection newSize) 01136 { 01137 if (!m_model) return; 01138 01139 NoteModel::EditCommand *command = 01140 new NoteModel::EditCommand(m_model, tr("Resize Selection")); 01141 01142 NoteModel::PointList points = 01143 m_model->getPoints(s.getStartFrame(), s.getEndFrame()); 01144 01145 double ratio = 01146 double(newSize.getEndFrame() - newSize.getStartFrame()) / 01147 double(s.getEndFrame() - s.getStartFrame()); 01148 01149 for (NoteModel::PointList::iterator i = points.begin(); 01150 i != points.end(); ++i) { 01151 01152 if (s.contains(i->frame)) { 01153 01154 double targetStart = i->frame; 01155 targetStart = newSize.getStartFrame() + 01156 double(targetStart - s.getStartFrame()) * ratio; 01157 01158 double targetEnd = i->frame + i->duration; 01159 targetEnd = newSize.getStartFrame() + 01160 double(targetEnd - s.getStartFrame()) * ratio; 01161 01162 NoteModel::Point newPoint(*i); 01163 newPoint.frame = lrint(targetStart); 01164 newPoint.duration = lrint(targetEnd - targetStart); 01165 command->deletePoint(*i); 01166 command->addPoint(newPoint); 01167 } 01168 } 01169 01170 finish(command); 01171 } 01172 01173 void 01174 NoteLayer::deleteSelection(Selection s) 01175 { 01176 if (!m_model) return; 01177 01178 NoteModel::EditCommand *command = 01179 new NoteModel::EditCommand(m_model, tr("Delete Selected Points")); 01180 01181 NoteModel::PointList points = 01182 m_model->getPoints(s.getStartFrame(), s.getEndFrame()); 01183 01184 for (NoteModel::PointList::iterator i = points.begin(); 01185 i != points.end(); ++i) { 01186 01187 if (s.contains(i->frame)) { 01188 command->deletePoint(*i); 01189 } 01190 } 01191 01192 finish(command); 01193 } 01194 01195 void 01196 NoteLayer::copy(View *v, Selection s, Clipboard &to) 01197 { 01198 if (!m_model) return; 01199 01200 NoteModel::PointList points = 01201 m_model->getPoints(s.getStartFrame(), s.getEndFrame()); 01202 01203 for (NoteModel::PointList::iterator i = points.begin(); 01204 i != points.end(); ++i) { 01205 if (s.contains(i->frame)) { 01206 Clipboard::Point point(i->frame, i->value, i->duration, i->level, i->label); 01207 point.setReferenceFrame(alignToReference(v, i->frame)); 01208 to.addPoint(point); 01209 } 01210 } 01211 } 01212 01213 bool 01214 NoteLayer::paste(View *v, const Clipboard &from, int /* frameOffset */, bool /* interactive */) 01215 { 01216 if (!m_model) return false; 01217 01218 const Clipboard::PointList &points = from.getPoints(); 01219 01220 bool realign = false; 01221 01222 if (clipboardHasDifferentAlignment(v, from)) { 01223 01224 QMessageBox::StandardButton button = 01225 QMessageBox::question(v, tr("Re-align pasted items?"), 01226 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?"), 01227 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, 01228 QMessageBox::Yes); 01229 01230 if (button == QMessageBox::Cancel) { 01231 return false; 01232 } 01233 01234 if (button == QMessageBox::Yes) { 01235 realign = true; 01236 } 01237 } 01238 01239 NoteModel::EditCommand *command = 01240 new NoteModel::EditCommand(m_model, tr("Paste")); 01241 01242 for (Clipboard::PointList::const_iterator i = points.begin(); 01243 i != points.end(); ++i) { 01244 01245 if (!i->haveFrame()) continue; 01246 int frame = 0; 01247 01248 if (!realign) { 01249 01250 frame = i->getFrame(); 01251 01252 } else { 01253 01254 if (i->haveReferenceFrame()) { 01255 frame = i->getReferenceFrame(); 01256 frame = alignFromReference(v, frame); 01257 } else { 01258 frame = i->getFrame(); 01259 } 01260 } 01261 01262 NoteModel::Point newPoint(frame); 01263 01264 if (i->haveLabel()) newPoint.label = i->getLabel(); 01265 if (i->haveValue()) newPoint.value = i->getValue(); 01266 else newPoint.value = (m_model->getValueMinimum() + 01267 m_model->getValueMaximum()) / 2; 01268 if (i->haveLevel()) newPoint.level = i->getLevel(); 01269 if (i->haveDuration()) newPoint.duration = i->getDuration(); 01270 else { 01271 int nextFrame = frame; 01272 Clipboard::PointList::const_iterator j = i; 01273 for (; j != points.end(); ++j) { 01274 if (!j->haveFrame()) continue; 01275 if (j != i) break; 01276 } 01277 if (j != points.end()) { 01278 nextFrame = j->getFrame(); 01279 } 01280 if (nextFrame == frame) { 01281 newPoint.duration = m_model->getResolution(); 01282 } else { 01283 newPoint.duration = nextFrame - frame; 01284 } 01285 } 01286 01287 command->addPoint(newPoint); 01288 } 01289 01290 finish(command); 01291 return true; 01292 } 01293 01294 void 01295 NoteLayer::addNoteOn(int frame, int pitch, int velocity) 01296 { 01297 m_pendingNoteOns.insert(Note(frame, pitch, 0, float(velocity) / 127.0, "")); 01298 } 01299 01300 void 01301 NoteLayer::addNoteOff(int frame, int pitch) 01302 { 01303 for (NoteSet::iterator i = m_pendingNoteOns.begin(); 01304 i != m_pendingNoteOns.end(); ++i) { 01305 if (lrintf((*i).value) == pitch) { 01306 Note note(*i); 01307 m_pendingNoteOns.erase(i); 01308 note.duration = frame - note.frame; 01309 if (m_model) { 01310 NoteModel::AddPointCommand *c = new NoteModel::AddPointCommand 01311 (m_model, note, tr("Record Note")); 01312 // execute and bundle: 01313 CommandHistory::getInstance()->addCommand(c, true, true); 01314 } 01315 break; 01316 } 01317 } 01318 } 01319 01320 void 01321 NoteLayer::abandonNoteOns() 01322 { 01323 m_pendingNoteOns.clear(); 01324 } 01325 01326 int 01327 NoteLayer::getDefaultColourHint(bool darkbg, bool &impose) 01328 { 01329 impose = false; 01330 return ColourDatabase::getInstance()->getColourIndex 01331 (QString(darkbg ? "White" : "Black")); 01332 } 01333 01334 void 01335 NoteLayer::toXml(QTextStream &stream, 01336 QString indent, QString extraAttributes) const 01337 { 01338 SingleColourLayer::toXml(stream, indent, extraAttributes + 01339 QString(" verticalScale=\"%1\" scaleMinimum=\"%2\" scaleMaximum=\"%3\" ") 01340 .arg(m_verticalScale) 01341 .arg(m_scaleMinimum) 01342 .arg(m_scaleMaximum)); 01343 } 01344 01345 void 01346 NoteLayer::setProperties(const QXmlAttributes &attributes) 01347 { 01348 SingleColourLayer::setProperties(attributes); 01349 01350 bool ok, alsoOk; 01351 VerticalScale scale = (VerticalScale) 01352 attributes.value("verticalScale").toInt(&ok); 01353 if (ok) setVerticalScale(scale); 01354 01355 float min = attributes.value("scaleMinimum").toFloat(&ok); 01356 float max = attributes.value("scaleMaximum").toFloat(&alsoOk); 01357 if (ok && alsoOk && min != max) setDisplayExtents(min, max); 01358 } 01359 01360