Mercurial > hg > svcore
comparison data/model/NoteModel.h @ 1643:7a23dfe65d66 single-point
Update NoteModel to use EventSeries. This is incomplete and won't provide enough to update svgui for it yet; must also refactor to avoid duplication of nasty APIs when updating the rest of the models
author | Chris Cannam |
---|---|
date | Wed, 13 Mar 2019 14:50:10 +0000 |
parents | 24dc8cb42755 |
children | 513192aa9b03 |
comparison
equal
deleted
inserted
replaced
1642:d591836e47ef | 1643:7a23dfe65d66 |
---|---|
14 */ | 14 */ |
15 | 15 |
16 #ifndef SV_NOTE_MODEL_H | 16 #ifndef SV_NOTE_MODEL_H |
17 #define SV_NOTE_MODEL_H | 17 #define SV_NOTE_MODEL_H |
18 | 18 |
19 #include "IntervalModel.h" | 19 #include "Model.h" |
20 #include "TabularModel.h" | |
21 #include "base/UnitDatabase.h" | |
22 #include "base/EventSeries.h" | |
20 #include "base/NoteData.h" | 23 #include "base/NoteData.h" |
24 #include "base/NoteExportable.h" | |
21 #include "base/RealTime.h" | 25 #include "base/RealTime.h" |
22 #include "base/PlayParameterRepository.h" | 26 #include "base/PlayParameterRepository.h" |
23 #include "base/Pitch.h" | 27 #include "base/Pitch.h" |
24 | 28 #include "system/System.h" |
25 /** | 29 |
26 * NoteModel -- a concrete IntervalModel for notes. | 30 #include <QMutex> |
27 */ | 31 #include <QMutexLocker> |
28 | 32 |
29 /** | 33 class NoteModel : public Model, |
30 * Note type for use in a sparse model. All we mean by a "note" is | 34 public TabularModel, |
31 * something that has an onset time, a single value, a duration, and a | 35 public NoteExportable |
32 * level. Like other points, it can also have a label. With this | |
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 bool operator==(const Note &other) const { | |
96 // ew | |
97 Comparator c; | |
98 return !(c(*this, other) || c(other, *this)); | |
99 } | |
100 }; | |
101 | |
102 | |
103 class NoteModel : public IntervalModel<Note>, public NoteExportable | |
104 { | 36 { |
105 Q_OBJECT | 37 Q_OBJECT |
106 | 38 |
107 public: | 39 public: |
108 NoteModel(sv_samplerate_t sampleRate, int resolution, | 40 NoteModel(sv_samplerate_t sampleRate, int resolution, |
109 bool notifyOnAdd = true) : | 41 bool notifyOnAdd = true) : |
110 IntervalModel<Note>(sampleRate, resolution, notifyOnAdd), | 42 m_sampleRate(sampleRate), |
111 m_valueQuantization(0) | 43 m_resolution(resolution), |
112 { | 44 m_valueMinimum(0.f), |
45 m_valueMaximum(0.f), | |
46 m_haveExtents(false), | |
47 m_valueQuantization(0), | |
48 m_units(""), | |
49 m_extendTo(0), | |
50 m_notifyOnAdd(notifyOnAdd), | |
51 m_sinceLastNotifyMin(-1), | |
52 m_sinceLastNotifyMax(-1), | |
53 m_completion(0) { | |
113 PlayParameterRepository::getInstance()->addPlayable(this); | 54 PlayParameterRepository::getInstance()->addPlayable(this); |
114 } | 55 } |
115 | 56 |
116 NoteModel(sv_samplerate_t sampleRate, int resolution, | 57 NoteModel(sv_samplerate_t sampleRate, int resolution, |
117 float valueMinimum, float valueMaximum, | 58 float valueMinimum, float valueMaximum, |
118 bool notifyOnAdd = true) : | 59 bool notifyOnAdd = true) : |
119 IntervalModel<Note>(sampleRate, resolution, | 60 m_sampleRate(sampleRate), |
120 valueMinimum, valueMaximum, | 61 m_resolution(resolution), |
121 notifyOnAdd), | 62 m_valueMinimum(valueMinimum), |
122 m_valueQuantization(0) | 63 m_valueMaximum(valueMaximum), |
123 { | 64 m_haveExtents(true), |
65 m_valueQuantization(0), | |
66 m_units(""), | |
67 m_extendTo(0), | |
68 m_notifyOnAdd(notifyOnAdd), | |
69 m_sinceLastNotifyMin(-1), | |
70 m_sinceLastNotifyMax(-1), | |
71 m_completion(0) { | |
124 PlayParameterRepository::getInstance()->addPlayable(this); | 72 PlayParameterRepository::getInstance()->addPlayable(this); |
125 } | 73 } |
126 | 74 |
127 virtual ~NoteModel() | 75 virtual ~NoteModel() { |
128 { | |
129 PlayParameterRepository::getInstance()->removePlayable(this); | 76 PlayParameterRepository::getInstance()->removePlayable(this); |
77 } | |
78 | |
79 QString getTypeName() const override { return tr("Note"); } | |
80 | |
81 bool isOK() const override { return true; } | |
82 sv_frame_t getStartFrame() const override { return m_events.getStartFrame(); } | |
83 sv_frame_t getEndFrame() const override { return m_events.getEndFrame(); } | |
84 sv_samplerate_t getSampleRate() const override { return m_sampleRate; } | |
85 | |
86 bool canPlay() const override { return true; } | |
87 QString getDefaultPlayClipId() const override { | |
88 return "elecpiano"; | |
89 } | |
90 | |
91 QString getScaleUnits() const { return m_units; } | |
92 void setScaleUnits(QString units) { | |
93 m_units = units; | |
94 UnitDatabase::getInstance()->registerUnit(units); | |
130 } | 95 } |
131 | 96 |
132 float getValueQuantization() const { return m_valueQuantization; } | 97 float getValueQuantization() const { return m_valueQuantization; } |
133 void setValueQuantization(float q) { m_valueQuantization = q; } | 98 void setValueQuantization(float q) { m_valueQuantization = q; } |
134 | 99 |
135 QString getTypeName() const override { return tr("Note"); } | 100 float getValueMinimum() const { return m_valueMinimum; } |
136 | 101 float getValueMaximum() const { return m_valueMaximum; } |
137 bool canPlay() const override { return true; } | 102 |
138 | 103 int getCompletion() const { return m_completion; } |
139 QString getDefaultPlayClipId() const override | 104 |
140 { | 105 void setCompletion(int completion, bool update = true) { |
141 return "elecpiano"; | 106 |
142 } | 107 bool emitCompletionChanged = true; |
143 | 108 bool emitGeneralModelChanged = false; |
109 bool emitRegionChanged = false; | |
110 | |
111 { | |
112 QMutexLocker locker(&m_mutex); | |
113 | |
114 if (m_completion != completion) { | |
115 m_completion = completion; | |
116 | |
117 if (completion == 100) { | |
118 | |
119 if (m_notifyOnAdd) { | |
120 emitCompletionChanged = false; | |
121 } | |
122 | |
123 m_notifyOnAdd = true; // henceforth | |
124 emitGeneralModelChanged = true; | |
125 | |
126 } else if (!m_notifyOnAdd) { | |
127 | |
128 if (update && | |
129 m_sinceLastNotifyMin >= 0 && | |
130 m_sinceLastNotifyMax >= 0) { | |
131 emitRegionChanged = true; | |
132 } | |
133 } | |
134 } | |
135 } | |
136 | |
137 if (emitCompletionChanged) { | |
138 emit completionChanged(); | |
139 } | |
140 if (emitGeneralModelChanged) { | |
141 emit modelChanged(); | |
142 } | |
143 if (emitRegionChanged) { | |
144 emit modelChangedWithin(m_sinceLastNotifyMin, m_sinceLastNotifyMax); | |
145 m_sinceLastNotifyMin = m_sinceLastNotifyMax = -1; | |
146 } | |
147 } | |
148 | |
144 void toXml(QTextStream &out, | 149 void toXml(QTextStream &out, |
145 QString indent = "", | 150 QString indent = "", |
146 QString extraAttributes = "") const override | 151 QString extraAttributes = "") const override { |
147 { | 152 |
148 std::cerr << "NoteModel::toXml: extraAttributes = \"" | 153 //!!! what is valueQuantization used for? |
149 << extraAttributes.toStdString() << "\"" << std::endl; | 154 |
150 | 155 Model::toXml |
151 IntervalModel<Note>::toXml | |
152 (out, | 156 (out, |
153 indent, | 157 indent, |
154 QString("%1 subtype=\"note\" valueQuantization=\"%2\"") | 158 QString("type=\"sparse\" dimensions=\"3\" resolution=\"%1\" " |
155 .arg(extraAttributes).arg(m_valueQuantization)); | 159 "notifyOnAdd=\"%2\" dataset=\"%3\" subtype=\"note\" " |
160 "valueQuantization=\"%4\" minimum=\"%5\" maximum=\"%6\" " | |
161 "units=\"%7\" %8") | |
162 .arg(m_resolution) | |
163 .arg(m_notifyOnAdd ? "true" : "false") | |
164 .arg(getObjectExportId(&m_events)) | |
165 .arg(m_valueQuantization) | |
166 .arg(m_valueMinimum) | |
167 .arg(m_valueMaximum) | |
168 .arg(m_units) | |
169 .arg(extraAttributes)); | |
170 | |
171 m_events.toXml(out, indent, QString("dimensions=\"3\"")); | |
156 } | 172 } |
157 | 173 |
158 /** | 174 /** |
159 * TabularModel methods. | 175 * TabularModel methods. |
160 */ | 176 */ |
161 | 177 |
162 int getColumnCount() const override | 178 int getRowCount() const override { |
163 { | 179 return m_events.count(); |
180 } | |
181 | |
182 int getColumnCount() const override { | |
164 return 6; | 183 return 6; |
165 } | 184 } |
166 | 185 |
167 QString getHeading(int column) const override | 186 bool isColumnTimeValue(int column) const override { |
168 { | 187 // NB duration is not a "time value" -- that's for columns |
188 // whose sort ordering is exactly that of the frame time | |
189 return (column < 2); | |
190 } | |
191 | |
192 sv_frame_t getFrameForRow(int row) const override { | |
193 if (row < 0 || row >= m_events.count()) { | |
194 return 0; | |
195 } | |
196 Event e = m_events.getEventByIndex(row); | |
197 return e.getFrame(); | |
198 } | |
199 | |
200 int getRowForFrame(sv_frame_t frame) const override { | |
201 return m_events.getIndexForEvent(Event(frame)); | |
202 } | |
203 | |
204 QString getHeading(int column) const override { | |
169 switch (column) { | 205 switch (column) { |
170 case 0: return tr("Time"); | 206 case 0: return tr("Time"); |
171 case 1: return tr("Frame"); | 207 case 1: return tr("Frame"); |
172 case 2: return tr("Pitch"); | 208 case 2: return tr("Pitch"); |
173 case 3: return tr("Duration"); | 209 case 3: return tr("Duration"); |
175 case 5: return tr("Label"); | 211 case 5: return tr("Label"); |
176 default: return tr("Unknown"); | 212 default: return tr("Unknown"); |
177 } | 213 } |
178 } | 214 } |
179 | 215 |
180 QVariant getData(int row, int column, int role) const override | 216 QVariant getData(int row, int column, int role) const override { |
217 | |
218 if (row < 0 || row >= m_events.count()) { | |
219 return QVariant(); | |
220 } | |
221 | |
222 Event e = m_events.getEventByIndex(row); | |
223 | |
224 switch (column) { | |
225 case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role); | |
226 case 1: return int(e.getFrame()); | |
227 case 2: return adaptValueForRole(e.getValue(), getScaleUnits(), role); | |
228 case 3: return int(e.getDuration()); | |
229 case 4: return e.getLevel(); | |
230 case 5: return e.getLabel(); | |
231 default: return QVariant(); | |
232 } | |
233 } | |
234 | |
235 class EditCommand : public Command | |
181 { | 236 { |
182 if (column < 4) { | 237 public: |
183 return IntervalModel<Note>::getData(row, column, role); | 238 //!!! borrowed ptr |
184 } | 239 EditCommand(NoteModel *model, QString name) : |
185 | 240 m_model(model), m_name(name) { } |
186 PointListConstIterator i = getPointListIteratorForRow(row); | 241 |
187 if (i == m_points.end()) return QVariant(); | 242 QString getName() const override { |
188 | 243 return m_name; |
189 switch (column) { | 244 } |
190 case 4: return i->level; | 245 |
191 case 5: return i->label; | 246 void addPoint(Event e) { |
192 default: return QVariant(); | 247 m_add.insert(e); |
193 } | 248 } |
249 void deletePoint(Event e) { | |
250 m_remove.insert(e); | |
251 } | |
252 | |
253 void execute() override { | |
254 for (const Event &e: m_add) m_model->addPoint(e); | |
255 for (const Event &e: m_remove) m_model->deletePoint(e); | |
256 } | |
257 | |
258 void unexecute() override { | |
259 for (const Event &e: m_remove) m_model->addPoint(e); | |
260 for (const Event &e: m_add) m_model->deletePoint(e); | |
261 } | |
262 | |
263 private: | |
264 NoteModel *m_model; | |
265 std::set<Event> m_add; | |
266 std::set<Event> m_remove; | |
267 QString m_name; | |
268 }; | |
269 | |
270 //!!! rename Point to Note throughout? Just because we can now? | |
271 void addPoint(Event e) { | |
272 | |
273 bool allChange = false; | |
274 | |
275 { | |
276 QMutexLocker locker(&m_mutex); | |
277 m_events.add(e); | |
278 //!!!??? if (point.getLabel() != "") m_hasTextLabels = true; | |
279 | |
280 float v = e.getValue(); | |
281 if (!ISNAN(v) && !ISINF(v)) { | |
282 if (!m_haveExtents || v < m_valueMinimum) { | |
283 m_valueMinimum = v; allChange = true; | |
284 } | |
285 if (!m_haveExtents || v > m_valueMaximum) { | |
286 m_valueMaximum = v; allChange = true; | |
287 } | |
288 m_haveExtents = true; | |
289 } | |
290 | |
291 sv_frame_t f = e.getFrame(); | |
292 | |
293 if (!m_notifyOnAdd) { | |
294 if (m_sinceLastNotifyMin == -1 || f < m_sinceLastNotifyMin) { | |
295 m_sinceLastNotifyMin = f; | |
296 } | |
297 if (m_sinceLastNotifyMax == -1 || f > m_sinceLastNotifyMax) { | |
298 m_sinceLastNotifyMax = f; | |
299 } | |
300 } | |
301 } | |
302 | |
303 if (m_notifyOnAdd) { | |
304 emit modelChangedWithin(e.getFrame(), | |
305 e.getFrame() + e.getDuration() + m_resolution); | |
306 } | |
307 if (allChange) { | |
308 emit modelChanged(); | |
309 } | |
310 } | |
311 | |
312 void deletePoint(Event e) { | |
313 { | |
314 QMutexLocker locker(&m_mutex); | |
315 m_events.remove(e); | |
316 } | |
317 emit modelChangedWithin(e.getFrame(), | |
318 e.getFrame() + e.getDuration() + m_resolution); | |
319 } | |
320 | |
321 EventVector getPoints() const /*!!! override? - and/or rename? */ { | |
322 EventVector ee; | |
323 for (int i = 0; i < m_events.count(); ++i) { | |
324 ee.push_back(m_events.getEventByIndex(i)); | |
325 } | |
326 return ee; | |
327 } | |
328 | |
329 //!!! bleah | |
330 EventVector getPoints(sv_frame_t start, sv_frame_t end) const { | |
331 return m_events.getEventsSpanning(start, end - start); | |
332 } | |
333 | |
334 int getPointCount() const { | |
335 return m_events.count(); | |
336 } | |
337 | |
338 bool isEmpty() const { | |
339 return m_events.isEmpty(); | |
340 } | |
341 | |
342 bool containsPoint(const Event &e) const { | |
343 return m_events.contains(e); | |
194 } | 344 } |
195 | 345 |
196 Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override | 346 Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override |
197 { | 347 { |
198 if (column < 4) { | 348 if (row < 0 || row >= m_events.count()) return nullptr; |
199 return IntervalModel<Note>::getSetDataCommand | 349 if (role != Qt::EditRole) return nullptr; |
200 (row, column, value, role); | 350 |
201 } | 351 Event e0 = m_events.getEventByIndex(row); |
202 | 352 Event e1; |
203 if (role != Qt::EditRole) return 0; | 353 |
204 PointListConstIterator i = getPointListIteratorForRow(row); | 354 switch (column) { |
205 if (i == m_points.end()) return 0; | 355 case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() * |
356 getSampleRate()))); break; | |
357 case 1: e1 = e0.withFrame(value.toInt()); break; | |
358 case 2: e1 = e0.withValue(float(value.toDouble())); break; | |
359 case 3: e1 = e0.withDuration(value.toInt()); break; | |
360 case 4: e1 = e0.withLevel(float(value.toDouble())); break; | |
361 case 5: e1 = e0.withLabel(value.toString()); break; | |
362 } | |
363 | |
206 EditCommand *command = new EditCommand(this, tr("Edit Data")); | 364 EditCommand *command = new EditCommand(this, tr("Edit Data")); |
207 | 365 command->deletePoint(e0); |
208 Point point(*i); | 366 command->addPoint(e1); |
209 command->deletePoint(point); | 367 return command; |
210 | |
211 switch (column) { | |
212 case 4: point.level = float(value.toDouble()); break; | |
213 case 5: point.label = value.toString(); break; | |
214 } | |
215 | |
216 command->addPoint(point); | |
217 return command->finish(); | |
218 } | 368 } |
219 | 369 |
220 SortType getSortType(int column) const override | 370 SortType getSortType(int column) const override |
221 { | 371 { |
222 if (column == 5) return SortAlphabetical; | 372 if (column == 5) return SortAlphabetical; |
226 /** | 376 /** |
227 * NoteExportable methods. | 377 * NoteExportable methods. |
228 */ | 378 */ |
229 | 379 |
230 NoteList getNotes() const override { | 380 NoteList getNotes() const override { |
231 return getNotesWithin(getStartFrame(), getEndFrame()); | 381 return getNotesStartingWithin(getStartFrame(), |
232 } | 382 getEndFrame() - getStartFrame()); |
233 | 383 } |
234 NoteList getNotesWithin(sv_frame_t startFrame, sv_frame_t endFrame) const override { | 384 |
235 | 385 NoteList getNotesActiveAt(sv_frame_t frame) const override { |
236 PointList points = getPoints(startFrame, endFrame); | 386 |
237 NoteList notes; | 387 NoteList notes; |
238 | 388 EventVector ee = m_events.getEventsCovering(frame); |
239 for (PointList::iterator pli = | 389 for (const auto &e: ee) { |
240 points.begin(); pli != points.end(); ++pli) { | 390 notes.push_back(e.toNoteData(getSampleRate(), |
241 | 391 getScaleUnits() != "Hz")); |
242 sv_frame_t duration = pli->duration; | 392 } |
243 if (duration == 0 || duration == 1) { | |
244 duration = sv_frame_t(getSampleRate() / 20); | |
245 } | |
246 | |
247 int pitch = int(lrintf(pli->value)); | |
248 | |
249 int velocity = 100; | |
250 if (pli->level > 0.f && pli->level <= 1.f) { | |
251 velocity = int(lrintf(pli->level * 127)); | |
252 } | |
253 | |
254 NoteData note(pli->frame, duration, pitch, velocity); | |
255 | |
256 if (getScaleUnits() == "Hz") { | |
257 note.frequency = pli->value; | |
258 note.midiPitch = Pitch::getPitchForFrequency(note.frequency); | |
259 note.isMidiPitchQuantized = false; | |
260 } | |
261 | |
262 notes.push_back(note); | |
263 } | |
264 | |
265 return notes; | 393 return notes; |
266 } | 394 } |
395 | |
396 NoteList getNotesStartingWithin(sv_frame_t startFrame, | |
397 sv_frame_t duration) const override { | |
398 | |
399 NoteList notes; | |
400 EventVector ee = m_events.getEventsStartingWithin(startFrame, duration); | |
401 for (const auto &e: ee) { | |
402 notes.push_back(e.toNoteData(getSampleRate(), | |
403 getScaleUnits() != "Hz")); | |
404 } | |
405 return notes; | |
406 } | |
267 | 407 |
268 protected: | 408 protected: |
409 sv_samplerate_t m_sampleRate; | |
410 int m_resolution; | |
411 | |
412 float m_valueMinimum; | |
413 float m_valueMaximum; | |
414 bool m_haveExtents; | |
269 float m_valueQuantization; | 415 float m_valueQuantization; |
416 QString m_units; | |
417 | |
418 sv_frame_t m_extendTo; | |
419 | |
420 bool m_notifyOnAdd; | |
421 sv_frame_t m_sinceLastNotifyMin; | |
422 sv_frame_t m_sinceLastNotifyMax; | |
423 | |
424 EventSeries m_events; | |
425 | |
426 int m_completion; | |
427 | |
428 mutable QMutex m_mutex; | |
429 | |
430 //!!! do we have general docs for ownership and synchronisation of models? | |
431 // this might be a good opportunity to stop using bare pointers to them | |
270 }; | 432 }; |
271 | 433 |
272 #endif | 434 #endif |