Mercurial > hg > svcore
comparison data/model/SparseOneDimensionalModel.h @ 1658:5b7b01da430a single-point
Start updating SparseOneDimensionalModel
author | Chris Cannam |
---|---|
date | Wed, 20 Mar 2019 16:22:13 +0000 |
parents | 7a23dfe65d66 |
children | 8bf3a52a1604 |
comparison
equal
deleted
inserted
replaced
1657:31b46a5647db | 1658:5b7b01da430a |
---|---|
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_ONE_DIMENSIONAL_MODEL_H | 15 #ifndef SV_SPARSE_ONE_DIMENSIONAL_MODEL_H |
17 #define SV_SPARSE_ONE_DIMENSIONAL_MODEL_H | 16 #define SV_SPARSE_ONE_DIMENSIONAL_MODEL_H |
18 | 17 |
19 #include "SparseModel.h" | 18 #include "EventCommands.h" |
19 #include "TabularModel.h" | |
20 #include "Model.h" | |
21 #include "DeferredNotifier.h" | |
22 | |
20 #include "base/NoteData.h" | 23 #include "base/NoteData.h" |
24 #include "base/EventSeries.h" | |
21 #include "base/NoteExportable.h" | 25 #include "base/NoteExportable.h" |
22 #include "base/PlayParameterRepository.h" | 26 #include "base/PlayParameterRepository.h" |
23 #include "base/RealTime.h" | 27 #include "base/RealTime.h" |
24 | 28 |
29 #include "system/System.h" | |
30 | |
25 #include <QStringList> | 31 #include <QStringList> |
26 | 32 |
27 struct OneDimensionalPoint | 33 /** |
28 { | 34 * A model representing a series of time instants with optional labels |
29 public: | 35 * but without values. |
30 OneDimensionalPoint(sv_frame_t _frame) : frame(_frame) { } | 36 */ |
31 OneDimensionalPoint(sv_frame_t _frame, QString _label) : frame(_frame), label(_label) { } | 37 class SparseOneDimensionalModel : public Model, |
32 | 38 public TabularModel, |
33 int getDimensions() const { return 1; } | 39 public EventEditable, |
34 | |
35 sv_frame_t frame; | |
36 QString label; | |
37 | |
38 QString getLabel() const { return label; } | |
39 | |
40 void toXml(QTextStream &stream, | |
41 QString indent = "", | |
42 QString extraAttributes = "") const | |
43 { | |
44 stream << QString("%1<point frame=\"%2\" label=\"%3\" %4/>\n") | |
45 .arg(indent).arg(frame).arg(XmlExportable::encodeEntities(label)) | |
46 .arg(extraAttributes); | |
47 } | |
48 | |
49 QString toDelimitedDataString(QString delimiter, DataExportOptions, sv_samplerate_t sampleRate) const | |
50 { | |
51 QStringList list; | |
52 list << RealTime::frame2RealTime(frame, sampleRate).toString().c_str(); | |
53 if (label != "") list << label; | |
54 return list.join(delimiter); | |
55 } | |
56 | |
57 struct Comparator { | |
58 bool operator()(const OneDimensionalPoint &p1, | |
59 const OneDimensionalPoint &p2) const { | |
60 if (p1.frame != p2.frame) return p1.frame < p2.frame; | |
61 return p1.label < p2.label; | |
62 } | |
63 }; | |
64 | |
65 struct OrderComparator { | |
66 bool operator()(const OneDimensionalPoint &p1, | |
67 const OneDimensionalPoint &p2) const { | |
68 return p1.frame < p2.frame; | |
69 } | |
70 }; | |
71 | |
72 bool operator==(const OneDimensionalPoint &p) const { | |
73 return (frame == p.frame && label == p.label); | |
74 } | |
75 }; | |
76 | |
77 | |
78 class SparseOneDimensionalModel : public SparseModel<OneDimensionalPoint>, | |
79 public NoteExportable | 40 public NoteExportable |
80 { | 41 { |
81 Q_OBJECT | 42 Q_OBJECT |
82 | 43 |
83 public: | 44 public: |
84 SparseOneDimensionalModel(sv_samplerate_t sampleRate, int resolution, | 45 SparseOneDimensionalModel(sv_samplerate_t sampleRate, |
46 int resolution, | |
85 bool notifyOnAdd = true) : | 47 bool notifyOnAdd = true) : |
86 SparseModel<OneDimensionalPoint>(sampleRate, resolution, notifyOnAdd) | 48 m_sampleRate(sampleRate), |
87 { | 49 m_resolution(resolution), |
50 m_notifier(this, | |
51 notifyOnAdd ? | |
52 DeferredNotifier::NOTIFY_ALWAYS : | |
53 DeferredNotifier::NOTIFY_DEFERRED), | |
54 m_completion(100) { | |
88 PlayParameterRepository::getInstance()->addPlayable(this); | 55 PlayParameterRepository::getInstance()->addPlayable(this); |
89 } | 56 } |
90 | 57 |
91 virtual ~SparseOneDimensionalModel() | 58 virtual ~SparseOneDimensionalModel() { |
92 { | |
93 PlayParameterRepository::getInstance()->removePlayable(this); | 59 PlayParameterRepository::getInstance()->removePlayable(this); |
94 } | 60 } |
95 | 61 |
62 QString getTypeName() const override { return tr("Sparse 1-D"); } | |
63 | |
64 bool isOK() const override { return true; } | |
65 sv_frame_t getStartFrame() const override { return m_events.getStartFrame(); } | |
66 sv_frame_t getEndFrame() const override { return m_events.getEndFrame(); } | |
67 sv_samplerate_t getSampleRate() const override { return m_sampleRate; } | |
68 int getResolution() const { return m_resolution; } | |
69 | |
96 bool canPlay() const override { return true; } | 70 bool canPlay() const override { return true; } |
97 | 71 QString getDefaultPlayClipId() const override { return "tap"; } |
98 QString getDefaultPlayClipId() const override | 72 |
99 { | 73 int getCompletion() const { return m_completion; } |
100 return "tap"; | 74 |
101 } | 75 void setCompletion(int completion, bool update = true) { |
102 | 76 |
103 int getIndexOf(const Point &point) | 77 { QMutexLocker locker(&m_mutex); |
104 { | 78 if (m_completion == completion) return; |
105 // slow | 79 m_completion = completion; |
106 int i = 0; | 80 } |
107 Point::Comparator comparator; | 81 |
108 for (PointList::const_iterator j = m_points.begin(); | 82 if (update) { |
109 j != m_points.end(); ++j, ++i) { | 83 m_notifier.makeDeferredNotifications(); |
110 if (!comparator(*j, point) && !comparator(point, *j)) return i; | 84 } |
111 } | 85 |
112 return -1; | 86 emit completionChanged(); |
113 } | 87 |
114 | 88 if (completion == 100) { |
115 QString getTypeName() const override { return tr("Sparse 1-D"); } | 89 // henceforth: |
116 | 90 m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS); |
91 emit modelChanged(); | |
92 } | |
93 } | |
94 | |
95 /** | |
96 * Query methods. | |
97 */ | |
98 | |
99 int getEventCount() const { | |
100 return m_events.count(); | |
101 } | |
102 bool isEmpty() const { | |
103 return m_events.isEmpty(); | |
104 } | |
105 bool containsEvent(const Event &e) const { | |
106 return m_events.contains(e); | |
107 } | |
108 EventVector getAllEvents() const { | |
109 return m_events.getAllEvents(); | |
110 } | |
111 EventVector getEventsSpanning(sv_frame_t f, sv_frame_t duration) const { | |
112 return m_events.getEventsSpanning(f, duration); | |
113 } | |
114 EventVector getEventsCovering(sv_frame_t f) const { | |
115 return m_events.getEventsCovering(f); | |
116 } | |
117 EventVector getEventsWithin(sv_frame_t f, sv_frame_t duration, | |
118 int overspill = 0) const { | |
119 return m_events.getEventsWithin(f, duration, overspill); | |
120 } | |
121 EventVector getEventsStartingWithin(sv_frame_t f, sv_frame_t duration) const { | |
122 return m_events.getEventsStartingWithin(f, duration); | |
123 } | |
124 EventVector getEventsStartingAt(sv_frame_t f) const { | |
125 return m_events.getEventsStartingAt(f); | |
126 } | |
127 bool getNearestEventMatching(sv_frame_t startSearchAt, | |
128 std::function<bool(Event)> predicate, | |
129 EventSeries::Direction direction, | |
130 Event &found) const { | |
131 return m_events.getNearestEventMatching | |
132 (startSearchAt, predicate, direction, found); | |
133 } | |
134 | |
135 /** | |
136 * Editing methods. | |
137 */ | |
138 void add(Event e) override { | |
139 | |
140 { QMutexLocker locker(&m_mutex); | |
141 m_events.add(e.withoutValue().withoutDuration()); | |
142 } | |
143 | |
144 m_notifier.update(e.getFrame(), m_resolution); | |
145 } | |
146 | |
147 void remove(Event e) override { | |
148 { QMutexLocker locker(&m_mutex); | |
149 m_events.remove(e); | |
150 } | |
151 emit modelChangedWithin(e.getFrame(), e.getFrame() + m_resolution); | |
152 } | |
153 | |
117 /** | 154 /** |
118 * TabularModel methods. | 155 * TabularModel methods. |
119 */ | 156 */ |
120 | 157 |
121 int getColumnCount() const override | 158 int getRowCount() const override { |
122 { | 159 return m_events.count(); |
160 } | |
161 | |
162 int getColumnCount() const override { | |
123 return 3; | 163 return 3; |
124 } | 164 } |
125 | 165 |
126 QString getHeading(int column) const override | 166 bool isColumnTimeValue(int column) const override { |
127 { | 167 // NB duration is not a "time value" -- that's for columns |
168 // whose sort ordering is exactly that of the frame time | |
169 return (column < 2); | |
170 } | |
171 | |
172 sv_frame_t getFrameForRow(int row) const override { | |
173 if (row < 0 || row >= m_events.count()) { | |
174 return 0; | |
175 } | |
176 Event e = m_events.getEventByIndex(row); | |
177 return e.getFrame(); | |
178 } | |
179 | |
180 int getRowForFrame(sv_frame_t frame) const override { | |
181 return m_events.getIndexForEvent(Event(frame)); | |
182 } | |
183 | |
184 QString getHeading(int column) const override { | |
128 switch (column) { | 185 switch (column) { |
129 case 0: return tr("Time"); | 186 case 0: return tr("Time"); |
130 case 1: return tr("Frame"); | 187 case 1: return tr("Frame"); |
131 case 2: return tr("Label"); | 188 case 2: return tr("Label"); |
132 default: return tr("Unknown"); | 189 default: return tr("Unknown"); |
133 } | 190 } |
134 } | 191 } |
135 | 192 |
136 QVariant getData(int row, int column, int role) const override | 193 SortType getSortType(int column) const override { |
137 { | |
138 if (column < 2) { | |
139 return SparseModel<OneDimensionalPoint>::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: return i->label; | |
148 default: return QVariant(); | |
149 } | |
150 } | |
151 | |
152 Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override | |
153 { | |
154 if (column < 2) { | |
155 return SparseModel<OneDimensionalPoint>::getSetDataCommand | |
156 (row, column, value, role); | |
157 } | |
158 | |
159 if (role != Qt::EditRole) return 0; | |
160 PointListConstIterator i = getPointListIteratorForRow(row); | |
161 if (i == m_points.end()) return 0; | |
162 EditCommand *command = new EditCommand(this, tr("Edit Data")); | |
163 | |
164 Point point(*i); | |
165 command->deletePoint(point); | |
166 | |
167 switch (column) { | |
168 case 2: point.label = value.toString(); break; | |
169 } | |
170 | |
171 command->addPoint(point); | |
172 return command->finish(); | |
173 } | |
174 | |
175 | |
176 bool isColumnTimeValue(int column) const override | |
177 { | |
178 return (column < 2); | |
179 } | |
180 | |
181 SortType getSortType(int column) const override | |
182 { | |
183 if (column == 2) return SortAlphabetical; | 194 if (column == 2) return SortAlphabetical; |
184 return SortNumeric; | 195 return SortNumeric; |
196 } | |
197 | |
198 QVariant getData(int row, int column, int role) const override { | |
199 | |
200 if (row < 0 || row >= m_events.count()) { | |
201 return QVariant(); | |
202 } | |
203 | |
204 Event e = m_events.getEventByIndex(row); | |
205 | |
206 switch (column) { | |
207 case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role); | |
208 case 1: return int(e.getFrame()); | |
209 case 2: return e.getLabel(); | |
210 default: return QVariant(); | |
211 } | |
212 } | |
213 | |
214 Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override { | |
215 if (row < 0 || row >= m_events.count()) return nullptr; | |
216 if (role != Qt::EditRole) return nullptr; | |
217 | |
218 Event e0 = m_events.getEventByIndex(row); | |
219 Event e1; | |
220 | |
221 switch (column) { | |
222 case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() * | |
223 getSampleRate()))); break; | |
224 case 1: e1 = e0.withFrame(value.toInt()); break; | |
225 case 2: e1 = e0.withLabel(value.toString()); break; | |
226 } | |
227 | |
228 ChangeEventsCommand *command = | |
229 new ChangeEventsCommand(this, tr("Edit Data")); | |
230 command->remove(e0); | |
231 command->add(e1); | |
232 return command->finish(); | |
185 } | 233 } |
186 | 234 |
187 /** | 235 /** |
188 * NoteExportable methods. | 236 * NoteExportable methods. |
189 */ | 237 */ |
198 } | 246 } |
199 | 247 |
200 NoteList getNotesStartingWithin(sv_frame_t startFrame, | 248 NoteList getNotesStartingWithin(sv_frame_t startFrame, |
201 sv_frame_t duration) const override { | 249 sv_frame_t duration) const override { |
202 | 250 |
203 PointList points = getPoints(startFrame, startFrame + duration); | |
204 NoteList notes; | 251 NoteList notes; |
205 | 252 EventVector ee = m_events.getEventsStartingWithin(startFrame, duration); |
206 for (PointList::iterator pli = | 253 for (const auto &e: ee) { |
207 points.begin(); pli != points.end(); ++pli) { | 254 notes.push_back(e.toNoteData(getSampleRate(), true)); |
208 | 255 } |
209 notes.push_back | |
210 (NoteData(pli->frame, | |
211 sv_frame_t(getSampleRate() / 6), // arbitrary short duration | |
212 64, // default pitch | |
213 100)); // default velocity | |
214 } | |
215 | |
216 return notes; | 256 return notes; |
217 } | 257 } |
258 | |
259 /** | |
260 * XmlExportable methods. | |
261 */ | |
262 void toXml(QTextStream &out, | |
263 QString indent = "", | |
264 QString extraAttributes = "") const override { | |
265 | |
266 Model::toXml | |
267 (out, | |
268 indent, | |
269 QString("type=\"sparse\" dimensions=\"1\" resolution=\"%1\" " | |
270 "notifyOnAdd=\"%2\" dataset=\"%3\" %4") | |
271 .arg(m_resolution) | |
272 .arg("true") // always true after model reaches 100% - | |
273 // subsequent events are always notified | |
274 .arg(getObjectExportId(&m_events)) | |
275 .arg(extraAttributes)); | |
276 | |
277 m_events.toXml(out, indent, QString("dimensions=\"1\"")); | |
278 } | |
279 | |
280 protected: | |
281 sv_samplerate_t m_sampleRate; | |
282 int m_resolution; | |
283 | |
284 DeferredNotifier m_notifier; | |
285 int m_completion; | |
286 | |
287 EventSeries m_events; | |
288 | |
289 mutable QMutex m_mutex; | |
218 }; | 290 }; |
219 | 291 |
220 #endif | 292 #endif |
221 | 293 |
222 | 294 |