Mercurial > hg > svcore
comparison data/model/BoxModel.h @ 1794:71e97de5053f
Merge from branch time-frequency-boxes
author | Chris Cannam |
---|---|
date | Wed, 25 Sep 2019 13:47:46 +0100 |
parents | cb45c0a1dfe1 |
children | 343ef2a866a4 |
comparison
equal
deleted
inserted
replaced
1788:7fc6256af2d2 | 1794:71e97de5053f |
---|---|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ | |
2 | |
3 /* | |
4 Sonic Visualiser | |
5 An audio file viewer and annotation editor. | |
6 Centre for Digital Music, Queen Mary, University of London. | |
7 | |
8 This program is free software; you can redistribute it and/or | |
9 modify it under the terms of the GNU General Public License as | |
10 published by the Free Software Foundation; either version 2 of the | |
11 License, or (at your option) any later version. See the file | |
12 COPYING included with this distribution for more information. | |
13 */ | |
14 | |
15 #ifndef SV_BOX_MODEL_H | |
16 #define SV_BOX_MODEL_H | |
17 | |
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" | |
26 | |
27 #include "system/System.h" | |
28 | |
29 #include <QMutex> | |
30 | |
31 /** | |
32 * BoxModel -- a model for annotations having start time, duration, | |
33 * and a value range. We use Events as usual for these, but treat the | |
34 * "value" as the lower value and "level" as the difference between | |
35 * lower and upper values, which is expected to be non-negative (if it | |
36 * is negative, abs(level) will be used). | |
37 * | |
38 * This is expected to be used most often for time-frequency boxes. | |
39 */ | |
40 class BoxModel : public Model, | |
41 public TabularModel, | |
42 public EventEditable | |
43 { | |
44 Q_OBJECT | |
45 | |
46 public: | |
47 BoxModel(sv_samplerate_t sampleRate, | |
48 int resolution, | |
49 bool notifyOnAdd = true) : | |
50 m_sampleRate(sampleRate), | |
51 m_resolution(resolution), | |
52 m_valueMinimum(0.f), | |
53 m_valueMaximum(0.f), | |
54 m_haveExtents(false), | |
55 m_notifier(this, | |
56 getId(), | |
57 notifyOnAdd ? | |
58 DeferredNotifier::NOTIFY_ALWAYS : | |
59 DeferredNotifier::NOTIFY_DEFERRED), | |
60 m_completion(100) { | |
61 } | |
62 | |
63 BoxModel(sv_samplerate_t sampleRate, int resolution, | |
64 float valueMinimum, float valueMaximum, | |
65 bool notifyOnAdd = true) : | |
66 m_sampleRate(sampleRate), | |
67 m_resolution(resolution), | |
68 m_valueMinimum(valueMinimum), | |
69 m_valueMaximum(valueMaximum), | |
70 m_haveExtents(true), | |
71 m_notifier(this, | |
72 getId(), | |
73 notifyOnAdd ? | |
74 DeferredNotifier::NOTIFY_ALWAYS : | |
75 DeferredNotifier::NOTIFY_DEFERRED), | |
76 m_completion(100) { | |
77 } | |
78 | |
79 virtual ~BoxModel() { | |
80 } | |
81 | |
82 QString getTypeName() const override { return tr("Box"); } | |
83 bool isSparse() const override { return true; } | |
84 bool isOK() const override { return true; } | |
85 | |
86 sv_frame_t getStartFrame() const override { | |
87 return m_events.getStartFrame(); | |
88 } | |
89 sv_frame_t getTrueEndFrame() const override { | |
90 if (m_events.isEmpty()) return 0; | |
91 sv_frame_t e = m_events.getEndFrame(); | |
92 if (e % m_resolution == 0) return e; | |
93 else return (e / m_resolution + 1) * m_resolution; | |
94 } | |
95 | |
96 sv_samplerate_t getSampleRate() const override { return m_sampleRate; } | |
97 int getResolution() const { return m_resolution; } | |
98 | |
99 QString getScaleUnits() const { return m_units; } | |
100 void setScaleUnits(QString units) { | |
101 m_units = units; | |
102 UnitDatabase::getInstance()->registerUnit(units); | |
103 } | |
104 | |
105 float getValueMinimum() const { return m_valueMinimum; } | |
106 float getValueMaximum() const { return m_valueMaximum; } | |
107 | |
108 int getCompletion() const override { return m_completion; } | |
109 | |
110 void setCompletion(int completion, bool update = true) { | |
111 | |
112 { QMutexLocker locker(&m_mutex); | |
113 if (m_completion == completion) return; | |
114 m_completion = completion; | |
115 } | |
116 | |
117 if (update) { | |
118 m_notifier.makeDeferredNotifications(); | |
119 } | |
120 | |
121 emit completionChanged(getId()); | |
122 | |
123 if (completion == 100) { | |
124 // henceforth: | |
125 m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS); | |
126 emit modelChanged(getId()); | |
127 } | |
128 } | |
129 | |
130 /** | |
131 * Query methods. | |
132 */ | |
133 int getEventCount() const { | |
134 return m_events.count(); | |
135 } | |
136 bool isEmpty() const { | |
137 return m_events.isEmpty(); | |
138 } | |
139 bool containsEvent(const Event &e) const { | |
140 return m_events.contains(e); | |
141 } | |
142 EventVector getAllEvents() const { | |
143 return m_events.getAllEvents(); | |
144 } | |
145 EventVector getEventsSpanning(sv_frame_t f, sv_frame_t duration) const { | |
146 return m_events.getEventsSpanning(f, duration); | |
147 } | |
148 EventVector getEventsCovering(sv_frame_t f) const { | |
149 return m_events.getEventsCovering(f); | |
150 } | |
151 EventVector getEventsWithin(sv_frame_t f, sv_frame_t duration) const { | |
152 return m_events.getEventsWithin(f, duration); | |
153 } | |
154 EventVector getEventsStartingWithin(sv_frame_t f, sv_frame_t duration) const { | |
155 return m_events.getEventsStartingWithin(f, duration); | |
156 } | |
157 EventVector getEventsStartingAt(sv_frame_t f) const { | |
158 return m_events.getEventsStartingAt(f); | |
159 } | |
160 bool getNearestEventMatching(sv_frame_t startSearchAt, | |
161 std::function<bool(Event)> predicate, | |
162 EventSeries::Direction direction, | |
163 Event &found) const { | |
164 return m_events.getNearestEventMatching | |
165 (startSearchAt, predicate, direction, found); | |
166 } | |
167 | |
168 /** | |
169 * Editing methods. | |
170 */ | |
171 void add(Event e) override { | |
172 | |
173 bool allChange = false; | |
174 | |
175 { | |
176 QMutexLocker locker(&m_mutex); | |
177 m_events.add(e); | |
178 | |
179 float f0 = e.getValue(); | |
180 float f1 = f0 + fabsf(e.getLevel()); | |
181 | |
182 if (!m_haveExtents || f0 < m_valueMinimum) { | |
183 m_valueMinimum = f0; allChange = true; | |
184 } | |
185 if (!m_haveExtents || f1 > m_valueMaximum) { | |
186 m_valueMaximum = f1; allChange = true; | |
187 } | |
188 m_haveExtents = true; | |
189 } | |
190 | |
191 m_notifier.update(e.getFrame(), e.getDuration() + m_resolution); | |
192 | |
193 if (allChange) { | |
194 emit modelChanged(getId()); | |
195 } | |
196 } | |
197 | |
198 void remove(Event e) override { | |
199 { | |
200 QMutexLocker locker(&m_mutex); | |
201 m_events.remove(e); | |
202 } | |
203 emit modelChangedWithin(getId(), | |
204 e.getFrame(), | |
205 e.getFrame() + e.getDuration() + m_resolution); | |
206 } | |
207 | |
208 /** | |
209 * TabularModel methods. | |
210 */ | |
211 | |
212 int getRowCount() const override { | |
213 return m_events.count(); | |
214 } | |
215 | |
216 int getColumnCount() const override { | |
217 return 6; | |
218 } | |
219 | |
220 bool isColumnTimeValue(int column) const override { | |
221 // NB duration is not a "time value" -- that's for columns | |
222 // whose sort ordering is exactly that of the frame time | |
223 return (column < 2); | |
224 } | |
225 | |
226 sv_frame_t getFrameForRow(int row) const override { | |
227 if (row < 0 || row >= m_events.count()) { | |
228 return 0; | |
229 } | |
230 Event e = m_events.getEventByIndex(row); | |
231 return e.getFrame(); | |
232 } | |
233 | |
234 int getRowForFrame(sv_frame_t frame) const override { | |
235 return m_events.getIndexForEvent(Event(frame)); | |
236 } | |
237 | |
238 QString getHeading(int column) const override { | |
239 switch (column) { | |
240 case 0: return tr("Time"); | |
241 case 1: return tr("Frame"); | |
242 case 2: return tr("Duration"); | |
243 case 3: return tr("Min Freq"); | |
244 case 4: return tr("Max Freq"); | |
245 case 5: return tr("Label"); | |
246 default: return tr("Unknown"); | |
247 } | |
248 } | |
249 | |
250 SortType getSortType(int column) const override { | |
251 if (column == 5) return SortAlphabetical; | |
252 return SortNumeric; | |
253 } | |
254 | |
255 QVariant getData(int row, int column, int role) const override { | |
256 | |
257 if (row < 0 || row >= m_events.count()) { | |
258 return QVariant(); | |
259 } | |
260 | |
261 Event e = m_events.getEventByIndex(row); | |
262 | |
263 switch (column) { | |
264 case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role); | |
265 case 1: return int(e.getFrame()); | |
266 case 2: return int(e.getDuration()); | |
267 case 3: return adaptValueForRole(e.getValue(), getScaleUnits(), role); | |
268 case 4: return adaptValueForRole(e.getValue() + fabsf(e.getLevel()), | |
269 getScaleUnits(), role); | |
270 case 5: return e.getLabel(); | |
271 default: return QVariant(); | |
272 } | |
273 } | |
274 | |
275 Command *getSetDataCommand(int row, int column, const QVariant &value, | |
276 int role) override { | |
277 | |
278 if (row < 0 || row >= m_events.count()) return nullptr; | |
279 if (role != Qt::EditRole) return nullptr; | |
280 | |
281 Event e0 = m_events.getEventByIndex(row); | |
282 Event e1; | |
283 | |
284 switch (column) { | |
285 case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() * | |
286 getSampleRate()))); break; | |
287 case 1: e1 = e0.withFrame(value.toInt()); break; | |
288 case 2: e1 = e0.withDuration(value.toInt()); break; | |
289 case 3: e1 = e0.withValue(float(value.toDouble())); break; | |
290 case 4: e1 = e0.withLevel(fabsf(float(value.toDouble()) - | |
291 e0.getValue())); break; | |
292 case 5: e1 = e0.withLabel(value.toString()); break; | |
293 } | |
294 | |
295 auto command = new ChangeEventsCommand(getId().untyped, tr("Edit Data")); | |
296 command->remove(e0); | |
297 command->add(e1); | |
298 return command->finish(); | |
299 } | |
300 | |
301 | |
302 /** | |
303 * XmlExportable methods. | |
304 */ | |
305 void toXml(QTextStream &out, | |
306 QString indent = "", | |
307 QString extraAttributes = "") const override { | |
308 | |
309 Model::toXml | |
310 (out, | |
311 indent, | |
312 QString("type=\"sparse\" dimensions=\"2\" resolution=\"%1\" " | |
313 "notifyOnAdd=\"%2\" dataset=\"%3\" subtype=\"%4\" " | |
314 "minimum=\"%5\" maximum=\"%6\" units=\"%7\" %8") | |
315 .arg(m_resolution) | |
316 .arg("true") // always true after model reaches 100% - | |
317 // subsequent events are always notified | |
318 .arg(m_events.getExportId()) | |
319 .arg("box") | |
320 .arg(m_valueMinimum) | |
321 .arg(m_valueMaximum) | |
322 .arg(encodeEntities(m_units)) | |
323 .arg(extraAttributes)); | |
324 | |
325 Event::ExportNameOptions options; | |
326 options.levelAttributeName = "extent"; | |
327 | |
328 m_events.toXml(out, indent, QString("dimensions=\"2\""), options); | |
329 } | |
330 | |
331 QString toDelimitedDataString(QString delimiter, | |
332 DataExportOptions, | |
333 sv_frame_t startFrame, | |
334 sv_frame_t duration) const override { | |
335 | |
336 // We need a custom format here | |
337 | |
338 EventVector ee = m_events.getEventsSpanning(startFrame, duration); | |
339 | |
340 QString s; | |
341 | |
342 for (auto e: ee) { | |
343 | |
344 QStringList list; | |
345 | |
346 list << RealTime::frame2RealTime | |
347 (e.getFrame(), getSampleRate()) | |
348 .toString().c_str() | |
349 << RealTime::frame2RealTime | |
350 (e.getFrame() + e.getDuration(), getSampleRate()) | |
351 .toString().c_str() | |
352 << QString("%1").arg(e.getValue()) | |
353 << QString("%1").arg(e.getValue() + fabsf(e.getLevel())); | |
354 | |
355 if (e.getLabel() != "") { | |
356 list << e.getLabel(); | |
357 } | |
358 | |
359 s += list.join(delimiter) + "\n"; | |
360 } | |
361 | |
362 return s; | |
363 } | |
364 | |
365 protected: | |
366 sv_samplerate_t m_sampleRate; | |
367 int m_resolution; | |
368 | |
369 float m_valueMinimum; | |
370 float m_valueMaximum; | |
371 bool m_haveExtents; | |
372 QString m_units; | |
373 DeferredNotifier m_notifier; | |
374 int m_completion; | |
375 | |
376 EventSeries m_events; | |
377 | |
378 mutable QMutex m_mutex; | |
379 }; | |
380 | |
381 #endif |