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_TIME_VALUE_MODEL_H 16 #define SV_SPARSE_TIME_VALUE_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 #include "base/PlayParameterRepository.h" 27 28 #include "system/System.h" 29 30 /** 31 * A model representing a wiggly-line plot with points at arbitrary 32 * intervals of the model resolution. 33 */ 34 class SparseTimeValueModel : public Model, 35 public TabularModel, 36 public EventEditable 37 { 38 Q_OBJECT 39 40 public: 41 SparseTimeValueModel(sv_samplerate_t sampleRate, 42 int resolution, 43 bool notifyOnAdd = true) : m_sampleRate(sampleRate)44 m_sampleRate(sampleRate), 45 m_resolution(resolution), 46 m_valueMinimum(0.f), 47 m_valueMaximum(0.f), 48 m_haveExtents(false), 49 m_haveTextLabels(false), 50 m_notifier(this, 51 getId(), 52 notifyOnAdd ? 53 DeferredNotifier::NOTIFY_ALWAYS : 54 DeferredNotifier::NOTIFY_DEFERRED), 55 m_completion(100) { 56 // Model is playable, but may not sound (if units not Hz or 57 // range unsuitable) 58 PlayParameterRepository::getInstance()->addPlayable 59 (getId().untyped, this); 60 } 61 62 SparseTimeValueModel(sv_samplerate_t sampleRate, int resolution, 63 float valueMinimum, float valueMaximum, 64 bool notifyOnAdd = true) : m_sampleRate(sampleRate)65 m_sampleRate(sampleRate), 66 m_resolution(resolution), 67 m_valueMinimum(valueMinimum), 68 m_valueMaximum(valueMaximum), 69 m_haveExtents(true), 70 m_haveTextLabels(false), 71 m_notifier(this, 72 getId(), 73 notifyOnAdd ? 74 DeferredNotifier::NOTIFY_ALWAYS : 75 DeferredNotifier::NOTIFY_DEFERRED), 76 m_completion(100) { 77 // Model is playable, but may not sound (if units not Hz or 78 // range unsuitable) 79 PlayParameterRepository::getInstance()->addPlayable 80 (getId().untyped, this); 81 } 82 ~SparseTimeValueModel()83 virtual ~SparseTimeValueModel() { 84 PlayParameterRepository::getInstance()->removePlayable 85 (getId().untyped); 86 } 87 getTypeName()88 QString getTypeName() const override { return tr("Sparse Time-Value"); } isSparse()89 bool isSparse() const override { return true; } isOK()90 bool isOK() const override { return true; } 91 getStartFrame()92 sv_frame_t getStartFrame() const override { 93 return m_events.getStartFrame(); 94 } getTrueEndFrame()95 sv_frame_t getTrueEndFrame() const override { 96 if (m_events.isEmpty()) return 0; 97 sv_frame_t e = m_events.getEndFrame() + 1; 98 if (e % m_resolution == 0) return e; 99 else return (e / m_resolution + 1) * m_resolution; 100 } 101 getSampleRate()102 sv_samplerate_t getSampleRate() const override { return m_sampleRate; } getResolution()103 int getResolution() const { return m_resolution; } 104 canPlay()105 bool canPlay() const override { return true; } getDefaultPlayAudible()106 bool getDefaultPlayAudible() const override { return false; } // user must unmute 107 getScaleUnits()108 QString getScaleUnits() const { 109 QMutexLocker locker(&m_mutex); 110 return m_units; 111 } setScaleUnits(QString units)112 void setScaleUnits(QString units) { 113 QMutexLocker locker(&m_mutex); 114 m_units = units; 115 UnitDatabase::getInstance()->registerUnit(units); 116 } 117 hasTextLabels()118 bool hasTextLabels() const { return m_haveTextLabels; } 119 getValueMinimum()120 float getValueMinimum() const { return m_valueMinimum; } getValueMaximum()121 float getValueMaximum() const { return m_valueMaximum; } 122 getCompletion()123 int getCompletion() const override { return m_completion; } 124 125 void setCompletion(int completion, bool update = true) { 126 127 { 128 if (m_completion == completion) return; 129 m_completion = completion; 130 } 131 132 if (update) { 133 m_notifier.makeDeferredNotifications(); 134 } 135 136 emit completionChanged(getId()); 137 138 if (completion == 100) { 139 // henceforth: 140 m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS); 141 emit modelChanged(getId()); 142 } 143 } 144 145 /** 146 * Query methods. 147 */ 148 getEventCount()149 int getEventCount() const { 150 return m_events.count(); 151 } isEmpty()152 bool isEmpty() const { 153 return m_events.isEmpty(); 154 } containsEvent(const Event & e)155 bool containsEvent(const Event &e) const { 156 return m_events.contains(e); 157 } getAllEvents()158 EventVector getAllEvents() const { 159 return m_events.getAllEvents(); 160 } getEventsSpanning(sv_frame_t f,sv_frame_t duration)161 EventVector getEventsSpanning(sv_frame_t f, sv_frame_t duration) const { 162 return m_events.getEventsSpanning(f, duration); 163 } getEventsCovering(sv_frame_t f)164 EventVector getEventsCovering(sv_frame_t f) const { 165 return m_events.getEventsCovering(f); 166 } 167 EventVector getEventsWithin(sv_frame_t f, sv_frame_t duration, 168 int overspill = 0) const { 169 return m_events.getEventsWithin(f, duration, overspill); 170 } getEventsStartingWithin(sv_frame_t f,sv_frame_t duration)171 EventVector getEventsStartingWithin(sv_frame_t f, sv_frame_t duration) const { 172 return m_events.getEventsStartingWithin(f, duration); 173 } getEventsStartingAt(sv_frame_t f)174 EventVector getEventsStartingAt(sv_frame_t f) const { 175 return m_events.getEventsStartingAt(f); 176 } getNearestEventMatching(sv_frame_t startSearchAt,std::function<bool (Event)> predicate,EventSeries::Direction direction,Event & found)177 bool getNearestEventMatching(sv_frame_t startSearchAt, 178 std::function<bool(Event)> predicate, 179 EventSeries::Direction direction, 180 Event &found) const { 181 return m_events.getNearestEventMatching 182 (startSearchAt, predicate, direction, found); 183 } 184 185 /** 186 * Editing methods. 187 */ add(Event e)188 void add(Event e) override { 189 190 bool allChange = false; 191 192 m_events.add(e.withoutDuration()); // can't have duration here 193 194 if (e.getLabel() != "") { 195 m_haveTextLabels = true; 196 } 197 198 float v = e.getValue(); 199 if (!ISNAN(v) && !ISINF(v)) { 200 if (!m_haveExtents || v < m_valueMinimum) { 201 m_valueMinimum = v; allChange = true; 202 } 203 if (!m_haveExtents || v > m_valueMaximum) { 204 m_valueMaximum = v; allChange = true; 205 } 206 m_haveExtents = true; 207 } 208 209 m_notifier.update(e.getFrame(), m_resolution); 210 211 if (allChange) { 212 emit modelChanged(getId()); 213 } 214 } 215 remove(Event e)216 void remove(Event e) override { 217 m_events.remove(e); 218 emit modelChangedWithin(getId(), 219 e.getFrame(), e.getFrame() + m_resolution); 220 } 221 222 /** 223 * TabularModel methods. 224 */ 225 getRowCount()226 int getRowCount() const override { 227 return m_events.count(); 228 } 229 getColumnCount()230 int getColumnCount() const override { 231 return 4; 232 } 233 isColumnTimeValue(int column)234 bool isColumnTimeValue(int column) const override { 235 return (column < 2); 236 } 237 getFrameForRow(int row)238 sv_frame_t getFrameForRow(int row) const override { 239 if (row < 0 || row >= m_events.count()) { 240 return 0; 241 } 242 Event e = m_events.getEventByIndex(row); 243 return e.getFrame(); 244 } 245 getRowForFrame(sv_frame_t frame)246 int getRowForFrame(sv_frame_t frame) const override { 247 return m_events.getIndexForEvent(Event(frame)); 248 } 249 getHeading(int column)250 QString getHeading(int column) const override { 251 switch (column) { 252 case 0: return tr("Time"); 253 case 1: return tr("Frame"); 254 case 2: return tr("Value"); 255 case 3: return tr("Label"); 256 default: return tr("Unknown"); 257 } 258 } 259 getSortType(int column)260 SortType getSortType(int column) const override { 261 if (column == 3) return SortAlphabetical; 262 return SortNumeric; 263 } 264 getData(int row,int column,int role)265 QVariant getData(int row, int column, int role) const override { 266 267 if (row < 0 || row >= m_events.count()) { 268 return QVariant(); 269 } 270 271 Event e = m_events.getEventByIndex(row); 272 273 switch (column) { 274 case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role); 275 case 1: return int(e.getFrame()); 276 case 2: return adaptValueForRole(e.getValue(), getScaleUnits(), role); 277 case 3: return e.getLabel(); 278 default: return QVariant(); 279 } 280 } 281 isEditable()282 bool isEditable() const override { return true; } 283 getSetDataCommand(int row,int column,const QVariant & value,int role)284 Command *getSetDataCommand(int row, int column, const QVariant &value, 285 int role) override { 286 if (row < 0 || row >= m_events.count()) return nullptr; 287 if (role != Qt::EditRole) return nullptr; 288 289 Event e0 = m_events.getEventByIndex(row); 290 Event e1; 291 292 switch (column) { 293 case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() * 294 getSampleRate()))); break; 295 case 1: e1 = e0.withFrame(value.toInt()); break; 296 case 2: e1 = e0.withValue(float(value.toDouble())); break; 297 case 3: e1 = e0.withLabel(value.toString()); break; 298 } 299 300 auto command = new ChangeEventsCommand(getId().untyped, tr("Edit Data")); 301 command->remove(e0); 302 command->add(e1); 303 return command->finish(); 304 } 305 getInsertRowCommand(int row)306 Command *getInsertRowCommand(int row) override { 307 if (row < 0 || row >= m_events.count()) return nullptr; 308 auto command = new ChangeEventsCommand(getId().untyped, 309 tr("Add Point")); 310 Event e = m_events.getEventByIndex(row); 311 command->add(e); 312 return command->finish(); 313 } 314 getRemoveRowCommand(int row)315 Command *getRemoveRowCommand(int row) override { 316 if (row < 0 || row >= m_events.count()) return nullptr; 317 auto command = new ChangeEventsCommand(getId().untyped, 318 tr("Delete Point")); 319 Event e = m_events.getEventByIndex(row); 320 command->remove(e); 321 return command->finish(); 322 } 323 324 /** 325 * XmlExportable methods. 326 */ 327 void toXml(QTextStream &out, 328 QString indent = "", 329 QString extraAttributes = "") const override { 330 331 Model::toXml 332 (out, 333 indent, 334 QString("type=\"sparse\" dimensions=\"2\" resolution=\"%1\" " 335 "notifyOnAdd=\"%2\" dataset=\"%3\" " 336 "minimum=\"%4\" maximum=\"%5\" " 337 "units=\"%6\" %7") 338 .arg(m_resolution) 339 .arg("true") // always true after model reaches 100% - 340 // subsequent events are always notified 341 .arg(m_events.getExportId()) 342 .arg(m_valueMinimum) 343 .arg(m_valueMaximum) 344 .arg(encodeEntities(m_units)) 345 .arg(extraAttributes)); 346 347 m_events.toXml(out, indent, QString("dimensions=\"2\"")); 348 } 349 toDelimitedDataString(QString delimiter,DataExportOptions options,sv_frame_t startFrame,sv_frame_t duration)350 QString toDelimitedDataString(QString delimiter, 351 DataExportOptions options, 352 sv_frame_t startFrame, 353 sv_frame_t duration) const override { 354 return m_events.toDelimitedDataString(delimiter, 355 options, 356 startFrame, 357 duration, 358 m_sampleRate, 359 m_resolution, 360 Event().withValue(0.f)); 361 } 362 363 protected: 364 sv_samplerate_t m_sampleRate; 365 int m_resolution; 366 367 std::atomic<float> m_valueMinimum; 368 std::atomic<float> m_valueMaximum; 369 std::atomic<bool> m_haveExtents; 370 std::atomic<bool> m_haveTextLabels; 371 QString m_units; 372 DeferredNotifier m_notifier; 373 std::atomic<int> m_completion; 374 375 EventSeries m_events; 376 }; 377 378 #endif 379 380 381 382