Mercurial > hg > svcore
comparison data/model/NoteModel.h @ 1713:978c143c767f
Merge from branch single-point
author | Chris Cannam |
---|---|
date | Fri, 17 May 2019 10:02:43 +0100 |
parents | 73077ec5aed6 |
children | 78fe29adfd16 |
comparison
equal
deleted
inserted
replaced
1709:ab4fd193262b | 1713:978c143c767f |
---|---|
2 | 2 |
3 /* | 3 /* |
4 Sonic Visualiser | 4 Sonic Visualiser |
5 An audio file viewer and annotation editor. | 5 An audio file viewer and annotation editor. |
6 Centre for Digital Music, Queen Mary, University of London. | 6 Centre for Digital Music, Queen Mary, University of London. |
7 This file copyright 2006 Chris Cannam. | |
8 | 7 |
9 This program is free software; you can redistribute it and/or | 8 This program is free software; you can redistribute it and/or |
10 modify it under the terms of the GNU General Public License as | 9 modify it under the terms of the GNU General Public License as |
11 published by the Free Software Foundation; either version 2 of the | 10 published by the Free Software Foundation; either version 2 of the |
12 License, or (at your option) any later version. See the file | 11 License, or (at your option) any later version. See the file |
14 */ | 13 */ |
15 | 14 |
16 #ifndef SV_NOTE_MODEL_H | 15 #ifndef SV_NOTE_MODEL_H |
17 #define SV_NOTE_MODEL_H | 16 #define SV_NOTE_MODEL_H |
18 | 17 |
19 #include "IntervalModel.h" | 18 #include "Model.h" |
20 #include "NoteData.h" | 19 #include "TabularModel.h" |
20 #include "EventCommands.h" | |
21 #include "DeferredNotifier.h" | |
22 #include "base/UnitDatabase.h" | |
23 #include "base/EventSeries.h" | |
24 #include "base/NoteData.h" | |
25 #include "base/NoteExportable.h" | |
21 #include "base/RealTime.h" | 26 #include "base/RealTime.h" |
22 #include "base/PlayParameterRepository.h" | 27 #include "base/PlayParameterRepository.h" |
23 #include "base/Pitch.h" | 28 #include "base/Pitch.h" |
24 | 29 #include "system/System.h" |
25 /** | 30 |
26 * NoteModel -- a concrete IntervalModel for notes. | 31 #include <QMutex> |
27 */ | 32 #include <QMutexLocker> |
28 | 33 |
29 /** | 34 class NoteModel : public Model, |
30 * Note type for use in a sparse model. All we mean by a "note" is | 35 public TabularModel, |
31 * something that has an onset time, a single value, a duration, and a | 36 public NoteExportable, |
32 * level. Like other points, it can also have a label. With this | 37 public EventEditable |
33 * point type, the model can be thought of as representing a simple | |
34 * MIDI-type piano roll, except that the y coordinates (values) do not | |
35 * have to be discrete integers. | |
36 */ | |
37 | |
38 struct Note | |
39 { | |
40 public: | |
41 Note(sv_frame_t _frame) : frame(_frame), value(0.0f), duration(0), level(1.f) { } | |
42 Note(sv_frame_t _frame, float _value, sv_frame_t _duration, float _level, QString _label) : | |
43 frame(_frame), value(_value), duration(_duration), level(_level), label(_label) { } | |
44 | |
45 int getDimensions() const { return 3; } | |
46 | |
47 sv_frame_t frame; | |
48 float value; | |
49 sv_frame_t duration; | |
50 float level; | |
51 QString label; | |
52 | |
53 QString getLabel() const { return label; } | |
54 | |
55 void toXml(QTextStream &stream, | |
56 QString indent = "", | |
57 QString extraAttributes = "") const | |
58 { | |
59 stream << | |
60 QString("%1<point frame=\"%2\" value=\"%3\" duration=\"%4\" level=\"%5\" label=\"%6\" %7/>\n") | |
61 .arg(indent).arg(frame).arg(value).arg(duration).arg(level) | |
62 .arg(XmlExportable::encodeEntities(label)).arg(extraAttributes); | |
63 } | |
64 | |
65 QString toDelimitedDataString(QString delimiter, DataExportOptions opts, sv_samplerate_t sampleRate) const { | |
66 QStringList list; | |
67 list << RealTime::frame2RealTime(frame, sampleRate).toString().c_str(); | |
68 list << QString("%1").arg(value); | |
69 list << RealTime::frame2RealTime(duration, sampleRate).toString().c_str(); | |
70 if (!(opts & DataExportOmitLevels)) { | |
71 list << QString("%1").arg(level); | |
72 } | |
73 if (label != "") list << label; | |
74 return list.join(delimiter); | |
75 } | |
76 | |
77 struct Comparator { | |
78 bool operator()(const Note &p1, | |
79 const Note &p2) const { | |
80 if (p1.frame != p2.frame) return p1.frame < p2.frame; | |
81 if (p1.value != p2.value) return p1.value < p2.value; | |
82 if (p1.duration != p2.duration) return p1.duration < p2.duration; | |
83 if (p1.level != p2.level) return p1.level < p2.level; | |
84 return p1.label < p2.label; | |
85 } | |
86 }; | |
87 | |
88 struct OrderComparator { | |
89 bool operator()(const Note &p1, | |
90 const Note &p2) const { | |
91 return p1.frame < p2.frame; | |
92 } | |
93 }; | |
94 }; | |
95 | |
96 | |
97 class NoteModel : public IntervalModel<Note>, public NoteExportable | |
98 { | 38 { |
99 Q_OBJECT | 39 Q_OBJECT |
100 | 40 |
101 public: | 41 public: |
102 NoteModel(sv_samplerate_t sampleRate, int resolution, | 42 enum Subtype { |
103 bool notifyOnAdd = true) : | 43 NORMAL_NOTE, |
104 IntervalModel<Note>(sampleRate, resolution, notifyOnAdd), | 44 FLEXI_NOTE |
105 m_valueQuantization(0) | 45 }; |
106 { | 46 |
47 NoteModel(sv_samplerate_t sampleRate, | |
48 int resolution, | |
49 bool notifyOnAdd = true, | |
50 Subtype subtype = NORMAL_NOTE) : | |
51 m_subtype(subtype), | |
52 m_sampleRate(sampleRate), | |
53 m_resolution(resolution), | |
54 m_valueMinimum(0.f), | |
55 m_valueMaximum(0.f), | |
56 m_haveExtents(false), | |
57 m_valueQuantization(0), | |
58 m_units(""), | |
59 m_extendTo(0), | |
60 m_notifier(this, | |
61 notifyOnAdd ? | |
62 DeferredNotifier::NOTIFY_ALWAYS : | |
63 DeferredNotifier::NOTIFY_DEFERRED), | |
64 m_completion(100) { | |
65 if (subtype == FLEXI_NOTE) { | |
66 m_valueMinimum = 33.f; | |
67 m_valueMaximum = 88.f; | |
68 } | |
107 PlayParameterRepository::getInstance()->addPlayable(this); | 69 PlayParameterRepository::getInstance()->addPlayable(this); |
108 } | 70 } |
109 | 71 |
110 NoteModel(sv_samplerate_t sampleRate, int resolution, | 72 NoteModel(sv_samplerate_t sampleRate, int resolution, |
111 float valueMinimum, float valueMaximum, | 73 float valueMinimum, float valueMaximum, |
112 bool notifyOnAdd = true) : | 74 bool notifyOnAdd = true, |
113 IntervalModel<Note>(sampleRate, resolution, | 75 Subtype subtype = NORMAL_NOTE) : |
114 valueMinimum, valueMaximum, | 76 m_subtype(subtype), |
115 notifyOnAdd), | 77 m_sampleRate(sampleRate), |
116 m_valueQuantization(0) | 78 m_resolution(resolution), |
117 { | 79 m_valueMinimum(valueMinimum), |
80 m_valueMaximum(valueMaximum), | |
81 m_haveExtents(true), | |
82 m_valueQuantization(0), | |
83 m_units(""), | |
84 m_extendTo(0), | |
85 m_notifier(this, | |
86 notifyOnAdd ? | |
87 DeferredNotifier::NOTIFY_ALWAYS : | |
88 DeferredNotifier::NOTIFY_DEFERRED), | |
89 m_completion(100) { | |
118 PlayParameterRepository::getInstance()->addPlayable(this); | 90 PlayParameterRepository::getInstance()->addPlayable(this); |
119 } | 91 } |
120 | 92 |
121 virtual ~NoteModel() | 93 virtual ~NoteModel() { |
122 { | |
123 PlayParameterRepository::getInstance()->removePlayable(this); | 94 PlayParameterRepository::getInstance()->removePlayable(this); |
95 } | |
96 | |
97 QString getTypeName() const override { return tr("Note"); } | |
98 Subtype getSubtype() const { return m_subtype; } | |
99 bool isSparse() const override { return true; } | |
100 bool isOK() const override { return true; } | |
101 | |
102 sv_frame_t getStartFrame() const override { | |
103 return m_events.getStartFrame(); | |
104 } | |
105 sv_frame_t getEndFrame() const override { | |
106 if (m_events.isEmpty()) return 0; | |
107 sv_frame_t e = m_events.getEndFrame(); | |
108 if (e % m_resolution == 0) return e; | |
109 else return (e / m_resolution + 1) * m_resolution; | |
110 } | |
111 | |
112 sv_samplerate_t getSampleRate() const override { return m_sampleRate; } | |
113 int getResolution() const { return m_resolution; } | |
114 | |
115 bool canPlay() const override { return true; } | |
116 QString getDefaultPlayClipId() const override { | |
117 return "elecpiano"; | |
118 } | |
119 | |
120 QString getScaleUnits() const { return m_units; } | |
121 void setScaleUnits(QString units) { | |
122 m_units = units; | |
123 UnitDatabase::getInstance()->registerUnit(units); | |
124 } | 124 } |
125 | 125 |
126 float getValueQuantization() const { return m_valueQuantization; } | 126 float getValueQuantization() const { return m_valueQuantization; } |
127 void setValueQuantization(float q) { m_valueQuantization = q; } | 127 void setValueQuantization(float q) { m_valueQuantization = q; } |
128 | 128 |
129 QString getTypeName() const override { return tr("Note"); } | 129 float getValueMinimum() const { return m_valueMinimum; } |
130 | 130 float getValueMaximum() const { return m_valueMaximum; } |
131 bool canPlay() const override { return true; } | 131 |
132 | 132 int getCompletion() const override { return m_completion; } |
133 QString getDefaultPlayClipId() const override | 133 |
134 { | 134 void setCompletion(int completion, bool update = true) { |
135 return "elecpiano"; | 135 |
136 } | 136 { QMutexLocker locker(&m_mutex); |
137 | 137 if (m_completion == completion) return; |
138 void toXml(QTextStream &out, | 138 m_completion = completion; |
139 QString indent = "", | 139 } |
140 QString extraAttributes = "") const override | 140 |
141 { | 141 if (update) { |
142 std::cerr << "NoteModel::toXml: extraAttributes = \"" | 142 m_notifier.makeDeferredNotifications(); |
143 << extraAttributes.toStdString() << std::endl; | 143 } |
144 | 144 |
145 IntervalModel<Note>::toXml | 145 emit completionChanged(); |
146 (out, | 146 |
147 indent, | 147 if (completion == 100) { |
148 QString("%1 subtype=\"note\" valueQuantization=\"%2\"") | 148 // henceforth: |
149 .arg(extraAttributes).arg(m_valueQuantization)); | 149 m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS); |
150 emit modelChanged(); | |
151 } | |
152 } | |
153 | |
154 /** | |
155 * Query methods. | |
156 */ | |
157 | |
158 int getEventCount() const { | |
159 return m_events.count(); | |
160 } | |
161 bool isEmpty() const { | |
162 return m_events.isEmpty(); | |
163 } | |
164 bool containsEvent(const Event &e) const { | |
165 return m_events.contains(e); | |
166 } | |
167 EventVector getAllEvents() const { | |
168 return m_events.getAllEvents(); | |
169 } | |
170 EventVector getEventsSpanning(sv_frame_t f, sv_frame_t duration) const { | |
171 return m_events.getEventsSpanning(f, duration); | |
172 } | |
173 EventVector getEventsCovering(sv_frame_t f) const { | |
174 return m_events.getEventsCovering(f); | |
175 } | |
176 EventVector getEventsWithin(sv_frame_t f, sv_frame_t duration) const { | |
177 return m_events.getEventsWithin(f, duration); | |
178 } | |
179 EventVector getEventsStartingWithin(sv_frame_t f, sv_frame_t duration) const { | |
180 return m_events.getEventsStartingWithin(f, duration); | |
181 } | |
182 EventVector getEventsStartingAt(sv_frame_t f) const { | |
183 return m_events.getEventsStartingAt(f); | |
184 } | |
185 bool getNearestEventMatching(sv_frame_t startSearchAt, | |
186 std::function<bool(Event)> predicate, | |
187 EventSeries::Direction direction, | |
188 Event &found) const { | |
189 return m_events.getNearestEventMatching | |
190 (startSearchAt, predicate, direction, found); | |
191 } | |
192 | |
193 /** | |
194 * Editing methods. | |
195 */ | |
196 void add(Event e) override { | |
197 | |
198 bool allChange = false; | |
199 | |
200 { | |
201 QMutexLocker locker(&m_mutex); | |
202 m_events.add(e); | |
203 | |
204 float v = e.getValue(); | |
205 if (!ISNAN(v) && !ISINF(v)) { | |
206 if (!m_haveExtents || v < m_valueMinimum) { | |
207 m_valueMinimum = v; allChange = true; | |
208 } | |
209 if (!m_haveExtents || v > m_valueMaximum) { | |
210 m_valueMaximum = v; allChange = true; | |
211 } | |
212 m_haveExtents = true; | |
213 } | |
214 } | |
215 | |
216 m_notifier.update(e.getFrame(), e.getDuration() + m_resolution); | |
217 | |
218 if (allChange) { | |
219 emit modelChanged(); | |
220 } | |
221 } | |
222 | |
223 void remove(Event e) override { | |
224 { | |
225 QMutexLocker locker(&m_mutex); | |
226 m_events.remove(e); | |
227 } | |
228 emit modelChangedWithin(e.getFrame(), | |
229 e.getFrame() + e.getDuration() + m_resolution); | |
150 } | 230 } |
151 | 231 |
152 /** | 232 /** |
153 * TabularModel methods. | 233 * TabularModel methods. |
154 */ | 234 */ |
155 | 235 |
156 int getColumnCount() const override | 236 int getRowCount() const override { |
157 { | 237 return m_events.count(); |
238 } | |
239 | |
240 int getColumnCount() const override { | |
158 return 6; | 241 return 6; |
159 } | 242 } |
160 | 243 |
161 QString getHeading(int column) const override | 244 bool isColumnTimeValue(int column) const override { |
162 { | 245 // NB duration is not a "time value" -- that's for columns |
246 // whose sort ordering is exactly that of the frame time | |
247 return (column < 2); | |
248 } | |
249 | |
250 sv_frame_t getFrameForRow(int row) const override { | |
251 if (row < 0 || row >= m_events.count()) { | |
252 return 0; | |
253 } | |
254 Event e = m_events.getEventByIndex(row); | |
255 return e.getFrame(); | |
256 } | |
257 | |
258 int getRowForFrame(sv_frame_t frame) const override { | |
259 return m_events.getIndexForEvent(Event(frame)); | |
260 } | |
261 | |
262 QString getHeading(int column) const override { | |
163 switch (column) { | 263 switch (column) { |
164 case 0: return tr("Time"); | 264 case 0: return tr("Time"); |
165 case 1: return tr("Frame"); | 265 case 1: return tr("Frame"); |
166 case 2: return tr("Pitch"); | 266 case 2: return tr("Pitch"); |
167 case 3: return tr("Duration"); | 267 case 3: return tr("Duration"); |
169 case 5: return tr("Label"); | 269 case 5: return tr("Label"); |
170 default: return tr("Unknown"); | 270 default: return tr("Unknown"); |
171 } | 271 } |
172 } | 272 } |
173 | 273 |
174 QVariant getData(int row, int column, int role) const override | 274 QVariant getData(int row, int column, int role) const override { |
175 { | 275 |
176 if (column < 4) { | 276 if (row < 0 || row >= m_events.count()) { |
177 return IntervalModel<Note>::getData(row, column, role); | 277 return QVariant(); |
178 } | 278 } |
179 | 279 |
180 PointListConstIterator i = getPointListIteratorForRow(row); | 280 Event e = m_events.getEventByIndex(row); |
181 if (i == m_points.end()) return QVariant(); | |
182 | 281 |
183 switch (column) { | 282 switch (column) { |
184 case 4: return i->level; | 283 case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role); |
185 case 5: return i->label; | 284 case 1: return int(e.getFrame()); |
285 case 2: return adaptValueForRole(e.getValue(), getScaleUnits(), role); | |
286 case 3: return int(e.getDuration()); | |
287 case 4: return e.getLevel(); | |
288 case 5: return e.getLabel(); | |
186 default: return QVariant(); | 289 default: return QVariant(); |
187 } | 290 } |
188 } | 291 } |
189 | 292 |
190 Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override | 293 Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override { |
191 { | 294 |
192 if (column < 4) { | 295 if (row < 0 || row >= m_events.count()) return nullptr; |
193 return IntervalModel<Note>::getSetDataCommand | 296 if (role != Qt::EditRole) return nullptr; |
194 (row, column, value, role); | 297 |
195 } | 298 Event e0 = m_events.getEventByIndex(row); |
196 | 299 Event e1; |
197 if (role != Qt::EditRole) return 0; | |
198 PointListConstIterator i = getPointListIteratorForRow(row); | |
199 if (i == m_points.end()) return 0; | |
200 EditCommand *command = new EditCommand(this, tr("Edit Data")); | |
201 | |
202 Point point(*i); | |
203 command->deletePoint(point); | |
204 | 300 |
205 switch (column) { | 301 switch (column) { |
206 case 4: point.level = float(value.toDouble()); break; | 302 case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() * |
207 case 5: point.label = value.toString(); break; | 303 getSampleRate()))); break; |
208 } | 304 case 1: e1 = e0.withFrame(value.toInt()); break; |
209 | 305 case 2: e1 = e0.withValue(float(value.toDouble())); break; |
210 command->addPoint(point); | 306 case 3: e1 = e0.withDuration(value.toInt()); break; |
307 case 4: e1 = e0.withLevel(float(value.toDouble())); break; | |
308 case 5: e1 = e0.withLabel(value.toString()); break; | |
309 } | |
310 | |
311 ChangeEventsCommand *command = | |
312 new ChangeEventsCommand(this, tr("Edit Data")); | |
313 command->remove(e0); | |
314 command->add(e1); | |
211 return command->finish(); | 315 return command->finish(); |
212 } | 316 } |
213 | 317 |
214 SortType getSortType(int column) const override | 318 SortType getSortType(int column) const override |
215 { | 319 { |
220 /** | 324 /** |
221 * NoteExportable methods. | 325 * NoteExportable methods. |
222 */ | 326 */ |
223 | 327 |
224 NoteList getNotes() const override { | 328 NoteList getNotes() const override { |
225 return getNotesWithin(getStartFrame(), getEndFrame()); | 329 return getNotesStartingWithin(getStartFrame(), |
226 } | 330 getEndFrame() - getStartFrame()); |
227 | 331 } |
228 NoteList getNotesWithin(sv_frame_t startFrame, sv_frame_t endFrame) const override { | 332 |
333 NoteList getNotesActiveAt(sv_frame_t frame) const override { | |
334 | |
335 NoteList notes; | |
336 EventVector ee = m_events.getEventsCovering(frame); | |
337 for (const auto &e: ee) { | |
338 notes.push_back(e.toNoteData(getSampleRate(), | |
339 getScaleUnits() != "Hz")); | |
340 } | |
341 return notes; | |
342 } | |
343 | |
344 NoteList getNotesStartingWithin(sv_frame_t startFrame, | |
345 sv_frame_t duration) const override { | |
346 | |
347 NoteList notes; | |
348 EventVector ee = m_events.getEventsStartingWithin(startFrame, duration); | |
349 for (const auto &e: ee) { | |
350 notes.push_back(e.toNoteData(getSampleRate(), | |
351 getScaleUnits() != "Hz")); | |
352 } | |
353 return notes; | |
354 } | |
355 | |
356 /** | |
357 * XmlExportable methods. | |
358 */ | |
359 | |
360 void toXml(QTextStream &out, | |
361 QString indent = "", | |
362 QString extraAttributes = "") const override { | |
363 | |
364 //!!! what is valueQuantization used for? | |
229 | 365 |
230 PointList points = getPoints(startFrame, endFrame); | 366 Model::toXml |
231 NoteList notes; | 367 (out, |
232 | 368 indent, |
233 for (PointList::iterator pli = | 369 QString("type=\"sparse\" dimensions=\"3\" resolution=\"%1\" " |
234 points.begin(); pli != points.end(); ++pli) { | 370 "notifyOnAdd=\"%2\" dataset=\"%3\" subtype=\"%4\" " |
235 | 371 "valueQuantization=\"%5\" minimum=\"%6\" maximum=\"%7\" " |
236 sv_frame_t duration = pli->duration; | 372 "units=\"%8\" %9") |
237 if (duration == 0 || duration == 1) { | 373 .arg(m_resolution) |
238 duration = sv_frame_t(getSampleRate() / 20); | 374 .arg("true") // always true after model reaches 100% - |
239 } | 375 // subsequent events are always notified |
240 | 376 .arg(m_events.getExportId()) |
241 int pitch = int(lrintf(pli->value)); | 377 .arg(m_subtype == FLEXI_NOTE ? "flexinote" : "note") |
242 | 378 .arg(m_valueQuantization) |
243 int velocity = 100; | 379 .arg(m_valueMinimum) |
244 if (pli->level > 0.f && pli->level <= 1.f) { | 380 .arg(m_valueMaximum) |
245 velocity = int(lrintf(pli->level * 127)); | 381 .arg(encodeEntities(m_units)) |
246 } | 382 .arg(extraAttributes)); |
247 | |
248 NoteData note(pli->frame, duration, pitch, velocity); | |
249 | |
250 if (getScaleUnits() == "Hz") { | |
251 note.frequency = pli->value; | |
252 note.midiPitch = Pitch::getPitchForFrequency(note.frequency); | |
253 note.isMidiPitchQuantized = false; | |
254 } | |
255 | 383 |
256 notes.push_back(note); | 384 m_events.toXml(out, indent, QString("dimensions=\"3\"")); |
257 } | 385 } |
258 | 386 |
259 return notes; | 387 QString toDelimitedDataString(QString delimiter, |
388 DataExportOptions options, | |
389 sv_frame_t startFrame, | |
390 sv_frame_t duration) const override { | |
391 return m_events.toDelimitedDataString | |
392 (delimiter, | |
393 options, | |
394 startFrame, | |
395 duration, | |
396 m_sampleRate, | |
397 m_resolution, | |
398 Event().withValue(0.f).withDuration(0.f).withLevel(0.f)); | |
260 } | 399 } |
261 | 400 |
262 protected: | 401 protected: |
402 Subtype m_subtype; | |
403 sv_samplerate_t m_sampleRate; | |
404 int m_resolution; | |
405 | |
406 float m_valueMinimum; | |
407 float m_valueMaximum; | |
408 bool m_haveExtents; | |
263 float m_valueQuantization; | 409 float m_valueQuantization; |
410 QString m_units; | |
411 sv_frame_t m_extendTo; | |
412 DeferredNotifier m_notifier; | |
413 int m_completion; | |
414 | |
415 EventSeries m_events; | |
416 | |
417 mutable QMutex m_mutex; | |
418 | |
419 //!!! do we have general docs for ownership and synchronisation of models? | |
420 // this might be a good opportunity to stop using bare pointers to them | |
264 }; | 421 }; |
265 | 422 |
266 #endif | 423 #endif |