| Chris@147 | 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */ | 
| Chris@147 | 2 | 
| Chris@147 | 3 /* | 
| Chris@147 | 4     Sonic Visualiser | 
| Chris@147 | 5     An audio file viewer and annotation editor. | 
| Chris@147 | 6     Centre for Digital Music, Queen Mary, University of London. | 
| Chris@147 | 7 | 
| Chris@147 | 8     This program is free software; you can redistribute it and/or | 
| Chris@147 | 9     modify it under the terms of the GNU General Public License as | 
| Chris@147 | 10     published by the Free Software Foundation; either version 2 of the | 
| Chris@147 | 11     License, or (at your option) any later version.  See the file | 
| Chris@147 | 12     COPYING included with this distribution for more information. | 
| Chris@147 | 13 */ | 
| Chris@147 | 14 | 
| Chris@1581 | 15 #ifndef SV_SPARSE_TIME_VALUE_MODEL_H | 
| Chris@1581 | 16 #define SV_SPARSE_TIME_VALUE_MODEL_H | 
| Chris@147 | 17 | 
| Chris@1651 | 18 #include "EventCommands.h" | 
| Chris@1651 | 19 #include "TabularModel.h" | 
| Chris@1651 | 20 #include "Model.h" | 
| Chris@1651 | 21 #include "DeferredNotifier.h" | 
| Chris@1651 | 22 | 
| Chris@1651 | 23 #include "base/RealTime.h" | 
| Chris@1651 | 24 #include "base/EventSeries.h" | 
| Chris@1651 | 25 #include "base/UnitDatabase.h" | 
| Chris@150 | 26 #include "base/PlayParameterRepository.h" | 
| Chris@1651 | 27 | 
| Chris@1651 | 28 #include "system/System.h" | 
| Chris@147 | 29 | 
| Chris@147 | 30 /** | 
| Chris@1651 | 31  * A model representing a wiggly-line plot with points at arbitrary | 
| Chris@1651 | 32  * intervals of the model resolution. | 
| Chris@147 | 33  */ | 
| Chris@1651 | 34 class SparseTimeValueModel : public Model, | 
| Chris@1651 | 35                              public TabularModel, | 
| Chris@1651 | 36                              public EventEditable | 
| Chris@147 | 37 { | 
| Chris@423 | 38     Q_OBJECT | 
| Chris@423 | 39 | 
| Chris@147 | 40 public: | 
| Chris@1651 | 41     SparseTimeValueModel(sv_samplerate_t sampleRate, | 
| Chris@1651 | 42                          int resolution, | 
| Chris@1429 | 43                          bool notifyOnAdd = true) : | 
| Chris@1651 | 44         m_sampleRate(sampleRate), | 
| Chris@1651 | 45         m_resolution(resolution), | 
| Chris@1651 | 46         m_valueMinimum(0.f), | 
| Chris@1651 | 47         m_valueMaximum(0.f), | 
| Chris@1651 | 48         m_haveExtents(false), | 
| Chris@1651 | 49         m_haveTextLabels(false), | 
| Chris@1651 | 50         m_notifier(this, | 
| Chris@1651 | 51                    notifyOnAdd ? | 
| Chris@1651 | 52                    DeferredNotifier::NOTIFY_ALWAYS : | 
| Chris@1651 | 53                    DeferredNotifier::NOTIFY_DEFERRED), | 
| Chris@1651 | 54         m_completion(0) { | 
| Chris@868 | 55         // Model is playable, but may not sound (if units not Hz or | 
| Chris@868 | 56         // range unsuitable) | 
| Chris@1429 | 57         PlayParameterRepository::getInstance()->addPlayable(this); | 
| Chris@256 | 58     } | 
| Chris@256 | 59 | 
| Chris@1040 | 60     SparseTimeValueModel(sv_samplerate_t sampleRate, int resolution, | 
| Chris@1429 | 61                          float valueMinimum, float valueMaximum, | 
| Chris@1429 | 62                          bool notifyOnAdd = true) : | 
| Chris@1651 | 63         m_sampleRate(sampleRate), | 
| Chris@1651 | 64         m_resolution(resolution), | 
| Chris@1651 | 65         m_valueMinimum(valueMinimum), | 
| Chris@1651 | 66         m_valueMaximum(valueMaximum), | 
| Chris@1651 | 67         m_haveExtents(false), | 
| Chris@1651 | 68         m_haveTextLabels(false), | 
| Chris@1651 | 69         m_notifier(this, | 
| Chris@1651 | 70                    notifyOnAdd ? | 
| Chris@1651 | 71                    DeferredNotifier::NOTIFY_ALWAYS : | 
| Chris@1651 | 72                    DeferredNotifier::NOTIFY_DEFERRED), | 
| Chris@1651 | 73         m_completion(0) { | 
| Chris@868 | 74         // Model is playable, but may not sound (if units not Hz or | 
| Chris@868 | 75         // range unsuitable) | 
| Chris@1429 | 76         PlayParameterRepository::getInstance()->addPlayable(this); | 
| Chris@868 | 77     } | 
| Chris@868 | 78 | 
| Chris@1651 | 79     virtual ~SparseTimeValueModel() { | 
| Chris@1429 | 80         PlayParameterRepository::getInstance()->removePlayable(this); | 
| Chris@147 | 81     } | 
| Chris@345 | 82 | 
| Chris@1580 | 83     QString getTypeName() const override { return tr("Sparse Time-Value"); } | 
| Chris@420 | 84 | 
| Chris@1651 | 85     bool isOK() const override { return true; } | 
| Chris@1651 | 86     sv_frame_t getStartFrame() const override { return m_events.getStartFrame(); } | 
| Chris@1651 | 87     sv_frame_t getEndFrame() const override { return m_events.getEndFrame(); } | 
| Chris@1651 | 88     sv_samplerate_t getSampleRate() const override { return m_sampleRate; } | 
| Chris@1651 | 89     int getResolution() const { return m_resolution; } | 
| Chris@1651 | 90 | 
| Chris@1580 | 91     bool canPlay() const override { return true; } | 
| Chris@1580 | 92     bool getDefaultPlayAudible() const override { return false; } // user must unmute | 
| Chris@868 | 93 | 
| Chris@1651 | 94     QString getScaleUnits() const { return m_units; } | 
| Chris@1651 | 95     void setScaleUnits(QString units) { | 
| Chris@1651 | 96         m_units = units; | 
| Chris@1651 | 97         UnitDatabase::getInstance()->registerUnit(units); | 
| Chris@1651 | 98     } | 
| Chris@1651 | 99 | 
| Chris@1651 | 100     bool hasTextLabels() const { return m_haveTextLabels; } | 
| Chris@1651 | 101 | 
| Chris@1651 | 102     float getValueMinimum() const { return m_valueMinimum; } | 
| Chris@1651 | 103     float getValueMaximum() const { return m_valueMaximum; } | 
| Chris@1651 | 104 | 
| Chris@1651 | 105     int getCompletion() const { return m_completion; } | 
| Chris@1651 | 106 | 
| Chris@1651 | 107     void setCompletion(int completion, bool update = true) { | 
| Chris@1651 | 108 | 
| Chris@1651 | 109         {   QMutexLocker locker(&m_mutex); | 
| Chris@1651 | 110             if (m_completion == completion) return; | 
| Chris@1651 | 111             m_completion = completion; | 
| Chris@1651 | 112         } | 
| Chris@1651 | 113 | 
| Chris@1651 | 114         if (update) { | 
| Chris@1651 | 115             m_notifier.makeDeferredNotifications(); | 
| Chris@1651 | 116         } | 
| Chris@1651 | 117 | 
| Chris@1651 | 118         emit completionChanged(); | 
| Chris@1651 | 119 | 
| Chris@1651 | 120         if (completion == 100) { | 
| Chris@1651 | 121             // henceforth: | 
| Chris@1651 | 122             m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS); | 
| Chris@1651 | 123             emit modelChanged(); | 
| Chris@1651 | 124         } | 
| Chris@1651 | 125     } | 
| Chris@1651 | 126 | 
| Chris@1651 | 127     /** | 
| Chris@1651 | 128      * Query methods. | 
| Chris@1651 | 129      */ | 
| Chris@1651 | 130 | 
| Chris@1651 | 131     int getEventCount() const { | 
| Chris@1651 | 132         return m_events.count(); | 
| Chris@1651 | 133     } | 
| Chris@1651 | 134     bool isEmpty() const { | 
| Chris@1651 | 135         return m_events.isEmpty(); | 
| Chris@1651 | 136     } | 
| Chris@1651 | 137     bool containsEvent(const Event &e) const { | 
| Chris@1651 | 138         return m_events.contains(e); | 
| Chris@1651 | 139     } | 
| Chris@1651 | 140     EventVector getAllEvents() const { | 
| Chris@1651 | 141         return m_events.getAllEvents(); | 
| Chris@1651 | 142     } | 
| Chris@1651 | 143     EventVector getEventsSpanning(sv_frame_t f, sv_frame_t duration) const { | 
| Chris@1651 | 144         return m_events.getEventsSpanning(f, duration); | 
| Chris@1651 | 145     } | 
| Chris@1651 | 146     EventVector getEventsWithin(sv_frame_t f, sv_frame_t duration) const { | 
| Chris@1651 | 147         return m_events.getEventsWithin(f, duration); | 
| Chris@1651 | 148     } | 
| Chris@1651 | 149     EventVector getEventsStartingWithin(sv_frame_t f, sv_frame_t duration) const { | 
| Chris@1651 | 150         return m_events.getEventsStartingWithin(f, duration); | 
| Chris@1651 | 151     } | 
| Chris@1651 | 152     EventVector getEventsCovering(sv_frame_t f) const { | 
| Chris@1651 | 153         return m_events.getEventsCovering(f); | 
| Chris@1651 | 154     } | 
| Chris@1651 | 155 | 
| Chris@1651 | 156     /** | 
| Chris@1651 | 157      * Editing methods. | 
| Chris@1651 | 158      */ | 
| Chris@1651 | 159     void add(Event e) override { | 
| Chris@1651 | 160 | 
| Chris@1651 | 161         bool allChange = false; | 
| Chris@1651 | 162 | 
| Chris@1651 | 163         { | 
| Chris@1651 | 164             QMutexLocker locker(&m_mutex); | 
| Chris@1651 | 165             m_events.add(e); | 
| Chris@1651 | 166 | 
| Chris@1651 | 167             if (e.getLabel() != "") { | 
| Chris@1651 | 168                 m_haveTextLabels = true; | 
| Chris@1651 | 169             } | 
| Chris@1651 | 170 | 
| Chris@1651 | 171             float v = e.getValue(); | 
| Chris@1651 | 172             if (!ISNAN(v) && !ISINF(v)) { | 
| Chris@1651 | 173                 if (!m_haveExtents || v < m_valueMinimum) { | 
| Chris@1651 | 174                     m_valueMinimum = v; allChange = true; | 
| Chris@1651 | 175                 } | 
| Chris@1651 | 176                 if (!m_haveExtents || v > m_valueMaximum) { | 
| Chris@1651 | 177                     m_valueMaximum = v; allChange = true; | 
| Chris@1651 | 178                 } | 
| Chris@1651 | 179                 m_haveExtents = true; | 
| Chris@1651 | 180             } | 
| Chris@1651 | 181         } | 
| Chris@1651 | 182 | 
| Chris@1651 | 183         m_notifier.update(e.getFrame(), m_resolution); | 
| Chris@1651 | 184 | 
| Chris@1651 | 185         if (allChange) { | 
| Chris@1651 | 186             emit modelChanged(); | 
| Chris@1651 | 187         } | 
| Chris@1651 | 188     } | 
| Chris@1651 | 189 | 
| Chris@1651 | 190     void remove(Event e) override { | 
| Chris@1651 | 191         { | 
| Chris@1651 | 192             QMutexLocker locker(&m_mutex); | 
| Chris@1651 | 193             m_events.remove(e); | 
| Chris@1651 | 194         } | 
| Chris@1651 | 195         emit modelChangedWithin(e.getFrame(), e.getFrame() + m_resolution); | 
| Chris@1651 | 196     } | 
| Chris@1651 | 197 | 
| Chris@420 | 198     /** | 
| Chris@420 | 199      * TabularModel methods. | 
| Chris@420 | 200      */ | 
| Chris@420 | 201 | 
| Chris@1651 | 202     int getRowCount() const override { | 
| Chris@1651 | 203         return m_events.count(); | 
| Chris@1651 | 204     } | 
| Chris@1651 | 205 | 
| Chris@1651 | 206     int getColumnCount() const override { | 
| Chris@420 | 207         return 4; | 
| Chris@420 | 208     } | 
| Chris@420 | 209 | 
| Chris@1651 | 210     bool isColumnTimeValue(int column) const override { | 
| Chris@1651 | 211         // NB duration is not a "time value" -- that's for columns | 
| Chris@1651 | 212         // whose sort ordering is exactly that of the frame time | 
| Chris@1651 | 213         return (column < 2); | 
| Chris@1651 | 214     } | 
| Chris@1651 | 215 | 
| Chris@1651 | 216     sv_frame_t getFrameForRow(int row) const override { | 
| Chris@1651 | 217         if (row < 0 || row >= m_events.count()) { | 
| Chris@1651 | 218             return 0; | 
| Chris@1651 | 219         } | 
| Chris@1651 | 220         Event e = m_events.getEventByIndex(row); | 
| Chris@1651 | 221         return e.getFrame(); | 
| Chris@1651 | 222     } | 
| Chris@1651 | 223 | 
| Chris@1651 | 224     int getRowForFrame(sv_frame_t frame) const override { | 
| Chris@1651 | 225         return m_events.getIndexForEvent(Event(frame)); | 
| Chris@1651 | 226     } | 
| Chris@1651 | 227 | 
| Chris@1651 | 228     QString getHeading(int column) const override { | 
| Chris@420 | 229         switch (column) { | 
| Chris@420 | 230         case 0: return tr("Time"); | 
| Chris@420 | 231         case 1: return tr("Frame"); | 
| Chris@420 | 232         case 2: return tr("Value"); | 
| Chris@420 | 233         case 3: return tr("Label"); | 
| Chris@420 | 234         default: return tr("Unknown"); | 
| Chris@420 | 235         } | 
| Chris@420 | 236     } | 
| Chris@420 | 237 | 
| Chris@1651 | 238     SortType getSortType(int column) const override { | 
| Chris@1651 | 239         if (column == 3) return SortAlphabetical; | 
| Chris@1651 | 240         return SortNumeric; | 
| Chris@1651 | 241     } | 
| Chris@1651 | 242 | 
| Chris@1651 | 243     QVariant getData(int row, int column, int role) const override { | 
| Chris@1651 | 244 | 
| Chris@1651 | 245         if (row < 0 || row >= m_events.count()) { | 
| Chris@1651 | 246             return QVariant(); | 
| Chris@425 | 247         } | 
| Chris@425 | 248 | 
| Chris@1651 | 249         Event e = m_events.getEventByIndex(row); | 
| Chris@420 | 250 | 
| Chris@420 | 251         switch (column) { | 
| Chris@1651 | 252         case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role); | 
| Chris@1651 | 253         case 1: return int(e.getFrame()); | 
| Chris@1651 | 254         case 2: return adaptValueForRole(e.getValue(), getScaleUnits(), role); | 
| Chris@1651 | 255         case 3: return e.getLabel(); | 
| Chris@420 | 256         default: return QVariant(); | 
| Chris@420 | 257         } | 
| Chris@420 | 258     } | 
| Chris@420 | 259 | 
| Chris@1651 | 260     Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override { | 
| Chris@1651 | 261         if (row < 0 || row >= m_events.count()) return nullptr; | 
| Chris@1651 | 262         if (role != Qt::EditRole) return nullptr; | 
| Chris@1651 | 263 | 
| Chris@1651 | 264         Event e0 = m_events.getEventByIndex(row); | 
| Chris@1651 | 265         Event e1; | 
| Chris@1651 | 266 | 
| Chris@1651 | 267         switch (column) { | 
| Chris@1651 | 268         case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() * | 
| Chris@1651 | 269                                                    getSampleRate()))); break; | 
| Chris@1651 | 270         case 1: e1 = e0.withFrame(value.toInt()); break; | 
| Chris@1651 | 271         case 2: e1 = e0.withValue(float(value.toDouble())); break; | 
| Chris@1651 | 272         case 3: e1 = e0.withLabel(value.toString()); break; | 
| Chris@425 | 273         } | 
| Chris@425 | 274 | 
| Chris@1651 | 275         ChangeEventsCommand *command = | 
| Chris@1651 | 276             new ChangeEventsCommand(this, tr("Edit Data")); | 
| Chris@1651 | 277         command->remove(e0); | 
| Chris@1651 | 278         command->add(e1); | 
| Chris@420 | 279         return command->finish(); | 
| Chris@420 | 280     } | 
| Chris@1651 | 281 | 
| Chris@1651 | 282     /** | 
| Chris@1651 | 283      * XmlExportable methods. | 
| Chris@1651 | 284      */ | 
| Chris@1651 | 285     void toXml(QTextStream &out, | 
| Chris@1651 | 286                QString indent = "", | 
| Chris@1651 | 287                QString extraAttributes = "") const override { | 
| Chris@420 | 288 | 
| Chris@1651 | 289         Model::toXml | 
| Chris@1651 | 290             (out, | 
| Chris@1651 | 291              indent, | 
| Chris@1651 | 292              QString("type=\"sparse\" dimensions=\"2\" resolution=\"%1\" " | 
| Chris@1651 | 293                      "notifyOnAdd=\"%2\" dataset=\"%3\" " | 
| Chris@1651 | 294                      "minimum=\"%4\" maximum=\"%5\" " | 
| Chris@1651 | 295                      "units=\"%6\" %7") | 
| Chris@1651 | 296              .arg(m_resolution) | 
| Chris@1651 | 297              .arg("true") // always true after model reaches 100% - | 
| Chris@1651 | 298                           // subsequent events are always notified | 
| Chris@1651 | 299              .arg(getObjectExportId(&m_events)) | 
| Chris@1651 | 300              .arg(m_valueMinimum) | 
| Chris@1651 | 301              .arg(m_valueMaximum) | 
| Chris@1651 | 302              .arg(encodeEntities(m_units)) | 
| Chris@1651 | 303              .arg(extraAttributes)); | 
| Chris@1651 | 304 | 
| Chris@1651 | 305         m_events.toXml(out, indent, QString("dimensions=\"2\"")); | 
| Chris@420 | 306     } | 
| Chris@1651 | 307 | 
| Chris@1651 | 308 protected: | 
| Chris@1651 | 309     sv_samplerate_t m_sampleRate; | 
| Chris@1651 | 310     int m_resolution; | 
| Chris@422 | 311 | 
| Chris@1651 | 312     float m_valueMinimum; | 
| Chris@1651 | 313     float m_valueMaximum; | 
| Chris@1651 | 314     bool m_haveExtents; | 
| Chris@1651 | 315     bool m_haveTextLabels; | 
| Chris@1651 | 316     QString m_units; | 
| Chris@1651 | 317     DeferredNotifier m_notifier; | 
| Chris@1651 | 318     int m_completion; | 
| Chris@1651 | 319 | 
| Chris@1651 | 320     EventSeries m_events; | 
| Chris@1651 | 321 | 
| Chris@1651 | 322     mutable QMutex m_mutex; | 
| Chris@147 | 323 }; | 
| Chris@147 | 324 | 
| Chris@147 | 325 | 
| Chris@147 | 326 #endif | 
| Chris@147 | 327 | 
| Chris@147 | 328 | 
| Chris@147 | 329 |