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