comparison data/model/BoxModel.h @ 1791:c2388289fce8 time-frequency-boxes

Rename TimeFrequencyBoxModel to simply BoxModel
author Chris Cannam
date Wed, 25 Sep 2019 09:44:02 +0100
parents data/model/TimeFrequencyBoxModel.h@baafe1bb7e51
children cb45c0a1dfe1
comparison
equal deleted inserted replaced
1790:dd51797e528e 1791:c2388289fce8
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