Mercurial > hg > svcore
comparison data/model/TimeFrequencyBoxModel.h @ 1785:894c2a780444 time-frequency-boxes
Introduce time-frequency box model
author | Chris Cannam |
---|---|
date | Thu, 19 Sep 2019 15:17:56 +0100 |
parents | |
children | a72921e2194f |
comparison
equal
deleted
inserted
replaced
1784:4eac4bf35b45 | 1785:894c2a780444 |
---|---|
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_TIME_FREQUENCY_BOX_MODEL_H | |
16 #define SV_TIME_FREQUENCY_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 * TimeFrequencyBoxModel -- a model for annotations having start time, | |
33 * duration, and a frequency range. We use Events as usual for these, | |
34 * but treat the "value" as the lower frequency and "level" as the | |
35 * difference between lower and upper frequencies, which is expected | |
36 * to be non-negative (if it is negative, abs(level) will be used). | |
37 */ | |
38 class TimeFrequencyBoxModel : public Model, | |
39 public TabularModel, | |
40 public EventEditable | |
41 { | |
42 Q_OBJECT | |
43 | |
44 public: | |
45 TimeFrequencyBoxModel(sv_samplerate_t sampleRate, | |
46 int resolution, | |
47 bool notifyOnAdd = true) : | |
48 m_sampleRate(sampleRate), | |
49 m_resolution(resolution), | |
50 m_frequencyMinimum(0.f), | |
51 m_frequencyMaximum(0.f), | |
52 m_haveExtents(false), | |
53 m_notifier(this, | |
54 getId(), | |
55 notifyOnAdd ? | |
56 DeferredNotifier::NOTIFY_ALWAYS : | |
57 DeferredNotifier::NOTIFY_DEFERRED), | |
58 m_completion(100) { | |
59 } | |
60 | |
61 TimeFrequencyBoxModel(sv_samplerate_t sampleRate, int resolution, | |
62 float frequencyMinimum, float frequencyMaximum, | |
63 bool notifyOnAdd = true) : | |
64 m_sampleRate(sampleRate), | |
65 m_resolution(resolution), | |
66 m_frequencyMinimum(frequencyMinimum), | |
67 m_frequencyMaximum(frequencyMaximum), | |
68 m_haveExtents(true), | |
69 m_notifier(this, | |
70 getId(), | |
71 notifyOnAdd ? | |
72 DeferredNotifier::NOTIFY_ALWAYS : | |
73 DeferredNotifier::NOTIFY_DEFERRED), | |
74 m_completion(100) { | |
75 } | |
76 | |
77 virtual ~TimeFrequencyBoxModel() { | |
78 } | |
79 | |
80 QString getTypeName() const override { return tr("Time-Frequency Box"); } | |
81 bool isSparse() const override { return true; } | |
82 bool isOK() const override { return true; } | |
83 | |
84 sv_frame_t getStartFrame() const override { | |
85 return m_events.getStartFrame(); | |
86 } | |
87 sv_frame_t getTrueEndFrame() const override { | |
88 if (m_events.isEmpty()) return 0; | |
89 sv_frame_t e = m_events.getEndFrame(); | |
90 if (e % m_resolution == 0) return e; | |
91 else return (e / m_resolution + 1) * m_resolution; | |
92 } | |
93 | |
94 sv_samplerate_t getSampleRate() const override { return m_sampleRate; } | |
95 int getResolution() const { return m_resolution; } | |
96 | |
97 QString getScaleUnits() const { return "Hz"; } | |
98 | |
99 float getFrequencyMinimum() const { return m_frequencyMinimum; } | |
100 float getFrequencyMaximum() const { return m_frequencyMaximum; } | |
101 | |
102 int getCompletion() const override { return m_completion; } | |
103 | |
104 void setCompletion(int completion, bool update = true) { | |
105 | |
106 { QMutexLocker locker(&m_mutex); | |
107 if (m_completion == completion) return; | |
108 m_completion = completion; | |
109 } | |
110 | |
111 if (update) { | |
112 m_notifier.makeDeferredNotifications(); | |
113 } | |
114 | |
115 emit completionChanged(getId()); | |
116 | |
117 if (completion == 100) { | |
118 // henceforth: | |
119 m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS); | |
120 emit modelChanged(getId()); | |
121 } | |
122 } | |
123 | |
124 /** | |
125 * Query methods. | |
126 */ | |
127 int getEventCount() const { | |
128 return m_events.count(); | |
129 } | |
130 bool isEmpty() const { | |
131 return m_events.isEmpty(); | |
132 } | |
133 bool containsEvent(const Event &e) const { | |
134 return m_events.contains(e); | |
135 } | |
136 EventVector getAllEvents() const { | |
137 return m_events.getAllEvents(); | |
138 } | |
139 EventVector getEventsSpanning(sv_frame_t f, sv_frame_t duration) const { | |
140 return m_events.getEventsSpanning(f, duration); | |
141 } | |
142 EventVector getEventsCovering(sv_frame_t f) const { | |
143 return m_events.getEventsCovering(f); | |
144 } | |
145 EventVector getEventsWithin(sv_frame_t f, sv_frame_t duration) const { | |
146 return m_events.getEventsWithin(f, duration); | |
147 } | |
148 EventVector getEventsStartingWithin(sv_frame_t f, sv_frame_t duration) const { | |
149 return m_events.getEventsStartingWithin(f, duration); | |
150 } | |
151 EventVector getEventsStartingAt(sv_frame_t f) const { | |
152 return m_events.getEventsStartingAt(f); | |
153 } | |
154 bool getNearestEventMatching(sv_frame_t startSearchAt, | |
155 std::function<bool(Event)> predicate, | |
156 EventSeries::Direction direction, | |
157 Event &found) const { | |
158 return m_events.getNearestEventMatching | |
159 (startSearchAt, predicate, direction, found); | |
160 } | |
161 | |
162 /** | |
163 * Editing methods. | |
164 */ | |
165 void add(Event e) override { | |
166 | |
167 bool allChange = false; | |
168 | |
169 { | |
170 QMutexLocker locker(&m_mutex); | |
171 m_events.add(e); | |
172 | |
173 float f0 = e.getValue(); | |
174 float f1 = f0 + fabsf(e.getLevel()); | |
175 | |
176 if (!m_haveExtents || f0 < m_frequencyMinimum) { | |
177 m_frequencyMinimum = f0; allChange = true; | |
178 } | |
179 if (!m_haveExtents || f1 > m_frequencyMaximum) { | |
180 m_frequencyMaximum = f1; allChange = true; | |
181 } | |
182 m_haveExtents = true; | |
183 } | |
184 | |
185 m_notifier.update(e.getFrame(), e.getDuration() + m_resolution); | |
186 | |
187 if (allChange) { | |
188 emit modelChanged(getId()); | |
189 } | |
190 } | |
191 | |
192 void remove(Event e) override { | |
193 { | |
194 QMutexLocker locker(&m_mutex); | |
195 m_events.remove(e); | |
196 } | |
197 emit modelChangedWithin(getId(), | |
198 e.getFrame(), | |
199 e.getFrame() + e.getDuration() + m_resolution); | |
200 } | |
201 | |
202 /** | |
203 * TabularModel methods. | |
204 */ | |
205 | |
206 int getRowCount() const override { | |
207 return m_events.count(); | |
208 } | |
209 | |
210 int getColumnCount() const override { | |
211 return 5; | |
212 } | |
213 | |
214 bool isColumnTimeValue(int column) const override { | |
215 // NB duration is not a "time value" -- that's for columns | |
216 // whose sort ordering is exactly that of the frame time | |
217 return (column < 2); | |
218 } | |
219 | |
220 sv_frame_t getFrameForRow(int row) const override { | |
221 if (row < 0 || row >= m_events.count()) { | |
222 return 0; | |
223 } | |
224 Event e = m_events.getEventByIndex(row); | |
225 return e.getFrame(); | |
226 } | |
227 | |
228 int getRowForFrame(sv_frame_t frame) const override { | |
229 return m_events.getIndexForEvent(Event(frame)); | |
230 } | |
231 | |
232 QString getHeading(int column) const override { | |
233 switch (column) { | |
234 case 0: return tr("Time"); | |
235 case 1: return tr("Frame"); | |
236 case 2: return tr("Duration"); | |
237 case 3: return tr("Minimum Frequency"); | |
238 case 4: return tr("Maximum Frequency"); | |
239 case 5: return tr("Label"); | |
240 default: return tr("Unknown"); | |
241 } | |
242 } | |
243 | |
244 SortType getSortType(int column) const override { | |
245 if (column == 5) return SortAlphabetical; | |
246 return SortNumeric; | |
247 } | |
248 | |
249 QVariant getData(int row, int column, int role) const override { | |
250 | |
251 if (row < 0 || row >= m_events.count()) { | |
252 return QVariant(); | |
253 } | |
254 | |
255 Event e = m_events.getEventByIndex(row); | |
256 | |
257 switch (column) { | |
258 case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role); | |
259 case 1: return int(e.getFrame()); | |
260 case 2: return int(e.getDuration()); | |
261 case 3: return adaptValueForRole(e.getValue(), getScaleUnits(), role); | |
262 case 4: return adaptValueForRole(e.getValue() + fabsf(e.getLevel()), | |
263 getScaleUnits(), role); | |
264 case 5: return e.getLabel(); | |
265 default: return QVariant(); | |
266 } | |
267 } | |
268 | |
269 Command *getSetDataCommand(int row, int column, const QVariant &value, | |
270 int role) override { | |
271 | |
272 if (row < 0 || row >= m_events.count()) return nullptr; | |
273 if (role != Qt::EditRole) return nullptr; | |
274 | |
275 Event e0 = m_events.getEventByIndex(row); | |
276 Event e1; | |
277 | |
278 switch (column) { | |
279 case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() * | |
280 getSampleRate()))); break; | |
281 case 1: e1 = e0.withFrame(value.toInt()); break; | |
282 case 2: e1 = e0.withDuration(value.toInt()); break; | |
283 case 3: e1 = e0.withValue(float(value.toDouble())); break; | |
284 case 4: e1 = e0.withLevel(fabsf(float(value.toDouble()) - | |
285 e0.getValue())); break; | |
286 case 5: e1 = e0.withLabel(value.toString()); break; | |
287 } | |
288 | |
289 auto command = new ChangeEventsCommand(getId().untyped, tr("Edit Data")); | |
290 command->remove(e0); | |
291 command->add(e1); | |
292 return command->finish(); | |
293 } | |
294 | |
295 | |
296 /** | |
297 * XmlExportable methods. | |
298 */ | |
299 void toXml(QTextStream &out, | |
300 QString indent = "", | |
301 QString extraAttributes = "") const override { | |
302 | |
303 Model::toXml | |
304 (out, | |
305 indent, | |
306 QString("type=\"sparse\" dimensions=\"4\" resolution=\"%1\" " | |
307 "notifyOnAdd=\"%2\" dataset=\"%3\" subtype=\"%4\" " | |
308 "minimum=\"%5\" maximum=\"%6\" units=\"%7\" %8") | |
309 .arg(m_resolution) | |
310 .arg("true") // always true after model reaches 100% - | |
311 // subsequent events are always notified | |
312 .arg(m_events.getExportId()) | |
313 .arg("timefrequency") | |
314 .arg(m_frequencyMinimum) | |
315 .arg(m_frequencyMaximum) | |
316 .arg(encodeEntities(m_units)) | |
317 .arg(extraAttributes)); | |
318 | |
319 m_events.toXml(out, indent, QString("dimensions=\"3\"")); | |
320 } | |
321 | |
322 QString toDelimitedDataString(QString delimiter, | |
323 DataExportOptions options, | |
324 sv_frame_t startFrame, | |
325 sv_frame_t duration) const override { | |
326 return m_events.toDelimitedDataString | |
327 (delimiter, | |
328 options, | |
329 startFrame, | |
330 duration, | |
331 m_sampleRate, | |
332 m_resolution, | |
333 Event().withValue(0.f).withDuration(m_resolution)); | |
334 } | |
335 | |
336 protected: | |
337 sv_samplerate_t m_sampleRate; | |
338 int m_resolution; | |
339 | |
340 float m_frequencyMinimum; | |
341 float m_frequencyMaximum; | |
342 bool m_haveExtents; | |
343 QString m_units; | |
344 DeferredNotifier m_notifier; | |
345 int m_completion; | |
346 | |
347 EventSeries m_events; | |
348 | |
349 mutable QMutex m_mutex; | |
350 }; | |
351 | |
352 #endif |