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