Mercurial > hg > svgui
diff layer/NoteLayer.cpp @ 1551:e79731086b0f
Fixes to NoteLayer, particularly to calculation of vertical scale when model unit is not Hz. To avoid inconsistency we now behave as if the unit is always Hz from the point of view of the external API and display, converting at the point where we obtain values from the events themselves. Also various fixes to editing.
author | Chris Cannam |
---|---|
date | Thu, 21 Nov 2019 14:02:57 +0000 |
parents | e6362cf5ff1d |
children | 045063dcd2bc |
line wrap: on
line diff
--- a/layer/NoteLayer.cpp Thu Oct 17 14:44:22 2019 +0100 +++ b/layer/NoteLayer.cpp Thu Nov 21 14:02:57 2019 +0000 @@ -48,6 +48,7 @@ NoteLayer::NoteLayer() : SingleColourLayer(), + m_modelUsesHz(true), m_editing(false), m_dragPointX(0), m_dragPointY(0), @@ -86,6 +87,9 @@ if (newModel) { connectSignals(m_model); + + QString unit = newModel->getScaleUnits(); + m_modelUsesHz = (unit.toLower() == "hz"); } m_scaleMinimum = 0; @@ -131,9 +135,7 @@ QString NoteLayer::getScaleUnits() const { - auto model = ModelById::getAs<NoteModel>(m_model); - if (model) return model->getScaleUnits(); - else return ""; + return "Hz"; } int @@ -191,8 +193,9 @@ } else if (name == "Scale Units") { auto model = ModelById::getAs<NoteModel>(m_model); if (model) { - model->setScaleUnits - (UnitDatabase::getInstance()->getUnitById(value)); + QString unit = UnitDatabase::getInstance()->getUnitById(value); + model->setScaleUnits(unit); + m_modelUsesHz = (unit.toLower() == "hz"); emit modelChanged(m_model); } } else { @@ -215,15 +218,43 @@ return !v->shouldIlluminateLocalFeatures(this, discard); } -bool -NoteLayer::shouldConvertMIDIToHz() const +double +NoteLayer::valueOf(const Event &e) const { - QString unit = getScaleUnits(); - return (unit != "Hz"); -// if (unit == "" || -// unit.startsWith("MIDI") || -// unit.startsWith("midi")) return true; -// return false; + return convertValueFromEventValue(e.getValue()); +} + +Event +NoteLayer::eventWithValue(const Event &e, double value) const +{ + return e.withValue(convertValueToEventValue(value)); +} + +double +NoteLayer::convertValueFromEventValue(float eventValue) const +{ + if (m_modelUsesHz) { + return eventValue; + } else { + double v = eventValue; + if (v < 0) v = 0; + if (v > 127) v = 127; + int p = int(round(v)); + double c = 100.0 * (v - p); + return Pitch::getFrequencyForPitch(p, c); + } +} + +float +NoteLayer::convertValueToEventValue(double value) const +{ + if (m_modelUsesHz) { + return float(value); + } else { + float c = 0; + int p = Pitch::getPitchForFrequency(value, &c); + return float(p) + c / 100.f; + } } bool @@ -232,17 +263,14 @@ { auto model = ModelById::getAs<NoteModel>(m_model); if (!model) return false; - min = model->getValueMinimum(); - max = model->getValueMaximum(); - if (shouldConvertMIDIToHz()) { - unit = "Hz"; - min = Pitch::getFrequencyForPitch(int(lrint(min))); - max = Pitch::getFrequencyForPitch(int(lrint(max + 1))); - } else unit = getScaleUnits(); + min = convertValueFromEventValue(model->getValueMinimum()); + max = convertValueFromEventValue(model->getValueMaximum()); + min /= 1.06; + max *= 1.06; + unit = "Hz"; - if (m_verticalScale == MIDIRangeScale || - m_verticalScale == LogScale) { + if (m_verticalScale != LinearScale) { logarithmic = true; } @@ -262,20 +290,16 @@ } if (m_scaleMinimum == m_scaleMaximum) { - min = model->getValueMinimum(); - max = model->getValueMaximum(); + QString unit; + bool log = false; + getValueExtents(min, max, log, unit); } else { min = m_scaleMinimum; max = m_scaleMaximum; } - if (shouldConvertMIDIToHz()) { - min = Pitch::getFrequencyForPitch(int(lrint(min))); - max = Pitch::getFrequencyForPitch(int(lrint(max + 1))); - } - #ifdef DEBUG_NOTE_LAYER - cerr << "NoteLayer::getDisplayExtents: min = " << min << ", max = " << max << " (m_scaleMinimum = " << m_scaleMinimum << ", m_scaleMaximum = " << m_scaleMaximum << ")" << endl; + SVCERR << "NoteLayer::getDisplayExtents: min = " << min << ", max = " << max << " (m_scaleMinimum = " << m_scaleMinimum << ", m_scaleMaximum = " << m_scaleMaximum << ")" << endl; #endif return true; @@ -298,7 +322,7 @@ m_scaleMaximum = max; #ifdef DEBUG_NOTE_LAYER - cerr << "NoteLayer::setDisplayExtents: min = " << min << ", max = " << max << endl; + SVCERR << "NoteLayer::setDisplayExtents: min = " << min << ", max = " << max << endl; #endif emit layerParametersChanged(); @@ -377,7 +401,7 @@ } #ifdef DEBUG_NOTE_LAYER - cerr << "NoteLayer::setVerticalZoomStep: " << step << ": " << newmin << " -> " << newmax << " (range " << newdist << ")" << endl; + SVCERR << "NoteLayer::setVerticalZoomStep: " << step << ": " << newmin << " -> " << newmax << " (range " << newdist << ")" << endl; #endif setDisplayExtents(newmin, newmax); @@ -443,7 +467,7 @@ int nearestDistance = -1; for (const auto &p: onPoints) { - int distance = getYForValue(v, p.getValue()) - y; + int distance = getYForValue(v, valueOf(p)) - y; if (distance < 0) distance = -distance; if (nearestDistance == -1 || distance < nearestDistance) { nearestDistance = distance; @@ -477,12 +501,13 @@ for (i = points.begin(); i != points.end(); ++i) { - int y = getYForValue(v, i->getValue()); + int y = getYForValue(v, valueOf(*i)); int h = 3; if (model->getValueQuantization() != 0.0) { h = y - getYForValue - (v, i->getValue() + model->getValueQuantization()); + (v, convertValueFromEventValue(i->getValue() + + model->getValueQuantization())); if (h < 3) h = 3; } @@ -501,28 +526,27 @@ QString pitchText; - float value = note.getValue(); + if (m_modelUsesHz) { + + float value = note.getValue(); - if (shouldConvertMIDIToHz()) { - - int mnote = int(lrint(value)); - int cents = int(lrint((value - float(mnote)) * 100)); - double freq = Pitch::getFrequencyForPitch(mnote, cents); - pitchText = tr("%1 (%2, %3 Hz)") - .arg(Pitch::getPitchLabel(mnote, cents)) - .arg(mnote) - .arg(freq); - - } else if (getScaleUnits() == "Hz") { - pitchText = tr("%1 Hz (%2, %3)") .arg(value) .arg(Pitch::getPitchLabelForFrequency(value)) .arg(Pitch::getPitchForFrequency(value)); } else { - pitchText = tr("%1 %2") - .arg(value).arg(getScaleUnits()); + + float eventValue = note.getValue(); + double value = convertValueFromEventValue(eventValue); + + int mnote = int(lrint(eventValue)); + int cents = int(lrint((eventValue - float(mnote)) * 100)); + + pitchText = tr("%1 (%2, %3 Hz)") + .arg(Pitch::getPitchLabel(mnote, cents)) + .arg(eventValue) + .arg(value); } QString text; @@ -540,7 +564,8 @@ .arg(note.getLabel()); } - pos = QPoint(v->getXForFrame(note.getFrame()), getYForValue(v, value)); + pos = QPoint(v->getXForFrame(note.getFrame()), + getYForValue(v, valueOf(note))); return text; } @@ -593,24 +618,15 @@ auto model = ModelById::getAs<NoteModel>(m_model); if (!model) return; - QString queryUnits; - if (shouldConvertMIDIToHz()) queryUnits = "Hz"; - else queryUnits = getScaleUnits(); - if (shouldAutoAlign()) { - if (!v->getVisibleExtentsForUnit(queryUnits, min, max, log)) { + if (!v->getVisibleExtentsForUnit("Hz", min, max, log)) { - min = model->getValueMinimum(); - max = model->getValueMaximum(); - - if (shouldConvertMIDIToHz()) { - min = Pitch::getFrequencyForPitch(int(lrint(min))); - max = Pitch::getFrequencyForPitch(int(lrint(max + 1))); - } + QString unit; + getValueExtents(min, max, log, unit); #ifdef DEBUG_NOTE_LAYER - cerr << "NoteLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl; + SVCERR << "NoteLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl; #endif } else if (log) { @@ -618,24 +634,15 @@ LogRange::mapRange(min, max); #ifdef DEBUG_NOTE_LAYER - cerr << "NoteLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl; + SVCERR << "NoteLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl; #endif - } } else { getDisplayExtents(min, max); - if (m_verticalScale == MIDIRangeScale) { - min = Pitch::getFrequencyForPitch(0); - max = Pitch::getFrequencyForPitch(127); - } else if (shouldConvertMIDIToHz()) { - min = Pitch::getFrequencyForPitch(int(lrint(min))); - max = Pitch::getFrequencyForPitch(int(lrint(max + 1))); - } - - if (m_verticalScale == LogScale || m_verticalScale == MIDIRangeScale) { + if (m_verticalScale != LinearScale) { LogRange::mapRange(min, max); log = true; } @@ -654,27 +661,19 @@ getScaleExtents(v, min, max, logarithmic); #ifdef DEBUG_NOTE_LAYER - cerr << "NoteLayer[" << this << "]::getYForValue(" << val << "): min = " << min << ", max = " << max << ", log = " << logarithmic << endl; + SVCERR << "NoteLayer[" << this << "]::getYForValue(" << val << "): min = " << min << ", max = " << max << ", log = " << logarithmic << endl; #endif - if (shouldConvertMIDIToHz()) { - val = Pitch::getFrequencyForPitch(int(lrint(val)), - int(lrint((val - rint(val)) * 100))); -#ifdef DEBUG_NOTE_LAYER - cerr << "shouldConvertMIDIToHz true, val now = " << val << endl; -#endif - } - if (logarithmic) { val = LogRange::map(val); #ifdef DEBUG_NOTE_LAYER - cerr << "logarithmic true, val now = " << val << endl; + SVCERR << "logarithmic true, val now = " << val << endl; #endif } int y = int(h - ((val - min) * h) / (max - min)) - 1; #ifdef DEBUG_NOTE_LAYER - cerr << "y = " << y << endl; + SVCERR << "y = " << y << endl; #endif return y; } @@ -694,10 +693,6 @@ val = pow(10.0, val); } - if (shouldConvertMIDIToHz()) { - val = Pitch::getPitchForFrequency(val); - } - return val; } @@ -734,20 +729,20 @@ // SVDEBUG << "NoteLayer::paint: resolution is " // << model->getResolution() << " frames" << endl; - double min = model->getValueMinimum(); - double max = model->getValueMaximum(); + double min = convertValueFromEventValue(model->getValueMinimum()); + double max = convertValueFromEventValue(model->getValueMaximum()); if (max == min) max = min + 1.0; QPoint localPos; Event illuminatePoint; bool shouldIlluminate = false; - if (v->shouldIlluminateLocalFeatures(this, localPos)) { + if (m_editing || m_editIsOpen) { + shouldIlluminate = true; + illuminatePoint = m_editingPoint; + } else if (v->shouldIlluminateLocalFeatures(this, localPos)) { shouldIlluminate = getPointToDrag(v, localPos.x(), localPos.y(), illuminatePoint); - } else if (m_editIsOpen) { - shouldIlluminate = true; - illuminatePoint = m_editingPoint; } paint.save(); @@ -759,12 +754,14 @@ const Event &p(*i); int x = v->getXForFrame(p.getFrame()); - int y = getYForValue(v, p.getValue()); + int y = getYForValue(v, valueOf(p)); int w = v->getXForFrame(p.getFrame() + p.getDuration()) - x; int h = 3; if (model->getValueQuantization() != 0.0) { - h = y - getYForValue(v, p.getValue() + model->getValueQuantization()); + h = y - getYForValue + (v, convertValueFromEventValue + (p.getValue() + model->getValueQuantization())); if (h < 3) h = 3; } @@ -782,7 +779,17 @@ // which is too new for us #pragma GCC diagnostic ignored "-Wdeprecated-declarations" - QString vlabel = QString("%1%2").arg(p.getValue()).arg(getScaleUnits()); + QString vlabel; + if (m_modelUsesHz) { + vlabel = QString("%1%2") + .arg(p.getValue()) + .arg(model->getScaleUnits()); + } else { + vlabel = QString("%1 %2") + .arg(p.getValue()) + .arg(model->getScaleUnits()); + } + PaintAssistant::drawVisibleText(v, paint, x - paint.fontMetrics().width(vlabel) - 2, y + paint.fontMetrics().height()/2 @@ -814,7 +821,7 @@ return 0; } - if (m_verticalScale == LogScale || m_verticalScale == MIDIRangeScale) { + if (m_verticalScale != LinearScale) { return LogNumericalScale().getWidth(v, paint) + 10; // for piano } else { return LinearNumericalScale().getWidth(v, paint); @@ -842,7 +849,7 @@ LinearNumericalScale().paintVertical(v, this, paint, 0, min, max); } - if (logarithmic && (getScaleUnits() == "Hz")) { + if (logarithmic) { PianoScale().paintPianoVertical (v, paint, QRect(w - 10, 0, 10, h), LogRange::unmap(min), @@ -873,8 +880,10 @@ frame = frame / model->getResolution() * model->getResolution(); double value = getValueForY(v, e->y()); + float eventValue = convertValueToEventValue(value); + eventValue = roundf(eventValue); - m_editingPoint = Event(frame, float(value), 0, 0.8f, tr("New Point")); + m_editingPoint = Event(frame, eventValue, 0, 0.8f, tr("New Point")); m_originalPoint = m_editingPoint; if (m_editingCommand) finish(m_editingCommand); @@ -897,6 +906,8 @@ frame = frame / model->getResolution() * model->getResolution(); double newValue = getValueForY(v, e->y()); + float newEventValue = convertValueToEventValue(newValue); + newEventValue = roundf(newEventValue); sv_frame_t newFrame = m_editingPoint.getFrame(); sv_frame_t newDuration = frame - newFrame; @@ -910,8 +921,8 @@ m_editingCommand->remove(m_editingPoint); m_editingPoint = m_editingPoint .withFrame(newFrame) - .withValue(float(newValue)) - .withDuration(newDuration); + .withDuration(newDuration) + .withValue(newEventValue); m_editingCommand->add(m_editingPoint); } @@ -981,7 +992,7 @@ m_originalPoint = m_editingPoint; m_dragPointX = v->getXForFrame(m_editingPoint.getFrame()); - m_dragPointY = getYForValue(v, m_editingPoint.getValue()); + m_dragPointY = getYForValue(v, valueOf(m_editingPoint)); if (m_editingCommand) { finish(m_editingCommand); @@ -1010,7 +1021,9 @@ if (frame < 0) frame = 0; frame = frame / model->getResolution() * model->getResolution(); - double value = getValueForY(v, newy); + double newValue = getValueForY(v, newy); + float newEventValue = convertValueToEventValue(newValue); + newEventValue = roundf(newEventValue); if (!m_editingCommand) { m_editingCommand = new ChangeEventsCommand @@ -1020,7 +1033,7 @@ m_editingCommand->remove(m_editingPoint); m_editingPoint = m_editingPoint .withFrame(frame) - .withValue(float(value)); + .withValue(newEventValue); m_editingCommand->add(m_editingPoint); } @@ -1270,7 +1283,9 @@ void NoteLayer::addNoteOn(sv_frame_t frame, int pitch, int velocity) { - m_pendingNoteOns.insert(Event(frame, float(pitch), 0, + double value = Pitch::getFrequencyForPitch(pitch); + float eventValue = convertValueToEventValue(value); + m_pendingNoteOns.insert(Event(frame, eventValue, 0, float(velocity) / 127.f, QString())); } @@ -1283,8 +1298,10 @@ i != m_pendingNoteOns.end(); ++i) { Event p = *i; + double value = valueOf(p); + int eventPitch = Pitch::getPitchForFrequency(value); - if (lrintf(p.getValue()) == pitch) { + if (eventPitch == pitch) { m_pendingNoteOns.erase(i); Event note = p.withDuration(frame - p.getFrame()); if (model) {