| 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@1495 | 15 #ifndef SV_NOTE_MODEL_H | 
| Chris@1495 | 16 #define SV_NOTE_MODEL_H | 
| Chris@147 | 17 | 
| Chris@1643 | 18 #include "Model.h" | 
| Chris@1643 | 19 #include "TabularModel.h" | 
| Chris@1648 | 20 #include "EventCommands.h" | 
| Chris@1651 | 21 #include "DeferredNotifier.h" | 
| Chris@1643 | 22 #include "base/UnitDatabase.h" | 
| Chris@1643 | 23 #include "base/EventSeries.h" | 
| Chris@1615 | 24 #include "base/NoteData.h" | 
| Chris@1643 | 25 #include "base/NoteExportable.h" | 
| Chris@391 | 26 #include "base/RealTime.h" | 
| Chris@150 | 27 #include "base/PlayParameterRepository.h" | 
| Chris@852 | 28 #include "base/Pitch.h" | 
| Chris@1643 | 29 #include "system/System.h" | 
| Chris@147 | 30 | 
| Chris@1643 | 31 #include <QMutex> | 
| Chris@1643 | 32 #include <QMutexLocker> | 
| Chris@441 | 33 | 
| Chris@1643 | 34 class NoteModel : public Model, | 
| Chris@1643 | 35                   public TabularModel, | 
| Chris@1648 | 36                   public NoteExportable, | 
| Chris@1648 | 37                   public EventEditable | 
| Chris@147 | 38 { | 
| Chris@423 | 39     Q_OBJECT | 
| Chris@423 | 40 | 
| Chris@147 | 41 public: | 
| Chris@1647 | 42     enum Subtype { | 
| Chris@1647 | 43         NORMAL_NOTE, | 
| Chris@1647 | 44         FLEXI_NOTE | 
| Chris@1647 | 45     }; | 
| Chris@1647 | 46 | 
| Chris@1644 | 47     NoteModel(sv_samplerate_t sampleRate, | 
| Chris@1644 | 48               int resolution, | 
| Chris@1647 | 49               bool notifyOnAdd = true, | 
| Chris@1647 | 50               Subtype subtype = NORMAL_NOTE) : | 
| Chris@1647 | 51         m_subtype(subtype), | 
| Chris@1643 | 52         m_sampleRate(sampleRate), | 
| Chris@1643 | 53         m_resolution(resolution), | 
| Chris@1643 | 54         m_valueMinimum(0.f), | 
| Chris@1643 | 55         m_valueMaximum(0.f), | 
| Chris@1643 | 56         m_haveExtents(false), | 
| Chris@1643 | 57         m_valueQuantization(0), | 
| Chris@1643 | 58         m_units(""), | 
| Chris@1643 | 59         m_extendTo(0), | 
| Chris@1651 | 60         m_notifier(this, | 
| Chris@1752 | 61                    getId(), | 
| Chris@1651 | 62                    notifyOnAdd ? | 
| Chris@1651 | 63                    DeferredNotifier::NOTIFY_ALWAYS : | 
| Chris@1651 | 64                    DeferredNotifier::NOTIFY_DEFERRED), | 
| Chris@1655 | 65         m_completion(100) { | 
| Chris@1647 | 66         if (subtype == FLEXI_NOTE) { | 
| Chris@1647 | 67             m_valueMinimum = 33.f; | 
| Chris@1647 | 68             m_valueMaximum = 88.f; | 
| Chris@1647 | 69         } | 
| Chris@1751 | 70         PlayParameterRepository::getInstance()->addPlayable | 
| Chris@1751 | 71             (getId().untyped, this); | 
| Chris@256 | 72     } | 
| Chris@256 | 73 | 
| Chris@1040 | 74     NoteModel(sv_samplerate_t sampleRate, int resolution, | 
| Chris@1429 | 75               float valueMinimum, float valueMaximum, | 
| Chris@1647 | 76               bool notifyOnAdd = true, | 
| Chris@1647 | 77               Subtype subtype = NORMAL_NOTE) : | 
| Chris@1647 | 78         m_subtype(subtype), | 
| Chris@1643 | 79         m_sampleRate(sampleRate), | 
| Chris@1643 | 80         m_resolution(resolution), | 
| Chris@1643 | 81         m_valueMinimum(valueMinimum), | 
| Chris@1643 | 82         m_valueMaximum(valueMaximum), | 
| Chris@1643 | 83         m_haveExtents(true), | 
| Chris@1643 | 84         m_valueQuantization(0), | 
| Chris@1643 | 85         m_units(""), | 
| Chris@1643 | 86         m_extendTo(0), | 
| Chris@1651 | 87         m_notifier(this, | 
| Chris@1752 | 88                    getId(), | 
| Chris@1651 | 89                    notifyOnAdd ? | 
| Chris@1651 | 90                    DeferredNotifier::NOTIFY_ALWAYS : | 
| Chris@1651 | 91                    DeferredNotifier::NOTIFY_DEFERRED), | 
| Chris@1655 | 92         m_completion(100) { | 
| Chris@1751 | 93         PlayParameterRepository::getInstance()->addPlayable | 
| Chris@1751 | 94             (getId().untyped, this); | 
| Chris@391 | 95     } | 
| Chris@391 | 96 | 
| Chris@1643 | 97     virtual ~NoteModel() { | 
| Chris@1751 | 98         PlayParameterRepository::getInstance()->removePlayable | 
| Chris@1751 | 99             (getId().untyped); | 
| Chris@147 | 100     } | 
| Chris@1647 | 101 | 
| Chris@1643 | 102     QString getTypeName() const override { return tr("Note"); } | 
| Chris@1647 | 103     Subtype getSubtype() const { return m_subtype; } | 
| Chris@1692 | 104     bool isSparse() const override { return true; } | 
| Chris@1659 | 105     bool isOK() const override { return true; } | 
| Chris@1659 | 106 | 
| Chris@1659 | 107     sv_frame_t getStartFrame() const override { | 
| Chris@1659 | 108         return m_events.getStartFrame(); | 
| Chris@1659 | 109     } | 
| Chris@1725 | 110     sv_frame_t getTrueEndFrame() const override { | 
| Chris@1659 | 111         if (m_events.isEmpty()) return 0; | 
| Chris@1659 | 112         sv_frame_t e = m_events.getEndFrame(); | 
| Chris@1659 | 113         if (e % m_resolution == 0) return e; | 
| Chris@1659 | 114         else return (e / m_resolution + 1) * m_resolution; | 
| Chris@1659 | 115     } | 
| Chris@1643 | 116 | 
| Chris@1643 | 117     sv_samplerate_t getSampleRate() const override { return m_sampleRate; } | 
| Chris@1644 | 118     int getResolution() const { return m_resolution; } | 
| Chris@1643 | 119 | 
| Chris@1643 | 120     bool canPlay() const override { return true; } | 
| Chris@1643 | 121     QString getDefaultPlayClipId() const override { | 
| Chris@1643 | 122         return "elecpiano"; | 
| Chris@1643 | 123     } | 
| Chris@1643 | 124 | 
| Chris@1643 | 125     QString getScaleUnits() const { return m_units; } | 
| Chris@1643 | 126     void setScaleUnits(QString units) { | 
| Chris@1643 | 127         m_units = units; | 
| Chris@1643 | 128         UnitDatabase::getInstance()->registerUnit(units); | 
| Chris@1643 | 129     } | 
| Chris@147 | 130 | 
| Chris@147 | 131     float getValueQuantization() const { return m_valueQuantization; } | 
| Chris@147 | 132     void setValueQuantization(float q) { m_valueQuantization = q; } | 
| Chris@147 | 133 | 
| Chris@1643 | 134     float getValueMinimum() const { return m_valueMinimum; } | 
| Chris@1643 | 135     float getValueMaximum() const { return m_valueMaximum; } | 
| Chris@1643 | 136 | 
| Chris@1671 | 137     int getCompletion() const override { return m_completion; } | 
| Chris@345 | 138 | 
| Chris@1643 | 139     void setCompletion(int completion, bool update = true) { | 
| Chris@391 | 140 | 
| Chris@1651 | 141         {   QMutexLocker locker(&m_mutex); | 
| Chris@1651 | 142             if (m_completion == completion) return; | 
| Chris@1651 | 143             m_completion = completion; | 
| Chris@1643 | 144         } | 
| Chris@1643 | 145 | 
| Chris@1651 | 146         if (update) { | 
| Chris@1651 | 147             m_notifier.makeDeferredNotifications(); | 
| Chris@1643 | 148         } | 
| Chris@1651 | 149 | 
| Chris@1752 | 150         emit completionChanged(getId()); | 
| Chris@1651 | 151 | 
| Chris@1651 | 152         if (completion == 100) { | 
| Chris@1651 | 153             // henceforth: | 
| Chris@1651 | 154             m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS); | 
| Chris@1752 | 155             emit modelChanged(getId()); | 
| Chris@1643 | 156         } | 
| Chris@391 | 157     } | 
| Chris@1644 | 158 | 
| Chris@1644 | 159     /** | 
| Chris@1644 | 160      * Query methods. | 
| Chris@1644 | 161      */ | 
| Chris@1644 | 162 | 
| Chris@1644 | 163     int getEventCount() const { | 
| Chris@1644 | 164         return m_events.count(); | 
| Chris@1644 | 165     } | 
| Chris@1644 | 166     bool isEmpty() const { | 
| Chris@1644 | 167         return m_events.isEmpty(); | 
| Chris@1644 | 168     } | 
| Chris@1644 | 169     bool containsEvent(const Event &e) const { | 
| Chris@1644 | 170         return m_events.contains(e); | 
| Chris@1644 | 171     } | 
| Chris@1644 | 172     EventVector getAllEvents() const { | 
| Chris@1644 | 173         return m_events.getAllEvents(); | 
| Chris@1644 | 174     } | 
| Chris@1644 | 175     EventVector getEventsSpanning(sv_frame_t f, sv_frame_t duration) const { | 
| Chris@1644 | 176         return m_events.getEventsSpanning(f, duration); | 
| Chris@1644 | 177     } | 
| Chris@1656 | 178     EventVector getEventsCovering(sv_frame_t f) const { | 
| Chris@1656 | 179         return m_events.getEventsCovering(f); | 
| Chris@1656 | 180     } | 
| Chris@1644 | 181     EventVector getEventsWithin(sv_frame_t f, sv_frame_t duration) const { | 
| Chris@1644 | 182         return m_events.getEventsWithin(f, duration); | 
| Chris@1644 | 183     } | 
| Chris@1644 | 184     EventVector getEventsStartingWithin(sv_frame_t f, sv_frame_t duration) const { | 
| Chris@1644 | 185         return m_events.getEventsStartingWithin(f, duration); | 
| Chris@1644 | 186     } | 
| Chris@1656 | 187     EventVector getEventsStartingAt(sv_frame_t f) const { | 
| Chris@1656 | 188         return m_events.getEventsStartingAt(f); | 
| Chris@1644 | 189     } | 
| Chris@1657 | 190     bool getNearestEventMatching(sv_frame_t startSearchAt, | 
| Chris@1657 | 191                                  std::function<bool(Event)> predicate, | 
| Chris@1657 | 192                                  EventSeries::Direction direction, | 
| Chris@1657 | 193                                  Event &found) const { | 
| Chris@1657 | 194         return m_events.getNearestEventMatching | 
| Chris@1657 | 195             (startSearchAt, predicate, direction, found); | 
| Chris@1657 | 196     } | 
| Chris@1644 | 197 | 
| Chris@1644 | 198     /** | 
| Chris@1648 | 199      * Editing methods. | 
| Chris@1644 | 200      */ | 
| Chris@1648 | 201     void add(Event e) override { | 
| Chris@1644 | 202 | 
| Chris@1644 | 203         bool allChange = false; | 
| Chris@1644 | 204 | 
| Chris@1644 | 205         { | 
| Chris@1644 | 206             QMutexLocker locker(&m_mutex); | 
| Chris@1644 | 207             m_events.add(e); | 
| Chris@1644 | 208 | 
| Chris@1644 | 209             float v = e.getValue(); | 
| Chris@1644 | 210             if (!ISNAN(v) && !ISINF(v)) { | 
| Chris@1644 | 211                 if (!m_haveExtents || v < m_valueMinimum) { | 
| Chris@1644 | 212                     m_valueMinimum = v; allChange = true; | 
| Chris@1644 | 213                 } | 
| Chris@1644 | 214                 if (!m_haveExtents || v > m_valueMaximum) { | 
| Chris@1644 | 215                     m_valueMaximum = v; allChange = true; | 
| Chris@1644 | 216                 } | 
| Chris@1644 | 217                 m_haveExtents = true; | 
| Chris@1644 | 218             } | 
| Chris@1644 | 219         } | 
| Chris@1644 | 220 | 
| Chris@1651 | 221         m_notifier.update(e.getFrame(), e.getDuration() + m_resolution); | 
| Chris@1651 | 222 | 
| Chris@1644 | 223         if (allChange) { | 
| Chris@1752 | 224             emit modelChanged(getId()); | 
| Chris@1644 | 225         } | 
| Chris@1644 | 226     } | 
| Chris@1644 | 227 | 
| Chris@1648 | 228     void remove(Event e) override { | 
| Chris@1644 | 229         { | 
| Chris@1644 | 230             QMutexLocker locker(&m_mutex); | 
| Chris@1644 | 231             m_events.remove(e); | 
| Chris@1644 | 232         } | 
| Chris@1752 | 233         emit modelChangedWithin(getId(), | 
| Chris@1752 | 234                                 e.getFrame(), | 
| Chris@1644 | 235                                 e.getFrame() + e.getDuration() + m_resolution); | 
| Chris@147 | 236     } | 
| Chris@147 | 237 | 
| Chris@424 | 238     /** | 
| Chris@424 | 239      * TabularModel methods. | 
| Chris@424 | 240      */ | 
| Chris@1643 | 241 | 
| Chris@1643 | 242     int getRowCount() const override { | 
| Chris@1643 | 243         return m_events.count(); | 
| Chris@1643 | 244     } | 
| Chris@424 | 245 | 
| Chris@1643 | 246     int getColumnCount() const override { | 
| Chris@424 | 247         return 6; | 
| Chris@424 | 248     } | 
| Chris@424 | 249 | 
| Chris@1643 | 250     bool isColumnTimeValue(int column) const override { | 
| Chris@1643 | 251         // NB duration is not a "time value" -- that's for columns | 
| Chris@1643 | 252         // whose sort ordering is exactly that of the frame time | 
| Chris@1643 | 253         return (column < 2); | 
| Chris@1643 | 254     } | 
| Chris@1643 | 255 | 
| Chris@1643 | 256     sv_frame_t getFrameForRow(int row) const override { | 
| Chris@1643 | 257         if (row < 0 || row >= m_events.count()) { | 
| Chris@1643 | 258             return 0; | 
| Chris@1643 | 259         } | 
| Chris@1643 | 260         Event e = m_events.getEventByIndex(row); | 
| Chris@1643 | 261         return e.getFrame(); | 
| Chris@1643 | 262     } | 
| Chris@1643 | 263 | 
| Chris@1643 | 264     int getRowForFrame(sv_frame_t frame) const override { | 
| Chris@1643 | 265         return m_events.getIndexForEvent(Event(frame)); | 
| Chris@1643 | 266     } | 
| Chris@1643 | 267 | 
| Chris@1643 | 268     QString getHeading(int column) const override { | 
| Chris@424 | 269         switch (column) { | 
| Chris@424 | 270         case 0: return tr("Time"); | 
| Chris@424 | 271         case 1: return tr("Frame"); | 
| Chris@424 | 272         case 2: return tr("Pitch"); | 
| Chris@424 | 273         case 3: return tr("Duration"); | 
| Chris@424 | 274         case 4: return tr("Level"); | 
| Chris@424 | 275         case 5: return tr("Label"); | 
| Chris@424 | 276         default: return tr("Unknown"); | 
| Chris@424 | 277         } | 
| Chris@424 | 278     } | 
| Chris@424 | 279 | 
| Chris@1643 | 280     QVariant getData(int row, int column, int role) const override { | 
| Chris@1643 | 281 | 
| Chris@1643 | 282         if (row < 0 || row >= m_events.count()) { | 
| Chris@1643 | 283             return QVariant(); | 
| Chris@425 | 284         } | 
| Chris@425 | 285 | 
| Chris@1643 | 286         Event e = m_events.getEventByIndex(row); | 
| Chris@424 | 287 | 
| Chris@424 | 288         switch (column) { | 
| Chris@1643 | 289         case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role); | 
| Chris@1643 | 290         case 1: return int(e.getFrame()); | 
| Chris@1643 | 291         case 2: return adaptValueForRole(e.getValue(), getScaleUnits(), role); | 
| Chris@1643 | 292         case 3: return int(e.getDuration()); | 
| Chris@1643 | 293         case 4: return e.getLevel(); | 
| Chris@1643 | 294         case 5: return e.getLabel(); | 
| Chris@424 | 295         default: return QVariant(); | 
| Chris@424 | 296         } | 
| Chris@424 | 297     } | 
| Chris@424 | 298 | 
| Chris@1649 | 299     Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override { | 
| Chris@1649 | 300 | 
| Chris@1643 | 301         if (row < 0 || row >= m_events.count()) return nullptr; | 
| Chris@1643 | 302         if (role != Qt::EditRole) return nullptr; | 
| Chris@1643 | 303 | 
| Chris@1643 | 304         Event e0 = m_events.getEventByIndex(row); | 
| Chris@1643 | 305         Event e1; | 
| Chris@1643 | 306 | 
| Chris@1643 | 307         switch (column) { | 
| Chris@1643 | 308         case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() * | 
| Chris@1643 | 309                                                    getSampleRate()))); break; | 
| Chris@1643 | 310         case 1: e1 = e0.withFrame(value.toInt()); break; | 
| Chris@1643 | 311         case 2: e1 = e0.withValue(float(value.toDouble())); break; | 
| Chris@1643 | 312         case 3: e1 = e0.withDuration(value.toInt()); break; | 
| Chris@1643 | 313         case 4: e1 = e0.withLevel(float(value.toDouble())); break; | 
| Chris@1643 | 314         case 5: e1 = e0.withLabel(value.toString()); break; | 
| Chris@425 | 315         } | 
| Chris@425 | 316 | 
| Chris@1742 | 317         auto command = new ChangeEventsCommand(getId().untyped, tr("Edit Data")); | 
| Chris@1644 | 318         command->remove(e0); | 
| Chris@1644 | 319         command->add(e1); | 
| Chris@1644 | 320         return command->finish(); | 
| Chris@424 | 321     } | 
| Chris@424 | 322 | 
| Chris@1580 | 323     SortType getSortType(int column) const override | 
| Chris@424 | 324     { | 
| Chris@424 | 325         if (column == 5) return SortAlphabetical; | 
| Chris@424 | 326         return SortNumeric; | 
| Chris@424 | 327     } | 
| Chris@424 | 328 | 
| Chris@852 | 329     /** | 
| Chris@852 | 330      * NoteExportable methods. | 
| Chris@852 | 331      */ | 
| Chris@852 | 332 | 
| Chris@1580 | 333     NoteList getNotes() const override { | 
| Chris@1643 | 334         return getNotesStartingWithin(getStartFrame(), | 
| Chris@1643 | 335                                       getEndFrame() - getStartFrame()); | 
| Chris@852 | 336     } | 
| Chris@852 | 337 | 
| Chris@1643 | 338     NoteList getNotesActiveAt(sv_frame_t frame) const override { | 
| Chris@1643 | 339 | 
| Chris@852 | 340         NoteList notes; | 
| Chris@1643 | 341         EventVector ee = m_events.getEventsCovering(frame); | 
| Chris@1643 | 342         for (const auto &e: ee) { | 
| Chris@1643 | 343             notes.push_back(e.toNoteData(getSampleRate(), | 
| Chris@1643 | 344                                          getScaleUnits() != "Hz")); | 
| Chris@1643 | 345         } | 
| Chris@1643 | 346         return notes; | 
| Chris@1643 | 347     } | 
| Chris@1643 | 348 | 
| Chris@1643 | 349     NoteList getNotesStartingWithin(sv_frame_t startFrame, | 
| Chris@1643 | 350                                     sv_frame_t duration) const override { | 
| Chris@852 | 351 | 
| Chris@1643 | 352         NoteList notes; | 
| Chris@1643 | 353         EventVector ee = m_events.getEventsStartingWithin(startFrame, duration); | 
| Chris@1643 | 354         for (const auto &e: ee) { | 
| Chris@1643 | 355             notes.push_back(e.toNoteData(getSampleRate(), | 
| Chris@1643 | 356                                          getScaleUnits() != "Hz")); | 
| Chris@852 | 357         } | 
| Chris@852 | 358         return notes; | 
| Chris@852 | 359     } | 
| Chris@852 | 360 | 
| Chris@1644 | 361     /** | 
| Chris@1644 | 362      * XmlExportable methods. | 
| Chris@1644 | 363      */ | 
| Chris@1644 | 364 | 
| Chris@1644 | 365     void toXml(QTextStream &out, | 
| Chris@1644 | 366                QString indent = "", | 
| Chris@1644 | 367                QString extraAttributes = "") const override { | 
| Chris@1644 | 368 | 
| Chris@1644 | 369         //!!! what is valueQuantization used for? | 
| Chris@1644 | 370 | 
| Chris@1644 | 371         Model::toXml | 
| Chris@1644 | 372             (out, | 
| Chris@1644 | 373              indent, | 
| Chris@1644 | 374              QString("type=\"sparse\" dimensions=\"3\" resolution=\"%1\" " | 
| Chris@1647 | 375                      "notifyOnAdd=\"%2\" dataset=\"%3\" subtype=\"%4\" " | 
| Chris@1647 | 376                      "valueQuantization=\"%5\" minimum=\"%6\" maximum=\"%7\" " | 
| Chris@1647 | 377                      "units=\"%8\" %9") | 
| Chris@1644 | 378              .arg(m_resolution) | 
| Chris@1651 | 379              .arg("true") // always true after model reaches 100% - | 
| Chris@1651 | 380                           // subsequent events are always notified | 
| Chris@1677 | 381              .arg(m_events.getExportId()) | 
| Chris@1647 | 382              .arg(m_subtype == FLEXI_NOTE ? "flexinote" : "note") | 
| Chris@1644 | 383              .arg(m_valueQuantization) | 
| Chris@1644 | 384              .arg(m_valueMinimum) | 
| Chris@1644 | 385              .arg(m_valueMaximum) | 
| Chris@1651 | 386              .arg(encodeEntities(m_units)) | 
| Chris@1644 | 387              .arg(extraAttributes)); | 
| Chris@1647 | 388 | 
| Chris@1644 | 389         m_events.toXml(out, indent, QString("dimensions=\"3\"")); | 
| Chris@1644 | 390     } | 
| Chris@1644 | 391 | 
| Chris@1679 | 392     QString toDelimitedDataString(QString delimiter, | 
| Chris@1679 | 393                                   DataExportOptions options, | 
| Chris@1679 | 394                                   sv_frame_t startFrame, | 
| Chris@1679 | 395                                   sv_frame_t duration) const override { | 
| Chris@1679 | 396         return m_events.toDelimitedDataString | 
| Chris@1679 | 397             (delimiter, | 
| Chris@1679 | 398              options, | 
| Chris@1679 | 399              startFrame, | 
| Chris@1679 | 400              duration, | 
| Chris@1679 | 401              m_sampleRate, | 
| Chris@1679 | 402              m_resolution, | 
| Chris@1679 | 403              Event().withValue(0.f).withDuration(0.f).withLevel(0.f)); | 
| Chris@1679 | 404     } | 
| Chris@1679 | 405 | 
| Chris@147 | 406 protected: | 
| Chris@1647 | 407     Subtype m_subtype; | 
| Chris@1643 | 408     sv_samplerate_t m_sampleRate; | 
| Chris@1643 | 409     int m_resolution; | 
| Chris@1643 | 410 | 
| Chris@1643 | 411     float m_valueMinimum; | 
| Chris@1643 | 412     float m_valueMaximum; | 
| Chris@1643 | 413     bool m_haveExtents; | 
| Chris@147 | 414     float m_valueQuantization; | 
| Chris@1643 | 415     QString m_units; | 
| Chris@1643 | 416     sv_frame_t m_extendTo; | 
| Chris@1651 | 417     DeferredNotifier m_notifier; | 
| Chris@1651 | 418     int m_completion; | 
| Chris@1643 | 419 | 
| Chris@1643 | 420     EventSeries m_events; | 
| Chris@1643 | 421 | 
| Chris@1643 | 422     mutable QMutex m_mutex; | 
| Chris@1643 | 423 | 
| Chris@1643 | 424     //!!! do we have general docs for ownership and synchronisation of models? | 
| Chris@1643 | 425     // this might be a good opportunity to stop using bare pointers to them | 
| Chris@147 | 426 }; | 
| Chris@147 | 427 | 
| Chris@147 | 428 #endif |