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@1581: #ifndef SV_SPARSE_ONE_DIMENSIONAL_MODEL_H Chris@1581: #define SV_SPARSE_ONE_DIMENSIONAL_MODEL_H Chris@147: Chris@1658: #include "EventCommands.h" Chris@1658: #include "TabularModel.h" Chris@1658: #include "Model.h" Chris@1658: #include "DeferredNotifier.h" Chris@1658: Chris@1615: #include "base/NoteData.h" Chris@1658: #include "base/EventSeries.h" Chris@1643: #include "base/NoteExportable.h" Chris@150: #include "base/PlayParameterRepository.h" Chris@147: #include "base/RealTime.h" Chris@147: Chris@1658: #include "system/System.h" Chris@1658: Chris@387: #include Chris@387: Chris@1658: /** Chris@1658: * A model representing a series of time instants with optional labels Chris@1658: * but without values. Chris@1658: */ Chris@1658: class SparseOneDimensionalModel : public Model, Chris@1658: public TabularModel, Chris@1658: public EventEditable, Chris@852: public NoteExportable Chris@147: { Chris@423: Q_OBJECT Chris@423: Chris@147: public: Chris@1658: SparseOneDimensionalModel(sv_samplerate_t sampleRate, Chris@1658: int resolution, Chris@1429: bool notifyOnAdd = true) : Chris@1658: m_sampleRate(sampleRate), Chris@1658: m_resolution(resolution), Chris@1660: m_haveTextLabels(false), Chris@1658: m_notifier(this, Chris@1752: getId(), Chris@1658: notifyOnAdd ? Chris@1658: DeferredNotifier::NOTIFY_ALWAYS : Chris@1658: DeferredNotifier::NOTIFY_DEFERRED), Chris@1658: m_completion(100) { Chris@1751: PlayParameterRepository::getInstance()->addPlayable Chris@1751: (getId().untyped, this); Chris@391: } Chris@391: Chris@1658: virtual ~SparseOneDimensionalModel() { Chris@1751: PlayParameterRepository::getInstance()->removePlayable Chris@1751: (getId().untyped); Chris@391: } Chris@391: Chris@1580: QString getTypeName() const override { return tr("Sparse 1-D"); } Chris@1692: bool isSparse() const override { return true; } Chris@1659: bool isOK() const override { return true; } Chris@420: Chris@1659: sv_frame_t getStartFrame() const override { Chris@1659: return m_events.getStartFrame(); Chris@1659: } Chris@1725: sv_frame_t getTrueEndFrame() const override { Chris@1659: if (m_events.isEmpty()) return 0; Chris@1659: sv_frame_t e = m_events.getEndFrame() + 1; Chris@1659: if (e % m_resolution == 0) return e; Chris@1659: else return (e / m_resolution + 1) * m_resolution; Chris@1659: } Chris@1659: Chris@1658: sv_samplerate_t getSampleRate() const override { return m_sampleRate; } Chris@1658: int getResolution() const { return m_resolution; } Chris@1658: Chris@1658: bool canPlay() const override { return true; } Chris@1658: QString getDefaultPlayClipId() const override { return "tap"; } Chris@1658: Chris@1660: bool hasTextLabels() const { return m_haveTextLabels; } Chris@1671: Chris@1671: int getCompletion() const override { return m_completion; } Chris@1658: Chris@1658: void setCompletion(int completion, bool update = true) { Chris@1658: Chris@1798: if (m_completion == completion) return; Chris@1798: m_completion = completion; Chris@1658: Chris@1658: if (update) { Chris@1658: m_notifier.makeDeferredNotifications(); Chris@1658: } Chris@1658: Chris@1752: emit completionChanged(getId()); Chris@1658: Chris@1658: if (completion == 100) { Chris@1658: // henceforth: Chris@1658: m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS); Chris@1752: emit modelChanged(getId()); Chris@1658: } Chris@1658: } Chris@1658: Chris@1658: /** Chris@1658: * Query methods. Chris@1658: */ Chris@1658: Chris@1658: int getEventCount() const { Chris@1658: return m_events.count(); Chris@1658: } Chris@1658: bool isEmpty() const { Chris@1658: return m_events.isEmpty(); Chris@1658: } Chris@1658: bool containsEvent(const Event &e) const { Chris@1658: return m_events.contains(e); Chris@1658: } Chris@1658: EventVector getAllEvents() const { Chris@1658: return m_events.getAllEvents(); Chris@1658: } Chris@1658: EventVector getEventsSpanning(sv_frame_t f, sv_frame_t duration) const { Chris@1658: return m_events.getEventsSpanning(f, duration); Chris@1658: } Chris@1658: EventVector getEventsCovering(sv_frame_t f) const { Chris@1658: return m_events.getEventsCovering(f); Chris@1658: } Chris@1658: EventVector getEventsWithin(sv_frame_t f, sv_frame_t duration, Chris@1658: int overspill = 0) const { Chris@1658: return m_events.getEventsWithin(f, duration, overspill); Chris@1658: } Chris@1658: EventVector getEventsStartingWithin(sv_frame_t f, sv_frame_t duration) const { Chris@1658: return m_events.getEventsStartingWithin(f, duration); Chris@1658: } Chris@1658: EventVector getEventsStartingAt(sv_frame_t f) const { Chris@1658: return m_events.getEventsStartingAt(f); Chris@1658: } Chris@1658: bool getNearestEventMatching(sv_frame_t startSearchAt, Chris@1658: std::function predicate, Chris@1658: EventSeries::Direction direction, Chris@1658: Event &found) const { Chris@1658: return m_events.getNearestEventMatching Chris@1658: (startSearchAt, predicate, direction, found); Chris@1658: } Chris@1658: Chris@1658: /** Chris@1658: * Editing methods. Chris@1658: */ Chris@1658: void add(Event e) override { Chris@1658: Chris@1798: m_events.add(e.withoutValue().withoutDuration()); Chris@1660: Chris@1798: if (e.getLabel() != "") { Chris@1798: m_haveTextLabels = true; Chris@1658: } Chris@1658: Chris@1658: m_notifier.update(e.getFrame(), m_resolution); Chris@1658: } Chris@1658: Chris@1658: void remove(Event e) override { Chris@1798: m_events.remove(e); Chris@1752: emit modelChangedWithin(getId(), Chris@1752: e.getFrame(), e.getFrame() + m_resolution); Chris@1658: } Chris@1658: Chris@420: /** Chris@420: * TabularModel methods. Chris@420: */ Chris@420: Chris@1658: int getRowCount() const override { Chris@1658: return m_events.count(); Chris@1658: } Chris@1658: Chris@1658: int getColumnCount() const override { Chris@420: return 3; Chris@420: } Chris@420: Chris@1658: bool isColumnTimeValue(int column) const override { Chris@1658: return (column < 2); Chris@1658: } Chris@1658: Chris@1658: sv_frame_t getFrameForRow(int row) const override { Chris@1658: if (row < 0 || row >= m_events.count()) { Chris@1658: return 0; Chris@1658: } Chris@1658: Event e = m_events.getEventByIndex(row); Chris@1658: return e.getFrame(); Chris@1658: } Chris@1658: Chris@1658: int getRowForFrame(sv_frame_t frame) const override { Chris@1658: return m_events.getIndexForEvent(Event(frame)); Chris@1658: } Chris@1658: Chris@1658: QString getHeading(int column) const override { Chris@420: switch (column) { Chris@420: case 0: return tr("Time"); Chris@420: case 1: return tr("Frame"); Chris@420: case 2: return tr("Label"); Chris@420: default: return tr("Unknown"); Chris@420: } Chris@420: } Chris@420: Chris@1658: SortType getSortType(int column) const override { Chris@1658: if (column == 2) return SortAlphabetical; Chris@1658: return SortNumeric; Chris@1658: } Chris@1658: Chris@1658: QVariant getData(int row, int column, int role) const override { Chris@1658: Chris@1658: if (row < 0 || row >= m_events.count()) { Chris@1658: return QVariant(); Chris@425: } Chris@425: Chris@1658: Event e = m_events.getEventByIndex(row); Chris@420: Chris@420: switch (column) { Chris@1658: case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role); Chris@1658: case 1: return int(e.getFrame()); Chris@1658: case 2: return e.getLabel(); Chris@420: default: return QVariant(); Chris@420: } Chris@420: } Chris@420: Chris@1658: Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override { Chris@1658: if (row < 0 || row >= m_events.count()) return nullptr; Chris@1658: if (role != Qt::EditRole) return nullptr; Chris@1658: Chris@1658: Event e0 = m_events.getEventByIndex(row); Chris@1658: Event e1; Chris@1658: Chris@1658: switch (column) { Chris@1658: case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() * Chris@1658: getSampleRate()))); break; Chris@1658: case 1: e1 = e0.withFrame(value.toInt()); break; Chris@1658: case 2: e1 = e0.withLabel(value.toString()); break; Chris@425: } Chris@425: Chris@1742: auto command = new ChangeEventsCommand(getId().untyped, tr("Edit Data")); Chris@1658: command->remove(e0); Chris@1658: command->add(e1); Chris@424: return command->finish(); 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 Point")); 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 Point")); 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: return getNotesStartingWithin(frame, 1); Chris@1643: } Chris@1643: Chris@1643: NoteList getNotesStartingWithin(sv_frame_t startFrame, Chris@1643: sv_frame_t duration) const override { Chris@852: Chris@852: NoteList notes; Chris@1658: EventVector ee = m_events.getEventsStartingWithin(startFrame, duration); Chris@1658: for (const auto &e: ee) { Chris@1658: notes.push_back(e.toNoteData(getSampleRate(), true)); Chris@852: } Chris@852: return notes; Chris@852: } Chris@1658: Chris@1658: /** Chris@1658: * XmlExportable methods. Chris@1658: */ Chris@1658: void toXml(QTextStream &out, Chris@1658: QString indent = "", Chris@1658: QString extraAttributes = "") const override { Chris@1658: Chris@1658: Model::toXml Chris@1658: (out, Chris@1658: indent, Chris@1658: QString("type=\"sparse\" dimensions=\"1\" resolution=\"%1\" " Chris@1658: "notifyOnAdd=\"%2\" dataset=\"%3\" %4") Chris@1658: .arg(m_resolution) Chris@1658: .arg("true") // always true after model reaches 100% - Chris@1658: // subsequent events are always notified Chris@1677: .arg(m_events.getExportId()) Chris@1658: .arg(extraAttributes)); Chris@1658: Chris@1658: m_events.toXml(out, indent, QString("dimensions=\"1\"")); Chris@1658: } Chris@1679: Chris@1833: QVector Chris@1833: getStringExportHeaders(DataExportOptions options) const override { Chris@1833: return m_events.getStringExportHeaders(options, {}); Chris@1815: } Chris@1815: Chris@1833: QVector> 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(options, Chris@1833: startFrame, Chris@1833: duration, Chris@1833: m_sampleRate, Chris@1833: m_resolution, Chris@1833: {}); Chris@1679: } Chris@1658: Chris@1658: protected: Chris@1658: sv_samplerate_t m_sampleRate; Chris@1658: int m_resolution; Chris@1658: Chris@1798: std::atomic m_haveTextLabels; Chris@1658: DeferredNotifier m_notifier; Chris@1798: std::atomic m_completion; Chris@1658: Chris@1658: EventSeries m_events; Chris@147: }; Chris@147: Chris@147: #endif Chris@147: Chris@147: