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: This file copyright 2006 Chris Cannam. 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@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 Chris@1643: #include Chris@441: Chris@1643: class NoteModel : public Model, Chris@1643: public TabularModel, Chris@1643: public NoteExportable Chris@147: { Chris@423: Q_OBJECT Chris@423: Chris@147: public: Chris@1644: NoteModel(sv_samplerate_t sampleRate, Chris@1644: int resolution, Chris@1429: bool notifyOnAdd = true) : 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@1643: m_extendTo(0), Chris@1643: m_notifyOnAdd(notifyOnAdd), Chris@1643: m_sinceLastNotifyMin(-1), Chris@1643: m_sinceLastNotifyMax(-1), Chris@1643: m_completion(0) { Chris@1429: PlayParameterRepository::getInstance()->addPlayable(this); Chris@256: } Chris@256: Chris@1040: NoteModel(sv_samplerate_t sampleRate, int resolution, Chris@1429: float valueMinimum, float valueMaximum, Chris@1429: bool notifyOnAdd = true) : 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@1643: m_extendTo(0), Chris@1643: m_notifyOnAdd(notifyOnAdd), Chris@1643: m_sinceLastNotifyMin(-1), Chris@1643: m_sinceLastNotifyMax(-1), Chris@1643: m_completion(0) { Chris@1429: PlayParameterRepository::getInstance()->addPlayable(this); Chris@391: } Chris@391: Chris@1643: virtual ~NoteModel() { Chris@391: PlayParameterRepository::getInstance()->removePlayable(this); Chris@147: } Chris@1643: Chris@1643: QString getTypeName() const override { return tr("Note"); } Chris@1643: Chris@1643: bool isOK() const override { return true; } Chris@1643: sv_frame_t getStartFrame() const override { return m_events.getStartFrame(); } Chris@1643: sv_frame_t getEndFrame() const override { return m_events.getEndFrame(); } 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@1643: QString getScaleUnits() const { return m_units; } Chris@1643: void setScaleUnits(QString units) { 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@1643: int getCompletion() const { return m_completion; } Chris@345: Chris@1643: void setCompletion(int completion, bool update = true) { Chris@391: Chris@1643: bool emitCompletionChanged = true; Chris@1643: bool emitGeneralModelChanged = false; Chris@1643: bool emitRegionChanged = false; Chris@1643: Chris@1643: { Chris@1643: QMutexLocker locker(&m_mutex); Chris@1643: Chris@1643: if (m_completion != completion) { Chris@1643: m_completion = completion; Chris@1643: Chris@1643: if (completion == 100) { Chris@1643: Chris@1643: if (m_notifyOnAdd) { Chris@1643: emitCompletionChanged = false; Chris@1643: } Chris@1643: Chris@1643: m_notifyOnAdd = true; // henceforth Chris@1643: emitGeneralModelChanged = true; Chris@1643: Chris@1643: } else if (!m_notifyOnAdd) { Chris@1643: Chris@1643: if (update && Chris@1643: m_sinceLastNotifyMin >= 0 && Chris@1643: m_sinceLastNotifyMax >= 0) { Chris@1643: emitRegionChanged = true; Chris@1643: } Chris@1643: } Chris@1643: } Chris@1643: } Chris@1643: Chris@1643: if (emitCompletionChanged) { Chris@1643: emit completionChanged(); Chris@1643: } Chris@1643: if (emitGeneralModelChanged) { Chris@1643: emit modelChanged(); Chris@1643: } Chris@1643: if (emitRegionChanged) { Chris@1643: emit modelChangedWithin(m_sinceLastNotifyMin, m_sinceLastNotifyMax); Chris@1643: m_sinceLastNotifyMin = m_sinceLastNotifyMax = -1; 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@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@1644: EventVector getEventsCovering(sv_frame_t f) const { Chris@1644: return m_events.getEventsCovering(f); Chris@1644: } Chris@1644: Chris@1644: /** Chris@1644: * Editing commands and methods. Chris@1644: */ Chris@1643: Chris@1644: class EditCommand : public Command Chris@1644: { Chris@1644: public: Chris@1644: //!!! borrowed ptr Chris@1644: EditCommand(NoteModel *model, QString name) : Chris@1644: m_model(model), m_name(name) { } Chris@391: Chris@1644: QString getName() const override { Chris@1644: return m_name; Chris@1644: } Chris@1644: Chris@1644: void add(Event e) { Chris@1644: m_add.insert(e); Chris@1644: } Chris@1644: Chris@1644: void remove(Event e) { Chris@1644: m_remove.insert(e); Chris@1644: } Chris@1643: Chris@1644: void execute() override { Chris@1644: for (const Event &e: m_add) m_model->add(e); Chris@1644: for (const Event &e: m_remove) m_model->remove(e); Chris@1644: } Chris@1643: Chris@1644: void unexecute() override { Chris@1644: for (const Event &e: m_remove) m_model->add(e); Chris@1644: for (const Event &e: m_add) m_model->remove(e); Chris@1644: } Chris@1644: Chris@1644: EditCommand *finish() { Chris@1644: if (m_add.empty() && m_remove.empty()) { Chris@1644: delete this; Chris@1644: return nullptr; Chris@1644: } else { Chris@1644: return this; Chris@1644: } Chris@1644: } Chris@1644: Chris@1644: private: Chris@1644: NoteModel *m_model; Chris@1644: std::set m_add; Chris@1644: std::set m_remove; Chris@1644: QString m_name; Chris@1644: }; Chris@1644: Chris@1644: void add(Event e) { Chris@1644: Chris@1644: bool allChange = false; Chris@1644: Chris@1644: { Chris@1644: QMutexLocker locker(&m_mutex); Chris@1644: m_events.add(e); Chris@1644: //!!!??? if (point.getLabel() != "") m_hasTextLabels = true; Chris@1644: Chris@1644: float v = e.getValue(); Chris@1644: if (!ISNAN(v) && !ISINF(v)) { Chris@1644: if (!m_haveExtents || v < m_valueMinimum) { Chris@1644: m_valueMinimum = v; allChange = true; Chris@1644: } Chris@1644: if (!m_haveExtents || v > m_valueMaximum) { Chris@1644: m_valueMaximum = v; allChange = true; Chris@1644: } Chris@1644: m_haveExtents = true; Chris@1644: } Chris@1644: Chris@1644: sv_frame_t f = e.getFrame(); Chris@1644: Chris@1644: if (!m_notifyOnAdd) { Chris@1644: if (m_sinceLastNotifyMin == -1 || f < m_sinceLastNotifyMin) { Chris@1644: m_sinceLastNotifyMin = f; Chris@1644: } Chris@1644: if (m_sinceLastNotifyMax == -1 || f > m_sinceLastNotifyMax) { Chris@1644: m_sinceLastNotifyMax = f; Chris@1644: } Chris@1644: } Chris@1644: } Chris@1644: Chris@1644: if (m_notifyOnAdd) { Chris@1644: emit modelChangedWithin(e.getFrame(), Chris@1644: e.getFrame() + e.getDuration() + m_resolution); Chris@1644: } Chris@1644: if (allChange) { Chris@1644: emit modelChanged(); Chris@1644: } Chris@1644: } Chris@1644: Chris@1644: void remove(Event e) { Chris@1644: { Chris@1644: QMutexLocker locker(&m_mutex); Chris@1644: m_events.remove(e); Chris@1644: } Chris@1644: emit modelChangedWithin(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@1580: Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override Chris@424: { 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@424: EditCommand *command = new EditCommand(this, 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@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@1644: "notifyOnAdd=\"%2\" dataset=\"%3\" subtype=\"note\" " Chris@1644: "valueQuantization=\"%4\" minimum=\"%5\" maximum=\"%6\" " Chris@1644: "units=\"%7\" %8") Chris@1644: .arg(m_resolution) Chris@1644: .arg(m_notifyOnAdd ? "true" : "false") Chris@1644: .arg(getObjectExportId(&m_events)) Chris@1644: .arg(m_valueQuantization) Chris@1644: .arg(m_valueMinimum) Chris@1644: .arg(m_valueMaximum) Chris@1644: .arg(m_units) Chris@1644: .arg(extraAttributes)); Chris@1644: Chris@1644: m_events.toXml(out, indent, QString("dimensions=\"3\"")); Chris@1644: } Chris@1644: Chris@147: protected: Chris@1643: sv_samplerate_t m_sampleRate; Chris@1643: int m_resolution; Chris@1643: Chris@1643: float m_valueMinimum; Chris@1643: float m_valueMaximum; Chris@1643: bool m_haveExtents; Chris@147: float m_valueQuantization; Chris@1643: QString m_units; Chris@1643: Chris@1643: sv_frame_t m_extendTo; Chris@1643: Chris@1643: bool m_notifyOnAdd; Chris@1643: sv_frame_t m_sinceLastNotifyMin; Chris@1643: sv_frame_t m_sinceLastNotifyMax; Chris@1643: Chris@1643: EventSeries m_events; Chris@1643: Chris@1643: int m_completion; Chris@1643: Chris@1643: mutable QMutex m_mutex; Chris@1643: Chris@1643: //!!! do we have general docs for ownership and synchronisation of models? Chris@1643: // this might be a good opportunity to stop using bare pointers to them Chris@147: }; Chris@147: Chris@147: #endif