Mercurial > hg > svcore
comparison data/model/SparseTimeValueModel.h @ 1651:7a56bb85030f single-point
Introduce deferred notifier, + start converting sparse time-value model (perhaps we should rename it too)
author | Chris Cannam |
---|---|
date | Mon, 18 Mar 2019 14:17:20 +0000 |
parents | ad5f892c0c4d |
children | 0cfb882155a6 |
comparison
equal
deleted
inserted
replaced
1650:bbfb5a1e4b84 | 1651:7a56bb85030f |
---|---|
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_SPARSE_TIME_VALUE_MODEL_H | 15 #ifndef SV_SPARSE_TIME_VALUE_MODEL_H |
17 #define SV_SPARSE_TIME_VALUE_MODEL_H | 16 #define SV_SPARSE_TIME_VALUE_MODEL_H |
18 | 17 |
19 #include "SparseValueModel.h" | 18 #include "EventCommands.h" |
19 #include "TabularModel.h" | |
20 #include "Model.h" | |
21 #include "DeferredNotifier.h" | |
22 | |
23 #include "base/RealTime.h" | |
24 #include "base/EventSeries.h" | |
25 #include "base/UnitDatabase.h" | |
20 #include "base/PlayParameterRepository.h" | 26 #include "base/PlayParameterRepository.h" |
21 #include "base/RealTime.h" | 27 |
28 #include "system/System.h" | |
22 | 29 |
23 /** | 30 /** |
24 * Time/value point type for use in a SparseModel or SparseValueModel. | 31 * A model representing a wiggly-line plot with points at arbitrary |
25 * With this point type, the model basically represents a wiggly-line | 32 * intervals of the model resolution. |
26 * plot with points at arbitrary intervals of the model resolution. | |
27 */ | 33 */ |
28 | 34 class SparseTimeValueModel : public Model, |
29 struct TimeValuePoint | 35 public TabularModel, |
30 { | 36 public EventEditable |
31 public: | |
32 TimeValuePoint(sv_frame_t _frame) : frame(_frame), value(0.0f) { } | |
33 TimeValuePoint(sv_frame_t _frame, float _value, QString _label) : | |
34 frame(_frame), value(_value), label(_label) { } | |
35 | |
36 int getDimensions() const { return 2; } | |
37 | |
38 sv_frame_t frame; | |
39 float value; | |
40 QString label; | |
41 | |
42 QString getLabel() const { return label; } | |
43 | |
44 void toXml(QTextStream &stream, QString indent = "", | |
45 QString extraAttributes = "") const | |
46 { | |
47 stream << QString("%1<point frame=\"%2\" value=\"%3\" label=\"%4\" %5/>\n") | |
48 .arg(indent).arg(frame).arg(value).arg(XmlExportable::encodeEntities(label)) | |
49 .arg(extraAttributes); | |
50 } | |
51 | |
52 QString toDelimitedDataString(QString delimiter, DataExportOptions, sv_samplerate_t sampleRate) const | |
53 { | |
54 QStringList list; | |
55 list << RealTime::frame2RealTime(frame, sampleRate).toString().c_str(); | |
56 list << QString("%1").arg(value); | |
57 if (label != "") list << label; | |
58 return list.join(delimiter); | |
59 } | |
60 | |
61 struct Comparator { | |
62 bool operator()(const TimeValuePoint &p1, | |
63 const TimeValuePoint &p2) const { | |
64 if (p1.frame != p2.frame) return p1.frame < p2.frame; | |
65 if (p1.value != p2.value) return p1.value < p2.value; | |
66 return p1.label < p2.label; | |
67 } | |
68 }; | |
69 | |
70 struct OrderComparator { | |
71 bool operator()(const TimeValuePoint &p1, | |
72 const TimeValuePoint &p2) const { | |
73 return p1.frame < p2.frame; | |
74 } | |
75 }; | |
76 }; | |
77 | |
78 | |
79 class SparseTimeValueModel : public SparseValueModel<TimeValuePoint> | |
80 { | 37 { |
81 Q_OBJECT | 38 Q_OBJECT |
82 | 39 |
83 public: | 40 public: |
84 SparseTimeValueModel(sv_samplerate_t sampleRate, int resolution, | 41 SparseTimeValueModel(sv_samplerate_t sampleRate, |
42 int resolution, | |
85 bool notifyOnAdd = true) : | 43 bool notifyOnAdd = true) : |
86 SparseValueModel<TimeValuePoint>(sampleRate, resolution, | 44 m_sampleRate(sampleRate), |
87 notifyOnAdd) | 45 m_resolution(resolution), |
88 { | 46 m_valueMinimum(0.f), |
47 m_valueMaximum(0.f), | |
48 m_haveExtents(false), | |
49 m_haveTextLabels(false), | |
50 m_notifier(this, | |
51 notifyOnAdd ? | |
52 DeferredNotifier::NOTIFY_ALWAYS : | |
53 DeferredNotifier::NOTIFY_DEFERRED), | |
54 m_completion(0) { | |
89 // Model is playable, but may not sound (if units not Hz or | 55 // Model is playable, but may not sound (if units not Hz or |
90 // range unsuitable) | 56 // range unsuitable) |
91 PlayParameterRepository::getInstance()->addPlayable(this); | 57 PlayParameterRepository::getInstance()->addPlayable(this); |
92 } | 58 } |
93 | 59 |
94 SparseTimeValueModel(sv_samplerate_t sampleRate, int resolution, | 60 SparseTimeValueModel(sv_samplerate_t sampleRate, int resolution, |
95 float valueMinimum, float valueMaximum, | 61 float valueMinimum, float valueMaximum, |
96 bool notifyOnAdd = true) : | 62 bool notifyOnAdd = true) : |
97 SparseValueModel<TimeValuePoint>(sampleRate, resolution, | 63 m_sampleRate(sampleRate), |
98 valueMinimum, valueMaximum, | 64 m_resolution(resolution), |
99 notifyOnAdd) | 65 m_valueMinimum(valueMinimum), |
100 { | 66 m_valueMaximum(valueMaximum), |
67 m_haveExtents(false), | |
68 m_haveTextLabels(false), | |
69 m_notifier(this, | |
70 notifyOnAdd ? | |
71 DeferredNotifier::NOTIFY_ALWAYS : | |
72 DeferredNotifier::NOTIFY_DEFERRED), | |
73 m_completion(0) { | |
101 // Model is playable, but may not sound (if units not Hz or | 74 // Model is playable, but may not sound (if units not Hz or |
102 // range unsuitable) | 75 // range unsuitable) |
103 PlayParameterRepository::getInstance()->addPlayable(this); | 76 PlayParameterRepository::getInstance()->addPlayable(this); |
104 } | 77 } |
105 | 78 |
106 virtual ~SparseTimeValueModel() | 79 virtual ~SparseTimeValueModel() { |
107 { | |
108 PlayParameterRepository::getInstance()->removePlayable(this); | 80 PlayParameterRepository::getInstance()->removePlayable(this); |
109 } | 81 } |
110 | 82 |
111 QString getTypeName() const override { return tr("Sparse Time-Value"); } | 83 QString getTypeName() const override { return tr("Sparse Time-Value"); } |
84 | |
85 bool isOK() const override { return true; } | |
86 sv_frame_t getStartFrame() const override { return m_events.getStartFrame(); } | |
87 sv_frame_t getEndFrame() const override { return m_events.getEndFrame(); } | |
88 sv_samplerate_t getSampleRate() const override { return m_sampleRate; } | |
89 int getResolution() const { return m_resolution; } | |
112 | 90 |
113 bool canPlay() const override { return true; } | 91 bool canPlay() const override { return true; } |
114 bool getDefaultPlayAudible() const override { return false; } // user must unmute | 92 bool getDefaultPlayAudible() const override { return false; } // user must unmute |
115 | 93 |
94 QString getScaleUnits() const { return m_units; } | |
95 void setScaleUnits(QString units) { | |
96 m_units = units; | |
97 UnitDatabase::getInstance()->registerUnit(units); | |
98 } | |
99 | |
100 bool hasTextLabels() const { return m_haveTextLabels; } | |
101 | |
102 float getValueMinimum() const { return m_valueMinimum; } | |
103 float getValueMaximum() const { return m_valueMaximum; } | |
104 | |
105 int getCompletion() const { return m_completion; } | |
106 | |
107 void setCompletion(int completion, bool update = true) { | |
108 | |
109 { QMutexLocker locker(&m_mutex); | |
110 if (m_completion == completion) return; | |
111 m_completion = completion; | |
112 } | |
113 | |
114 if (update) { | |
115 m_notifier.makeDeferredNotifications(); | |
116 } | |
117 | |
118 emit completionChanged(); | |
119 | |
120 if (completion == 100) { | |
121 // henceforth: | |
122 m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS); | |
123 emit modelChanged(); | |
124 } | |
125 } | |
126 | |
127 /** | |
128 * Query methods. | |
129 */ | |
130 | |
131 int getEventCount() const { | |
132 return m_events.count(); | |
133 } | |
134 bool isEmpty() const { | |
135 return m_events.isEmpty(); | |
136 } | |
137 bool containsEvent(const Event &e) const { | |
138 return m_events.contains(e); | |
139 } | |
140 EventVector getAllEvents() const { | |
141 return m_events.getAllEvents(); | |
142 } | |
143 EventVector getEventsSpanning(sv_frame_t f, sv_frame_t duration) const { | |
144 return m_events.getEventsSpanning(f, duration); | |
145 } | |
146 EventVector getEventsWithin(sv_frame_t f, sv_frame_t duration) const { | |
147 return m_events.getEventsWithin(f, duration); | |
148 } | |
149 EventVector getEventsStartingWithin(sv_frame_t f, sv_frame_t duration) const { | |
150 return m_events.getEventsStartingWithin(f, duration); | |
151 } | |
152 EventVector getEventsCovering(sv_frame_t f) const { | |
153 return m_events.getEventsCovering(f); | |
154 } | |
155 | |
156 /** | |
157 * Editing methods. | |
158 */ | |
159 void add(Event e) override { | |
160 | |
161 bool allChange = false; | |
162 | |
163 { | |
164 QMutexLocker locker(&m_mutex); | |
165 m_events.add(e); | |
166 | |
167 if (e.getLabel() != "") { | |
168 m_haveTextLabels = true; | |
169 } | |
170 | |
171 float v = e.getValue(); | |
172 if (!ISNAN(v) && !ISINF(v)) { | |
173 if (!m_haveExtents || v < m_valueMinimum) { | |
174 m_valueMinimum = v; allChange = true; | |
175 } | |
176 if (!m_haveExtents || v > m_valueMaximum) { | |
177 m_valueMaximum = v; allChange = true; | |
178 } | |
179 m_haveExtents = true; | |
180 } | |
181 } | |
182 | |
183 m_notifier.update(e.getFrame(), m_resolution); | |
184 | |
185 if (allChange) { | |
186 emit modelChanged(); | |
187 } | |
188 } | |
189 | |
190 void remove(Event e) override { | |
191 { | |
192 QMutexLocker locker(&m_mutex); | |
193 m_events.remove(e); | |
194 } | |
195 emit modelChangedWithin(e.getFrame(), e.getFrame() + m_resolution); | |
196 } | |
197 | |
116 /** | 198 /** |
117 * TabularModel methods. | 199 * TabularModel methods. |
118 */ | 200 */ |
119 | 201 |
120 int getColumnCount() const override | 202 int getRowCount() const override { |
121 { | 203 return m_events.count(); |
204 } | |
205 | |
206 int getColumnCount() const override { | |
122 return 4; | 207 return 4; |
123 } | 208 } |
124 | 209 |
125 QString getHeading(int column) const override | 210 bool isColumnTimeValue(int column) const override { |
126 { | 211 // NB duration is not a "time value" -- that's for columns |
212 // whose sort ordering is exactly that of the frame time | |
213 return (column < 2); | |
214 } | |
215 | |
216 sv_frame_t getFrameForRow(int row) const override { | |
217 if (row < 0 || row >= m_events.count()) { | |
218 return 0; | |
219 } | |
220 Event e = m_events.getEventByIndex(row); | |
221 return e.getFrame(); | |
222 } | |
223 | |
224 int getRowForFrame(sv_frame_t frame) const override { | |
225 return m_events.getIndexForEvent(Event(frame)); | |
226 } | |
227 | |
228 QString getHeading(int column) const override { | |
127 switch (column) { | 229 switch (column) { |
128 case 0: return tr("Time"); | 230 case 0: return tr("Time"); |
129 case 1: return tr("Frame"); | 231 case 1: return tr("Frame"); |
130 case 2: return tr("Value"); | 232 case 2: return tr("Value"); |
131 case 3: return tr("Label"); | 233 case 3: return tr("Label"); |
132 default: return tr("Unknown"); | 234 default: return tr("Unknown"); |
133 } | 235 } |
134 } | 236 } |
135 | 237 |
136 QVariant getData(int row, int column, int role) const override | 238 SortType getSortType(int column) const override { |
137 { | |
138 if (column < 2) { | |
139 return SparseValueModel<TimeValuePoint>::getData | |
140 (row, column, role); | |
141 } | |
142 | |
143 PointListConstIterator i = getPointListIteratorForRow(row); | |
144 if (i == m_points.end()) return QVariant(); | |
145 | |
146 switch (column) { | |
147 case 2: | |
148 if (role == Qt::EditRole || role == SortRole) return i->value; | |
149 else return QString("%1 %2").arg(i->value).arg(getScaleUnits()); | |
150 case 3: return i->label; | |
151 default: return QVariant(); | |
152 } | |
153 } | |
154 | |
155 Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override | |
156 { | |
157 if (column < 2) { | |
158 return SparseValueModel<TimeValuePoint>::getSetDataCommand | |
159 (row, column, value, role); | |
160 } | |
161 | |
162 if (role != Qt::EditRole) return 0; | |
163 PointListConstIterator i = getPointListIteratorForRow(row); | |
164 if (i == m_points.end()) return 0; | |
165 EditCommand *command = new EditCommand(this, tr("Edit Data")); | |
166 | |
167 Point point(*i); | |
168 command->deletePoint(point); | |
169 | |
170 switch (column) { | |
171 case 2: point.value = float(value.toDouble()); break; | |
172 case 3: point.label = value.toString(); break; | |
173 } | |
174 | |
175 command->addPoint(point); | |
176 return command->finish(); | |
177 } | |
178 | |
179 bool isColumnTimeValue(int column) const override | |
180 { | |
181 return (column < 2); | |
182 } | |
183 | |
184 SortType getSortType(int column) const override | |
185 { | |
186 if (column == 3) return SortAlphabetical; | 239 if (column == 3) return SortAlphabetical; |
187 return SortNumeric; | 240 return SortNumeric; |
188 } | 241 } |
242 | |
243 QVariant getData(int row, int column, int role) const override { | |
244 | |
245 if (row < 0 || row >= m_events.count()) { | |
246 return QVariant(); | |
247 } | |
248 | |
249 Event e = m_events.getEventByIndex(row); | |
250 | |
251 switch (column) { | |
252 case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role); | |
253 case 1: return int(e.getFrame()); | |
254 case 2: return adaptValueForRole(e.getValue(), getScaleUnits(), role); | |
255 case 3: return e.getLabel(); | |
256 default: return QVariant(); | |
257 } | |
258 } | |
259 | |
260 Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override { | |
261 if (row < 0 || row >= m_events.count()) return nullptr; | |
262 if (role != Qt::EditRole) return nullptr; | |
263 | |
264 Event e0 = m_events.getEventByIndex(row); | |
265 Event e1; | |
266 | |
267 switch (column) { | |
268 case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() * | |
269 getSampleRate()))); break; | |
270 case 1: e1 = e0.withFrame(value.toInt()); break; | |
271 case 2: e1 = e0.withValue(float(value.toDouble())); break; | |
272 case 3: e1 = e0.withLabel(value.toString()); break; | |
273 } | |
274 | |
275 ChangeEventsCommand *command = | |
276 new ChangeEventsCommand(this, tr("Edit Data")); | |
277 command->remove(e0); | |
278 command->add(e1); | |
279 return command->finish(); | |
280 } | |
281 | |
282 /** | |
283 * XmlExportable methods. | |
284 */ | |
285 void toXml(QTextStream &out, | |
286 QString indent = "", | |
287 QString extraAttributes = "") const override { | |
288 | |
289 Model::toXml | |
290 (out, | |
291 indent, | |
292 QString("type=\"sparse\" dimensions=\"2\" resolution=\"%1\" " | |
293 "notifyOnAdd=\"%2\" dataset=\"%3\" " | |
294 "minimum=\"%4\" maximum=\"%5\" " | |
295 "units=\"%6\" %7") | |
296 .arg(m_resolution) | |
297 .arg("true") // always true after model reaches 100% - | |
298 // subsequent events are always notified | |
299 .arg(getObjectExportId(&m_events)) | |
300 .arg(m_valueMinimum) | |
301 .arg(m_valueMaximum) | |
302 .arg(encodeEntities(m_units)) | |
303 .arg(extraAttributes)); | |
304 | |
305 m_events.toXml(out, indent, QString("dimensions=\"2\"")); | |
306 } | |
307 | |
308 protected: | |
309 sv_samplerate_t m_sampleRate; | |
310 int m_resolution; | |
311 | |
312 float m_valueMinimum; | |
313 float m_valueMaximum; | |
314 bool m_haveExtents; | |
315 bool m_haveTextLabels; | |
316 QString m_units; | |
317 DeferredNotifier m_notifier; | |
318 int m_completion; | |
319 | |
320 EventSeries m_events; | |
321 | |
322 mutable QMutex m_mutex; | |
189 }; | 323 }; |
190 | 324 |
191 | 325 |
192 #endif | 326 #endif |
193 | 327 |