Chris@1785: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@1785: Chris@1785: /* Chris@1785: Sonic Visualiser Chris@1785: An audio file viewer and annotation editor. Chris@1785: Centre for Digital Music, Queen Mary, University of London. Chris@1785: Chris@1785: This program is free software; you can redistribute it and/or Chris@1785: modify it under the terms of the GNU General Public License as Chris@1785: published by the Free Software Foundation; either version 2 of the Chris@1785: License, or (at your option) any later version. See the file Chris@1785: COPYING included with this distribution for more information. Chris@1785: */ Chris@1785: Chris@1785: #ifndef SV_TIME_FREQUENCY_BOX_MODEL_H Chris@1785: #define SV_TIME_FREQUENCY_BOX_MODEL_H Chris@1785: Chris@1785: #include "EventCommands.h" Chris@1785: #include "TabularModel.h" Chris@1785: #include "Model.h" Chris@1785: #include "DeferredNotifier.h" Chris@1785: Chris@1785: #include "base/RealTime.h" Chris@1785: #include "base/EventSeries.h" Chris@1785: #include "base/UnitDatabase.h" Chris@1785: Chris@1785: #include "system/System.h" Chris@1785: Chris@1785: #include Chris@1785: Chris@1785: /** Chris@1785: * TimeFrequencyBoxModel -- a model for annotations having start time, Chris@1785: * duration, and a frequency range. We use Events as usual for these, Chris@1785: * but treat the "value" as the lower frequency and "level" as the Chris@1785: * difference between lower and upper frequencies, which is expected Chris@1785: * to be non-negative (if it is negative, abs(level) will be used). Chris@1785: */ Chris@1785: class TimeFrequencyBoxModel : public Model, Chris@1785: public TabularModel, Chris@1785: public EventEditable Chris@1785: { Chris@1785: Q_OBJECT Chris@1785: Chris@1785: public: Chris@1785: TimeFrequencyBoxModel(sv_samplerate_t sampleRate, Chris@1785: int resolution, Chris@1785: bool notifyOnAdd = true) : Chris@1785: m_sampleRate(sampleRate), Chris@1785: m_resolution(resolution), Chris@1785: m_frequencyMinimum(0.f), Chris@1785: m_frequencyMaximum(0.f), Chris@1785: m_haveExtents(false), Chris@1785: m_notifier(this, Chris@1785: getId(), Chris@1785: notifyOnAdd ? Chris@1785: DeferredNotifier::NOTIFY_ALWAYS : Chris@1785: DeferredNotifier::NOTIFY_DEFERRED), Chris@1785: m_completion(100) { Chris@1785: } Chris@1785: Chris@1785: TimeFrequencyBoxModel(sv_samplerate_t sampleRate, int resolution, Chris@1785: float frequencyMinimum, float frequencyMaximum, Chris@1785: bool notifyOnAdd = true) : Chris@1785: m_sampleRate(sampleRate), Chris@1785: m_resolution(resolution), Chris@1785: m_frequencyMinimum(frequencyMinimum), Chris@1785: m_frequencyMaximum(frequencyMaximum), Chris@1785: m_haveExtents(true), Chris@1785: m_notifier(this, Chris@1785: getId(), Chris@1785: notifyOnAdd ? Chris@1785: DeferredNotifier::NOTIFY_ALWAYS : Chris@1785: DeferredNotifier::NOTIFY_DEFERRED), Chris@1785: m_completion(100) { Chris@1785: } Chris@1785: Chris@1785: virtual ~TimeFrequencyBoxModel() { Chris@1785: } Chris@1785: Chris@1785: QString getTypeName() const override { return tr("Time-Frequency Box"); } Chris@1785: bool isSparse() const override { return true; } Chris@1785: bool isOK() const override { return true; } Chris@1785: Chris@1785: sv_frame_t getStartFrame() const override { Chris@1785: return m_events.getStartFrame(); Chris@1785: } Chris@1785: sv_frame_t getTrueEndFrame() const override { Chris@1785: if (m_events.isEmpty()) return 0; Chris@1785: sv_frame_t e = m_events.getEndFrame(); Chris@1785: if (e % m_resolution == 0) return e; Chris@1785: else return (e / m_resolution + 1) * m_resolution; Chris@1785: } Chris@1785: Chris@1785: sv_samplerate_t getSampleRate() const override { return m_sampleRate; } Chris@1785: int getResolution() const { return m_resolution; } Chris@1785: Chris@1785: QString getScaleUnits() const { return "Hz"; } Chris@1785: Chris@1785: float getFrequencyMinimum() const { return m_frequencyMinimum; } Chris@1785: float getFrequencyMaximum() const { return m_frequencyMaximum; } Chris@1785: Chris@1785: int getCompletion() const override { return m_completion; } Chris@1785: Chris@1785: void setCompletion(int completion, bool update = true) { Chris@1785: Chris@1785: { QMutexLocker locker(&m_mutex); Chris@1785: if (m_completion == completion) return; Chris@1785: m_completion = completion; Chris@1785: } Chris@1785: Chris@1785: if (update) { Chris@1785: m_notifier.makeDeferredNotifications(); Chris@1785: } Chris@1785: Chris@1785: emit completionChanged(getId()); Chris@1785: Chris@1785: if (completion == 100) { Chris@1785: // henceforth: Chris@1785: m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS); Chris@1785: emit modelChanged(getId()); Chris@1785: } Chris@1785: } Chris@1785: Chris@1785: /** Chris@1785: * Query methods. Chris@1785: */ Chris@1785: int getEventCount() const { Chris@1785: return m_events.count(); Chris@1785: } Chris@1785: bool isEmpty() const { Chris@1785: return m_events.isEmpty(); Chris@1785: } Chris@1785: bool containsEvent(const Event &e) const { Chris@1785: return m_events.contains(e); Chris@1785: } Chris@1785: EventVector getAllEvents() const { Chris@1785: return m_events.getAllEvents(); Chris@1785: } Chris@1785: EventVector getEventsSpanning(sv_frame_t f, sv_frame_t duration) const { Chris@1785: return m_events.getEventsSpanning(f, duration); Chris@1785: } Chris@1785: EventVector getEventsCovering(sv_frame_t f) const { Chris@1785: return m_events.getEventsCovering(f); Chris@1785: } Chris@1785: EventVector getEventsWithin(sv_frame_t f, sv_frame_t duration) const { Chris@1785: return m_events.getEventsWithin(f, duration); Chris@1785: } Chris@1785: EventVector getEventsStartingWithin(sv_frame_t f, sv_frame_t duration) const { Chris@1785: return m_events.getEventsStartingWithin(f, duration); Chris@1785: } Chris@1785: EventVector getEventsStartingAt(sv_frame_t f) const { Chris@1785: return m_events.getEventsStartingAt(f); Chris@1785: } Chris@1785: bool getNearestEventMatching(sv_frame_t startSearchAt, Chris@1785: std::function predicate, Chris@1785: EventSeries::Direction direction, Chris@1785: Event &found) const { Chris@1785: return m_events.getNearestEventMatching Chris@1785: (startSearchAt, predicate, direction, found); Chris@1785: } Chris@1785: Chris@1785: /** Chris@1785: * Editing methods. Chris@1785: */ Chris@1785: void add(Event e) override { Chris@1785: Chris@1785: bool allChange = false; Chris@1785: Chris@1785: { Chris@1785: QMutexLocker locker(&m_mutex); Chris@1785: m_events.add(e); Chris@1785: Chris@1785: float f0 = e.getValue(); Chris@1785: float f1 = f0 + fabsf(e.getLevel()); Chris@1785: Chris@1785: if (!m_haveExtents || f0 < m_frequencyMinimum) { Chris@1785: m_frequencyMinimum = f0; allChange = true; Chris@1785: } Chris@1785: if (!m_haveExtents || f1 > m_frequencyMaximum) { Chris@1785: m_frequencyMaximum = f1; allChange = true; Chris@1785: } Chris@1785: m_haveExtents = true; Chris@1785: } Chris@1785: Chris@1785: m_notifier.update(e.getFrame(), e.getDuration() + m_resolution); Chris@1785: Chris@1785: if (allChange) { Chris@1785: emit modelChanged(getId()); Chris@1785: } Chris@1785: } Chris@1785: Chris@1785: void remove(Event e) override { Chris@1785: { Chris@1785: QMutexLocker locker(&m_mutex); Chris@1785: m_events.remove(e); Chris@1785: } Chris@1785: emit modelChangedWithin(getId(), Chris@1785: e.getFrame(), Chris@1785: e.getFrame() + e.getDuration() + m_resolution); Chris@1785: } Chris@1785: Chris@1785: /** Chris@1785: * TabularModel methods. Chris@1785: */ Chris@1785: Chris@1785: int getRowCount() const override { Chris@1785: return m_events.count(); Chris@1785: } Chris@1785: Chris@1785: int getColumnCount() const override { Chris@1785: return 5; Chris@1785: } Chris@1785: Chris@1785: bool isColumnTimeValue(int column) const override { Chris@1785: // NB duration is not a "time value" -- that's for columns Chris@1785: // whose sort ordering is exactly that of the frame time Chris@1785: return (column < 2); Chris@1785: } Chris@1785: Chris@1785: sv_frame_t getFrameForRow(int row) const override { Chris@1785: if (row < 0 || row >= m_events.count()) { Chris@1785: return 0; Chris@1785: } Chris@1785: Event e = m_events.getEventByIndex(row); Chris@1785: return e.getFrame(); Chris@1785: } Chris@1785: Chris@1785: int getRowForFrame(sv_frame_t frame) const override { Chris@1785: return m_events.getIndexForEvent(Event(frame)); Chris@1785: } Chris@1785: Chris@1785: QString getHeading(int column) const override { Chris@1785: switch (column) { Chris@1785: case 0: return tr("Time"); Chris@1785: case 1: return tr("Frame"); Chris@1785: case 2: return tr("Duration"); Chris@1785: case 3: return tr("Minimum Frequency"); Chris@1785: case 4: return tr("Maximum Frequency"); Chris@1785: case 5: return tr("Label"); Chris@1785: default: return tr("Unknown"); Chris@1785: } Chris@1785: } Chris@1785: Chris@1785: SortType getSortType(int column) const override { Chris@1785: if (column == 5) return SortAlphabetical; Chris@1785: return SortNumeric; Chris@1785: } Chris@1785: Chris@1785: QVariant getData(int row, int column, int role) const override { Chris@1785: Chris@1785: if (row < 0 || row >= m_events.count()) { Chris@1785: return QVariant(); Chris@1785: } Chris@1785: Chris@1785: Event e = m_events.getEventByIndex(row); Chris@1785: Chris@1785: switch (column) { Chris@1785: case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role); Chris@1785: case 1: return int(e.getFrame()); Chris@1785: case 2: return int(e.getDuration()); Chris@1785: case 3: return adaptValueForRole(e.getValue(), getScaleUnits(), role); Chris@1785: case 4: return adaptValueForRole(e.getValue() + fabsf(e.getLevel()), Chris@1785: getScaleUnits(), role); Chris@1785: case 5: return e.getLabel(); Chris@1785: default: return QVariant(); Chris@1785: } Chris@1785: } Chris@1785: Chris@1785: Command *getSetDataCommand(int row, int column, const QVariant &value, Chris@1785: int role) override { Chris@1785: Chris@1785: if (row < 0 || row >= m_events.count()) return nullptr; Chris@1785: if (role != Qt::EditRole) return nullptr; Chris@1785: Chris@1785: Event e0 = m_events.getEventByIndex(row); Chris@1785: Event e1; Chris@1785: Chris@1785: switch (column) { Chris@1785: case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() * Chris@1785: getSampleRate()))); break; Chris@1785: case 1: e1 = e0.withFrame(value.toInt()); break; Chris@1785: case 2: e1 = e0.withDuration(value.toInt()); break; Chris@1785: case 3: e1 = e0.withValue(float(value.toDouble())); break; Chris@1785: case 4: e1 = e0.withLevel(fabsf(float(value.toDouble()) - Chris@1785: e0.getValue())); break; Chris@1785: case 5: e1 = e0.withLabel(value.toString()); break; Chris@1785: } Chris@1785: Chris@1785: auto command = new ChangeEventsCommand(getId().untyped, tr("Edit Data")); Chris@1785: command->remove(e0); Chris@1785: command->add(e1); Chris@1785: return command->finish(); Chris@1785: } Chris@1785: Chris@1785: Chris@1785: /** Chris@1785: * XmlExportable methods. Chris@1785: */ Chris@1785: void toXml(QTextStream &out, Chris@1785: QString indent = "", Chris@1785: QString extraAttributes = "") const override { Chris@1785: Chris@1785: Model::toXml Chris@1785: (out, Chris@1785: indent, Chris@1785: QString("type=\"sparse\" dimensions=\"4\" resolution=\"%1\" " Chris@1785: "notifyOnAdd=\"%2\" dataset=\"%3\" subtype=\"%4\" " Chris@1785: "minimum=\"%5\" maximum=\"%6\" units=\"%7\" %8") Chris@1785: .arg(m_resolution) Chris@1785: .arg("true") // always true after model reaches 100% - Chris@1785: // subsequent events are always notified Chris@1785: .arg(m_events.getExportId()) Chris@1785: .arg("timefrequency") Chris@1785: .arg(m_frequencyMinimum) Chris@1785: .arg(m_frequencyMaximum) Chris@1785: .arg(encodeEntities(m_units)) Chris@1785: .arg(extraAttributes)); Chris@1785: Chris@1785: m_events.toXml(out, indent, QString("dimensions=\"3\"")); Chris@1785: } Chris@1785: Chris@1785: QString toDelimitedDataString(QString delimiter, Chris@1785: DataExportOptions options, Chris@1785: sv_frame_t startFrame, Chris@1785: sv_frame_t duration) const override { Chris@1785: return m_events.toDelimitedDataString Chris@1785: (delimiter, Chris@1785: options, Chris@1785: startFrame, Chris@1785: duration, Chris@1785: m_sampleRate, Chris@1785: m_resolution, Chris@1785: Event().withValue(0.f).withDuration(m_resolution)); Chris@1785: } Chris@1785: Chris@1785: protected: Chris@1785: sv_samplerate_t m_sampleRate; Chris@1785: int m_resolution; Chris@1785: Chris@1785: float m_frequencyMinimum; Chris@1785: float m_frequencyMaximum; Chris@1785: bool m_haveExtents; Chris@1785: QString m_units; Chris@1785: DeferredNotifier m_notifier; Chris@1785: int m_completion; Chris@1785: Chris@1785: EventSeries m_events; Chris@1785: Chris@1785: mutable QMutex m_mutex; Chris@1785: }; Chris@1785: Chris@1785: #endif