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