Chris@147: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
Chris@147: 
Chris@147: /*
Chris@147:     Sonic Visualiser
Chris@147:     An audio file viewer and annotation editor.
Chris@147:     Centre for Digital Music, Queen Mary, University of London.
Chris@147:     
Chris@147:     This program is free software; you can redistribute it and/or
Chris@147:     modify it under the terms of the GNU General Public License as
Chris@147:     published by the Free Software Foundation; either version 2 of the
Chris@147:     License, or (at your option) any later version.  See the file
Chris@147:     COPYING included with this distribution for more information.
Chris@147: */
Chris@147: 
Chris@1495: #ifndef SV_NOTE_MODEL_H
Chris@1495: #define SV_NOTE_MODEL_H
Chris@147: 
Chris@1643: #include "Model.h"
Chris@1643: #include "TabularModel.h"
Chris@1648: #include "EventCommands.h"
Chris@1651: #include "DeferredNotifier.h"
Chris@1643: #include "base/UnitDatabase.h"
Chris@1643: #include "base/EventSeries.h"
Chris@1615: #include "base/NoteData.h"
Chris@1643: #include "base/NoteExportable.h"
Chris@391: #include "base/RealTime.h"
Chris@150: #include "base/PlayParameterRepository.h"
Chris@852: #include "base/Pitch.h"
Chris@1643: #include "system/System.h"
Chris@147: 
Chris@1643: #include <QMutexLocker>
Chris@441: 
Chris@1643: class NoteModel : public Model,
Chris@1643:                   public TabularModel,
Chris@1648:                   public NoteExportable,
Chris@1648:                   public EventEditable
Chris@147: {
Chris@423:     Q_OBJECT
Chris@423:     
Chris@147: public:
Chris@1647:     enum Subtype {
Chris@1647:         NORMAL_NOTE,
Chris@1647:         FLEXI_NOTE
Chris@1647:     };
Chris@1647:     
Chris@1644:     NoteModel(sv_samplerate_t sampleRate,
Chris@1644:               int resolution,
Chris@1647:               bool notifyOnAdd = true,
Chris@1647:               Subtype subtype = NORMAL_NOTE) :
Chris@1647:         m_subtype(subtype),
Chris@1643:         m_sampleRate(sampleRate),
Chris@1643:         m_resolution(resolution),
Chris@1643:         m_valueMinimum(0.f),
Chris@1643:         m_valueMaximum(0.f),
Chris@1643:         m_haveExtents(false),
Chris@1643:         m_valueQuantization(0),
Chris@1643:         m_units(""),
Chris@1651:         m_notifier(this,
Chris@1752:                    getId(),
Chris@1651:                    notifyOnAdd ?
Chris@1651:                    DeferredNotifier::NOTIFY_ALWAYS :
Chris@1651:                    DeferredNotifier::NOTIFY_DEFERRED),
Chris@1655:         m_completion(100) {
Chris@1647:         if (subtype == FLEXI_NOTE) {
Chris@1647:             m_valueMinimum = 33.f;
Chris@1647:             m_valueMaximum = 88.f;
Chris@1647:         }
Chris@1751:         PlayParameterRepository::getInstance()->addPlayable
Chris@1751:             (getId().untyped, this);
Chris@256:     }
Chris@256: 
Chris@1040:     NoteModel(sv_samplerate_t sampleRate, int resolution,
Chris@1429:               float valueMinimum, float valueMaximum,
Chris@1647:               bool notifyOnAdd = true,
Chris@1647:               Subtype subtype = NORMAL_NOTE) :
Chris@1647:         m_subtype(subtype),
Chris@1643:         m_sampleRate(sampleRate),
Chris@1643:         m_resolution(resolution),
Chris@1643:         m_valueMinimum(valueMinimum),
Chris@1643:         m_valueMaximum(valueMaximum),
Chris@1643:         m_haveExtents(true),
Chris@1643:         m_valueQuantization(0),
Chris@1643:         m_units(""),
Chris@1651:         m_notifier(this,
Chris@1752:                    getId(),
Chris@1651:                    notifyOnAdd ?
Chris@1651:                    DeferredNotifier::NOTIFY_ALWAYS :
Chris@1651:                    DeferredNotifier::NOTIFY_DEFERRED),
Chris@1655:         m_completion(100) {
Chris@1751:         PlayParameterRepository::getInstance()->addPlayable
Chris@1751:             (getId().untyped, this);
Chris@391:     }
Chris@391: 
Chris@1643:     virtual ~NoteModel() {
Chris@1751:         PlayParameterRepository::getInstance()->removePlayable
Chris@1751:             (getId().untyped);
Chris@147:     }
Chris@1647: 
Chris@1643:     QString getTypeName() const override { return tr("Note"); }
Chris@1647:     Subtype getSubtype() const { return m_subtype; }
Chris@1692:     bool isSparse() const override { return true; }
Chris@1659:     bool isOK() const override { return true; }
Chris@1659:     
Chris@1659:     sv_frame_t getStartFrame() const override {
Chris@1798:         QMutexLocker locker(&m_mutex);
Chris@1659:         return m_events.getStartFrame();
Chris@1659:     }
Chris@1725:     sv_frame_t getTrueEndFrame() const override {
Chris@1798:         QMutexLocker locker(&m_mutex);
Chris@1659:         if (m_events.isEmpty()) return 0;
Chris@1659:         sv_frame_t e = m_events.getEndFrame();
Chris@1659:         if (e % m_resolution == 0) return e;
Chris@1659:         else return (e / m_resolution + 1) * m_resolution;
Chris@1659:     }
Chris@1643: 
Chris@1643:     sv_samplerate_t getSampleRate() const override { return m_sampleRate; }
Chris@1644:     int getResolution() const { return m_resolution; }
Chris@1643: 
Chris@1643:     bool canPlay() const override { return true; }
Chris@1643:     QString getDefaultPlayClipId() const override {
Chris@1643:         return "elecpiano";
Chris@1643:     }
Chris@1643: 
Chris@1798:     QString getScaleUnits() const {
Chris@1798:         QMutexLocker locker(&m_mutex);
Chris@1798:         return m_units;
Chris@1798:     }
Chris@1643:     void setScaleUnits(QString units) {
Chris@1798:         QMutexLocker locker(&m_mutex);
Chris@1643:         m_units = units;
Chris@1643:         UnitDatabase::getInstance()->registerUnit(units);
Chris@1643:     }
Chris@147: 
Chris@147:     float getValueQuantization() const { return m_valueQuantization; }
Chris@147:     void setValueQuantization(float q) { m_valueQuantization = q; }
Chris@147: 
Chris@1643:     float getValueMinimum() const { return m_valueMinimum; }
Chris@1643:     float getValueMaximum() const { return m_valueMaximum; }
Chris@1643:     
Chris@1671:     int getCompletion() const override { return m_completion; }
Chris@345: 
Chris@1643:     void setCompletion(int completion, bool update = true) {
Chris@391: 
Chris@1798:         {
Chris@1651:             if (m_completion == completion) return;
Chris@1651:             m_completion = completion;
Chris@1643:         }
Chris@1643: 
Chris@1651:         if (update) {
Chris@1651:             m_notifier.makeDeferredNotifications();
Chris@1643:         }
Chris@1651:         
Chris@1752:         emit completionChanged(getId());
Chris@1651: 
Chris@1651:         if (completion == 100) {
Chris@1651:             // henceforth:
Chris@1651:             m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS);
Chris@1752:             emit modelChanged(getId());
Chris@1643:         }
Chris@391:     }
Chris@1644: 
Chris@1644:     /**
Chris@1644:      * Query methods.
Chris@1644:      */
Chris@1644: 
Chris@1644:     int getEventCount() const {
Chris@1644:         return m_events.count();
Chris@1644:     }
Chris@1644:     bool isEmpty() const {
Chris@1644:         return m_events.isEmpty();
Chris@1644:     }
Chris@1644:     bool containsEvent(const Event &e) const {
Chris@1644:         return m_events.contains(e);
Chris@1644:     }
Chris@1644:     EventVector getAllEvents() const {
Chris@1644:         return m_events.getAllEvents();
Chris@1644:     }
Chris@1644:     EventVector getEventsSpanning(sv_frame_t f, sv_frame_t duration) const {
Chris@1644:         return m_events.getEventsSpanning(f, duration);
Chris@1644:     }
Chris@1656:     EventVector getEventsCovering(sv_frame_t f) const {
Chris@1656:         return m_events.getEventsCovering(f);
Chris@1656:     }
Chris@1644:     EventVector getEventsWithin(sv_frame_t f, sv_frame_t duration) const {
Chris@1644:         return m_events.getEventsWithin(f, duration);
Chris@1644:     }
Chris@1644:     EventVector getEventsStartingWithin(sv_frame_t f, sv_frame_t duration) const {
Chris@1644:         return m_events.getEventsStartingWithin(f, duration);
Chris@1644:     }
Chris@1656:     EventVector getEventsStartingAt(sv_frame_t f) const {
Chris@1656:         return m_events.getEventsStartingAt(f);
Chris@1644:     }
Chris@1657:     bool getNearestEventMatching(sv_frame_t startSearchAt,
Chris@1657:                                  std::function<bool(Event)> predicate,
Chris@1657:                                  EventSeries::Direction direction,
Chris@1657:                                  Event &found) const {
Chris@1657:         return m_events.getNearestEventMatching
Chris@1657:             (startSearchAt, predicate, direction, found);
Chris@1657:     }
Chris@1810:     int getIndexForEvent(const Event &e) {
Chris@1810:         return m_events.getIndexForEvent(e);
Chris@1810:     }
Chris@1644: 
Chris@1644:     /**
Chris@1648:      * Editing methods.
Chris@1644:      */
Chris@1648:     void add(Event e) override {
Chris@1644: 
Chris@1644:         bool allChange = false;
Chris@1644:            
Chris@1798:         m_events.add(e);
Chris@1798:         float v = e.getValue();
Chris@1798:         if (!ISNAN(v) && !ISINF(v)) {
Chris@1798:             if (!m_haveExtents || v < m_valueMinimum) {
Chris@1798:                 m_valueMinimum = v; allChange = true;
Chris@1644:             }
Chris@1798:             if (!m_haveExtents || v > m_valueMaximum) {
Chris@1798:                 m_valueMaximum = v; allChange = true;
Chris@1798:             }
Chris@1798:             m_haveExtents = true;
Chris@1644:         }
Chris@1644:         
Chris@1651:         m_notifier.update(e.getFrame(), e.getDuration() + m_resolution);
Chris@1651: 
Chris@1644:         if (allChange) {
Chris@1752:             emit modelChanged(getId());
Chris@1644:         }
Chris@1644:     }
Chris@1644:     
Chris@1648:     void remove(Event e) override {
Chris@1798:         m_events.remove(e);
Chris@1752:         emit modelChangedWithin(getId(),
Chris@1752:                                 e.getFrame(),
Chris@1644:                                 e.getFrame() + e.getDuration() + m_resolution);
Chris@147:     }
Chris@147: 
Chris@424:     /**
Chris@424:      * TabularModel methods.  
Chris@424:      */
Chris@1643: 
Chris@1643:     int getRowCount() const override {
Chris@1643:         return m_events.count();
Chris@1643:     }
Chris@424:     
Chris@1643:     int getColumnCount() const override {
Chris@424:         return 6;
Chris@424:     }
Chris@424: 
Chris@1643:     bool isColumnTimeValue(int column) const override {
Chris@1643:         // NB duration is not a "time value" -- that's for columns
Chris@1643:         // whose sort ordering is exactly that of the frame time
Chris@1643:         return (column < 2);
Chris@1643:     }
Chris@1643: 
Chris@1643:     sv_frame_t getFrameForRow(int row) const override {
Chris@1643:         if (row < 0 || row >= m_events.count()) {
Chris@1643:             return 0;
Chris@1643:         }
Chris@1643:         Event e = m_events.getEventByIndex(row);
Chris@1643:         return e.getFrame();
Chris@1643:     }
Chris@1643: 
Chris@1643:     int getRowForFrame(sv_frame_t frame) const override {
Chris@1643:         return m_events.getIndexForEvent(Event(frame));
Chris@1643:     }
Chris@1643:     
Chris@1643:     QString getHeading(int column) const override {
Chris@424:         switch (column) {
Chris@424:         case 0: return tr("Time");
Chris@424:         case 1: return tr("Frame");
Chris@424:         case 2: return tr("Pitch");
Chris@424:         case 3: return tr("Duration");
Chris@424:         case 4: return tr("Level");
Chris@424:         case 5: return tr("Label");
Chris@424:         default: return tr("Unknown");
Chris@424:         }
Chris@424:     }
Chris@424: 
Chris@1643:     QVariant getData(int row, int column, int role) const override {
Chris@1643: 
Chris@1643:         if (row < 0 || row >= m_events.count()) {
Chris@1643:             return QVariant();
Chris@425:         }
Chris@425: 
Chris@1643:         Event e = m_events.getEventByIndex(row);
Chris@424: 
Chris@424:         switch (column) {
Chris@1643:         case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role);
Chris@1643:         case 1: return int(e.getFrame());
Chris@1643:         case 2: return adaptValueForRole(e.getValue(), getScaleUnits(), role);
Chris@1643:         case 3: return int(e.getDuration());
Chris@1643:         case 4: return e.getLevel();
Chris@1643:         case 5: return e.getLabel();
Chris@424:         default: return QVariant();
Chris@424:         }
Chris@424:     }
Chris@424: 
Chris@1649:     Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override {
Chris@1649:         
Chris@1643:         if (row < 0 || row >= m_events.count()) return nullptr;
Chris@1643:         if (role != Qt::EditRole) return nullptr;
Chris@1643: 
Chris@1643:         Event e0 = m_events.getEventByIndex(row);
Chris@1643:         Event e1;
Chris@1643: 
Chris@1643:         switch (column) {
Chris@1643:         case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() *
Chris@1643:                                                    getSampleRate()))); break;
Chris@1643:         case 1: e1 = e0.withFrame(value.toInt()); break;
Chris@1643:         case 2: e1 = e0.withValue(float(value.toDouble())); break;
Chris@1643:         case 3: e1 = e0.withDuration(value.toInt()); break;
Chris@1643:         case 4: e1 = e0.withLevel(float(value.toDouble())); break;
Chris@1643:         case 5: e1 = e0.withLabel(value.toString()); break;
Chris@425:         }
Chris@425: 
Chris@1742:         auto command = new ChangeEventsCommand(getId().untyped, tr("Edit Data"));
Chris@1644:         command->remove(e0);
Chris@1644:         command->add(e1);
Chris@1644:         return command->finish();
Chris@424:     }
Chris@424: 
Chris@1580:     SortType getSortType(int column) const override
Chris@424:     {
Chris@424:         if (column == 5) return SortAlphabetical;
Chris@424:         return SortNumeric;
Chris@424:     }
Chris@424: 
Chris@1804:     bool isEditable() const override { return true; }
Chris@1804: 
Chris@1804:     Command *getInsertRowCommand(int row) override {
Chris@1804:         if (row < 0 || row >= m_events.count()) return nullptr;
Chris@1804:         auto command = new ChangeEventsCommand(getId().untyped,
Chris@1804:                                                tr("Add Note"));
Chris@1804:         Event e = m_events.getEventByIndex(row);
Chris@1804:         command->add(e);
Chris@1804:         return command->finish();
Chris@1804:     }
Chris@1804: 
Chris@1804:     Command *getRemoveRowCommand(int row) override {
Chris@1804:         if (row < 0 || row >= m_events.count()) return nullptr;
Chris@1804:         auto command = new ChangeEventsCommand(getId().untyped,
Chris@1804:                                                tr("Delete Note"));
Chris@1804:         Event e = m_events.getEventByIndex(row);
Chris@1804:         command->remove(e);
Chris@1804:         return command->finish();
Chris@1804:     }
Chris@1804: 
Chris@852:     /**
Chris@852:      * NoteExportable methods.
Chris@852:      */
Chris@852: 
Chris@1580:     NoteList getNotes() const override {
Chris@1643:         return getNotesStartingWithin(getStartFrame(),
Chris@1643:                                       getEndFrame() - getStartFrame());
Chris@852:     }
Chris@852: 
Chris@1643:     NoteList getNotesActiveAt(sv_frame_t frame) const override {
Chris@1643: 
Chris@852:         NoteList notes;
Chris@1643:         EventVector ee = m_events.getEventsCovering(frame);
Chris@1643:         for (const auto &e: ee) {
Chris@1643:             notes.push_back(e.toNoteData(getSampleRate(),
Chris@1643:                                          getScaleUnits() != "Hz"));
Chris@1643:         }
Chris@1643:         return notes;
Chris@1643:     }
Chris@1643:     
Chris@1643:     NoteList getNotesStartingWithin(sv_frame_t startFrame,
Chris@1643:                                     sv_frame_t duration) const override {
Chris@852: 
Chris@1643:         NoteList notes;
Chris@1643:         EventVector ee = m_events.getEventsStartingWithin(startFrame, duration);
Chris@1643:         for (const auto &e: ee) {
Chris@1643:             notes.push_back(e.toNoteData(getSampleRate(),
Chris@1643:                                          getScaleUnits() != "Hz"));
Chris@852:         }
Chris@852:         return notes;
Chris@852:     }
Chris@852: 
Chris@1644:     /**
Chris@1644:      * XmlExportable methods.
Chris@1644:      */
Chris@1644:     
Chris@1644:     void toXml(QTextStream &out,
Chris@1644:                QString indent = "",
Chris@1644:                QString extraAttributes = "") const override {
Chris@1644: 
Chris@1644:         //!!! what is valueQuantization used for?
Chris@1644:         
Chris@1644:         Model::toXml
Chris@1644:             (out,
Chris@1644:              indent,
Chris@1644:              QString("type=\"sparse\" dimensions=\"3\" resolution=\"%1\" "
Chris@1647:                      "notifyOnAdd=\"%2\" dataset=\"%3\" subtype=\"%4\" "
Chris@1647:                      "valueQuantization=\"%5\" minimum=\"%6\" maximum=\"%7\" "
Chris@1647:                      "units=\"%8\" %9")
Chris@1644:              .arg(m_resolution)
Chris@1651:              .arg("true") // always true after model reaches 100% -
Chris@1651:                           // subsequent events are always notified
Chris@1677:              .arg(m_events.getExportId())
Chris@1647:              .arg(m_subtype == FLEXI_NOTE ? "flexinote" : "note")
Chris@1644:              .arg(m_valueQuantization)
Chris@1644:              .arg(m_valueMinimum)
Chris@1644:              .arg(m_valueMaximum)
Chris@1651:              .arg(encodeEntities(m_units))
Chris@1644:              .arg(extraAttributes));
Chris@1647:         
Chris@1644:         m_events.toXml(out, indent, QString("dimensions=\"3\""));
Chris@1644:     }
Chris@1644: 
Chris@1833:     QVector<QString>
Chris@1833:     getStringExportHeaders(DataExportOptions options) const override {
Chris@1833:         return m_events.getStringExportHeaders(options, {});
Chris@1815:     }
Chris@1815:     
Chris@1833:     QVector<QVector<QString>>
Chris@1833:     toStringExportRows(DataExportOptions options,
Chris@1833:                        sv_frame_t startFrame,
Chris@1833:                        sv_frame_t duration) const override {
Chris@1833:         return m_events.toStringExportRows
Chris@1833:             (options,
Chris@1679:              startFrame,
Chris@1679:              duration,
Chris@1679:              m_sampleRate,
Chris@1679:              m_resolution,
Chris@1679:              Event().withValue(0.f).withDuration(0.f).withLevel(0.f));
Chris@1679:     }
Chris@1679: 
Chris@147: protected:
Chris@1647:     Subtype m_subtype;
Chris@1643:     sv_samplerate_t m_sampleRate;
Chris@1643:     int m_resolution;
Chris@1643: 
Chris@1798:     std::atomic<float> m_valueMinimum;
Chris@1798:     std::atomic<float> m_valueMaximum;
Chris@1798:     std::atomic<bool> m_haveExtents;
Chris@147:     float m_valueQuantization;
Chris@1643:     QString m_units;
Chris@1651:     DeferredNotifier m_notifier;
Chris@1798:     std::atomic<int> m_completion;
Chris@1643: 
Chris@1643:     EventSeries m_events;
Chris@147: };
Chris@147: 
Chris@147: #endif