svgui  1.9
NoteLayer.cpp
Go to the documentation of this file.
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