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_SPARSE_ONE_DIMENSIONAL_MODEL_H
16 #define SV_SPARSE_ONE_DIMENSIONAL_MODEL_H
17 
18 #include "EventCommands.h"
19 #include "TabularModel.h"
20 #include "Model.h"
21 #include "DeferredNotifier.h"
22 
23 #include "base/NoteData.h"
24 #include "base/EventSeries.h"
25 #include "base/NoteExportable.h"
26 #include "base/PlayParameterRepository.h"
27 #include "base/RealTime.h"
28 
29 #include "system/System.h"
30 
31 #include <QStringList>
32 
33 /**
34  * A model representing a series of time instants with optional labels
35  * but without values.
36  */
37 class SparseOneDimensionalModel : public Model,
38                                   public TabularModel,
39                                   public EventEditable,
40                                   public NoteExportable
41 {
42     Q_OBJECT
43 
44 public:
45     SparseOneDimensionalModel(sv_samplerate_t sampleRate,
46                               int resolution,
47                               bool notifyOnAdd = true) :
48         m_sampleRate(sampleRate),
49         m_resolution(resolution),
50         m_haveTextLabels(false),
51         m_notifier(this,
52                    getId(),
53                    notifyOnAdd ?
54                    DeferredNotifier::NOTIFY_ALWAYS :
55                    DeferredNotifier::NOTIFY_DEFERRED),
56         m_completion(100) {
57         PlayParameterRepository::getInstance()->addPlayable
58             (getId().untyped, this);
59     }
60 
61     virtual ~SparseOneDimensionalModel() {
62         PlayParameterRepository::getInstance()->removePlayable
63             (getId().untyped);
64     }
65 
66     QString getTypeName() const override { return tr("Sparse 1-D"); }
67     bool isSparse() const override { return true; }
68     bool isOK() const override { return true; }
69 
70     sv_frame_t getStartFrame() const override {
71         return m_events.getStartFrame();
72     }
73     sv_frame_t getTrueEndFrame() const override {
74         if (m_events.isEmpty()) return 0;
75         sv_frame_t e = m_events.getEndFrame() + 1;
76         if (e % m_resolution == 0) return e;
77         else return (e / m_resolution + 1) * m_resolution;
78     }
79 
80     sv_samplerate_t getSampleRate() const override { return m_sampleRate; }
81     int getResolution() const { return m_resolution; }
82 
83     bool canPlay() const override { return true; }
84     QString getDefaultPlayClipId() const override { return "tap"; }
85 
86     bool hasTextLabels() const { return m_haveTextLabels; }
87 
88     int getCompletion() const override { return m_completion; }
89 
90     void setCompletion(int completion, bool update = true) {
91 
92         if (m_completion == completion) return;
93         m_completion = completion;
94 
95         if (update) {
96             m_notifier.makeDeferredNotifications();
97         }
98 
99         emit completionChanged(getId());
100 
101         if (completion == 100) {
102             // henceforth:
103             m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS);
104             emit modelChanged(getId());
105         }
106     }
107 
108     /**
109      * Query methods.
110      */
111 
112     int getEventCount() const {
113         return m_events.count();
114     }
115     bool isEmpty() const {
116         return m_events.isEmpty();
117     }
118     bool containsEvent(const Event &e) const {
119         return m_events.contains(e);
120     }
121     EventVector getAllEvents() const {
122         return m_events.getAllEvents();
123     }
124     EventVector getEventsSpanning(sv_frame_t f, sv_frame_t duration) const {
125         return m_events.getEventsSpanning(f, duration);
126     }
127     EventVector getEventsCovering(sv_frame_t f) const {
128         return m_events.getEventsCovering(f);
129     }
130     EventVector getEventsWithin(sv_frame_t f, sv_frame_t duration,
131                                 int overspill = 0) const {
132         return m_events.getEventsWithin(f, duration, overspill);
133     }
134     EventVector getEventsStartingWithin(sv_frame_t f, sv_frame_t duration) const {
135         return m_events.getEventsStartingWithin(f, duration);
136     }
137     EventVector getEventsStartingAt(sv_frame_t f) const {
138         return m_events.getEventsStartingAt(f);
139     }
140     bool getNearestEventMatching(sv_frame_t startSearchAt,
141                                  std::function<bool(Event)> predicate,
142                                  EventSeries::Direction direction,
143                                  Event &found) const {
144         return m_events.getNearestEventMatching
145             (startSearchAt, predicate, direction, found);
146     }
147 
148     /**
149      * Editing methods.
150      */
151     void add(Event e) override {
152 
153         m_events.add(e.withoutValue().withoutDuration());
154 
155         if (e.getLabel() != "") {
156             m_haveTextLabels = true;
157         }
158 
159         m_notifier.update(e.getFrame(), m_resolution);
160     }
161 
162     void remove(Event e) override {
163         m_events.remove(e);
164         emit modelChangedWithin(getId(),
165                                 e.getFrame(), e.getFrame() + m_resolution);
166     }
167 
168     /**
169      * TabularModel methods.
170      */
171 
172     int getRowCount() const override {
173         return m_events.count();
174     }
175 
176     int getColumnCount() const override {
177         return 3;
178     }
179 
180     bool isColumnTimeValue(int column) const override {
181         return (column < 2);
182     }
183 
184     sv_frame_t getFrameForRow(int row) const override {
185         if (row < 0 || row >= m_events.count()) {
186             return 0;
187         }
188         Event e = m_events.getEventByIndex(row);
189         return e.getFrame();
190     }
191 
192     int getRowForFrame(sv_frame_t frame) const override {
193         return m_events.getIndexForEvent(Event(frame));
194     }
195 
196     QString getHeading(int column) const override {
197         switch (column) {
198         case 0: return tr("Time");
199         case 1: return tr("Frame");
200         case 2: return tr("Label");
201         default: return tr("Unknown");
202         }
203     }
204 
205     SortType getSortType(int column) const override {
206         if (column == 2) return SortAlphabetical;
207         return SortNumeric;
208     }
209 
210     QVariant getData(int row, int column, int role) const override {
211 
212         if (row < 0 || row >= m_events.count()) {
213             return QVariant();
214         }
215 
216         Event e = m_events.getEventByIndex(row);
217 
218         switch (column) {
219         case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role);
220         case 1: return int(e.getFrame());
221         case 2: return e.getLabel();
222         default: return QVariant();
223         }
224     }
225 
226     Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override {
227         if (row < 0 || row >= m_events.count()) return nullptr;
228         if (role != Qt::EditRole) return nullptr;
229 
230         Event e0 = m_events.getEventByIndex(row);
231         Event e1;
232 
233         switch (column) {
234         case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() *
235                                                    getSampleRate()))); break;
236         case 1: e1 = e0.withFrame(value.toInt()); break;
237         case 2: e1 = e0.withLabel(value.toString()); break;
238         }
239 
240         auto command = new ChangeEventsCommand(getId().untyped, tr("Edit Data"));
241         command->remove(e0);
242         command->add(e1);
243         return command->finish();
244     }
245 
246     bool isEditable() const override { return true; }
247 
248     Command *getInsertRowCommand(int row) override {
249         if (row < 0 || row >= m_events.count()) return nullptr;
250         auto command = new ChangeEventsCommand(getId().untyped,
251                                                tr("Add Point"));
252         Event e = m_events.getEventByIndex(row);
253         command->add(e);
254         return command->finish();
255     }
256 
257     Command *getRemoveRowCommand(int row) override {
258         if (row < 0 || row >= m_events.count()) return nullptr;
259         auto command = new ChangeEventsCommand(getId().untyped,
260                                                tr("Delete Point"));
261         Event e = m_events.getEventByIndex(row);
262         command->remove(e);
263         return command->finish();
264     }
265 
266     /**
267      * NoteExportable methods.
268      */
269 
270     NoteList getNotes() const override {
271         return getNotesStartingWithin(getStartFrame(),
272                                       getEndFrame() - getStartFrame());
273     }
274 
275     NoteList getNotesActiveAt(sv_frame_t frame) const override {
276         return getNotesStartingWithin(frame, 1);
277     }
278 
279     NoteList getNotesStartingWithin(sv_frame_t startFrame,
280                                     sv_frame_t duration) const override {
281 
282         NoteList notes;
283         EventVector ee = m_events.getEventsStartingWithin(startFrame, duration);
284         for (const auto &e: ee) {
285             notes.push_back(e.toNoteData(getSampleRate(), true));
286         }
287         return notes;
288     }
289 
290     /**
291      * XmlExportable methods.
292      */
293     void toXml(QTextStream &out,
294                QString indent = "",
295                QString extraAttributes = "") const override {
296 
297         Model::toXml
298             (out,
299              indent,
300              QString("type=\"sparse\" dimensions=\"1\" resolution=\"%1\" "
301                      "notifyOnAdd=\"%2\" dataset=\"%3\" %4")
302              .arg(m_resolution)
303              .arg("true") // always true after model reaches 100% -
304                           // subsequent events are always notified
305              .arg(m_events.getExportId())
306              .arg(extraAttributes));
307 
308         m_events.toXml(out, indent, QString("dimensions=\"1\""));
309     }
310 
311     QString toDelimitedDataString(QString delimiter,
312                                   DataExportOptions options,
313                                   sv_frame_t startFrame,
314                                   sv_frame_t duration) const override {
315         return m_events.toDelimitedDataString(delimiter,
316                                               options,
317                                               startFrame,
318                                               duration,
319                                               m_sampleRate,
320                                               m_resolution,
321                                               Event());
322     }
323 
324 protected:
325     sv_samplerate_t m_sampleRate;
326     int m_resolution;
327 
328     std::atomic<bool> m_haveTextLabels;
329     DeferredNotifier m_notifier;
330     std::atomic<int> m_completion;
331 
332     EventSeries m_events;
333 };
334 
335 #endif
336 
337 
338