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