# HG changeset patch # User Chris Cannam # Date 1563369891 -3600 # Node ID 85b9b466a59fb44841852965eeaaf3f24d2ed3c4 # Parent 649ac57c5a2dee598aa00eaa29a2d58a7e404575# Parent 2e2497cba59e10fcb53fd9e0bc14620e15135266 Merge from branch by-id diff -r 649ac57c5a2d -r 85b9b466a59f base/ById.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/ById.cpp Wed Jul 17 14:24:51 2019 +0100 @@ -0,0 +1,142 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "ById.h" + +#include +#include + +int IdAlloc::getNextId() +{ + static int nextId = 0; + static QMutex mutex; + QMutexLocker locker(&mutex); + int i = nextId; + if (nextId == INT_MAX) { + nextId = INT_MIN; + } else { + ++nextId; + if (nextId == 0 || nextId == NO_ID) { + throw std::runtime_error("Internal ID limit exceeded!"); + } + } + return i; +} + +class AnyById::Impl +{ +public: + ~Impl() { + QMutexLocker locker(&m_mutex); + bool empty = true; + for (const auto &p: m_items) { + if (p.second && p.second.use_count() > 0) { + empty = false; + break; + } + } + if (!empty) { + SVCERR << "WARNING: ById map is not empty at close; some items have not been released" << endl; + SVCERR << " Unreleased items are:" << endl; + for (const auto &p: m_items) { + auto ptr = p.second; + if (ptr && ptr.use_count() > 0) { + QString message = QString("id #%1: type %2") + .arg(p.first).arg(typeid(*ptr.get()).name()); + if (auto qobj = std::dynamic_pointer_cast(ptr)) { + message += QString(", object name \"%1\"") + .arg(qobj->objectName()); + } + message += QString(", use count %1").arg(ptr.use_count()); + SVCERR << " - " << message << endl; + } + } + } + } + + int add(std::shared_ptr item) { + int id = item->getUntypedId(); + if (id == IdAlloc::NO_ID) { + throw std::logic_error("item id should never be NO_ID"); + } + SVCERR << "ById::add(#" << id << ") of type " + << typeid(*item.get()).name() << endl; + QMutexLocker locker(&m_mutex); + if (m_items.find(id) != m_items.end()) { + SVCERR << "ById::add: item with id " << id + << " is already recorded (existing item type is " + << typeid(*m_items.find(id)->second.get()).name() + << ", proposed is " + << typeid(*item.get()).name() << ")" << endl; + throw std::logic_error("item id is already recorded in add"); + } + m_items[id] = item; + return id; + } + + void release(int id) { + if (id == IdAlloc::NO_ID) { + return; + } + SVCERR << "ById::release(#" << id << ")" << endl; + QMutexLocker locker(&m_mutex); + if (m_items.find(id) == m_items.end()) { + SVCERR << "ById::release: unknown item id " << id << endl; + throw std::logic_error("unknown item id in release"); + } + m_items.erase(id); + } + + std::shared_ptr get(int id) const { + if (id == IdAlloc::NO_ID) { + return {}; // this id cannot be added: avoid locking + } + QMutexLocker locker(&m_mutex); + const auto &itr = m_items.find(id); + if (itr != m_items.end()) { + return itr->second; + } else { + return {}; + } + } + +private: + mutable QMutex m_mutex; + std::unordered_map> m_items; +}; + +int +AnyById::add(std::shared_ptr item) +{ + return impl().add(item); +} + +void +AnyById::release(int id) +{ + impl().release(id); +} + +std::shared_ptr +AnyById::get(int id) +{ + return impl().get(id); +} + +AnyById::Impl & +AnyById::impl() +{ + static Impl impl; + return impl; +} diff -r 649ac57c5a2d -r 85b9b466a59f base/ById.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/ById.h Wed Jul 17 14:24:51 2019 +0100 @@ -0,0 +1,269 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef SV_BY_ID_H +#define SV_BY_ID_H + +#include "Debug.h" + +#include +#include +#include +#include + +#include +#include + +#include "XmlExportable.h" + +/* + * ById - central pool of objects to be retrieved by persistent id. + * + * This is a pretty simple mechanism for obtaining safe "borrowed" + * references to shared objects, including across threads, based on an + * object ID. + * + * A class (call it C) inherits WithTypedId. This produces a type + * C::Id containing a numerical id. Each instance of C (or subclass + * thereof) has an internal id of type C::Id whose value is unique + * among all ids ever possessed by any instances of all classes that + * use this id mechanism (within a single run of the program). + * + * Then we have a static store of type TypedById. This holds + * a set of heap-allocated C objects (or subclass thereof) and hands + * out shared_ptr references to them when queried by id. The + * application calls add() to pass an object to the store (which takes + * ownership of it), and the application calls release() when it + * thinks it has finished with an object, to request the store to + * delete it. + * + * Note that an object's id can't (without shenanigans) be queried + * directly from that object - it is returned when the object is added + * to a ById store. So if you have an object id, you know that the + * object must have been added to a store at some point. + * + * The goal is to improve code that would previously have retained a + * bare pointer to a heap-allocated object that it did not own. For + * example, in Sonic Visualiser we have a Model hierarchy of complex + * mutable objects, and any given model may be referred to by many + * different layers, transforms (as both source and target) etc. Using + * bare pointers for those references means that we need everything to + * be notified (and act properly on the notification) if a model is + * about to be deleted. Using a Model::Id instead gives the code a + * guarantee: if the model has been deleted since you last looked at + * it, then the ById store will return a null shared_ptr from its + * get() function for that id; but if it returns a non-null + * shared_ptr, then the object being pointed to can't be deleted while + * that shared_ptr is in scope. + * + * Example: + * + * class Thing : public WithTypedId { Thing(int x) { } }; + * typedef TypedById ThingById; + * + * // application creates a new Thing + * // ... + * auto thing = std::make_shared(10); + * auto thingId = ThingById::add(thing); + * + * // application then passes thingId to something else, without + * // storing the shared_ptr anywhere - the ById store manages that + * + * // code elsewhere now has the thingId, and needs to use the Thing + * // ... + * void doSomething() { + * auto thing = ThingById::get(m_thingId); + * if (!thing) { // the Thing has been deleted, stop acting on it + * return; // (this may be an error or it may be unexceptional) + * } + * // now we have a guarantee that the thing ptr will be valid + * // until it goes out of scope when doSomething returns + * } + * + * // application wants to be rid of the Thing + * ThingById::release(thingId); + */ + +//!!! to do: review how often we are calling getAs<...> when we could +// just be using get + +struct IdAlloc { + + // The value NO_ID (-1) is never allocated + static const int NO_ID = -1; + + static int getNextId(); +}; + +template +struct TypedId { + + int untyped; + + TypedId() : untyped(IdAlloc::NO_ID) {} + + TypedId(const TypedId &) =default; + TypedId &operator=(const TypedId &) =default; + + bool operator==(const TypedId &other) const { + return untyped == other.untyped; + } + bool operator!=(const TypedId &other) const { + return untyped != other.untyped; + } + bool operator<(const TypedId &other) const { + return untyped < other.untyped; + } + bool isNone() const { + return untyped == IdAlloc::NO_ID; + } +}; + +template +std::ostream & +operator<<(std::ostream &ostr, const TypedId &id) +{ + // For diagnostic purposes only. Do not use these IDs for + // serialisation - see XmlExportable instead. + if (id.isNone()) { + return (ostr << ""); + } else { + return (ostr << "#" << id.untyped); + } +} + +class WithId +{ +public: + WithId() : + m_id(IdAlloc::getNextId()) { + } + virtual ~WithId() { + } + +protected: + friend class AnyById; + + /** + * Return an id for this object. The id is a unique number for + * this object among all objects that implement WithId within this + * single run of the application. + */ + int getUntypedId() const { + return m_id; + } + +private: + int m_id; +}; + +template +class WithTypedId : virtual public WithId +{ +public: + typedef TypedId Id; + + WithTypedId() : WithId() { } + +protected: + template + friend class TypedById; + + /** + * Return an id for this object. The id is a unique value for this + * object among all objects that implement WithTypedId within this + * single run of the application. + */ + Id getId() const { + Id id; + id.untyped = getUntypedId(); + return id; + } +}; + +class AnyById +{ +public: + static int add(std::shared_ptr); + static void release(int); + static std::shared_ptr get(int); + + template + static bool isa(int id) { + std::shared_ptr p = get(id); + return bool(std::dynamic_pointer_cast(p)); + } + + template + static std::shared_ptr getAs(int id) { + std::shared_ptr p = get(id); + return std::dynamic_pointer_cast(p); + } + +private: + class Impl; + static Impl &impl(); +}; + +template +class TypedById +{ +public: + static Id add(std::shared_ptr item) { + Id id; + id.untyped = AnyById::add(item); + return id; + } + + static void release(Id id) { + AnyById::release(id.untyped); + } + static void release(std::shared_ptr item) { + release(item->getId()); + } + + template + static bool isa(Id id) { + return AnyById::isa(id.untyped); + } + + template + static std::shared_ptr getAs(Id id) { + return AnyById::getAs(id.untyped); + } + + static std::shared_ptr get(Id id) { + return getAs(id); + } + + /** + * If the Item type is an XmlExportable, return the export ID of + * the given item ID. A call to this function will fail to compile + * if the Item is not an XmlExportable. + * + * The export ID is a simple int, and is only allocated when first + * requested, so objects that are never exported don't get one. + */ + static int getExportId(Id id) { + auto exportable = getAs(id); + if (exportable) { + return exportable->getExportId(); + } else { + return XmlExportable::NO_ID; + } + } +}; + +#endif + diff -r 649ac57c5a2d -r 85b9b466a59f base/PlayParameterRepository.cpp --- a/base/PlayParameterRepository.cpp Thu Jun 20 14:58:20 2019 +0100 +++ b/base/PlayParameterRepository.cpp Wed Jul 17 14:24:51 2019 +0100 @@ -33,19 +33,15 @@ } void -PlayParameterRepository::addPlayable(const Playable *playable) +PlayParameterRepository::addPlayable(int playableId, const Playable *playable) { -// cerr << "PlayParameterRepository:addPlayable playable = " << playable << endl; - - if (!getPlayParameters(playable)) { - + if (!getPlayParameters(playableId)) { + // Give all playables the same type of play parameters for the // moment -// cerr << "PlayParameterRepository:addPlayable: Adding play parameters for " << playable << endl; - - PlayParameters *params = new PlayParameters; - m_playParameters[playable] = params; + auto params = std::make_shared(); + m_playParameters[playableId] = params; params->setPlayClipId (playable->getDefaultPlayClipId()); @@ -53,64 +49,65 @@ params->setPlayAudible (playable->getDefaultPlayAudible()); - connect(params, SIGNAL(playParametersChanged()), + connect(params.get(), SIGNAL(playParametersChanged()), this, SLOT(playParametersChanged())); - connect(params, SIGNAL(playClipIdChanged(QString)), + connect(params.get(), SIGNAL(playClipIdChanged(QString)), this, SLOT(playClipIdChanged(QString))); - -// cerr << "Connected play parameters " << params << " for playable " -// << playable << " to this " << this << endl; } } void -PlayParameterRepository::removePlayable(const Playable *playable) +PlayParameterRepository::removePlayable(int playableId) { - if (m_playParameters.find(playable) == m_playParameters.end()) { - cerr << "WARNING: PlayParameterRepository::removePlayable: unknown playable " << playable << endl; + if (m_playParameters.find(playableId) == m_playParameters.end()) { return; } - delete m_playParameters[playable]; - m_playParameters.erase(playable); + m_playParameters.erase(playableId); } void -PlayParameterRepository::copyParameters(const Playable *from, const Playable *to) +PlayParameterRepository::copyParameters(int from, int to) { if (!getPlayParameters(from)) { cerr << "ERROR: PlayParameterRepository::copyParameters: source playable unknown" << endl; return; } if (!getPlayParameters(to)) { - cerr << "WARNING: PlayParameterRepository::copyParameters: target playable unknown, adding it now" << endl; - addPlayable(to); + cerr << "ERROR: PlayParameterRepository::copyParameters: target playable unknown" << endl; + return; } - getPlayParameters(to)->copyFrom(getPlayParameters(from)); + getPlayParameters(to)->copyFrom(getPlayParameters(from).get()); } -PlayParameters * -PlayParameterRepository::getPlayParameters(const Playable *playable) +std::shared_ptr +PlayParameterRepository::getPlayParameters(int playableId) { - if (m_playParameters.find(playable) == m_playParameters.end()) return nullptr; - return m_playParameters.find(playable)->second; + if (m_playParameters.find(playableId) == m_playParameters.end()) { + return nullptr; + } + return m_playParameters.find(playableId)->second; } void PlayParameterRepository::playParametersChanged() { PlayParameters *params = dynamic_cast(sender()); - emit playParametersChanged(params); + for (auto i: m_playParameters) { + if (i.second.get() == params) { + emit playParametersChanged(i.first); + return; + } + } } void PlayParameterRepository::playClipIdChanged(QString id) { PlayParameters *params = dynamic_cast(sender()); - for (PlayableParameterMap::iterator i = m_playParameters.begin(); - i != m_playParameters.end(); ++i) { - if (i->second == params) { - emit playClipIdChanged(i->first, id); + for (auto i: m_playParameters) { + if (i.second.get() == params) { + emit playClipIdChanged(i.first, id); return; } } @@ -119,18 +116,14 @@ void PlayParameterRepository::clear() { -// cerr << "PlayParameterRepository: PlayParameterRepository::clear" << endl; - while (!m_playParameters.empty()) { - delete m_playParameters.begin()->second; - m_playParameters.erase(m_playParameters.begin()); - } + m_playParameters.clear(); } -PlayParameterRepository::EditCommand::EditCommand(PlayParameters *params) : +PlayParameterRepository::EditCommand::EditCommand(std::shared_ptr params) : m_params(params) { - m_from.copyFrom(m_params); - m_to.copyFrom(m_params); + m_from.copyFrom(m_params.get()); + m_to.copyFrom(m_params.get()); } void diff -r 649ac57c5a2d -r 85b9b466a59f base/PlayParameterRepository.h --- a/base/PlayParameterRepository.h Thu Jun 20 14:58:20 2019 +0100 +++ b/base/PlayParameterRepository.h Wed Jul 17 14:24:51 2019 +0100 @@ -22,6 +22,7 @@ class Playable; #include +#include #include #include @@ -35,18 +36,34 @@ virtual ~PlayParameterRepository(); - void addPlayable(const Playable *playable); - void removePlayable(const Playable *playable); - void copyParameters(const Playable *from, const Playable *to); + /** + * Register a playable. The id can be anything you like, so long + * as it is unique among playables. + */ + void addPlayable(int id, const Playable *); - PlayParameters *getPlayParameters(const Playable *playable); + /** + * Unregister a playable. This must happen before a playable is + * deleted. + */ + void removePlayable(int id); + + /** + * Copy the play parameters from one playable to another. + */ + void copyParameters(int fromId, int toId); + + /** + * Retrieve the play parameters for a playable. + */ + std::shared_ptr getPlayParameters(int id); void clear(); class EditCommand : public Command { public: - EditCommand(PlayParameters *params); + EditCommand(std::shared_ptr params); void setPlayMuted(bool); void setPlayAudible(bool); void setPlayPan(float); @@ -57,21 +74,21 @@ QString getName() const override; protected: - PlayParameters *m_params; + std::shared_ptr m_params; PlayParameters m_from; PlayParameters m_to; }; signals: - void playParametersChanged(PlayParameters *); - void playClipIdChanged(const Playable *, QString); + void playParametersChanged(int playableId); + void playClipIdChanged(int playableId, QString); protected slots: void playParametersChanged(); void playClipIdChanged(QString); protected: - typedef std::map PlayableParameterMap; + typedef std::map> PlayableParameterMap; PlayableParameterMap m_playParameters; static PlayParameterRepository *m_instance; diff -r 649ac57c5a2d -r 85b9b466a59f base/PlayParameters.h --- a/base/PlayParameters.h Thu Jun 20 14:58:20 2019 +0100 +++ b/base/PlayParameters.h Wed Jul 17 14:24:51 2019 +0100 @@ -37,8 +37,8 @@ virtual void copyFrom(const PlayParameters *); void toXml(QTextStream &stream, - QString indent = "", - QString extraAttributes = "") const override; + QString indent = "", + QString extraAttributes = "") const override; public slots: virtual void setPlayMuted(bool muted); diff -r 649ac57c5a2d -r 85b9b466a59f base/PropertyContainer.h --- a/base/PropertyContainer.h Thu Jun 20 14:58:20 2019 +0100 +++ b/base/PropertyContainer.h Wed Jul 17 14:24:51 2019 +0100 @@ -21,6 +21,7 @@ #include #include #include +#include class PlayParameters; class RangeMapper; @@ -111,7 +112,13 @@ virtual QString getPropertyContainerName() const = 0; virtual QString getPropertyContainerIconName() const = 0; - virtual PlayParameters *getPlayParameters() { return 0; } + /** + * Return the play parameters for this layer, if any. The return + * value is a shared_ptr that, if not null, can be passed to + * e.g. PlayParameterRepository::EditCommand to change the + * parameters. + */ + virtual std::shared_ptr getPlayParameters() { return {}; } signals: void propertyChanged(PropertyContainer::PropertyName); diff -r 649ac57c5a2d -r 85b9b466a59f base/TextMatcher.h --- a/base/TextMatcher.h Thu Jun 20 14:58:20 2019 +0100 +++ b/base/TextMatcher.h Wed Jul 17 14:24:51 2019 +0100 @@ -38,8 +38,9 @@ FragmentMap fragments; Match() : score(0) { } - Match(const Match &m) : - key(m.key), score(m.score), fragments(m.fragments) { } + + Match(const Match &m) =default; + Match &operator=(const Match &m) =default; bool operator<(const Match &m) const; // sort by score first }; diff -r 649ac57c5a2d -r 85b9b466a59f base/XmlExportable.h --- a/base/XmlExportable.h Thu Jun 20 14:58:20 2019 +0100 +++ b/base/XmlExportable.h Wed Jul 17 14:24:51 2019 +0100 @@ -25,7 +25,14 @@ class XmlExportable { public: - XmlExportable() : m_exportId(-1) { } + enum { + // The value NO_ID (-1) is never allocated as an export id + NO_ID = -1 + }; + + typedef int ExportId; + + XmlExportable() : m_exportId(NO_ID) { } virtual ~XmlExportable() { } /** @@ -33,7 +40,7 @@ * allocated the first time this is called, so objects on which * this is never called do not get allocated one. */ - int getExportId() const; + ExportId getExportId() const; /** * Stream this exportable object out to XML on a text stream. diff -r 649ac57c5a2d -r 85b9b466a59f base/test/TestById.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/test/TestById.h Wed Jul 17 14:24:51 2019 +0100 @@ -0,0 +1,210 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "../ById.h" + +#include +#include + +#include + +using namespace std; + +struct WithoutId {}; + +// We'll need to change access levels for getId() and getUntypedId() +// to test the raw calls + +struct A : public WithTypedId { public: using WithTypedId::getId; }; +struct B1 : public A {}; +struct B2 : public A {}; + +struct M {}; + +typedef TypedById AById; + +struct X : virtual public WithId { public: using WithId::getUntypedId; }; +struct Y : public X, public B2, public M {}; + +class TestById : public QObject +{ + Q_OBJECT + +private slots: + void ids() { + // Verify that ids are unique across all classes, not just + // within a class. These must be the first two WithId objects + // allocated in the first test in the suite, otherwise they + // could be different even if they were allocated from + // separate pools. + A a; + X x; + if (a.getId().untyped == x.getUntypedId()) { + std::cerr << "ERROR: a and x have the same id: " << a.getId() + << std::endl; + } + QVERIFY(a.getId().untyped != x.getUntypedId()); + + A aa; + QVERIFY(aa.getId().untyped != a.getId().untyped); + QVERIFY(aa.getId().untyped != x.getUntypedId()); + + // Check the actual ids that have been allocated. This is + // supposed to be a hidden implementation detail, but we want + // to make sure the test itself hasn't become broken in terms + // of allocation order (see comment above) + QCOMPARE(a.getId().untyped, 0); + QCOMPARE(x.getUntypedId(), 1); + QCOMPARE(aa.getId().untyped, 2); + + QVERIFY(!a.getId().isNone()); + QVERIFY(A::Id().isNone()); + } + + // NB each test must release all the items it adds to the ById store + + void anyEmpty() { + auto p = AnyById::get(0); + QVERIFY(!p); + } + + void anySimple() { + auto a = std::make_shared(); + int id = AnyById::add(a); + QCOMPARE(id, a->getId().untyped); + + auto aa = AnyById::getAs(id); + QVERIFY(!!aa); + QCOMPARE(aa->getId(), a->getId()); + QCOMPARE(aa.get(), a.get()); // same object, not just same id! + AnyById::release(id); + } + + void typedEmpty() { + auto p = AById::get({}); + QVERIFY(!p); + } + + void typedSimple() { + auto a = std::make_shared(); + AById::add(a); + + auto aa = AById::get(a->getId()); + QVERIFY(!!aa); + QCOMPARE(aa->getId(), a->getId()); + QCOMPARE(aa.get(), a.get()); // same object, not just same id! + AById::release(a); + } + + void typedReleaseById() { + auto a = std::make_shared(); + auto aid = AById::add(a); + + auto aa = AById::get(aid); + QVERIFY(!!aa); + AById::release(aid); + + aa = AById::get(aid); + QVERIFY(!aa); + } + + void typedReleaseByItem() { + auto a = std::make_shared(); + auto aid = AById::add(a); + + auto aa = AById::get(aid); + QVERIFY(!!aa); + AById::release(a); + + aa = AById::get(aid); + QVERIFY(!aa); + } + + void typedDowncast() { + auto a = std::make_shared(); + auto b1 = std::make_shared(); + AById::add(a); + AById::add(b1); + + auto bb1 = AById::getAs(a->getId()); + QVERIFY(!bb1); + + bb1 = AById::getAs(b1->getId()); + QVERIFY(!!bb1); + QCOMPARE(bb1->getId(), b1->getId()); + + auto bb2 = AById::getAs(b1->getId()); + QVERIFY(!bb2); + + AById::release(a); + AById::release(b1); + } + + void typedCrosscast() { + auto y = std::make_shared(); + AById::add(y); + + auto yy = AById::getAs(y->getId()); + QVERIFY(!!yy); + QCOMPARE(yy->getId(), y->getId()); + + yy = AnyById::getAs(y->getId().untyped); + QVERIFY(!!yy); + QCOMPARE(yy->getId(), y->getId()); + + auto xx = AById::getAs(y->getId()); + QVERIFY(!!xx); + QCOMPARE(xx->getUntypedId(), y->getId().untyped); + QCOMPARE(xx.get(), yy.get()); + + xx = AnyById::getAs(y->getId().untyped); + QVERIFY(!!xx); + QCOMPARE(xx->getUntypedId(), y->getId().untyped); + QCOMPARE(xx.get(), yy.get()); + + auto mm = AnyById::getAs(y->getId().untyped); + QVERIFY(!!mm); + QCOMPARE(mm.get(), yy.get()); + + AById::release(y); + } + + void duplicateAdd() { + auto a = std::make_shared(); + AById::add(a); + try { + AById::add(a); + std::cerr << "Failed to catch expected exception in duplicateAdd" + << std::endl; + QVERIFY(false); + } catch (const std::logic_error &) { + } + AById::release(a); + } + + void unknownRelease() { + auto a = std::make_shared(); + auto b1 = std::make_shared(); + AById::add(a); + try { + AById::release(b1); + std::cerr << "Failed to catch expected exception in unknownRelease" + << std::endl; + QVERIFY(false); + } catch (const std::logic_error &) { + } + AById::release(a); + } +}; + diff -r 649ac57c5a2d -r 85b9b466a59f base/test/files.pri --- a/base/test/files.pri Thu Jun 20 14:58:20 2019 +0100 +++ b/base/test/files.pri Wed Jul 17 14:24:51 2019 +0100 @@ -1,4 +1,5 @@ TEST_HEADERS = \ + TestById.h \ TestColumnOp.h \ TestLogRange.h \ TestMovingMedian.h \ diff -r 649ac57c5a2d -r 85b9b466a59f base/test/svcore-base-test.cpp --- a/base/test/svcore-base-test.cpp Thu Jun 20 14:58:20 2019 +0100 +++ b/base/test/svcore-base-test.cpp Wed Jul 17 14:24:51 2019 +0100 @@ -20,6 +20,7 @@ #include "TestVampRealTime.h" #include "TestColumnOp.h" #include "TestMovingMedian.h" +#include "TestById.h" #include "TestEventSeries.h" #include "StressEventSeries.h" @@ -91,13 +92,19 @@ if (QTest::qExec(&t, argc, argv) == 0) ++good; else ++bad; } -/* + { + TestById t; + if (QTest::qExec(&t, argc, argv) == 0) ++good; + else ++bad; + } + +#ifdef NOT_DEFINED { StressEventSeries t; if (QTest::qExec(&t, argc, argv) == 0) ++good; else ++bad; } -*/ +#endif if (bad > 0) { SVCERR << "\n********* " << bad << " test suite(s) failed!\n" << endl; diff -r 649ac57c5a2d -r 85b9b466a59f data/fileio/FileSource.cpp --- a/data/fileio/FileSource.cpp Thu Jun 20 14:58:20 2019 +0100 +++ b/data/fileio/FileSource.cpp Wed Jul 17 14:24:51 2019 +0100 @@ -883,7 +883,8 @@ QDir dir; try { - dir = TempDirectory::getInstance()->getSubDirectoryPath("download"); + dir.setPath(TempDirectory::getInstance()-> + getSubDirectoryPath("download")); } catch (const DirectoryCreationFailed &f) { #ifdef DEBUG_FILE_SOURCE cerr << "FileSource::createCacheFile: ERROR: Failed to create temporary directory: " << f.what() << endl; diff -r 649ac57c5a2d -r 85b9b466a59f data/midi/MIDIEvent.h --- a/data/midi/MIDIEvent.h Thu Jun 20 14:58:20 2019 +0100 +++ b/data/midi/MIDIEvent.h Wed Jul 17 14:24:51 2019 +0100 @@ -162,6 +162,9 @@ m_metaMessage(sysEx) { } + MIDIEvent(const MIDIEvent &) =default; + MIDIEvent& operator=(const MIDIEvent &) =default; + ~MIDIEvent() { } void setTime(const unsigned long &time) { m_deltaTime = time; } @@ -195,8 +198,6 @@ friend bool operator<(const MIDIEvent &a, const MIDIEvent &b); private: - MIDIEvent& operator=(const MIDIEvent); - unsigned long m_deltaTime; unsigned long m_duration; MIDIByte m_eventCode; diff -r 649ac57c5a2d -r 85b9b466a59f data/model/AggregateWaveModel.cpp --- a/data/model/AggregateWaveModel.cpp Thu Jun 20 14:58:20 2019 +0100 +++ b/data/model/AggregateWaveModel.cpp Wed Jul 17 14:24:51 2019 +0100 @@ -27,19 +27,37 @@ AggregateWaveModel::m_zoomConstraint; AggregateWaveModel::AggregateWaveModel(ChannelSpecList channelSpecs) : - m_components(channelSpecs), - m_invalidated(false) + m_components(channelSpecs) { - for (ChannelSpecList::const_iterator i = channelSpecs.begin(); - i != channelSpecs.end(); ++i) { + sv_samplerate_t overallRate = 0; - connect(i->model, SIGNAL(aboutToBeDeleted()), - this, SLOT(componentModelAboutToBeDeleted())); - - if (i->model->getSampleRate() != - channelSpecs.begin()->model->getSampleRate()) { - SVDEBUG << "AggregateWaveModel::AggregateWaveModel: WARNING: Component models do not all have the same sample rate" << endl; - break; + for (int channel = 0; in_range_for(m_components, channel); ++channel) { + + auto model = ModelById::getAs + (m_components[channel].model); + + if (!model) { + SVCERR << "AggregateWaveModel: WARNING: component for channel " + << channel << " is not found or is of wrong model type" + << endl; + continue; + } + + sv_samplerate_t rate = model->getSampleRate(); + + if (!rate) { + SVCERR << "AggregateWaveModel: WARNING: component for channel " + << channel << " reports zero sample rate" << endl; + + } else if (!overallRate) { + + overallRate = rate; + + } else if (rate != overallRate) { + SVCERR << "AggregateWaveModel: WARNING: component for channel " + << channel << " has different sample rate from earlier " + << "channels (has " << rate << ", expected " << overallRate + << ")" << endl; } } } @@ -49,25 +67,15 @@ SVDEBUG << "AggregateWaveModel::~AggregateWaveModel" << endl; } -void -AggregateWaveModel::componentModelAboutToBeDeleted() -{ - SVDEBUG << "AggregateWaveModel::componentModelAboutToBeDeleted: invalidating" - << endl; - m_components.clear(); - m_invalidated = true; - emit modelInvalidated(); -} - bool AggregateWaveModel::isOK() const { - if (m_invalidated || m_components.empty()) { + if (m_components.empty()) { return false; } - for (ChannelSpecList::const_iterator i = m_components.begin(); - i != m_components.end(); ++i) { - if (!i->model->isOK()) { + for (const auto &c: m_components) { + auto model = ModelById::get(c.model); + if (!model || !model->isOK()) { return false; } } @@ -80,10 +88,11 @@ if (completion) *completion = 100; bool ready = true; - for (ChannelSpecList::const_iterator i = m_components.begin(); - i != m_components.end(); ++i) { + for (auto c: m_components) { int completionHere = 100; - if (!i->model->isReady(&completionHere)) { + auto model = ModelById::get(c.model); + if (!model) continue; + if (!model->isReady(&completionHere)) { ready = false; } if (completion && completionHere < *completion) { @@ -103,10 +112,10 @@ AggregateWaveModel::getFrameCount() const { sv_frame_t count = 0; - for (ChannelSpecList::const_iterator i = m_components.begin(); - i != m_components.end(); ++i) { - sv_frame_t thisCount = - i->model->getEndFrame() - i->model->getStartFrame(); + for (auto c: m_components) { + auto model = ModelById::get(c.model); + if (!model) continue; + sv_frame_t thisCount = model->getEndFrame() - model->getStartFrame(); if (thisCount > count) count = thisCount; } return count; @@ -121,19 +130,23 @@ sv_samplerate_t AggregateWaveModel::getSampleRate() const { - if (m_invalidated || m_components.empty()) return 0; - return m_components.begin()->model->getSampleRate(); + if (m_components.empty()) return 0; + auto model = ModelById::get(m_components.begin()->model); + if (!model) return 0; + return model->getSampleRate(); } floatvec_t AggregateWaveModel::getData(int channel, sv_frame_t start, sv_frame_t count) const { - if (m_invalidated || m_components.empty()) return {}; - + if (m_components.empty()) return {}; + int ch0 = channel, ch1 = channel; if (channel == -1) { ch0 = 0; ch1 = getChannelCount()-1; + } else if (!in_range_for(m_components, channel)) { + return {}; } floatvec_t result(count, 0.f); @@ -141,8 +154,11 @@ for (int c = ch0; c <= ch1; ++c) { - auto here = m_components[c].model->getData(m_components[c].channel, - start, count); + auto model = ModelById::getAs + (m_components[c].model); + if (!model) continue; + + auto here = model->getData(m_components[c].channel, start, count); if (sv_frame_t(here.size()) > longest) { longest = sv_frame_t(here.size()); } @@ -236,7 +252,9 @@ { QStringList componentStrings; for (const auto &c: m_components) { - componentStrings.push_back(QString("%1").arg(c.model->getExportId())); + auto model = ModelById::get(c.model); + if (!model) continue; + componentStrings.push_back(QString("%1").arg(model->getExportId())); } Model::toXml(out, indent, QString("type=\"aggregatewave\" components=\"%1\" %2") diff -r 649ac57c5a2d -r 85b9b466a59f data/model/AggregateWaveModel.h --- a/data/model/AggregateWaveModel.h Thu Jun 20 14:58:20 2019 +0100 +++ b/data/model/AggregateWaveModel.h Wed Jul 17 14:24:51 2019 +0100 @@ -28,9 +28,8 @@ public: struct ModelChannelSpec { - ModelChannelSpec(RangeSummarisableTimeValueModel *m, int c) : - model(m), channel(c) { } - RangeSummarisableTimeValueModel *model; + ModelChannelSpec(ModelId m, int c) : model(m), channel(c) { } + ModelId model; int channel; }; @@ -84,18 +83,15 @@ void modelChanged(); void modelChangedWithin(sv_frame_t, sv_frame_t); void completionChanged(); - void modelInvalidated(); protected slots: void componentModelChanged(); void componentModelChangedWithin(sv_frame_t, sv_frame_t); void componentModelCompletionChanged(); - void componentModelAboutToBeDeleted(); protected: ChannelSpecList m_components; static PowerOfSqrtTwoZoomConstraint m_zoomConstraint; - bool m_invalidated; // because one of its component models is aboutToBeDeleted }; #endif diff -r 649ac57c5a2d -r 85b9b466a59f data/model/AlignmentModel.cpp --- a/data/model/AlignmentModel.cpp Thu Jun 20 14:58:20 2019 +0100 +++ b/data/model/AlignmentModel.cpp Wed Jul 17 14:24:51 2019 +0100 @@ -19,35 +19,18 @@ //#define DEBUG_ALIGNMENT_MODEL 1 -AlignmentModel::AlignmentModel(Model *reference, - Model *aligned, - SparseTimeValueModel *path) : +AlignmentModel::AlignmentModel(ModelId reference, + ModelId aligned, + ModelId pathSource) : m_reference(reference), m_aligned(aligned), - m_rawPath(path), + m_pathSource(pathSource), m_path(nullptr), m_reversePath(nullptr), m_pathBegun(false), m_pathComplete(false) { - if (m_rawPath) { - - connect(m_rawPath, SIGNAL(modelChanged()), - this, SLOT(pathChanged())); - - connect(m_rawPath, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)), - this, SLOT(pathChangedWithin(sv_frame_t, sv_frame_t))); - - connect(m_rawPath, SIGNAL(completionChanged()), - this, SLOT(pathCompletionChanged())); - - constructPath(); - constructReversePath(); - } - - if (m_rawPath && m_rawPath->isReady()) { - pathCompletionChanged(); - } + setPathFrom(pathSource); if (m_reference == m_aligned) { // Trivial alignment, e.g. of main model to itself, which we @@ -62,51 +45,66 @@ #ifdef DEBUG_ALIGNMENT_MODEL SVCERR << "AlignmentModel(" << this << ")::~AlignmentModel()" << endl; #endif - - if (m_rawPath) m_rawPath->aboutToDelete(); - delete m_rawPath; - - if (m_path) m_path->aboutToDelete(); - delete m_path; - - if (m_reversePath) m_reversePath->aboutToDelete(); - delete m_reversePath; } bool AlignmentModel::isOK() const { if (m_error != "") return false; - if (m_rawPath) return m_rawPath->isOK(); + if (m_pathSource.isNone()) return true; + auto pathSourceModel = + ModelById::getAs(m_pathSource); + if (pathSourceModel) { + return pathSourceModel->isOK(); + } return true; } sv_frame_t AlignmentModel::getStartFrame() const { - sv_frame_t a = m_reference->getStartFrame(); - sv_frame_t b = m_aligned->getStartFrame(); - return std::min(a, b); + auto reference = ModelById::get(m_reference); + auto aligned = ModelById::get(m_aligned); + + if (reference && aligned) { + sv_frame_t a = reference->getStartFrame(); + sv_frame_t b = aligned->getStartFrame(); + return std::min(a, b); + } else { + return 0; + } } sv_frame_t AlignmentModel::getTrueEndFrame() const { - sv_frame_t a = m_reference->getEndFrame(); - sv_frame_t b = m_aligned->getEndFrame(); - return std::max(a, b); + auto reference = ModelById::get(m_reference); + auto aligned = ModelById::get(m_aligned); + + if (reference && aligned) { + sv_frame_t a = reference->getEndFrame(); + sv_frame_t b = aligned->getEndFrame(); + return std::max(a, b); + } else { + return 0; + } } sv_samplerate_t AlignmentModel::getSampleRate() const { - return m_reference->getSampleRate(); + auto reference = ModelById::get(m_reference); + if (reference) { + return reference->getSampleRate(); + } else { + return 0; + } } bool AlignmentModel::isReady(int *completion) const { - if (!m_pathBegun && m_rawPath) { + if (!m_pathBegun && !m_pathSource.isNone()) { if (completion) *completion = 0; #ifdef DEBUG_ALIGNMENT_MODEL SVCERR << "AlignmentModel::isReady: path not begun" << endl; @@ -120,9 +118,9 @@ #endif return true; } - if (!m_rawPath) { + if (m_pathSource.isNone()) { // lack of raw path could mean path is complete (in which case - // m_pathComplete true above) or else no alignment has been + // m_pathComplete true above) or else no path source has been // set at all yet (this case) if (completion) *completion = 0; #ifdef DEBUG_ALIGNMENT_MODEL @@ -130,7 +128,13 @@ #endif return false; } - return m_rawPath->isReady(completion); + auto pathSourceModel = + ModelById::getAs(m_pathSource); + if (pathSourceModel) { + return pathSourceModel->isReady(completion); + } else { + return true; // there is no meaningful answer here + } } const ZoomConstraint * @@ -139,13 +143,13 @@ return nullptr; } -const Model * +ModelId AlignmentModel::getReferenceModel() const { return m_reference; } -const Model * +ModelId AlignmentModel::getAlignedModel() const { return m_aligned; @@ -158,10 +162,16 @@ cerr << "AlignmentModel::toReference(" << frame << ")" << endl; #endif if (!m_path) { - if (!m_rawPath) return frame; + if (m_pathSource.isNone()) { + return frame; + } constructPath(); } - return align(m_path, frame); + if (!m_path) { + return frame; + } + + return performAlignment(*m_path, frame); } sv_frame_t @@ -171,25 +181,20 @@ cerr << "AlignmentModel::fromReference(" << frame << ")" << endl; #endif if (!m_reversePath) { - if (!m_rawPath) return frame; + if (m_pathSource.isNone()) { + return frame; + } constructReversePath(); } - return align(m_reversePath, frame); + if (!m_reversePath) { + return frame; + } + + return performAlignment(*m_reversePath, frame); } void -AlignmentModel::pathChanged() -{ - if (m_pathComplete) { - cerr << "AlignmentModel: deleting raw path model" << endl; - if (m_rawPath) m_rawPath->aboutToDelete(); - delete m_rawPath; - m_rawPath = nullptr; - } -} - -void -AlignmentModel::pathChangedWithin(sv_frame_t, sv_frame_t) +AlignmentModel::pathSourceChangedWithin(ModelId, sv_frame_t, sv_frame_t) { if (!m_pathComplete) return; constructPath(); @@ -197,15 +202,18 @@ } void -AlignmentModel::pathCompletionChanged() +AlignmentModel::pathSourceCompletionChanged(ModelId) { - if (!m_rawPath) return; + auto pathSourceModel = + ModelById::getAs(m_pathSource); + if (!pathSourceModel) return; + m_pathBegun = true; if (!m_pathComplete) { int completion = 0; - m_rawPath->isReady(&completion); + pathSourceModel->isReady(&completion); #ifdef DEBUG_ALIGNMENT_MODEL SVCERR << "AlignmentModel::pathCompletionChanged: completion = " @@ -225,32 +233,39 @@ } } - emit completionChanged(); + emit completionChanged(getId()); } void AlignmentModel::constructPath() const { + auto alignedModel = ModelById::get(m_aligned); + if (!alignedModel) return; + + auto pathSourceModel = + ModelById::getAs(m_pathSource); if (!m_path) { - if (!m_rawPath) { + if (!pathSourceModel) { cerr << "ERROR: AlignmentModel::constructPath: " - << "No raw path available" << endl; + << "No raw path available (id is " << m_pathSource + << ")" << endl; return; } - m_path = new PathModel - (m_rawPath->getSampleRate(), m_rawPath->getResolution(), false); + m_path.reset(new Path + (pathSourceModel->getSampleRate(), + pathSourceModel->getResolution())); } else { - if (!m_rawPath) return; + if (!pathSourceModel) return; } m_path->clear(); - EventVector points = m_rawPath->getAllEvents(); + EventVector points = pathSourceModel->getAllEvents(); for (const auto &p: points) { sv_frame_t frame = p.getFrame(); double value = p.getValue(); - sv_frame_t rframe = lrint(value * m_aligned->getSampleRate()); + sv_frame_t rframe = lrint(value * alignedModel->getSampleRate()); m_path->add(PathPoint(frame, rframe)); } @@ -268,20 +283,20 @@ << "No forward path available" << endl; return; } - m_reversePath = new PathModel - (m_path->getSampleRate(), m_path->getResolution(), false); + m_reversePath.reset(new Path + (m_path->getSampleRate(), + m_path->getResolution())); } else { if (!m_path) return; } m_reversePath->clear(); - PathModel::PointList points = m_path->getPoints(); + Path::Points points = m_path->getPoints(); - for (PathModel::PointList::const_iterator i = points.begin(); - i != points.end(); ++i) { - sv_frame_t frame = i->frame; - sv_frame_t rframe = i->mapframe; + for (auto p: points) { + sv_frame_t frame = p.frame; + sv_frame_t rframe = p.mapframe; m_reversePath->add(PathPoint(rframe, frame)); } @@ -291,16 +306,14 @@ } sv_frame_t -AlignmentModel::align(PathModel *path, sv_frame_t frame) const +AlignmentModel::performAlignment(const Path &path, sv_frame_t frame) const { - if (!path) return frame; - // The path consists of a series of points, each with frame equal // to the frame on the source model and mapframe equal to the // frame on the target model. Both should be monotonically // increasing. - const PathModel::PointList &points = path->getPoints(); + const Path::Points &points = path.getPoints(); if (points.empty()) { #ifdef DEBUG_ALIGNMENT_MODEL @@ -314,14 +327,16 @@ #endif PathPoint point(frame); - PathModel::PointList::const_iterator i = points.lower_bound(point); + Path::Points::const_iterator i = points.lower_bound(point); if (i == points.end()) { #ifdef DEBUG_ALIGNMENT_MODEL cerr << "Note: i == points.end()" << endl; #endif --i; } - while (i != points.begin() && i->frame > frame) --i; + while (i != points.begin() && i->frame > frame) { + --i; + } sv_frame_t foundFrame = i->frame; sv_frame_t foundMapFrame = i->mapframe; @@ -347,7 +362,9 @@ << followingMapFrame << endl; #endif - if (foundMapFrame < 0) return 0; + if (foundMapFrame < 0) { + return 0; + } sv_frame_t resultFrame = foundMapFrame; @@ -366,49 +383,37 @@ } void -AlignmentModel::setPathFrom(SparseTimeValueModel *rawpath) +AlignmentModel::setPathFrom(ModelId pathSource) { - if (m_rawPath) m_rawPath->aboutToDelete(); - delete m_rawPath; + m_pathSource = pathSource; + + auto pathSourceModel = + ModelById::getAs(m_pathSource); + + if (pathSourceModel) { - m_rawPath = rawpath; + connect(pathSourceModel.get(), + SIGNAL(modelChangedWithin(ModelId, sv_frame_t, sv_frame_t)), + this, SLOT(pathSourceChangedWithin(ModelId, sv_frame_t, sv_frame_t))); + + connect(pathSourceModel.get(), SIGNAL(completionChanged(ModelId)), + this, SLOT(pathSourceCompletionChanged(ModelId))); - if (!m_rawPath) { - return; + constructPath(); + constructReversePath(); + + if (pathSourceModel->isReady()) { + pathSourceCompletionChanged(m_pathSource); + } } - - connect(m_rawPath, SIGNAL(modelChanged()), - this, SLOT(pathChanged())); - - connect(m_rawPath, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)), - this, SLOT(pathChangedWithin(sv_frame_t, sv_frame_t))); - - connect(m_rawPath, SIGNAL(completionChanged()), - this, SLOT(pathCompletionChanged())); - - constructPath(); - constructReversePath(); - - if (m_rawPath->isReady()) { - pathCompletionChanged(); - } } void -AlignmentModel::setPath(PathModel *path) +AlignmentModel::setPath(const Path &path) { - if (m_path) m_path->aboutToDelete(); - delete m_path; - m_path = path; + m_path.reset(new Path(path)); m_pathComplete = true; -#ifdef DEBUG_ALIGNMENT_MODEL - cerr << "AlignmentModel::setPath: path = " << m_path << endl; -#endif constructReversePath(); -#ifdef DEBUG_ALIGNMENT_MODEL - cerr << "AlignmentModel::setPath: after construction path = " - << m_path << ", rpath = " << m_reversePath << endl; -#endif } void @@ -425,10 +430,8 @@ Model::toXml(stream, indent, QString("type=\"alignment\" reference=\"%1\" aligned=\"%2\" path=\"%3\" %4") - .arg(m_reference->getExportId()) - .arg(m_aligned->getExportId()) + .arg(ModelById::getExportId(m_reference)) + .arg(ModelById::getExportId(m_aligned)) .arg(m_path->getExportId()) .arg(extraAttributes)); } - - diff -r 649ac57c5a2d -r 85b9b466a59f data/model/AlignmentModel.h --- a/data/model/AlignmentModel.h Thu Jun 20 14:58:20 2019 +0100 +++ b/data/model/AlignmentModel.h Wed Jul 17 14:24:51 2019 +0100 @@ -17,7 +17,7 @@ #define SV_ALIGNMENT_MODEL_H #include "Model.h" -#include "PathModel.h" +#include "Path.h" #include "base/RealTime.h" #include @@ -30,9 +30,9 @@ Q_OBJECT public: - AlignmentModel(Model *reference, - Model *aligned, - SparseTimeValueModel *path); + AlignmentModel(ModelId reference /* any model */, + ModelId aligned /* any model */, + ModelId path /* a SparseTimeValueModel */); ~AlignmentModel(); bool isOK() const override; @@ -53,18 +53,18 @@ QString getTypeName() const override { return tr("Alignment"); } - const Model *getReferenceModel() const; - const Model *getAlignedModel() const; + ModelId getReferenceModel() const; + ModelId getAlignedModel() const; sv_frame_t toReference(sv_frame_t frame) const; sv_frame_t fromReference(sv_frame_t frame) const; - void setPathFrom(SparseTimeValueModel *rawpath); - void setPath(PathModel *path); + void setPathFrom(ModelId pathSource); // a SparseTimeValueModel + void setPath(const Path &path); void toXml(QTextStream &stream, - QString indent = "", - QString extraAttributes = "") const override; + QString indent = "", + QString extraAttributes = "") const override; QString toDelimitedDataString(QString, DataExportOptions, sv_frame_t, sv_frame_t) const override { @@ -72,22 +72,21 @@ } signals: - void modelChanged(); - void modelChangedWithin(sv_frame_t startFrame, sv_frame_t endFrame); - void completionChanged(); + void completionChanged(ModelId); protected slots: - void pathChanged(); - void pathChangedWithin(sv_frame_t startFrame, sv_frame_t endFrame); - void pathCompletionChanged(); + void pathSourceChangedWithin(ModelId, sv_frame_t startFrame, sv_frame_t endFrame); + void pathSourceCompletionChanged(ModelId); protected: - Model *m_reference; // I don't own this - Model *m_aligned; // I don't own this + ModelId m_reference; + ModelId m_aligned; - SparseTimeValueModel *m_rawPath; // I own this - mutable PathModel *m_path; // I own this - mutable PathModel *m_reversePath; // I own this + ModelId m_pathSource; // a SparseTimeValueModel, which we need a + // handle on only while it's still being generated + + mutable std::unique_ptr m_path; + mutable std::unique_ptr m_reversePath; bool m_pathBegun; bool m_pathComplete; QString m_error; @@ -95,7 +94,7 @@ void constructPath() const; void constructReversePath() const; - sv_frame_t align(PathModel *path, sv_frame_t frame) const; + sv_frame_t performAlignment(const Path &path, sv_frame_t frame) const; }; #endif diff -r 649ac57c5a2d -r 85b9b466a59f data/model/DeferredNotifier.h --- a/data/model/DeferredNotifier.h Thu Jun 20 14:58:20 2019 +0100 +++ b/data/model/DeferredNotifier.h Wed Jul 17 14:24:51 2019 +0100 @@ -30,7 +30,8 @@ NOTIFY_DEFERRED }; - DeferredNotifier(Model *m, Mode mode) : m_model(m), m_mode(mode) { } + DeferredNotifier(Model *m, ModelId id, Mode mode) : + m_model(m), m_modelId(id), m_mode(mode) { } Mode getMode() const { return m_mode; @@ -41,7 +42,7 @@ void update(sv_frame_t frame, sv_frame_t duration) { if (m_mode == NOTIFY_ALWAYS) { - m_model->modelChangedWithin(frame, frame + duration); + m_model->modelChangedWithin(m_modelId, frame, frame + duration); } else { QMutexLocker locker(&m_mutex); m_extents.sample(frame); @@ -60,7 +61,7 @@ } } if (shouldEmit) { - m_model->modelChangedWithin(from, to); + m_model->modelChangedWithin(m_modelId, from, to); QMutexLocker locker(&m_mutex); m_extents.reset(); } @@ -68,6 +69,7 @@ private: Model *m_model; + ModelId m_modelId; Mode m_mode; QMutex m_mutex; Extents m_extents; diff -r 649ac57c5a2d -r 85b9b466a59f data/model/Dense3DModelPeakCache.cpp --- a/data/model/Dense3DModelPeakCache.cpp Thu Jun 20 14:58:20 2019 +0100 +++ b/data/model/Dense3DModelPeakCache.cpp Wed Jul 17 14:24:51 2019 +0100 @@ -19,34 +19,36 @@ #include "base/HitCount.h" -Dense3DModelPeakCache::Dense3DModelPeakCache(const DenseThreeDimensionalModel *source, +Dense3DModelPeakCache::Dense3DModelPeakCache(ModelId sourceId, int columnsPerPeak) : - m_source(source), + m_source(sourceId), m_columnsPerPeak(columnsPerPeak) { - m_cache = new EditableDenseThreeDimensionalModel - (source->getSampleRate(), - getResolution(), - source->getHeight(), - EditableDenseThreeDimensionalModel::NoCompression, - false); + auto source = ModelById::getAs(m_source); + if (!source) { + SVCERR << "WARNING: Dense3DModelPeakCache constructed for unknown or wrong-type source model id " << m_source << endl; + m_source = {}; + return; + } - connect(source, SIGNAL(modelChanged()), - this, SLOT(sourceModelChanged())); - connect(source, SIGNAL(aboutToBeDeleted()), - this, SLOT(sourceModelAboutToBeDeleted())); + m_cache.reset(new EditableDenseThreeDimensionalModel + (source->getSampleRate(), + source->getResolution() * m_columnsPerPeak, + source->getHeight(), + EditableDenseThreeDimensionalModel::NoCompression, + false)); + + connect(source.get(), SIGNAL(modelChanged(ModelId)), + this, SLOT(sourceModelChanged(ModelId))); } Dense3DModelPeakCache::~Dense3DModelPeakCache() { - if (m_cache) m_cache->aboutToDelete(); - delete m_cache; } Dense3DModelPeakCache::Column Dense3DModelPeakCache::getColumn(int column) const { - if (!m_source) return Column(); if (!haveColumn(column)) fillColumn(column); return m_cache->getColumn(column); } @@ -54,15 +56,13 @@ float Dense3DModelPeakCache::getValueAt(int column, int n) const { - if (!m_source) return 0.f; if (!haveColumn(column)) fillColumn(column); return m_cache->getValueAt(column, n); } void -Dense3DModelPeakCache::sourceModelChanged() +Dense3DModelPeakCache::sourceModelChanged(ModelId) { - if (!m_source) return; if (m_coverage.size() > 0) { // The last peak may have come from an incomplete read, which // may since have been filled, so reset it @@ -71,12 +71,6 @@ m_coverage.resize(getWidth(), false); // retaining data } -void -Dense3DModelPeakCache::sourceModelAboutToBeDeleted() -{ - m_source = nullptr; -} - bool Dense3DModelPeakCache::haveColumn(int column) const { @@ -104,7 +98,10 @@ m_coverage.resize(column + 1, false); } - int sourceWidth = m_source->getWidth(); + auto source = ModelById::getAs(m_source); + if (!source) return; + + int sourceWidth = source->getWidth(); Column peak; int n = 0; @@ -113,7 +110,7 @@ int sourceColumn = column * m_columnsPerPeak + i; if (sourceColumn >= sourceWidth) break; - Column here = m_source->getColumn(sourceColumn); + Column here = source->getColumn(sourceColumn); // cerr << "Dense3DModelPeakCache::fillColumn(" << column << "): source col " // << sourceColumn << " of " << sourceWidth diff -r 649ac57c5a2d -r 85b9b466a59f data/model/Dense3DModelPeakCache.h --- a/data/model/Dense3DModelPeakCache.h Thu Jun 20 14:58:20 2019 +0100 +++ b/data/model/Dense3DModelPeakCache.h Wed Jul 17 14:24:51 2019 +0100 @@ -24,28 +24,33 @@ Q_OBJECT public: - Dense3DModelPeakCache(const DenseThreeDimensionalModel *source, + Dense3DModelPeakCache(ModelId source, // a DenseThreeDimensionalModel int columnsPerPeak); ~Dense3DModelPeakCache(); bool isOK() const override { - return m_source && m_source->isOK(); + auto source = ModelById::get(m_source); + return source && source->isOK(); } sv_samplerate_t getSampleRate() const override { - return m_source->getSampleRate(); + auto source = ModelById::get(m_source); + return source ? source->getSampleRate() : 0; } sv_frame_t getStartFrame() const override { - return m_source->getStartFrame(); + auto source = ModelById::get(m_source); + return source ? source->getStartFrame() : 0; } sv_frame_t getTrueEndFrame() const override { - return m_source->getTrueEndFrame(); + auto source = ModelById::get(m_source); + return source ? source->getTrueEndFrame() : 0; } int getResolution() const override { - return m_source->getResolution() * m_columnsPerPeak; + auto source = ModelById::getAs(m_source); + return source ? source->getResolution() * m_columnsPerPeak : 1; } virtual int getColumnsPerPeak() const { @@ -53,7 +58,9 @@ } int getWidth() const override { - int sourceWidth = m_source->getWidth(); + auto source = ModelById::getAs(m_source); + if (!source) return 0; + int sourceWidth = source->getWidth(); if ((sourceWidth % m_columnsPerPeak) == 0) { return sourceWidth / m_columnsPerPeak; } else { @@ -62,15 +69,18 @@ } int getHeight() const override { - return m_source->getHeight(); + auto source = ModelById::getAs(m_source); + return source ? source->getHeight() : 0; } float getMinimumLevel() const override { - return m_source->getMinimumLevel(); + auto source = ModelById::getAs(m_source); + return source ? source->getMinimumLevel() : 0.f; } float getMaximumLevel() const override { - return m_source->getMaximumLevel(); + auto source = ModelById::getAs(m_source); + return source ? source->getMaximumLevel() : 1.f; } /** @@ -84,17 +94,20 @@ float getValueAt(int col, int n) const override; QString getBinName(int n) const override { - return m_source->getBinName(n); + auto source = ModelById::getAs(m_source); + return source ? source->getBinName(n) : ""; } bool shouldUseLogValueScale() const override { - return m_source->shouldUseLogValueScale(); + auto source = ModelById::getAs(m_source); + return source ? source->shouldUseLogValueScale() : false; } QString getTypeName() const override { return tr("Dense 3-D Peak Cache"); } int getCompletion() const override { - return m_source->getCompletion(); + auto source = ModelById::get(m_source); + return source ? source->getCompletion() : 100; } QString toDelimitedDataString(QString, DataExportOptions, @@ -103,12 +116,11 @@ } protected slots: - void sourceModelChanged(); - void sourceModelAboutToBeDeleted(); + void sourceModelChanged(ModelId); private: - const DenseThreeDimensionalModel *m_source; - mutable EditableDenseThreeDimensionalModel *m_cache; + ModelId m_source; + mutable std::unique_ptr m_cache; mutable std::vector m_coverage; // must be bool, for space efficiency // (vector of bool uses 1-bit elements) int m_columnsPerPeak; diff -r 649ac57c5a2d -r 85b9b466a59f data/model/DenseTimeValueModel.cpp --- a/data/model/DenseTimeValueModel.cpp Thu Jun 20 14:58:20 2019 +0100 +++ b/data/model/DenseTimeValueModel.cpp Wed Jul 17 14:24:51 2019 +0100 @@ -14,19 +14,8 @@ */ #include "DenseTimeValueModel.h" -#include "base/PlayParameterRepository.h" #include - -DenseTimeValueModel::DenseTimeValueModel() -{ - PlayParameterRepository::getInstance()->addPlayable(this); -} - -DenseTimeValueModel::~DenseTimeValueModel() -{ - PlayParameterRepository::getInstance()->removePlayable(this); -} QString DenseTimeValueModel::toDelimitedDataString(QString delimiter, diff -r 649ac57c5a2d -r 85b9b466a59f data/model/DenseTimeValueModel.h --- a/data/model/DenseTimeValueModel.h Thu Jun 20 14:58:20 2019 +0100 +++ b/data/model/DenseTimeValueModel.h Wed Jul 17 14:24:51 2019 +0100 @@ -32,9 +32,9 @@ Q_OBJECT public: - DenseTimeValueModel(); + DenseTimeValueModel() { } - virtual ~DenseTimeValueModel(); + virtual ~DenseTimeValueModel() { } /** * Return the minimum possible value found in this model type. diff -r 649ac57c5a2d -r 85b9b466a59f data/model/EditableDenseThreeDimensionalModel.cpp --- a/data/model/EditableDenseThreeDimensionalModel.cpp Thu Jun 20 14:58:20 2019 +0100 +++ b/data/model/EditableDenseThreeDimensionalModel.cpp Wed Jul 17 14:24:51 2019 +0100 @@ -359,15 +359,16 @@ if (m_notifyOnAdd) { if (allChange) { - emit modelChanged(); + emit modelChanged(getId()); } else { - emit modelChangedWithin(windowStart, windowStart + m_resolution); + emit modelChangedWithin(getId(), + windowStart, windowStart + m_resolution); } } else { if (allChange) { m_sinceLastNotifyMin = -1; m_sinceLastNotifyMax = -1; - emit modelChanged(); + emit modelChanged(getId()); } else { if (m_sinceLastNotifyMin == -1 || windowStart < m_sinceLastNotifyMin) { @@ -393,14 +394,14 @@ { while ((int)m_binNames.size() <= n) m_binNames.push_back(""); m_binNames[n] = name; - emit modelChanged(); + emit modelChanged(getId()); } void EditableDenseThreeDimensionalModel::setBinNames(std::vector names) { m_binNames = names; - emit modelChanged(); + emit modelChanged(getId()); } bool @@ -474,21 +475,22 @@ if (completion == 100) { m_notifyOnAdd = true; // henceforth - emit modelChanged(); + emit modelChanged(getId()); } else if (!m_notifyOnAdd) { if (update && m_sinceLastNotifyMin >= 0 && m_sinceLastNotifyMax >= 0) { - emit modelChangedWithin(m_sinceLastNotifyMin, + emit modelChangedWithin(getId(), + m_sinceLastNotifyMin, m_sinceLastNotifyMax + m_resolution); m_sinceLastNotifyMin = m_sinceLastNotifyMax = -1; } else { - emit completionChanged(); + emit completionChanged(getId()); } } else { - emit completionChanged(); + emit completionChanged(getId()); } } } diff -r 649ac57c5a2d -r 85b9b466a59f data/model/EventCommands.h --- a/data/model/EventCommands.h Thu Jun 20 14:58:20 2019 +0100 +++ b/data/model/EventCommands.h Wed Jul 17 14:24:51 2019 +0100 @@ -18,6 +18,7 @@ #include "base/Event.h" #include "base/Command.h" +#include "base/ById.h" /** * Interface for classes that can be modified through these commands @@ -29,45 +30,77 @@ virtual void remove(Event e) = 0; }; +class WithEditable +{ +protected: + WithEditable(int editableId) : m_editableId(editableId) { } + + std::shared_ptr getEditable() { + auto editable = AnyById::getAs(m_editableId); + if (!editable) { + SVCERR << "WARNING: Id passed to EventEditable command is not that of an EventEditable" << endl; + } + return editable; + } + +private: + int m_editableId; +}; + /** - * Command to add an event to an editable containing events, with undo. + * Command to add an event to an editable containing events, with + * undo. The id must be that of a type that can be retrieved from the + * AnyById store and dynamic_cast to EventEditable. */ -class AddEventCommand : public Command +class AddEventCommand : public Command, + public WithEditable { public: - AddEventCommand(EventEditable *editable, const Event &e, QString name) : - m_editable(editable), m_event(e), m_name(name) { } + AddEventCommand(int editableId, const Event &e, QString name) : + WithEditable(editableId), m_event(e), m_name(name) { } QString getName() const override { return m_name; } Event getEvent() const { return m_event; } - void execute() override { m_editable->add(m_event); } - void unexecute() override { m_editable->remove(m_event); } + void execute() override { + auto editable = getEditable(); + if (editable) editable->add(m_event); + } + void unexecute() override { + auto editable = getEditable(); + if (editable) editable->remove(m_event); + } private: - EventEditable *m_editable; Event m_event; QString m_name; }; /** * Command to remove an event from an editable containing events, with - * undo. + * undo. The id must be that of a type that can be retrieved from the + * AnyById store and dynamic_cast to EventEditable. */ -class RemoveEventCommand : public Command +class RemoveEventCommand : public Command, + public WithEditable { public: - RemoveEventCommand(EventEditable *editable, const Event &e, QString name) : - m_editable(editable), m_event(e), m_name(name) { } + RemoveEventCommand(int editableId, const Event &e, QString name) : + WithEditable(editableId), m_event(e), m_name(name) { } QString getName() const override { return m_name; } Event getEvent() const { return m_event; } - void execute() override { m_editable->remove(m_event); } - void unexecute() override { m_editable->add(m_event); } + void execute() override { + auto editable = getEditable(); + if (editable) editable->remove(m_event); + } + void unexecute() override { + auto editable = getEditable(); + if (editable) editable->add(m_event); + } private: - EventEditable *m_editable; Event m_event; QString m_name; }; @@ -76,19 +109,20 @@ * Command to add or remove a series of events to or from an editable, * with undo. Creates and immediately executes a sub-command for each * add/remove requested. Consecutive add/remove pairs for the same - * point are collapsed. + * point are collapsed. The id must be that of a type that can be + * retrieved from the AnyById store and dynamic_cast to EventEditable. */ class ChangeEventsCommand : public MacroCommand { public: - ChangeEventsCommand(EventEditable *editable, QString name) : - MacroCommand(name), m_editable(editable) { } + ChangeEventsCommand(int editableId, QString name) : + MacroCommand(name), m_editableId(editableId) { } void add(Event e) { - addCommand(new AddEventCommand(m_editable, e, getName()), true); + addCommand(new AddEventCommand(m_editableId, e, getName()), true); } void remove(Event e) { - addCommand(new RemoveEventCommand(m_editable, e, getName()), true); + addCommand(new RemoveEventCommand(m_editableId, e, getName()), true); } /** @@ -134,7 +168,7 @@ MacroCommand::addCommand(command); } - EventEditable *m_editable; + int m_editableId; }; #endif diff -r 649ac57c5a2d -r 85b9b466a59f data/model/FFTModel.cpp --- a/data/model/FFTModel.cpp Thu Jun 20 14:58:20 2019 +0100 +++ b/data/model/FFTModel.cpp Wed Jul 17 14:24:51 2019 +0100 @@ -32,13 +32,13 @@ static HitCount inSmallCache("FFTModel: Small FFT cache"); static HitCount inSourceCache("FFTModel: Source data cache"); -FFTModel::FFTModel(const DenseTimeValueModel *model, +FFTModel::FFTModel(ModelId modelId, int channel, WindowType windowType, int windowSize, int windowIncrement, int fftSize) : - m_model(model), + m_model(modelId), m_channel(channel), m_windowType(windowType), m_windowSize(windowSize), @@ -61,32 +61,51 @@ m_fft.initFloat(); - connect(model, SIGNAL(modelChanged()), - this, SIGNAL(modelChanged())); - connect(model, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)), - this, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t))); - connect(model, SIGNAL(aboutToBeDeleted()), - this, SLOT(sourceModelAboutToBeDeleted())); + auto model = ModelById::getAs(m_model); + if (model) { + connect(model.get(), SIGNAL(modelChanged(ModelId)), + this, SIGNAL(modelChanged(ModelId))); + connect(model.get(), SIGNAL(modelChangedWithin(ModelId, sv_frame_t, sv_frame_t)), + this, SIGNAL(modelChangedWithin(ModelId, sv_frame_t, sv_frame_t))); + } } FFTModel::~FFTModel() { } -void -FFTModel::sourceModelAboutToBeDeleted() +bool +FFTModel::isOK() const { - if (m_model) { - SVDEBUG << "FFTModel[" << this << "]::sourceModelAboutToBeDeleted(" << m_model << ")" << endl; - m_model = nullptr; + auto model = ModelById::getAs(m_model); + return (model && model->isOK()); +} + +int +FFTModel::getCompletion() const +{ + int c = 100; + auto model = ModelById::getAs(m_model); + if (model) { + if (model->isReady(&c)) return 100; } + return c; +} + +sv_samplerate_t +FFTModel::getSampleRate() const +{ + auto model = ModelById::getAs(m_model); + if (model) return model->getSampleRate(); + else return 0; } int FFTModel::getWidth() const { - if (!m_model) return 0; - return int((m_model->getEndFrame() - m_model->getStartFrame()) + auto model = ModelById::getAs(m_model); + if (!model) return 0; + return int((model->getEndFrame() - model->getStartFrame()) / m_windowIncrement) + 1; } @@ -277,7 +296,8 @@ { Profiler profiler("FFTModel::getSourceDataUncached"); - if (!m_model) return {}; + auto model = ModelById::getAs(m_model); + if (!model) return {}; decltype(range.first) pfx = 0; if (range.first < 0) { @@ -285,14 +305,14 @@ range = { 0, range.second }; } - auto data = m_model->getData(m_channel, - range.first, - range.second - range.first); + auto data = model->getData(m_channel, + range.first, + range.second - range.first); if (data.empty()) { SVDEBUG << "NOTE: empty source data for range (" << range.first << "," << range.second << ") (model end frame " - << m_model->getEndFrame() << ")" << endl; + << model->getEndFrame() << ")" << endl; } // don't return a partial frame @@ -304,7 +324,7 @@ } if (m_channel == -1) { - int channels = m_model->getChannelCount(); + int channels = model->getChannelCount(); if (channels > 1) { int n = int(data.size()); float factor = 1.f / float(channels); diff -r 649ac57c5a2d -r 85b9b466a59f data/model/FFTModel.h --- a/data/model/FFTModel.h Thu Jun 20 14:58:20 2019 +0100 +++ b/data/model/FFTModel.h Wed Jul 17 14:24:51 2019 +0100 @@ -50,7 +50,7 @@ * unless the channel is -1 in which case merge all available * channels. */ - FFTModel(const DenseTimeValueModel *model, + FFTModel(ModelId model, // a DenseTimeValueModel int channel, WindowType windowType, int windowSize, @@ -63,14 +63,12 @@ int getWidth() const override; int getHeight() const override; float getValueAt(int x, int y) const override { return getMagnitudeAt(x, y); } - bool isOK() const override { return m_model && m_model->isOK(); } + bool isOK() const override; sv_frame_t getStartFrame() const override { return 0; } sv_frame_t getTrueEndFrame() const override { return sv_frame_t(getWidth()) * getResolution() + getResolution(); } - sv_samplerate_t getSampleRate() const override { - return isOK() ? m_model->getSampleRate() : 0; - } + sv_samplerate_t getSampleRate() const override; int getResolution() const override { return m_windowIncrement; } virtual int getYBinCount() const { return getHeight(); } float getMinimumLevel() const override { return 0.f; } // Can't provide @@ -79,13 +77,7 @@ virtual Column getPhases(int x) const; QString getBinName(int n) const override; bool shouldUseLogValueScale() const override { return true; } - int getCompletion() const override { - int c = 100; - if (m_model) { - if (m_model->isReady(&c)) return 100; - } - return c; - } + int getCompletion() const override; virtual QString getError() const { return ""; } //!!!??? virtual sv_frame_t getFillExtent() const { return getEndFrame(); } QString toDelimitedDataString(QString, DataExportOptions, @@ -143,14 +135,11 @@ QString getTypeName() const override { return tr("FFT"); } -public slots: - void sourceModelAboutToBeDeleted(); +private: + FFTModel(const FFTModel &) =delete; + FFTModel &operator=(const FFTModel &) =delete; -private: - FFTModel(const FFTModel &); // not implemented - FFTModel &operator=(const FFTModel &); // not implemented - - const DenseTimeValueModel *m_model; + const ModelId m_model; // a DenseTimeValueModel int m_channel; WindowType m_windowType; int m_windowSize; diff -r 649ac57c5a2d -r 85b9b466a59f data/model/ImageModel.h --- a/data/model/ImageModel.h Thu Jun 20 14:58:20 2019 +0100 +++ b/data/model/ImageModel.h Wed Jul 17 14:24:51 2019 +0100 @@ -48,6 +48,7 @@ m_sampleRate(sampleRate), m_resolution(resolution), m_notifier(this, + getId(), notifyOnAdd ? DeferredNotifier::NOTIFY_ALWAYS : DeferredNotifier::NOTIFY_DEFERRED), @@ -84,12 +85,12 @@ m_notifier.makeDeferredNotifications(); } - emit completionChanged(); + emit completionChanged(getId()); if (completion == 100) { // henceforth: m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS); - emit modelChanged(); + emit modelChanged(getId()); } } @@ -152,7 +153,8 @@ { QMutexLocker locker(&m_mutex); m_events.remove(e); } - emit modelChangedWithin(e.getFrame(), e.getFrame() + m_resolution); + emit modelChangedWithin(getId(), + e.getFrame(), e.getFrame() + m_resolution); } /** @@ -231,8 +233,7 @@ case 3: e1 = e0.withLabel(value.toString()); break; } - ChangeEventsCommand *command = - new ChangeEventsCommand(this, tr("Edit Data")); + auto command = new ChangeEventsCommand(getId().untyped, tr("Edit Data")); command->remove(e0); command->add(e1); return command->finish(); diff -r 649ac57c5a2d -r 85b9b466a59f data/model/Labeller.h --- a/data/model/Labeller.h Thu Jun 20 14:58:20 2019 +0100 +++ b/data/model/Labeller.h Wed Jul 17 14:24:51 2019 +0100 @@ -225,14 +225,16 @@ * Relabel all events in the given event vector that lie within * the given multi-selection, according to the labelling * properties of this labeller. Return a command that has been - * executed but not yet added to the history. + * executed but not yet added to the history. The id must be that + * of a type that can be retrieved from the AnyById store and + * dynamic_cast to EventEditable. */ - Command *labelAll(EventEditable *editable, + Command *labelAll(int editableId, MultiSelection *ms, const EventVector &allEvents) { - ChangeEventsCommand *command = new ChangeEventsCommand - (editable, tr("Label Points")); + auto command = new ChangeEventsCommand + (editableId, tr("Label Points")); Event prev; bool havePrev = false; @@ -270,15 +272,17 @@ * that event lies within the given multi-selection, add n-1 new * events at equally spaced intervals between it and the following * event. Return a command that has been executed but not yet - * added to the history. + * added to the history. The id must be that of a type that can + * be retrieved from the AnyById store and dynamic_cast to + * EventEditable. */ - Command *subdivide(EventEditable *editable, + Command *subdivide(int editableId, MultiSelection *ms, const EventVector &allEvents, int n) { - ChangeEventsCommand *command = new ChangeEventsCommand - (editable, tr("Subdivide Points")); + auto command = new ChangeEventsCommand + (editableId, tr("Subdivide Points")); for (auto i = allEvents.begin(); i != allEvents.end(); ++i) { @@ -319,15 +323,17 @@ * multi-selection, and a number n, remove all but every nth event * from the vector within the extents of the multi-selection. * Return a command that has been executed but not yet added to - * the history. + * the history. The id must be that of a type that can be + * retrieved from the AnyById store and dynamic_cast to + * EventEditable. */ - Command *winnow(EventEditable *editable, + Command *winnow(int editableId, MultiSelection *ms, const EventVector &allEvents, int n) { - ChangeEventsCommand *command = new ChangeEventsCommand - (editable, tr("Winnow Points")); + auto command = new ChangeEventsCommand + (editableId, tr("Winnow Points")); int counter = 0; diff -r 649ac57c5a2d -r 85b9b466a59f data/model/Model.cpp --- a/data/model/Model.cpp Thu Jun 20 14:58:20 2019 +0100 +++ b/data/model/Model.cpp Wed Jul 17 14:24:51 2019 +0100 @@ -24,141 +24,90 @@ Model::~Model() { - SVDEBUG << "Model::~Model(" << this << ")" << endl; - - if (!m_aboutToDelete) { - SVDEBUG << "NOTE: Model(" << this << ", \"" - << objectName() << "\", type uri <" - << m_typeUri << ">)::~Model(): Model deleted " - << "with no aboutToDelete notification" - << endl; - } - - if (m_alignment) { - m_alignment->aboutToDelete(); - delete m_alignment; - } -} - -int -Model::getNextId() -{ - static int nextId = 0; - static QMutex mutex; - QMutexLocker locker(&mutex); - int i = nextId; - if (nextId == INT_MAX) { - nextId = INT_MIN; - } - ++nextId; - return i; + SVDEBUG << "Model::~Model: " << this << " with id " << getId() << endl; } void -Model::setSourceModel(Model *model) +Model::setSourceModel(ModelId modelId) { - if (m_sourceModel) { - disconnect(m_sourceModel, SIGNAL(aboutToBeDeleted()), - this, SLOT(sourceModelAboutToBeDeleted())); - } + m_sourceModel = modelId; - m_sourceModel = model; - - if (m_sourceModel) { - connect(m_sourceModel, SIGNAL(alignmentCompletionChanged()), - this, SIGNAL(alignmentCompletionChanged())); - connect(m_sourceModel, SIGNAL(aboutToBeDeleted()), - this, SLOT(sourceModelAboutToBeDeleted())); + auto model = ModelById::get(m_sourceModel); + if (model) { + connect(model.get(), SIGNAL(alignmentCompletionChanged(ModelId)), + this, SIGNAL(alignmentCompletionChanged(ModelId))); } } void -Model::aboutToDelete() -{ - SVDEBUG << "Model(" << this << ", \"" - << objectName() << "\", type name \"" - << getTypeName() << "\", type uri <" - << m_typeUri << ">)::aboutToDelete()" << endl; - - if (m_aboutToDelete) { - SVDEBUG << "WARNING: Model(" << this << ", \"" - << objectName() << "\", type uri <" - << m_typeUri << ">)::aboutToDelete: " - << "aboutToDelete called more than once for the same model" - << endl; - } - - emit aboutToBeDeleted(); - m_aboutToDelete = true; -} - -void -Model::sourceModelAboutToBeDeleted() -{ - m_sourceModel = nullptr; -} - -void -Model::setAlignment(AlignmentModel *alignment) +Model::setAlignment(ModelId alignmentModel) { SVDEBUG << "Model(" << this << "): accepting alignment model " - << alignment << endl; - - if (m_alignment) { - m_alignment->aboutToDelete(); - delete m_alignment; + << alignmentModel << endl; + + if (auto model = ModelById::get(m_alignmentModel)) { + disconnect(model.get(), SIGNAL(completionChanged(ModelId)), + this, SIGNAL(alignmentCompletionChanged(ModelId))); } - m_alignment = alignment; + m_alignmentModel = alignmentModel; - if (m_alignment) { - connect(m_alignment, SIGNAL(completionChanged()), - this, SIGNAL(alignmentCompletionChanged())); + if (auto model = ModelById::get(m_alignmentModel)) { + connect(model.get(), SIGNAL(completionChanged(ModelId)), + this, SIGNAL(alignmentCompletionChanged(ModelId))); } } -const AlignmentModel * +const ModelId Model::getAlignment() const { - return m_alignment; + return m_alignmentModel; } -const Model * +const ModelId Model::getAlignmentReference() const { - if (!m_alignment) { - if (m_sourceModel) return m_sourceModel->getAlignmentReference(); - return nullptr; - } - return m_alignment->getReferenceModel(); + auto model = ModelById::getAs(m_alignmentModel); + if (model) return model->getReferenceModel(); + else return {}; } sv_frame_t Model::alignToReference(sv_frame_t frame) const { -// cerr << "Model(" << this << ")::alignToReference(" << frame << ")" << endl; - if (!m_alignment) { - if (m_sourceModel) return m_sourceModel->alignToReference(frame); - else return frame; + auto alignmentModel = ModelById::getAs(m_alignmentModel); + + if (!alignmentModel) { + auto sourceModel = ModelById::get(m_sourceModel); + if (sourceModel) { + return sourceModel->alignToReference(frame); + } + return frame; } - sv_frame_t refFrame = m_alignment->toReference(frame); - const Model *m = m_alignment->getReferenceModel(); - if (m && refFrame > m->getEndFrame()) refFrame = m->getEndFrame(); -// cerr << "have alignment, aligned is " << refFrame << endl; + + sv_frame_t refFrame = alignmentModel->toReference(frame); + auto refModel = ModelById::get(alignmentModel->getReferenceModel()); + if (refModel && refFrame > refModel->getEndFrame()) { + refFrame = refModel->getEndFrame(); + } return refFrame; } sv_frame_t Model::alignFromReference(sv_frame_t refFrame) const { -// cerr << "Model(" << this << ")::alignFromReference(" << refFrame << ")" << endl; - if (!m_alignment) { - if (m_sourceModel) return m_sourceModel->alignFromReference(refFrame); - else return refFrame; + auto alignmentModel = ModelById::getAs(m_alignmentModel); + + if (!alignmentModel) { + auto sourceModel = ModelById::get(m_sourceModel); + if (sourceModel) { + return sourceModel->alignFromReference(refFrame); + } + return refFrame; } - sv_frame_t frame = m_alignment->fromReference(refFrame); + + sv_frame_t frame = alignmentModel->fromReference(refFrame); if (frame > getEndFrame()) frame = getEndFrame(); -// cerr << "have alignment, aligned is " << frame << endl; return frame; } @@ -166,17 +115,26 @@ Model::getAlignmentCompletion() const { #ifdef DEBUG_COMPLETION - SVCERR << "Model(" << this << ")::getAlignmentCompletion: m_alignment = " - << m_alignment << endl; + SVCERR << "Model(" << this + << ")::getAlignmentCompletion: m_alignmentModel = " + << m_alignmentModel << endl; #endif - if (!m_alignment) { - if (m_sourceModel) return m_sourceModel->getAlignmentCompletion(); - else return 100; + + auto alignmentModel = ModelById::getAs(m_alignmentModel); + + if (!alignmentModel) { + auto sourceModel = ModelById::get(m_sourceModel); + if (sourceModel) { + return sourceModel->getAlignmentCompletion(); + } + return 100; } + int completion = 0; - (void)m_alignment->isReady(&completion); + (void)alignmentModel->isReady(&completion); #ifdef DEBUG_COMPLETION - SVCERR << "Model(" << this << ")::getAlignmentCompletion: completion = " << completion + SVCERR << "Model(" << this + << ")::getAlignmentCompletion: completion = " << completion << endl; #endif return completion; @@ -185,21 +143,24 @@ QString Model::getTitle() const { - if (m_sourceModel) return m_sourceModel->getTitle(); + auto source = ModelById::get(m_sourceModel); + if (source) return source->getTitle(); else return ""; } QString Model::getMaker() const { - if (m_sourceModel) return m_sourceModel->getMaker(); + auto source = ModelById::get(m_sourceModel); + if (source) return source->getMaker(); else return ""; } QString Model::getLocation() const { - if (m_sourceModel) return m_sourceModel->getLocation(); + auto source = ModelById::get(m_sourceModel); + if (source) return source->getLocation(); else return ""; } diff -r 649ac57c5a2d -r 85b9b466a59f data/model/Model.h --- a/data/model/Model.h Thu Jun 20 14:58:20 2019 +0100 +++ b/data/model/Model.h Wed Jul 17 14:24:51 2019 +0100 @@ -19,6 +19,7 @@ #include #include +#include "base/ById.h" #include "base/XmlExportable.h" #include "base/Playable.h" #include "base/BaseTypes.h" @@ -27,20 +28,20 @@ class ZoomConstraint; class AlignmentModel; -typedef int ModelId; - /** * Model is the base class for all data models that represent any sort * of data on a time scale based on an audio frame rate. */ - class Model : public QObject, + public WithTypedId, public XmlExportable, public Playable { Q_OBJECT public: + typedef Id ModelId; + virtual ~Model(); /** @@ -132,33 +133,6 @@ virtual bool isSparse() const { return false; } /** - * Return an id for this model. The id is guaranteed to be a - * unique identifier for this model among all models that may ever - * exist within this single run of the application. - */ - ModelId getId() const { return m_id; } - - /** - * Mark the model as abandoning. This means that the application - * no longer needs it, so it can stop doing any background - * calculations it may be involved in. Note that as far as the - * model API is concerned, this does nothing more than tell the - * model to return true from isAbandoning(). The actual response - * to this will depend on the model's context -- it's possible - * nothing at all will change. - */ - virtual void abandon() { - m_abandoning = true; - } - - /** - * Query whether the model has been marked as abandoning. - */ - virtual bool isAbandoning() const { - return m_abandoning; - } - - /** * Return true if the model has finished loading or calculating * all its data, for a model that is capable of calculating in a * background thread. @@ -208,28 +182,27 @@ } /** - * If this model was derived from another, return the model it was - * derived from. The assumption is that the source model's - * alignment will also apply to this model, unless some other - * property (such as a specific alignment model set on this model) - * indicates otherwise. + * If this model was derived from another, return the id of the + * model it was derived from. The assumption is that the source + * model's alignment will also apply to this model, unless some + * other property (such as a specific alignment model set on this + * model) indicates otherwise. */ - virtual Model *getSourceModel() const { + virtual ModelId getSourceModel() const { return m_sourceModel; } /** * Set the source model for this model. */ - virtual void setSourceModel(Model *model); + virtual void setSourceModel(ModelId model); /** - * Specify an aligment between this model's timeline and that of a - * reference model. The alignment model records both the - * reference and the alignment. This model takes ownership of the - * alignment model. + * Specify an alignment between this model's timeline and that of + * a reference model. The alignment model, of type AlignmentModel, + * records both the reference and the alignment. */ - virtual void setAlignment(AlignmentModel *alignment); + virtual void setAlignment(ModelId alignmentModel); /** * Retrieve the alignment model for this model. This is not a @@ -240,13 +213,13 @@ * application for this function is in streaming out alignments to * the session file. */ - virtual const AlignmentModel *getAlignment() const; + virtual const ModelId getAlignment() const; /** * Return the reference model for the current alignment timeline, * if any. */ - virtual const Model *getAlignmentReference() const; + virtual const ModelId getAlignmentReference() const; /** * Return the frame number of the reference model that corresponds @@ -290,22 +263,18 @@ sv_frame_t startFrame, sv_frame_t duration) const = 0; -public slots: - void aboutToDelete(); - void sourceModelAboutToBeDeleted(); - signals: /** * Emitted when a model has been edited (or more data retrieved * from cache, in the case of a cached model that generates slowly) */ - void modelChanged(); + void modelChanged(ModelId myId); /** * Emitted when a model has been edited (or more data retrieved * from cache, in the case of a cached model that generates slowly) */ - void modelChangedWithin(sv_frame_t startFrame, sv_frame_t endFrame); + void modelChangedWithin(ModelId myId, sv_frame_t startFrame, sv_frame_t endFrame); /** * Emitted when some internal processing has advanced a stage, but @@ -313,52 +282,35 @@ * updating any progress meters or other monitoring, but not * refreshing the actual view. */ - void completionChanged(); + void completionChanged(ModelId myId); /** * Emitted when internal processing is complete (i.e. when * isReady() would return true, with completion at 100). */ - void ready(); + void ready(ModelId myId); /** * Emitted when the completion percentage changes for the * calculation of this model's alignment model. */ - void alignmentCompletionChanged(); - - /** - * Emitted when something notifies this model (through calling - * aboutToDelete() that it is about to delete it. Note that this - * depends on an external agent such as a Document object or - * owning model telling the model that it is about to delete it; - * there is nothing in the model to guarantee that this signal - * will be emitted before the actual deletion. - */ - void aboutToBeDeleted(); + void alignmentCompletionChanged(ModelId myId); protected: Model() : - m_id(getNextId()), - m_sourceModel(0), - m_alignment(0), - m_abandoning(false), - m_aboutToDelete(false), m_extendTo(0) { } // Not provided. - Model(const Model &); - Model &operator=(const Model &); + Model(const Model &) =delete; + Model &operator=(const Model &) =delete; - const ModelId m_id; - Model *m_sourceModel; - AlignmentModel *m_alignment; + ModelId m_sourceModel; + ModelId m_alignmentModel; QString m_typeUri; - bool m_abandoning; - bool m_aboutToDelete; sv_frame_t m_extendTo; - - int getNextId(); }; +typedef Model::Id ModelId; +typedef TypedById ModelById; + #endif diff -r 649ac57c5a2d -r 85b9b466a59f data/model/ModelDataTableModel.cpp --- a/data/model/ModelDataTableModel.cpp Thu Jun 20 14:58:20 2019 +0100 +++ b/data/model/ModelDataTableModel.cpp Wed Jul 17 14:24:51 2019 +0100 @@ -22,19 +22,18 @@ #include #include -ModelDataTableModel::ModelDataTableModel(TabularModel *m) : +ModelDataTableModel::ModelDataTableModel(ModelId m) : m_model(m), m_sortColumn(0), m_sortOrdering(Qt::AscendingOrder), m_currentRow(0) { - Model *baseModel = dynamic_cast(m); - - connect(baseModel, SIGNAL(modelChanged()), this, SLOT(modelChanged())); - connect(baseModel, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)), - this, SLOT(modelChangedWithin(sv_frame_t, sv_frame_t))); - connect(baseModel, SIGNAL(aboutToBeDeleted()), - this, SLOT(modelAboutToBeDeleted())); + auto model = ModelById::get(m); + if (model) { + connect(model.get(), SIGNAL(modelChanged()), this, SLOT(modelChanged())); + connect(model.get(), SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)), + this, SLOT(modelChangedWithin(sv_frame_t, sv_frame_t))); + } } ModelDataTableModel::~ModelDataTableModel() @@ -44,19 +43,21 @@ QVariant ModelDataTableModel::data(const QModelIndex &index, int role) const { - if (!m_model) return QVariant(); + auto model = getTabularModel(); + if (!model) return QVariant(); if (role != Qt::EditRole && role != Qt::DisplayRole) return QVariant(); if (!index.isValid()) return QVariant(); - QVariant d = m_model->getData(getUnsorted(index.row()), index.column(), role); + QVariant d = model->getData(getUnsorted(index.row()), index.column(), role); return d; } bool ModelDataTableModel::setData(const QModelIndex &index, const QVariant &value, int role) { - if (!m_model) return false; + auto model = getTabularModel(); + if (!model) return false; if (!index.isValid()) return false; - Command *command = m_model->getSetDataCommand(getUnsorted(index.row()), + Command *command = model->getSetDataCommand(getUnsorted(index.row()), index.column(), value, role); if (command) { @@ -70,10 +71,11 @@ bool ModelDataTableModel::insertRow(int row, const QModelIndex &parent) { - if (!m_model) return false; + auto model = getTabularModel(); + if (!model) return false; if (parent.isValid()) return false; - Command *command = m_model->getInsertRowCommand(getUnsorted(row)); + Command *command = model->getInsertRowCommand(getUnsorted(row)); if (command) { emit addCommand(command); @@ -85,10 +87,11 @@ bool ModelDataTableModel::removeRow(int row, const QModelIndex &parent) { - if (!m_model) return false; + auto model = getTabularModel(); + if (!model) return false; if (parent.isValid()) return false; - Command *command = m_model->getRemoveRowCommand(getUnsorted(row)); + Command *command = model->getRemoveRowCommand(getUnsorted(row)); if (command) { emit addCommand(command); @@ -108,13 +111,14 @@ QVariant ModelDataTableModel::headerData(int section, Qt::Orientation orientation, int role) const { - if (!m_model) return QVariant(); + auto model = getTabularModel(); + if (!model) return QVariant(); if (orientation == Qt::Vertical && role == Qt::DisplayRole) { return section + 1; } if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { - return m_model->getHeading(section); + return model->getHeading(section); } return QVariant(); } @@ -134,38 +138,44 @@ int ModelDataTableModel::rowCount(const QModelIndex &parent) const { - if (!m_model) return 0; + auto model = getTabularModel(); + if (!model) return 0; if (parent.isValid()) return 0; - int count = m_model->getRowCount(); + int count = model->getRowCount(); return count; } int ModelDataTableModel::columnCount(const QModelIndex &parent) const { - if (!m_model) return 0; + auto model = getTabularModel(); + if (!model) return 0; if (parent.isValid()) return 0; - return m_model->getColumnCount(); + return model->getColumnCount(); } QModelIndex ModelDataTableModel::getModelIndexForFrame(sv_frame_t frame) const { - if (!m_model) return createIndex(0, 0); - int row = m_model->getRowForFrame(frame); + auto model = getTabularModel(); + if (!model) return createIndex(0, 0); + int row = model->getRowForFrame(frame); return createIndex(getSorted(row), 0, (void *)nullptr); } sv_frame_t ModelDataTableModel::getFrameForModelIndex(const QModelIndex &index) const { - if (!m_model) return 0; - return m_model->getFrameForRow(getUnsorted(index.row())); + auto model = getTabularModel(); + if (!model) return 0; + return model->getFrameForRow(getUnsorted(index.row())); } QModelIndex ModelDataTableModel::findText(QString text) const { + auto model = getTabularModel(); + if (!model) return QModelIndex(); if (text == "") return QModelIndex(); int rows = rowCount(); int cols = columnCount(); @@ -173,10 +183,10 @@ for (int row = 1; row <= rows; ++row) { int wrapped = (row + current) % rows; for (int col = 0; col < cols; ++col) { - if (m_model->getSortType(col) != TabularModel::SortAlphabetical) { + if (model->getSortType(col) != TabularModel::SortAlphabetical) { continue; } - QString cell = m_model->getData(getUnsorted(wrapped), col, + QString cell = model->getData(getUnsorted(wrapped), col, Qt::DisplayRole).toString(); if (cell.contains(text, Qt::CaseInsensitive)) { return createIndex(wrapped, col); @@ -243,19 +253,13 @@ emit layoutChanged(); } -void -ModelDataTableModel::modelAboutToBeDeleted() -{ - m_model = nullptr; - emit modelRemoved(); -} - int ModelDataTableModel::getSorted(int row) const { - if (!m_model) return row; + auto model = getTabularModel(); + if (!model) return row; - if (m_model->isColumnTimeValue(m_sortColumn)) { + if (model->isColumnTimeValue(m_sortColumn)) { if (m_sortOrdering == Qt::AscendingOrder) { return row; } else { @@ -280,9 +284,10 @@ int ModelDataTableModel::getUnsorted(int row) const { - if (!m_model) return row; + auto model = getTabularModel(); + if (!model) return row; - if (m_model->isColumnTimeValue(m_sortColumn)) { + if (model->isColumnTimeValue(m_sortColumn)) { if (m_sortOrdering == Qt::AscendingOrder) { return row; } else { @@ -309,9 +314,10 @@ void ModelDataTableModel::resort() const { - if (!m_model) return; + auto model = getTabularModel(); + if (!model) return; - bool numeric = (m_model->getSortType(m_sortColumn) == + bool numeric = (model->getSortType(m_sortColumn) == TabularModel::SortNumeric); // cerr << "resort: numeric == " << numeric << endl; @@ -342,15 +348,16 @@ void ModelDataTableModel::resortNumeric() const { - if (!m_model) return; + auto model = getTabularModel(); + if (!model) return; typedef std::multimap MapType; MapType rowMap; - int rows = m_model->getRowCount(); + int rows = model->getRowCount(); for (int i = 0; i < rows; ++i) { - QVariant value = m_model->getData(i, m_sortColumn, TabularModel::SortRole); + QVariant value = model->getData(i, m_sortColumn, TabularModel::SortRole); rowMap.insert(MapType::value_type(value.toDouble(), i)); } @@ -365,16 +372,17 @@ void ModelDataTableModel::resortAlphabetical() const { - if (!m_model) return; + auto model = getTabularModel(); + if (!model) return; typedef std::multimap MapType; MapType rowMap; - int rows = m_model->getRowCount(); + int rows = model->getRowCount(); for (int i = 0; i < rows; ++i) { QVariant value = - m_model->getData(i, m_sortColumn, TabularModel::SortRole); + model->getData(i, m_sortColumn, TabularModel::SortRole); rowMap.insert(MapType::value_type(value.toString(), i)); } diff -r 649ac57c5a2d -r 85b9b466a59f data/model/ModelDataTableModel.h --- a/data/model/ModelDataTableModel.h Thu Jun 20 14:58:20 2019 +0100 +++ b/data/model/ModelDataTableModel.h Wed Jul 17 14:24:51 2019 +0100 @@ -22,6 +22,9 @@ #include "base/BaseTypes.h" +#include "TabularModel.h" +#include "Model.h" + class TabularModel; class Command; @@ -30,7 +33,7 @@ Q_OBJECT public: - ModelDataTableModel(TabularModel *m); + ModelDataTableModel(ModelId modelId); // a TabularModel virtual ~ModelDataTableModel(); QVariant data(const QModelIndex &index, int role) const override; @@ -72,10 +75,13 @@ protected slots: void modelChanged(); void modelChangedWithin(sv_frame_t, sv_frame_t); - void modelAboutToBeDeleted(); protected: - TabularModel *m_model; + std::shared_ptr getTabularModel() const { + return ModelById::getAs(m_model); + } + + ModelId m_model; int m_sortColumn; Qt::SortOrder m_sortOrdering; int m_currentRow; diff -r 649ac57c5a2d -r 85b9b466a59f data/model/NoteModel.h --- a/data/model/NoteModel.h Thu Jun 20 14:58:20 2019 +0100 +++ b/data/model/NoteModel.h Wed Jul 17 14:24:51 2019 +0100 @@ -58,6 +58,7 @@ m_units(""), m_extendTo(0), m_notifier(this, + getId(), notifyOnAdd ? DeferredNotifier::NOTIFY_ALWAYS : DeferredNotifier::NOTIFY_DEFERRED), @@ -66,7 +67,8 @@ m_valueMinimum = 33.f; m_valueMaximum = 88.f; } - PlayParameterRepository::getInstance()->addPlayable(this); + PlayParameterRepository::getInstance()->addPlayable + (getId().untyped, this); } NoteModel(sv_samplerate_t sampleRate, int resolution, @@ -83,15 +85,18 @@ m_units(""), m_extendTo(0), m_notifier(this, + getId(), notifyOnAdd ? DeferredNotifier::NOTIFY_ALWAYS : DeferredNotifier::NOTIFY_DEFERRED), m_completion(100) { - PlayParameterRepository::getInstance()->addPlayable(this); + PlayParameterRepository::getInstance()->addPlayable + (getId().untyped, this); } virtual ~NoteModel() { - PlayParameterRepository::getInstance()->removePlayable(this); + PlayParameterRepository::getInstance()->removePlayable + (getId().untyped); } QString getTypeName() const override { return tr("Note"); } @@ -142,12 +147,12 @@ m_notifier.makeDeferredNotifications(); } - emit completionChanged(); + emit completionChanged(getId()); if (completion == 100) { // henceforth: m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS); - emit modelChanged(); + emit modelChanged(getId()); } } @@ -216,7 +221,7 @@ m_notifier.update(e.getFrame(), e.getDuration() + m_resolution); if (allChange) { - emit modelChanged(); + emit modelChanged(getId()); } } @@ -225,7 +230,8 @@ QMutexLocker locker(&m_mutex); m_events.remove(e); } - emit modelChangedWithin(e.getFrame(), + emit modelChangedWithin(getId(), + e.getFrame(), e.getFrame() + e.getDuration() + m_resolution); } @@ -308,8 +314,7 @@ case 5: e1 = e0.withLabel(value.toString()); break; } - ChangeEventsCommand *command = - new ChangeEventsCommand(this, tr("Edit Data")); + auto command = new ChangeEventsCommand(getId().untyped, tr("Edit Data")); command->remove(e0); command->add(e1); return command->finish(); diff -r 649ac57c5a2d -r 85b9b466a59f data/model/Path.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/Path.h Wed Jul 17 14:24:51 2019 +0100 @@ -0,0 +1,164 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef SV_PATH_H +#define SV_PATH_H + +#include "base/XmlExportable.h" +#include "base/RealTime.h" +#include "base/BaseTypes.h" + +#include +#include + +struct PathPoint +{ + PathPoint(sv_frame_t _frame) : + frame(_frame), mapframe(_frame) { } + PathPoint(sv_frame_t _frame, sv_frame_t _mapframe) : + frame(_frame), mapframe(_mapframe) { } + + sv_frame_t frame; + sv_frame_t mapframe; + + void toXml(QTextStream &stream, QString indent = "", + QString extraAttributes = "") const { + stream << QString("%1\n") + .arg(indent).arg(frame).arg(mapframe).arg(extraAttributes); + } + + QString toDelimitedDataString(QString delimiter, DataExportOptions, + sv_samplerate_t sampleRate) const { + QStringList list; + list << RealTime::frame2RealTime(frame, sampleRate).toString().c_str(); + list << QString("%1").arg(mapframe); + return list.join(delimiter); + } + + bool operator<(const PathPoint &p2) const { + if (frame != p2.frame) return frame < p2.frame; + return mapframe < p2.mapframe; + } +}; + +class Path : public XmlExportable +{ +public: + Path(sv_samplerate_t sampleRate, int resolution) : + m_sampleRate(sampleRate), + m_resolution(resolution) { + } + Path(const Path &) =default; + Path &operator=(const Path &) =default; + + typedef std::set Points; + + sv_samplerate_t getSampleRate() const { return m_sampleRate; } + int getResolution() const { return m_resolution; } + + int getPointCount() const { + return int(m_points.size()); + } + + const Points &getPoints() const { + return m_points; + } + + void add(PathPoint p) { + m_points.insert(p); + } + + void remove(PathPoint p) { + m_points.erase(p); + } + + void clear() { + m_points.clear(); + } + + /** + * XmlExportable methods. + */ + void toXml(QTextStream &out, + QString indent = "", + QString extraAttributes = "") const override { + + // For historical reasons we serialise a Path as a model, + // although the class itself no longer is. + + // We also write start and end frames - which our API no + // longer exposes - just for backward compatibility + + sv_frame_t start = 0; + sv_frame_t end = 0; + if (!m_points.empty()) { + start = m_points.begin()->frame; + end = m_points.rbegin()->frame + m_resolution; + } + + // Our dataset doesn't have its own export ID, we just use + // ours. Actually any model could do that, since datasets + // aren't in the same id-space as models (or paths) when + // re-read + + out << indent; + out << QString("\n") + .arg(getExportId()) + .arg(m_sampleRate) + .arg(start) + .arg(end) + .arg(m_resolution) + .arg(getExportId()) + .arg(extraAttributes); + + out << indent << QString("\n") + .arg(getExportId()); + + for (PathPoint p: m_points) { + p.toXml(out, indent + " ", ""); + } + + out << indent << "\n"; + } + + QString toDelimitedDataString(QString delimiter, + DataExportOptions, + sv_frame_t startFrame, + sv_frame_t duration) const { + + QString s; + for (PathPoint p: m_points) { + if (p.frame < startFrame) continue; + if (p.frame >= startFrame + duration) break; + s += QString("%1%2%3\n") + .arg(p.frame) + .arg(delimiter) + .arg(p.mapframe); + } + + return s; + } + +protected: + sv_samplerate_t m_sampleRate; + int m_resolution; + Points m_points; +}; + + +#endif diff -r 649ac57c5a2d -r 85b9b466a59f data/model/PathModel.h --- a/data/model/PathModel.h Thu Jun 20 14:58:20 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,229 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - Sonic Visualiser - An audio file viewer and annotation editor. - Centre for Digital Music, Queen Mary, University of London. - This file copyright 2007 QMUL. - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef SV_PATH_MODEL_H -#define SV_PATH_MODEL_H - -#include "Model.h" -#include "DeferredNotifier.h" -#include "base/RealTime.h" -#include "base/BaseTypes.h" - -#include "base/XmlExportable.h" -#include "base/RealTime.h" - -#include -#include - -struct PathPoint -{ - PathPoint(sv_frame_t _frame) : - frame(_frame), mapframe(_frame) { } - PathPoint(sv_frame_t _frame, sv_frame_t _mapframe) : - frame(_frame), mapframe(_mapframe) { } - - sv_frame_t frame; - sv_frame_t mapframe; - - void toXml(QTextStream &stream, QString indent = "", - QString extraAttributes = "") const { - stream << QString("%1\n") - .arg(indent).arg(frame).arg(mapframe).arg(extraAttributes); - } - - QString toDelimitedDataString(QString delimiter, DataExportOptions, - sv_samplerate_t sampleRate) const { - QStringList list; - list << RealTime::frame2RealTime(frame, sampleRate).toString().c_str(); - list << QString("%1").arg(mapframe); - return list.join(delimiter); - } - - bool operator<(const PathPoint &p2) const { - if (frame != p2.frame) return frame < p2.frame; - return mapframe < p2.mapframe; - } -}; - -class PathModel : public Model -{ -public: - typedef std::set PointList; - - PathModel(sv_samplerate_t sampleRate, - int resolution, - bool notifyOnAdd = true) : - m_sampleRate(sampleRate), - m_resolution(resolution), - m_notifier(this, - notifyOnAdd ? - DeferredNotifier::NOTIFY_ALWAYS : - DeferredNotifier::NOTIFY_DEFERRED), - m_completion(100), - m_start(0), - m_end(0) { - } - - QString getTypeName() const override { return tr("Path"); } - bool isSparse() const override { return true; } - bool isOK() const override { return true; } - - sv_frame_t getStartFrame() const override { - return m_start; - } - sv_frame_t getTrueEndFrame() const override { - return m_end; - } - - sv_samplerate_t getSampleRate() const override { return m_sampleRate; } - int getResolution() const { return m_resolution; } - - int getCompletion() const override { return m_completion; } - - void setCompletion(int completion, bool update = true) { - - { QMutexLocker locker(&m_mutex); - if (m_completion == completion) return; - m_completion = completion; - } - - if (update) { - m_notifier.makeDeferredNotifications(); - } - - emit completionChanged(); - - if (completion == 100) { - // henceforth: - m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS); - emit modelChanged(); - } - } - - /** - * Query methods. - */ - int getPointCount() const { - return int(m_points.size()); - } - const PointList &getPoints() const { - return m_points; - } - - /** - * Editing methods. - */ - void add(PathPoint p) { - - { QMutexLocker locker(&m_mutex); - m_points.insert(p); - - if (m_start == m_end) { - m_start = p.frame; - m_end = m_start + m_resolution; - } else { - if (p.frame < m_start) { - m_start = p.frame; - } - if (p.frame + m_resolution > m_end) { - m_end = p.frame + m_resolution; - } - } - } - - m_notifier.update(p.frame, m_resolution); - } - - void remove(PathPoint p) { - { QMutexLocker locker(&m_mutex); - m_points.erase(p); - } - - emit modelChangedWithin(p.frame, p.frame + m_resolution); - } - - void clear() { - { QMutexLocker locker(&m_mutex); - m_start = m_end = 0; - m_points.clear(); - } - } - - /** - * XmlExportable methods. - */ - void toXml(QTextStream &out, - QString indent = "", - QString extraAttributes = "") const override { - - // Our dataset doesn't have its own export ID, we just use - // ours. Actually any model could do that, since datasets - // aren't in the same id-space as models when re-read - - Model::toXml - (out, - indent, - QString("type=\"sparse\" dimensions=\"2\" resolution=\"%1\" " - "notifyOnAdd=\"%2\" dataset=\"%3\" subtype=\"path\" %4") - .arg(m_resolution) - .arg("true") // always true after model reaches 100% - - // subsequent points are always notified - .arg(getExportId()) - .arg(extraAttributes)); - - out << indent << QString("\n") - .arg(getExportId()); - - for (PathPoint p: m_points) { - p.toXml(out, indent + " ", ""); - } - - out << indent << "\n"; - } - - QString toDelimitedDataString(QString delimiter, - DataExportOptions, - sv_frame_t startFrame, - sv_frame_t duration) const override { - - QString s; - for (PathPoint p: m_points) { - if (p.frame < startFrame) continue; - if (p.frame >= startFrame + duration) break; - s += QString("%1%2%3\n") - .arg(p.frame) - .arg(delimiter) - .arg(p.mapframe); - } - - return s; - } - -protected: - sv_samplerate_t m_sampleRate; - int m_resolution; - - DeferredNotifier m_notifier; - int m_completion; - - sv_frame_t m_start; - sv_frame_t m_end; - PointList m_points; - - mutable QMutex m_mutex; -}; - - -#endif diff -r 649ac57c5a2d -r 85b9b466a59f data/model/RangeSummarisableTimeValueModel.h --- a/data/model/RangeSummarisableTimeValueModel.h Thu Jun 20 14:58:20 2019 +0100 +++ b/data/model/RangeSummarisableTimeValueModel.h Wed Jul 17 14:24:51 2019 +0100 @@ -41,10 +41,11 @@ public: Range() : m_new(true), m_min(0.f), m_max(0.f), m_absmean(0.f) { } - Range(const Range &r) : - m_new(true), m_min(r.m_min), m_max(r.m_max), m_absmean(r.m_absmean) { } Range(float min, float max, float absmean) : - m_new(true), m_min(min), m_max(max), m_absmean(absmean) { } + m_new(false), m_min(min), m_max(max), m_absmean(absmean) { } + + Range(const Range &r) =default; + Range &operator=(const Range &) =default; float min() const { return m_min; } float max() const { return m_max; } diff -r 649ac57c5a2d -r 85b9b466a59f data/model/ReadOnlyWaveFileModel.cpp --- a/data/model/ReadOnlyWaveFileModel.cpp Thu Jun 20 14:58:20 2019 +0100 +++ b/data/model/ReadOnlyWaveFileModel.cpp Wed Jul 17 14:24:51 2019 +0100 @@ -21,12 +21,12 @@ #include "system/System.h" #include "base/Preferences.h" +#include "base/PlayParameterRepository.h" #include #include #include -//#include #include #include @@ -85,6 +85,9 @@ if (m_reader) setObjectName(m_reader->getTitle()); if (objectName() == "") setObjectName(QFileInfo(m_path).fileName()); if (isOK()) fillCache(); + + PlayParameterRepository::getInstance()->addPlayable + (getId().untyped, this); } ReadOnlyWaveFileModel::ReadOnlyWaveFileModel(FileSource source, AudioFileReader *reader) : @@ -106,10 +109,16 @@ if (m_reader) setObjectName(m_reader->getTitle()); if (objectName() == "") setObjectName(QFileInfo(m_path).fileName()); fillCache(); + + PlayParameterRepository::getInstance()->addPlayable + (getId().untyped, this); } ReadOnlyWaveFileModel::~ReadOnlyWaveFileModel() { + PlayParameterRepository::getInstance()->removePlayable + (getId().untyped); + m_exiting = true; if (m_fillThread) m_fillThread->wait(); if (m_myReader) delete m_reader; @@ -581,14 +590,14 @@ SVDEBUG << "ReadOnlyWaveFileModel(" << objectName() << ")::fillTimerTimedOut: extent = " << fillExtent << endl; #endif if (fillExtent > m_lastFillExtent) { - emit modelChangedWithin(m_lastFillExtent, fillExtent); + emit modelChangedWithin(getId(), m_lastFillExtent, fillExtent); m_lastFillExtent = fillExtent; } } else { #ifdef DEBUG_WAVE_FILE_MODEL SVDEBUG << "ReadOnlyWaveFileModel(" << objectName() << ")::fillTimerTimedOut: no thread" << endl; #endif - emit modelChanged(); + emit modelChanged(getId()); } } @@ -607,10 +616,10 @@ SVDEBUG << "ReadOnlyWaveFileModel(" << objectName() << ")::cacheFilled, about to emit things" << endl; #endif if (getEndFrame() > prevFillExtent) { - emit modelChangedWithin(prevFillExtent, getEndFrame()); + emit modelChangedWithin(getId(), prevFillExtent, getEndFrame()); } - emit modelChanged(); - emit ready(); + emit modelChanged(getId()); + emit ready(getId()); } void diff -r 649ac57c5a2d -r 85b9b466a59f data/model/RegionModel.h --- a/data/model/RegionModel.h Thu Jun 20 14:58:20 2019 +0100 +++ b/data/model/RegionModel.h Wed Jul 17 14:24:51 2019 +0100 @@ -50,6 +50,7 @@ m_valueQuantization(0), m_haveDistinctValues(false), m_notifier(this, + getId(), notifyOnAdd ? DeferredNotifier::NOTIFY_ALWAYS : DeferredNotifier::NOTIFY_DEFERRED), @@ -67,6 +68,7 @@ m_valueQuantization(0), m_haveDistinctValues(false), m_notifier(this, + getId(), notifyOnAdd ? DeferredNotifier::NOTIFY_ALWAYS : DeferredNotifier::NOTIFY_DEFERRED), @@ -120,12 +122,12 @@ m_notifier.makeDeferredNotifications(); } - emit completionChanged(); + emit completionChanged(getId()); if (completion == 100) { // henceforth: m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS); - emit modelChanged(); + emit modelChanged(getId()); } } @@ -197,7 +199,7 @@ m_notifier.update(e.getFrame(), e.getDuration() + m_resolution); if (allChange) { - emit modelChanged(); + emit modelChanged(getId()); } } @@ -206,7 +208,8 @@ QMutexLocker locker(&m_mutex); m_events.remove(e); } - emit modelChangedWithin(e.getFrame(), + emit modelChangedWithin(getId(), + e.getFrame(), e.getFrame() + e.getDuration() + m_resolution); } @@ -291,8 +294,7 @@ case 4: e1 = e0.withLabel(value.toString()); break; } - ChangeEventsCommand *command = - new ChangeEventsCommand(this, tr("Edit Data")); + auto command = new ChangeEventsCommand(getId().untyped, tr("Edit Data")); command->remove(e0); command->add(e1); return command->finish(); diff -r 649ac57c5a2d -r 85b9b466a59f data/model/SparseOneDimensionalModel.h --- a/data/model/SparseOneDimensionalModel.h Thu Jun 20 14:58:20 2019 +0100 +++ b/data/model/SparseOneDimensionalModel.h Wed Jul 17 14:24:51 2019 +0100 @@ -49,15 +49,18 @@ m_resolution(resolution), m_haveTextLabels(false), m_notifier(this, + getId(), notifyOnAdd ? DeferredNotifier::NOTIFY_ALWAYS : DeferredNotifier::NOTIFY_DEFERRED), m_completion(100) { - PlayParameterRepository::getInstance()->addPlayable(this); + PlayParameterRepository::getInstance()->addPlayable + (getId().untyped, this); } virtual ~SparseOneDimensionalModel() { - PlayParameterRepository::getInstance()->removePlayable(this); + PlayParameterRepository::getInstance()->removePlayable + (getId().untyped); } QString getTypeName() const override { return tr("Sparse 1-D"); } @@ -95,12 +98,12 @@ m_notifier.makeDeferredNotifications(); } - emit completionChanged(); + emit completionChanged(getId()); if (completion == 100) { // henceforth: m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS); - emit modelChanged(); + emit modelChanged(getId()); } } @@ -164,7 +167,8 @@ { QMutexLocker locker(&m_mutex); m_events.remove(e); } - emit modelChangedWithin(e.getFrame(), e.getFrame() + m_resolution); + emit modelChangedWithin(getId(), + e.getFrame(), e.getFrame() + m_resolution); } /** @@ -239,8 +243,7 @@ case 2: e1 = e0.withLabel(value.toString()); break; } - ChangeEventsCommand *command = - new ChangeEventsCommand(this, tr("Edit Data")); + auto command = new ChangeEventsCommand(getId().untyped, tr("Edit Data")); command->remove(e0); command->add(e1); return command->finish(); diff -r 649ac57c5a2d -r 85b9b466a59f data/model/SparseTimeValueModel.h --- a/data/model/SparseTimeValueModel.h Thu Jun 20 14:58:20 2019 +0100 +++ b/data/model/SparseTimeValueModel.h Wed Jul 17 14:24:51 2019 +0100 @@ -48,13 +48,15 @@ m_haveExtents(false), m_haveTextLabels(false), m_notifier(this, + getId(), notifyOnAdd ? DeferredNotifier::NOTIFY_ALWAYS : DeferredNotifier::NOTIFY_DEFERRED), m_completion(100) { // Model is playable, but may not sound (if units not Hz or // range unsuitable) - PlayParameterRepository::getInstance()->addPlayable(this); + PlayParameterRepository::getInstance()->addPlayable + (getId().untyped, this); } SparseTimeValueModel(sv_samplerate_t sampleRate, int resolution, @@ -67,17 +69,20 @@ m_haveExtents(true), m_haveTextLabels(false), m_notifier(this, + getId(), notifyOnAdd ? DeferredNotifier::NOTIFY_ALWAYS : DeferredNotifier::NOTIFY_DEFERRED), m_completion(100) { // Model is playable, but may not sound (if units not Hz or // range unsuitable) - PlayParameterRepository::getInstance()->addPlayable(this); + PlayParameterRepository::getInstance()->addPlayable + (getId().untyped, this); } virtual ~SparseTimeValueModel() { - PlayParameterRepository::getInstance()->removePlayable(this); + PlayParameterRepository::getInstance()->removePlayable + (getId().untyped); } QString getTypeName() const override { return tr("Sparse Time-Value"); } @@ -124,12 +129,12 @@ m_notifier.makeDeferredNotifications(); } - emit completionChanged(); + emit completionChanged(getId()); if (completion == 100) { // henceforth: m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS); - emit modelChanged(); + emit modelChanged(getId()); } } @@ -202,7 +207,7 @@ m_notifier.update(e.getFrame(), m_resolution); if (allChange) { - emit modelChanged(); + emit modelChanged(getId()); } } @@ -211,7 +216,8 @@ QMutexLocker locker(&m_mutex); m_events.remove(e); } - emit modelChangedWithin(e.getFrame(), e.getFrame() + m_resolution); + emit modelChangedWithin(getId(), + e.getFrame(), e.getFrame() + m_resolution); } /** @@ -289,8 +295,7 @@ case 3: e1 = e0.withLabel(value.toString()); break; } - ChangeEventsCommand *command = - new ChangeEventsCommand(this, tr("Edit Data")); + auto command = new ChangeEventsCommand(getId().untyped, tr("Edit Data")); command->remove(e0); command->add(e1); return command->finish(); diff -r 649ac57c5a2d -r 85b9b466a59f data/model/TextModel.h --- a/data/model/TextModel.h Thu Jun 20 14:58:20 2019 +0100 +++ b/data/model/TextModel.h Wed Jul 17 14:24:51 2019 +0100 @@ -46,6 +46,7 @@ m_sampleRate(sampleRate), m_resolution(resolution), m_notifier(this, + getId(), notifyOnAdd ? DeferredNotifier::NOTIFY_ALWAYS : DeferredNotifier::NOTIFY_DEFERRED), @@ -82,12 +83,12 @@ m_notifier.makeDeferredNotifications(); } - emit completionChanged(); + emit completionChanged(getId()); if (completion == 100) { // henceforth: m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS); - emit modelChanged(); + emit modelChanged(getId()); } } @@ -147,7 +148,8 @@ { QMutexLocker locker(&m_mutex); m_events.remove(e); } - emit modelChangedWithin(e.getFrame(), e.getFrame() + m_resolution); + emit modelChangedWithin(getId(), + e.getFrame(), e.getFrame() + m_resolution); } /** @@ -226,8 +228,7 @@ case 3: e1 = e0.withLabel(value.toString()); break; } - ChangeEventsCommand *command = - new ChangeEventsCommand(this, tr("Edit Data")); + auto command = new ChangeEventsCommand(getId().untyped, tr("Edit Data")); command->remove(e0); command->add(e1); return command->finish(); diff -r 649ac57c5a2d -r 85b9b466a59f data/model/WaveformOversampler.cpp --- a/data/model/WaveformOversampler.cpp Thu Jun 20 14:58:20 2019 +0100 +++ b/data/model/WaveformOversampler.cpp Wed Jul 17 14:24:51 2019 +0100 @@ -19,7 +19,7 @@ #include "data/model/DenseTimeValueModel.h" floatvec_t -WaveformOversampler::getOversampledData(const DenseTimeValueModel *source, +WaveformOversampler::getOversampledData(const DenseTimeValueModel &source, int channel, sv_frame_t sourceStartFrame, sv_frame_t sourceFrameCount, @@ -53,14 +53,14 @@ } floatvec_t -WaveformOversampler::getFixedRatioData(const DenseTimeValueModel *source, +WaveformOversampler::getFixedRatioData(const DenseTimeValueModel &source, int channel, sv_frame_t sourceStartFrame, sv_frame_t sourceFrameCount) { Profiler profiler("WaveformOversampler::getFixedRatioData"); - sv_frame_t sourceLength = source->getEndFrame(); + sv_frame_t sourceLength = source.getEndFrame(); if (sourceStartFrame + sourceFrameCount > sourceLength) { sourceFrameCount = sourceLength - sourceStartFrame; @@ -84,7 +84,7 @@ i1 = sourceLength; } - floatvec_t sourceData = source->getData(channel, i0, i1 - i0); + floatvec_t sourceData = source.getData(channel, i0, i1 - i0); for (sv_frame_t i = i0; i < i1; ++i) { float v = sourceData[i - i0]; diff -r 649ac57c5a2d -r 85b9b466a59f data/model/WaveformOversampler.h --- a/data/model/WaveformOversampler.h Thu Jun 20 14:58:20 2019 +0100 +++ b/data/model/WaveformOversampler.h Wed Jul 17 14:24:51 2019 +0100 @@ -38,14 +38,14 @@ * will have sourceFrameCount * oversampleBy samples, except when * truncated because the end of the model was reached. */ - static floatvec_t getOversampledData(const DenseTimeValueModel *source, + static floatvec_t getOversampledData(const DenseTimeValueModel &source, int channel, sv_frame_t sourceStartFrame, sv_frame_t sourceFrameCount, int oversampleBy); private: - static floatvec_t getFixedRatioData(const DenseTimeValueModel *source, + static floatvec_t getFixedRatioData(const DenseTimeValueModel &source, int channel, sv_frame_t sourceStartFrame, sv_frame_t sourceFrameCount); diff -r 649ac57c5a2d -r 85b9b466a59f data/model/WritableWaveFileModel.cpp --- a/data/model/WritableWaveFileModel.cpp Thu Jun 20 14:58:20 2019 +0100 +++ b/data/model/WritableWaveFileModel.cpp Wed Jul 17 14:24:51 2019 +0100 @@ -19,6 +19,7 @@ #include "base/TempDirectory.h" #include "base/Exceptions.h" +#include "base/PlayParameterRepository.h" #include "fileio/WavFileWriter.h" #include "fileio/WavFileReader.h" @@ -96,7 +97,8 @@ // so the filename only needs to be unique within that - // model ID should be ok QDir dir(TempDirectory::getInstance()->getPath()); - path = dir.filePath(QString("written_%1.wav").arg(getId())); + path = dir.filePath(QString("written_%1.wav") + .arg(getId().untyped)); } catch (const DirectoryCreationFailed &f) { SVCERR << "WritableWaveFileModel: Failed to create temporary directory" << endl; return; @@ -126,7 +128,8 @@ // Temp dir is exclusive to this run of the application, so // the filename only needs to be unique within that QDir dir(TempDirectory::getInstance()->getPath()); - m_temporaryPath = dir.filePath(QString("prenorm_%1.wav").arg(getId())); + m_temporaryPath = dir.filePath(QString("prenorm_%1.wav") + .arg(getId().untyped)); m_temporaryWriter = new WavFileWriter (m_temporaryPath, m_sampleRate, m_channels, @@ -158,10 +161,16 @@ connect(m_model, SIGNAL(modelChanged()), this, SIGNAL(modelChanged())); connect(m_model, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)), this, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t))); + + PlayParameterRepository::getInstance()->addPlayable + (getId().untyped, this); } WritableWaveFileModel::~WritableWaveFileModel() { + PlayParameterRepository::getInstance()->removePlayable + (getId().untyped); + delete m_model; delete m_targetWriter; delete m_temporaryWriter; @@ -247,8 +256,8 @@ m_reader->updateDone(); m_proportion = 100; - emit modelChanged(); - emit writeCompleted(); + emit modelChanged(getId()); + emit writeCompleted(getId()); } void diff -r 649ac57c5a2d -r 85b9b466a59f data/model/WritableWaveFileModel.h --- a/data/model/WritableWaveFileModel.h Thu Jun 20 14:58:20 2019 +0100 +++ b/data/model/WritableWaveFileModel.h Wed Jul 17 14:24:51 2019 +0100 @@ -197,7 +197,7 @@ QString extraAttributes = "") const override; signals: - void writeCompleted(); + void writeCompleted(ModelId); protected: ReadOnlyWaveFileModel *m_model; diff -r 649ac57c5a2d -r 85b9b466a59f data/model/test/TestFFTModel.h --- a/data/model/test/TestFFTModel.h Thu Jun 20 14:58:20 2019 +0100 +++ b/data/model/test/TestFFTModel.h Wed Jul 17 14:24:51 2019 +0100 @@ -35,7 +35,7 @@ Q_OBJECT private: - void test(DenseTimeValueModel *model, + void test(ModelId model, // a DenseTimeValueModel WindowType window, int windowSize, int windowIncrement, int fftSize, int columnNo, vector>> expectedValues, int expectedWidth) { @@ -88,6 +88,15 @@ } } + ModelId makeMock(std::vector sorts, int length, int pad) { + auto mwm = std::make_shared(sorts, length, pad); + return ModelById::add(mwm); + } + + void releaseMock(ModelId id) { + ModelById::release(id); + } + private slots: // NB. FFTModel columns are centred on the sample frame, and in @@ -101,153 +110,163 @@ // are those of our expected signal. void dc_simple_rect() { - MockWaveModel mwm({ DC }, 16, 4); - test(&mwm, RectangularWindow, 8, 8, 8, 0, + auto mwm = makeMock({ DC }, 16, 4); + test(mwm, RectangularWindow, 8, 8, 8, 0, { { {}, {}, {}, {}, {} } }, 4); - test(&mwm, RectangularWindow, 8, 8, 8, 1, + test(mwm, RectangularWindow, 8, 8, 8, 1, { { { 4.f, 0.f }, {}, {}, {}, {} } }, 4); - test(&mwm, RectangularWindow, 8, 8, 8, 2, + test(mwm, RectangularWindow, 8, 8, 8, 2, { { { 4.f, 0.f }, {}, {}, {}, {} } }, 4); - test(&mwm, RectangularWindow, 8, 8, 8, 3, + test(mwm, RectangularWindow, 8, 8, 8, 3, { { {}, {}, {}, {}, {} } }, 4); + releaseMock(mwm); } void dc_simple_hann() { // The Hann window function is a simple sinusoid with period // equal to twice the window size, and it halves the DC energy - MockWaveModel mwm({ DC }, 16, 4); - test(&mwm, HanningWindow, 8, 8, 8, 0, + auto mwm = makeMock({ DC }, 16, 4); + test(mwm, HanningWindow, 8, 8, 8, 0, { { {}, {}, {}, {}, {} } }, 4); - test(&mwm, HanningWindow, 8, 8, 8, 1, + test(mwm, HanningWindow, 8, 8, 8, 1, { { { 4.f, 0.f }, { 2.f, 0.f }, {}, {}, {} } }, 4); - test(&mwm, HanningWindow, 8, 8, 8, 2, + test(mwm, HanningWindow, 8, 8, 8, 2, { { { 4.f, 0.f }, { 2.f, 0.f }, {}, {}, {} } }, 4); - test(&mwm, HanningWindow, 8, 8, 8, 3, + test(mwm, HanningWindow, 8, 8, 8, 3, { { {}, {}, {}, {}, {} } }, 4); + releaseMock(mwm); } void dc_simple_hann_halfoverlap() { - MockWaveModel mwm({ DC }, 16, 4); - test(&mwm, HanningWindow, 8, 4, 8, 0, + auto mwm = makeMock({ DC }, 16, 4); + test(mwm, HanningWindow, 8, 4, 8, 0, { { {}, {}, {}, {}, {} } }, 7); - test(&mwm, HanningWindow, 8, 4, 8, 2, + test(mwm, HanningWindow, 8, 4, 8, 2, { { { 4.f, 0.f }, { 2.f, 0.f }, {}, {}, {} } }, 7); - test(&mwm, HanningWindow, 8, 4, 8, 3, + test(mwm, HanningWindow, 8, 4, 8, 3, { { { 4.f, 0.f }, { 2.f, 0.f }, {}, {}, {} } }, 7); - test(&mwm, HanningWindow, 8, 4, 8, 6, + test(mwm, HanningWindow, 8, 4, 8, 6, { { {}, {}, {}, {}, {} } }, 7); + releaseMock(mwm); } void sine_simple_rect() { - MockWaveModel mwm({ Sine }, 16, 4); + auto mwm = makeMock({ Sine }, 16, 4); // Sine: output is purely imaginary. Note the sign is flipped // (normally the first half of the output would have negative // sign for a sine starting at 0) because the model does an // FFT shift to centre the phase - test(&mwm, RectangularWindow, 8, 8, 8, 0, + test(mwm, RectangularWindow, 8, 8, 8, 0, { { {}, {}, {}, {}, {} } }, 4); - test(&mwm, RectangularWindow, 8, 8, 8, 1, + test(mwm, RectangularWindow, 8, 8, 8, 1, { { {}, { 0.f, 2.f }, {}, {}, {} } }, 4); - test(&mwm, RectangularWindow, 8, 8, 8, 2, + test(mwm, RectangularWindow, 8, 8, 8, 2, { { {}, { 0.f, 2.f }, {}, {}, {} } }, 4); - test(&mwm, RectangularWindow, 8, 8, 8, 3, + test(mwm, RectangularWindow, 8, 8, 8, 3, { { {}, {}, {}, {}, {} } }, 4); + releaseMock(mwm); } void cosine_simple_rect() { - MockWaveModel mwm({ Cosine }, 16, 4); + auto mwm = makeMock({ Cosine }, 16, 4); // Cosine: output is purely real. Note the sign is flipped // because the model does an FFT shift to centre the phase - test(&mwm, RectangularWindow, 8, 8, 8, 0, + test(mwm, RectangularWindow, 8, 8, 8, 0, { { {}, {}, {}, {}, {} } }, 4); - test(&mwm, RectangularWindow, 8, 8, 8, 1, + test(mwm, RectangularWindow, 8, 8, 8, 1, { { {}, { -2.f, 0.f }, {}, {}, {} } }, 4); - test(&mwm, RectangularWindow, 8, 8, 8, 2, + test(mwm, RectangularWindow, 8, 8, 8, 2, { { {}, { -2.f, 0.f }, {}, {}, {} } }, 4); - test(&mwm, RectangularWindow, 8, 8, 8, 3, + test(mwm, RectangularWindow, 8, 8, 8, 3, { { {}, {}, {}, {}, {} } }, 4); + releaseMock(mwm); } void twochan_simple_rect() { - MockWaveModel mwm({ Sine, Cosine }, 16, 4); + auto mwm = makeMock({ Sine, Cosine }, 16, 4); // Test that the two channels are read and converted separately - test(&mwm, RectangularWindow, 8, 8, 8, 0, + test(mwm, RectangularWindow, 8, 8, 8, 0, { { {}, {}, {}, {}, {} }, { {}, {}, {}, {}, {} } }, 4); - test(&mwm, RectangularWindow, 8, 8, 8, 1, + test(mwm, RectangularWindow, 8, 8, 8, 1, { { {}, { 0.f, 2.f }, {}, {}, {} }, { {}, { -2.f, 0.f }, {}, {}, {} } }, 4); - test(&mwm, RectangularWindow, 8, 8, 8, 2, + test(mwm, RectangularWindow, 8, 8, 8, 2, { { {}, { 0.f, 2.f }, {}, {}, {} }, { {}, { -2.f, 0.f }, {}, {}, {} } }, 4); - test(&mwm, RectangularWindow, 8, 8, 8, 3, + test(mwm, RectangularWindow, 8, 8, 8, 3, { { {}, {}, {}, {}, {} }, { {}, {}, {}, {}, {} } }, 4); + releaseMock(mwm); } void nyquist_simple_rect() { - MockWaveModel mwm({ Nyquist }, 16, 4); + auto mwm = makeMock({ Nyquist }, 16, 4); // Again, the sign is flipped. This has the same amount of // energy as the DC example - test(&mwm, RectangularWindow, 8, 8, 8, 0, + test(mwm, RectangularWindow, 8, 8, 8, 0, { { {}, {}, {}, {}, {} } }, 4); - test(&mwm, RectangularWindow, 8, 8, 8, 1, + test(mwm, RectangularWindow, 8, 8, 8, 1, { { {}, {}, {}, {}, { -4.f, 0.f } } }, 4); - test(&mwm, RectangularWindow, 8, 8, 8, 2, + test(mwm, RectangularWindow, 8, 8, 8, 2, { { {}, {}, {}, {}, { -4.f, 0.f } } }, 4); - test(&mwm, RectangularWindow, 8, 8, 8, 3, + test(mwm, RectangularWindow, 8, 8, 8, 3, { { {}, {}, {}, {}, {} } }, 4); + releaseMock(mwm); } void dirac_simple_rect() { - MockWaveModel mwm({ Dirac }, 16, 4); + auto mwm = makeMock({ Dirac }, 16, 4); // The window scales by 0.5 and some signs are flipped. Only // column 1 has any data (the single impulse). - test(&mwm, RectangularWindow, 8, 8, 8, 0, + test(mwm, RectangularWindow, 8, 8, 8, 0, { { {}, {}, {}, {}, {} } }, 4); - test(&mwm, RectangularWindow, 8, 8, 8, 1, + test(mwm, RectangularWindow, 8, 8, 8, 1, { { { 0.5f, 0.f }, { -0.5f, 0.f }, { 0.5f, 0.f }, { -0.5f, 0.f }, { 0.5f, 0.f } } }, 4); - test(&mwm, RectangularWindow, 8, 8, 8, 2, + test(mwm, RectangularWindow, 8, 8, 8, 2, { { {}, {}, {}, {}, {} } }, 4); - test(&mwm, RectangularWindow, 8, 8, 8, 3, + test(mwm, RectangularWindow, 8, 8, 8, 3, { { {}, {}, {}, {}, {} } }, 4); + releaseMock(mwm); } void dirac_simple_rect_2() { - MockWaveModel mwm({ Dirac }, 16, 8); + auto mwm = makeMock({ Dirac }, 16, 8); // With 8 samples padding, the FFT shift places the first // Dirac impulse at the start of column 1, thus giving all // positive values - test(&mwm, RectangularWindow, 8, 8, 8, 0, + test(mwm, RectangularWindow, 8, 8, 8, 0, { { {}, {}, {}, {}, {} } }, 5); - test(&mwm, RectangularWindow, 8, 8, 8, 1, + test(mwm, RectangularWindow, 8, 8, 8, 1, { { { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f } } }, 5); - test(&mwm, RectangularWindow, 8, 8, 8, 2, + test(mwm, RectangularWindow, 8, 8, 8, 2, { { {}, {}, {}, {}, {} } }, 5); - test(&mwm, RectangularWindow, 8, 8, 8, 3, + test(mwm, RectangularWindow, 8, 8, 8, 3, { { {}, {}, {}, {}, {} } }, 5); - test(&mwm, RectangularWindow, 8, 8, 8, 4, + test(mwm, RectangularWindow, 8, 8, 8, 4, { { {}, {}, {}, {}, {} } }, 5); + releaseMock(mwm); } void dirac_simple_rect_halfoverlap() { - MockWaveModel mwm({ Dirac }, 16, 4); - test(&mwm, RectangularWindow, 8, 4, 8, 0, + auto mwm = makeMock({ Dirac }, 16, 4); + test(mwm, RectangularWindow, 8, 4, 8, 0, { { {}, {}, {}, {}, {} } }, 7); - test(&mwm, RectangularWindow, 8, 4, 8, 1, + test(mwm, RectangularWindow, 8, 4, 8, 1, { { { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f } } }, 7); - test(&mwm, RectangularWindow, 8, 4, 8, 2, + test(mwm, RectangularWindow, 8, 4, 8, 2, { { { 0.5f, 0.f }, { -0.5f, 0.f }, { 0.5f, 0.f }, { -0.5f, 0.f }, { 0.5f, 0.f } } }, 7); - test(&mwm, RectangularWindow, 8, 4, 8, 3, + test(mwm, RectangularWindow, 8, 4, 8, 3, { { {}, {}, {}, {}, {} } }, 7); + releaseMock(mwm); } }; diff -r 649ac57c5a2d -r 85b9b466a59f data/model/test/TestSparseModels.h --- a/data/model/test/TestSparseModels.h Thu Jun 20 14:58:20 2019 +0100 +++ b/data/model/test/TestSparseModels.h Wed Jul 17 14:24:51 2019 +0100 @@ -18,7 +18,7 @@ #include "../SparseOneDimensionalModel.h" #include "../NoteModel.h" #include "../TextModel.h" -#include "../PathModel.h" +#include "../Path.h" #include "../ImageModel.h" #include @@ -252,7 +252,7 @@ } void path_xml() { - PathModel m(100, 10, false); + Path m(100, 10); PathPoint p1(20, 30); PathPoint p2(40, 60); PathPoint p3(50, 49); diff -r 649ac57c5a2d -r 85b9b466a59f data/model/test/TestWaveformOversampler.h --- a/data/model/test/TestWaveformOversampler.h Thu Jun 20 14:58:20 2019 +0100 +++ b/data/model/test/TestWaveformOversampler.h Wed Jul 17 14:24:51 2019 +0100 @@ -72,7 +72,7 @@ sv_frame_t sourceFrameCount, int oversampleBy) { return WaveformOversampler::getOversampledData - (m_sourceModel, 0, + (*m_sourceModel, 0, sourceStartFrame, sourceFrameCount, oversampleBy); } diff -r 649ac57c5a2d -r 85b9b466a59f files.pri --- a/files.pri Thu Jun 20 14:58:20 2019 +0100 +++ b/files.pri Wed Jul 17 14:24:51 2019 +0100 @@ -3,6 +3,7 @@ base/AudioPlaySource.h \ base/AudioRecordTarget.h \ base/BaseTypes.h \ + base/ById.h \ base/Clipboard.h \ base/ColumnOp.h \ base/Command.h \ @@ -89,7 +90,7 @@ data/model/Model.h \ data/model/ModelDataTableModel.h \ data/model/NoteModel.h \ - data/model/PathModel.h \ + data/model/Path.h \ data/model/PowerOfSqrtTwoZoomConstraint.h \ data/model/PowerOfTwoZoomConstraint.h \ data/model/RangeSummarisableTimeValueModel.h \ @@ -149,6 +150,7 @@ SVCORE_SOURCES = \ base/AudioLevel.cpp \ + base/ById.cpp \ base/Clipboard.cpp \ base/ColumnOp.cpp \ base/Command.cpp \ diff -r 649ac57c5a2d -r 85b9b466a59f rdf/RDFImporter.cpp --- a/rdf/RDFImporter.cpp Thu Jun 20 14:58:20 2019 +0100 +++ b/rdf/RDFImporter.cpp Wed Jul 17 14:24:51 2019 +0100 @@ -58,7 +58,7 @@ bool isOK(); QString getErrorString() const; - std::vector getDataModels(ProgressReporter *); + std::vector getDataModels(ProgressReporter *); protected: BasicStore *m_store; @@ -66,22 +66,22 @@ QString m_uristring; QString m_errorString; - std::map m_audioModelMap; + std::map m_audioModelMap; sv_samplerate_t m_sampleRate; - std::map > m_labelValueMap; + std::map > m_labelValueMap; - void getDataModelsAudio(std::vector &, ProgressReporter *); - void getDataModelsSparse(std::vector &, ProgressReporter *); - void getDataModelsDense(std::vector &, ProgressReporter *); + void getDataModelsAudio(std::vector &, ProgressReporter *); + void getDataModelsSparse(std::vector &, ProgressReporter *); + void getDataModelsDense(std::vector &, ProgressReporter *); - void getDenseModelTitle(Model *, QString, QString); + QString getDenseModelTitle(QString featureUri, QString featureTypeUri); void getDenseFeatureProperties(QString featureUri, sv_samplerate_t &sampleRate, int &windowLength, int &hopSize, int &width, int &height); - void fillModel(Model *, sv_frame_t, sv_frame_t, + void fillModel(ModelId, sv_frame_t, sv_frame_t, bool, std::vector &, QString); }; @@ -119,7 +119,7 @@ return m_d->getErrorString(); } -std::vector +std::vector RDFImporter::getDataModels(ProgressReporter *r) { return m_d->getDataModels(r); @@ -169,10 +169,10 @@ return m_errorString; } -std::vector +std::vector RDFImporterImpl::getDataModels(ProgressReporter *reporter) { - std::vector models; + std::vector models; getDataModelsAudio(models, reporter); @@ -206,7 +206,7 @@ } void -RDFImporterImpl::getDataModelsAudio(std::vector &models, +RDFImporterImpl::getDataModelsAudio(std::vector &models, ProgressReporter *reporter) { Nodes sigs = m_store->match @@ -270,24 +270,25 @@ reporter->setMessage(RDFImporter::tr("Importing audio referenced in RDF...")); } fs->waitForData(); - ReadOnlyWaveFileModel *newModel = new ReadOnlyWaveFileModel(*fs, m_sampleRate); + auto newModel = std::make_shared + (*fs, m_sampleRate); if (newModel->isOK()) { cerr << "Successfully created wave file model from source at \"" << source << "\"" << endl; - models.push_back(newModel); - m_audioModelMap[signal] = newModel; + auto modelId = ModelById::add(newModel); + models.push_back(modelId); + m_audioModelMap[signal] = modelId; if (m_sampleRate == 0) { m_sampleRate = newModel->getSampleRate(); } } else { m_errorString = QString("Failed to create wave file model from source at \"%1\"").arg(source); - delete newModel; } delete fs; } } void -RDFImporterImpl::getDataModelsDense(std::vector &models, +RDFImporterImpl::getDataModelsDense(std::vector &models, ProgressReporter *reporter) { if (reporter) { @@ -342,7 +343,7 @@ if (height == 1) { - SparseTimeValueModel *m = new SparseTimeValueModel + auto m = std::make_shared (sampleRate, hopSize, false); for (int j = 0; j < values.size(); ++j) { @@ -351,16 +352,13 @@ m->add(e); } - getDenseModelTitle(m, feature, type); - + m->setObjectName(getDenseModelTitle(feature, type)); m->setRDFTypeURI(type); - - models.push_back(m); + models.push_back(ModelById::add(m)); } else { - EditableDenseThreeDimensionalModel *m = - new EditableDenseThreeDimensionalModel + auto m = std::make_shared (sampleRate, hopSize, height, EditableDenseThreeDimensionalModel::NoCompression, false); @@ -380,18 +378,15 @@ m->setColumn(x++, column); } - getDenseModelTitle(m, feature, type); - + m->setObjectName(getDenseModelTitle(feature, type)); m->setRDFTypeURI(type); - - models.push_back(m); + models.push_back(ModelById::add(m)); } } } -void -RDFImporterImpl::getDenseModelTitle(Model *m, - QString featureUri, +QString +RDFImporterImpl::getDenseModelTitle(QString featureUri, QString featureTypeUri) { Node n = m_store->complete @@ -399,8 +394,7 @@ if (n.type == Node::Literal && n.value != "") { SVDEBUG << "RDFImporterImpl::getDenseModelTitle: Title (from signal) \"" << n.value << "\"" << endl; - m->setObjectName(n.value); - return; + return n.value; } n = m_store->complete @@ -408,11 +402,11 @@ if (n.type == Node::Literal && n.value != "") { SVDEBUG << "RDFImporterImpl::getDenseModelTitle: Title (from signal type) \"" << n.value << "\"" << endl; - m->setObjectName(n.value); - return; + return n.value; } SVDEBUG << "RDFImporterImpl::getDenseModelTitle: No title available for feature <" << featureUri << ">" << endl; + return {}; } void @@ -481,7 +475,7 @@ } void -RDFImporterImpl::getDataModelsSparse(std::vector &models, +RDFImporterImpl::getDataModelsSparse(std::vector &models, ProgressReporter *reporter) { if (reporter) { @@ -510,8 +504,8 @@ (Triple(Node(), expand("a"), expand("mo:Signal"))).subjects(); // Map from timeline uri to event type to dimensionality to - // presence of duration to model ptr. Whee! - std::map > > > + // presence of duration to model id. Whee! + std::map > > > modelMap; foreach (Node sig, sigs) { @@ -617,7 +611,7 @@ if (values.size() == 1) dimensions = 2; else if (values.size() > 1) dimensions = 3; - Model *model = nullptr; + ModelId modelId; if (modelMap[timeline][type][dimensions].find(haveDuration) == modelMap[timeline][type][dimensions].end()) { @@ -628,7 +622,9 @@ << ", time = " << time << ", duration = " << duration << endl; */ - + + Model *model = nullptr; + if (!haveDuration) { if (dimensions == 1) { @@ -680,16 +676,20 @@ } model->setObjectName(title); - modelMap[timeline][type][dimensions][haveDuration] = model; - models.push_back(model); + modelId = ModelById::add(std::shared_ptr(model)); + modelMap[timeline][type][dimensions][haveDuration] = modelId; + models.push_back(modelId); } - model = modelMap[timeline][type][dimensions][haveDuration]; + modelId = modelMap[timeline][type][dimensions][haveDuration]; - if (model) { - sv_frame_t ftime = RealTime::realTime2Frame(time, m_sampleRate); - sv_frame_t fduration = RealTime::realTime2Frame(duration, m_sampleRate); - fillModel(model, ftime, fduration, haveDuration, values, label); + if (!modelId.isNone()) { + sv_frame_t ftime = + RealTime::realTime2Frame(time, m_sampleRate); + sv_frame_t fduration = + RealTime::realTime2Frame(duration, m_sampleRate); + fillModel(modelId, ftime, fduration, + haveDuration, values, label); } } } @@ -697,7 +697,7 @@ } void -RDFImporterImpl::fillModel(Model *model, +RDFImporterImpl::fillModel(ModelId modelId, sv_frame_t ftime, sv_frame_t fduration, bool haveDuration, @@ -706,17 +706,13 @@ { // SVDEBUG << "RDFImporterImpl::fillModel: adding point at frame " << ftime << endl; - SparseOneDimensionalModel *sodm = - dynamic_cast(model); - if (sodm) { + if (auto sodm = ModelById::getAs(modelId)) { Event point(ftime, label); sodm->add(point); return; } - TextModel *tm = - dynamic_cast(model); - if (tm) { + if (auto tm = ModelById::getAs(modelId)) { Event e (ftime, values.empty() ? 0.5f : values[0] < 0.f ? 0.f : values[0] > 1.f ? 1.f : values[0], // I was young and feckless once too @@ -725,17 +721,13 @@ return; } - SparseTimeValueModel *stvm = - dynamic_cast(model); - if (stvm) { + if (auto stvm = ModelById::getAs(modelId)) { Event e(ftime, values.empty() ? 0.f : values[0], label); stvm->add(e); return; } - NoteModel *nm = - dynamic_cast(model); - if (nm) { + if (auto nm = ModelById::getAs(modelId)) { if (haveDuration) { float value = 0.f, level = 1.f; if (!values.empty()) { @@ -764,16 +756,14 @@ return; } - RegionModel *rm = - dynamic_cast(model); - if (rm) { + if (auto rm = ModelById::getAs(modelId)) { float value = 0.f; if (values.empty()) { // no values? map each unique label to a distinct value - if (m_labelValueMap[model].find(label) == m_labelValueMap[model].end()) { - m_labelValueMap[model][label] = rm->getValueMaximum() + 1.f; + if (m_labelValueMap[modelId].find(label) == m_labelValueMap[modelId].end()) { + m_labelValueMap[modelId][label] = rm->getValueMaximum() + 1.f; } - value = m_labelValueMap[model][label]; + value = m_labelValueMap[modelId][label]; } else { value = values[0]; } diff -r 649ac57c5a2d -r 85b9b466a59f rdf/RDFImporter.h --- a/rdf/RDFImporter.h Thu Jun 20 14:58:20 2019 +0100 +++ b/rdf/RDFImporter.h Wed Jul 17 14:24:51 2019 +0100 @@ -22,8 +22,8 @@ #include #include "base/BaseTypes.h" +#include "data/model/Model.h" -class Model; class RDFImporterImpl; class ProgressReporter; @@ -47,7 +47,12 @@ bool isOK(); QString getErrorString() const; - std::vector getDataModels(ProgressReporter *reporter); + /** + * Return a list of models imported from the RDF source. The + * models have been newly created and registered with ById; the + * caller must arrange to release them. + */ + std::vector getDataModels(ProgressReporter *reporter); enum RDFDocumentType { AudioRefAndAnnotations, diff -r 649ac57c5a2d -r 85b9b466a59f transform/FeatureExtractionModelTransformer.cpp --- a/transform/FeatureExtractionModelTransformer.cpp Thu Jun 20 14:58:20 2019 +0100 +++ b/transform/FeatureExtractionModelTransformer.cpp Wed Jul 17 14:24:51 2019 +0100 @@ -83,7 +83,7 @@ // initialise based purely on the first transform in the list (but // first check that they are actually similar as promised) - for (int j = 1; j < (int)m_transforms.size(); ++j) { + for (int j = 1; in_range_for(m_transforms, j); ++j) { if (!areTransformsSimilar(m_transforms[0], m_transforms[j])) { m_message = tr("Transforms supplied to a single FeatureExtractionModelTransformer instance must be similar in every respect except plugin output"); SVCERR << m_message << endl; @@ -104,7 +104,7 @@ return false; } - DenseTimeValueModel *input = getConformingInput(); + auto input = ModelById::getAs(getInputModel()); if (!input) { m_message = tr("Input model for feature extraction plugin \"%1\" is of wrong type (internal error?)").arg(pluginId); SVCERR << m_message << endl; @@ -158,7 +158,9 @@ SVDEBUG << "Initialisation failed, trying again with preferred step = " << preferredStep << ", block = " << preferredBlock << endl; - if (!m_plugin->initialise(channelCount, preferredStep, preferredBlock)) { + if (!m_plugin->initialise(channelCount, + preferredStep, + preferredBlock)) { SVDEBUG << "Initialisation failed again" << endl; @@ -221,20 +223,22 @@ return false; } - for (int j = 0; j < (int)m_transforms.size(); ++j) { + for (int j = 0; in_range_for(m_transforms, j); ++j) { - for (int i = 0; i < (int)outputs.size(); ++i) { -// SVDEBUG << "comparing output " << i << " name \"" << outputs[i].identifier << "\" with expected \"" << m_transform.getOutput() << "\"" << endl; + for (int i = 0; in_range_for(outputs, i); ++i) { + if (m_transforms[j].getOutput() == "" || - outputs[i].identifier == m_transforms[j].getOutput().toStdString()) { + outputs[i].identifier == + m_transforms[j].getOutput().toStdString()) { + m_outputNos.push_back(i); - m_descriptors.push_back(new Vamp::Plugin::OutputDescriptor(outputs[i])); + m_descriptors.push_back(outputs[i]); m_fixedRateFeatureNos.push_back(-1); // we increment before use break; } } - if ((int)m_descriptors.size() <= j) { + if (!in_range_for(m_descriptors, j)) { m_message = tr("Plugin \"%1\" has no output named \"%2\"") .arg(pluginId) .arg(m_transforms[j].getOutput()); @@ -243,7 +247,7 @@ } } - for (int j = 0; j < (int)m_transforms.size(); ++j) { + for (int j = 0; in_range_for(m_transforms, j); ++j) { createOutputModels(j); } @@ -271,16 +275,15 @@ m_message = e.what(); } m_plugin = nullptr; - - for (int j = 0; j < (int)m_descriptors.size(); ++j) { - delete m_descriptors[j]; - } + + m_descriptors.clear(); } void FeatureExtractionModelTransformer::createOutputModels(int n) { - DenseTimeValueModel *input = getConformingInput(); + auto input = ModelById::getAs(getInputModel()); + if (!input) return; PluginRDFDescription description(m_transforms[n].getPluginIdentifier()); QString outputId = m_transforms[n].getOutput(); @@ -288,20 +291,17 @@ int binCount = 1; float minValue = 0.0, maxValue = 0.0; bool haveExtents = false; - bool haveBinCount = m_descriptors[n]->hasFixedBinCount; + bool haveBinCount = m_descriptors[n].hasFixedBinCount; if (haveBinCount) { - binCount = (int)m_descriptors[n]->binCount; + binCount = (int)m_descriptors[n].binCount; } m_needAdditionalModels[n] = false; -// cerr << "FeatureExtractionModelTransformer: output bin count " -// << binCount << endl; - - if (binCount > 0 && m_descriptors[n]->hasKnownExtents) { - minValue = m_descriptors[n]->minValue; - maxValue = m_descriptors[n]->maxValue; + if (binCount > 0 && m_descriptors[n].hasKnownExtents) { + minValue = m_descriptors[n].minValue; + maxValue = m_descriptors[n].maxValue; haveExtents = true; } @@ -309,10 +309,10 @@ sv_samplerate_t outputRate = modelRate; int modelResolution = 1; - if (m_descriptors[n]->sampleType != + if (m_descriptors[n].sampleType != Vamp::Plugin::OutputDescriptor::OneSamplePerStep) { - outputRate = m_descriptors[n]->sampleRate; + outputRate = m_descriptors[n].sampleRate; //!!! SV doesn't actually support display of models that have //!!! different underlying rates together -- so we always set @@ -328,7 +328,7 @@ } } - switch (m_descriptors[n]->sampleType) { + switch (m_descriptors[n].sampleType) { case Vamp::Plugin::OutputDescriptor::VariableSampleRate: if (outputRate != 0.0) { @@ -342,7 +342,7 @@ case Vamp::Plugin::OutputDescriptor::FixedSampleRate: if (outputRate <= 0.0) { - SVDEBUG << "WARNING: Fixed sample-rate plugin reports invalid sample rate " << m_descriptors[n]->sampleRate << "; defaulting to input rate of " << input->getSampleRate() << endl; + SVDEBUG << "WARNING: Fixed sample-rate plugin reports invalid sample rate " << m_descriptors[n].sampleRate << "; defaulting to input rate of " << input->getSampleRate() << endl; modelResolution = 1; } else { modelResolution = int(round(modelRate / outputRate)); @@ -353,21 +353,23 @@ bool preDurationPlugin = (m_plugin->getVampApiVersion() < 2); - Model *out = nullptr; + std::shared_ptr out; if (binCount == 0 && - (preDurationPlugin || !m_descriptors[n]->hasDuration)) { + (preDurationPlugin || !m_descriptors[n].hasDuration)) { // Anything with no value and no duration is an instant - out = new SparseOneDimensionalModel(modelRate, modelResolution, false); + out = std::make_shared + (modelRate, modelResolution, false); + QString outputEventTypeURI = description.getOutputEventTypeURI(outputId); out->setRDFTypeURI(outputEventTypeURI); } else if ((preDurationPlugin && binCount > 1 && - (m_descriptors[n]->sampleType == + (m_descriptors[n].sampleType == Vamp::Plugin::OutputDescriptor::VariableSampleRate)) || - (!preDurationPlugin && m_descriptors[n]->hasDuration)) { + (!preDurationPlugin && m_descriptors[n].hasDuration)) { // For plugins using the old v1 API without explicit duration, // we treat anything that has multiple bins (i.e. that has the @@ -398,9 +400,9 @@ // Regions do not have units of Hz or MIDI things (a sweeping // assumption!) - if (m_descriptors[n]->unit == "Hz" || - m_descriptors[n]->unit.find("MIDI") != std::string::npos || - m_descriptors[n]->unit.find("midi") != std::string::npos) { + if (m_descriptors[n].unit == "Hz" || + m_descriptors[n].unit.find("MIDI") != std::string::npos || + m_descriptors[n].unit.find("midi") != std::string::npos) { isNoteModel = true; } @@ -420,8 +422,8 @@ model = new NoteModel (modelRate, modelResolution, false); } - model->setScaleUnits(m_descriptors[n]->unit.c_str()); - out = model; + model->setScaleUnits(m_descriptors[n].unit.c_str()); + out.reset(model); } else { @@ -433,15 +435,15 @@ model = new RegionModel (modelRate, modelResolution, false); } - model->setScaleUnits(m_descriptors[n]->unit.c_str()); - out = model; + model->setScaleUnits(m_descriptors[n].unit.c_str()); + out.reset(model); } QString outputEventTypeURI = description.getOutputEventTypeURI(outputId); out->setRDFTypeURI(outputEventTypeURI); } else if (binCount == 1 || - (m_descriptors[n]->sampleType == + (m_descriptors[n].sampleType == Vamp::Plugin::OutputDescriptor::VariableSampleRate)) { // Anything that is not a 1D, note, or interval model and that @@ -482,7 +484,7 @@ Vamp::Plugin::OutputList outputs = m_plugin->getOutputDescriptors(); model->setScaleUnits(outputs[m_outputNos[n]].unit.c_str()); - out = model; + out.reset(model); QString outputEventTypeURI = description.getOutputEventTypeURI(outputId); out->setRDFTypeURI(outputEventTypeURI); @@ -499,23 +501,23 @@ EditableDenseThreeDimensionalModel::BasicMultirateCompression, false); - if (!m_descriptors[n]->binNames.empty()) { + if (!m_descriptors[n].binNames.empty()) { std::vector names; - for (int i = 0; i < (int)m_descriptors[n]->binNames.size(); ++i) { - names.push_back(m_descriptors[n]->binNames[i].c_str()); + for (int i = 0; i < (int)m_descriptors[n].binNames.size(); ++i) { + names.push_back(m_descriptors[n].binNames[i].c_str()); } model->setBinNames(names); } - out = model; + out.reset(model); QString outputSignalTypeURI = description.getOutputSignalTypeURI(outputId); out->setRDFTypeURI(outputSignalTypeURI); } if (out) { - out->setSourceModel(input); - m_outputs.push_back(out); + out->setSourceModel(getInputModel()); + m_outputs.push_back(ModelById::add(out)); } } @@ -540,13 +542,9 @@ FeatureExtractionModelTransformer::getAdditionalOutputModels() { Models mm; - for (AdditionalModelMap::iterator i = m_additionalModels.begin(); - i != m_additionalModels.end(); ++i) { - for (std::map::iterator j = - i->second.begin(); - j != i->second.end(); ++j) { - SparseTimeValueModel *m = j->second; - if (m) mm.push_back(m); + for (auto mp : m_additionalModels) { + for (auto m: mp.second) { + mm.push_back(m.second); } } return mm; @@ -555,34 +553,45 @@ bool FeatureExtractionModelTransformer::willHaveAdditionalOutputModels() { - for (std::map::const_iterator i = - m_needAdditionalModels.begin(); - i != m_needAdditionalModels.end(); ++i) { - if (i->second) return true; + for (auto p : m_needAdditionalModels) { + if (p.second) return true; } return false; } -SparseTimeValueModel * +ModelId FeatureExtractionModelTransformer::getAdditionalModel(int n, int binNo) { -// std::cerr << "getAdditionalModel(" << n << ", " << binNo << ")" << std::endl; - if (binNo == 0) { - std::cerr << "Internal error: binNo == 0 in getAdditionalModel (should be using primary model)" << std::endl; - return nullptr; + SVCERR << "Internal error: binNo == 0 in getAdditionalModel (should be using primary model, not calling getAdditionalModel)" << endl; + return {}; } - if (!m_needAdditionalModels[n]) return nullptr; - if (!isOutput(n)) return nullptr; - if (m_additionalModels[n][binNo]) return m_additionalModels[n][binNo]; + if (!in_range_for(m_outputs, n)) { + SVCERR << "getAdditionalModel: Output " << n << " out of range" << endl; + return {}; + } - std::cerr << "getAdditionalModel(" << n << ", " << binNo << "): creating" << std::endl; + if (!in_range_for(m_needAdditionalModels, n) || + !m_needAdditionalModels[n]) { + return {}; + } + + if (!m_additionalModels[n][binNo].isNone()) { + return m_additionalModels[n][binNo]; + } - SparseTimeValueModel *baseModel = getConformingOutput(n); - if (!baseModel) return nullptr; + SVDEBUG << "getAdditionalModel(" << n << ", " << binNo + << "): creating" << endl; - std::cerr << "getAdditionalModel(" << n << ", " << binNo << "): (from " << baseModel << ")" << std::endl; + auto baseModel = ModelById::getAs(m_outputs[n]); + if (!baseModel) { + SVCERR << "getAdditionalModel: Output model not conformable, or has vanished" << endl; + return {}; + } + + SVDEBUG << "getAdditionalModel(" << n << ", " << binNo + << "): (from " << baseModel << ")" << endl; SparseTimeValueModel *additional = new SparseTimeValueModel(baseModel->getSampleRate(), @@ -594,21 +603,10 @@ additional->setScaleUnits(baseModel->getScaleUnits()); additional->setRDFTypeURI(baseModel->getRDFTypeURI()); - m_additionalModels[n][binNo] = additional; - return additional; -} - -DenseTimeValueModel * -FeatureExtractionModelTransformer::getConformingInput() -{ -// SVDEBUG << "FeatureExtractionModelTransformer::getConformingInput: input model is " << getInputModel() << endl; - - DenseTimeValueModel *dtvm = - dynamic_cast(getInputModel()); - if (!dtvm) { - SVDEBUG << "FeatureExtractionModelTransformer::getConformingInput: WARNING: Input model is not conformable to DenseTimeValueModel" << endl; - } - return dtvm; + ModelId additionalId = ModelById::add + (std::shared_ptr(additional)); + m_additionalModels[n][binNo] = additionalId; + return additionalId; } void @@ -624,12 +622,6 @@ m_message = e.what(); return; } - - DenseTimeValueModel *input = getConformingInput(); - if (!input) { - abandon(); - return; - } if (m_outputs.empty()) { abandon(); @@ -638,19 +630,47 @@ Transform primaryTransform = m_transforms[0]; - while (!input->isReady() && !m_abandoned) { - SVDEBUG << "FeatureExtractionModelTransformer::run: Waiting for input model to be ready..." << endl; - usleep(500000); + bool ready = false; + while (!ready && !m_abandoned) { + { // scope so as to release input shared_ptr before sleeping + auto input = ModelById::getAs(getInputModel()); + if (!input) { + abandon(); + return; + } + ready = input->isReady(); + } + if (!ready) { + SVDEBUG << "FeatureExtractionModelTransformer::run: Waiting for input model to be ready..." << endl; + usleep(500000); + } } - SVDEBUG << "FeatureExtractionModelTransformer::run: Waited, ready = " - << input->isReady() << ", m_abandoned = " << m_abandoned << endl; if (m_abandoned) return; - sv_samplerate_t sampleRate = input->getSampleRate(); + ModelId inputId = getInputModel(); - int channelCount = input->getChannelCount(); - if ((int)m_plugin->getMaxChannelCount() < channelCount) { - channelCount = 1; + sv_samplerate_t sampleRate; + int channelCount; + sv_frame_t startFrame; + sv_frame_t endFrame; + + { // scope so as not to have this borrowed pointer retained around + // the edges of the process loop + auto input = ModelById::getAs(inputId); + if (!input) { + abandon(); + return; + } + + sampleRate = input->getSampleRate(); + + channelCount = input->getChannelCount(); + if ((int)m_plugin->getMaxChannelCount() < channelCount) { + channelCount = 1; + } + + startFrame = input->getStartFrame(); + endFrame = input->getEndFrame(); } float **buffers = new float*[channelCount]; @@ -669,29 +689,25 @@ if (frequencyDomain) { for (int ch = 0; ch < channelCount; ++ch) { FFTModel *model = new FFTModel - (getConformingInput(), - channelCount == 1 ? m_input.getChannel() : ch, - primaryTransform.getWindowType(), - blockSize, - stepSize, - blockSize); + (inputId, + channelCount == 1 ? m_input.getChannel() : ch, + primaryTransform.getWindowType(), + blockSize, + stepSize, + blockSize); if (!model->isOK() || model->getError() != "") { QString err = model->getError(); delete model; - for (int j = 0; j < (int)m_outputNos.size(); ++j) { + for (int j = 0; in_range_for(m_outputNos, j); ++j) { setCompletion(j, 100); } //!!! need a better way to handle this -- previously we were using a QMessageBox but that isn't an appropriate thing to do here either throw AllocationFailed("Failed to create the FFT model for this feature extraction model transformer: error is: " + err); } fftModels.push_back(model); - cerr << "created model for channel " << ch << endl; } } - sv_frame_t startFrame = m_input.getModel()->getStartFrame(); - sv_frame_t endFrame = m_input.getModel()->getEndFrame(); - RealTime contextStartRT = primaryTransform.getStartTime(); RealTime contextDurationRT = primaryTransform.getDuration(); @@ -716,7 +732,7 @@ long prevCompletion = 0; - for (int j = 0; j < (int)m_outputNos.size(); ++j) { + for (int j = 0; in_range_for(m_outputNos, j); ++j) { setCompletion(j, 0); } @@ -734,10 +750,14 @@ if (frequencyDomain) { if (blockFrame - int(blockSize)/2 > - contextStart + contextDuration) break; + contextStart + contextDuration) { + break; + } } else { if (blockFrame >= - contextStart + contextDuration) break; + contextStart + contextDuration) { + break; + } } #ifdef DEBUG_FEATURE_EXTRACTION_TRANSFORMER_RUN @@ -750,7 +770,39 @@ ((((blockFrame - contextStart) / stepSize) * 99) / (contextDuration / stepSize + 1)); - // channelCount is either m_input.getModel()->channelCount or 1 + bool haveAllModels = true; + if (!ModelById::get(inputId)) { +#ifdef DEBUG_FEATURE_EXTRACTION_TRANSFORMER_RUN + SVDEBUG << "FeatureExtractionModelTransformer::run: Input model " << inputId << " no longer exists" << endl; +#endif + haveAllModels = false; + } else { +#ifdef DEBUG_FEATURE_EXTRACTION_TRANSFORMER_RUN + SVDEBUG << "Input model " << inputId << " still exists" << endl; +#endif + } + for (auto mid: m_outputs) { + if (!ModelById::get(mid)) { +#ifdef DEBUG_FEATURE_EXTRACTION_TRANSFORMER_RUN + SVDEBUG << "FeatureExtractionModelTransformer::run: Output model " << mid << " no longer exists" << endl; +#endif + haveAllModels = false; + } else { +#ifdef DEBUG_FEATURE_EXTRACTION_TRANSFORMER_RUN + SVDEBUG << "Output model " << mid << " still exists" << endl; +#endif + } + } + if (!haveAllModels) { + abandon(); + break; + } + +#ifdef DEBUG_FEATURE_EXTRACTION_TRANSFORMER_RUN + SVDEBUG << "FeatureExtractionModelTransformer::run: All models still exist" << endl; +#endif + + // channelCount is either input->channelCount or 1 if (frequencyDomain) { for (int ch = 0; ch < channelCount; ++ch) { @@ -781,22 +833,22 @@ if (m_abandoned) break; - Vamp::Plugin::FeatureSet features = m_plugin->process + auto features = m_plugin->process (buffers, RealTime::frame2RealTime(blockFrame, sampleRate) .toVampRealTime()); if (m_abandoned) break; - for (int j = 0; j < (int)m_outputNos.size(); ++j) { - for (int fi = 0; fi < (int)features[m_outputNos[j]].size(); ++fi) { - Vamp::Plugin::Feature feature = features[m_outputNos[j]][fi]; + for (int j = 0; in_range_for(m_outputNos, j); ++j) { + for (int fi = 0; in_range_for(features[m_outputNos[j]], fi); ++fi) { + auto feature = features[m_outputNos[j]][fi]; addFeature(j, blockFrame, feature); } } if (blockFrame == contextStart || completion > prevCompletion) { - for (int j = 0; j < (int)m_outputNos.size(); ++j) { + for (int j = 0; in_range_for(m_outputNos, j); ++j) { setCompletion(j, completion); } prevCompletion = completion; @@ -807,12 +859,15 @@ } if (!m_abandoned) { - Vamp::Plugin::FeatureSet features = m_plugin->getRemainingFeatures(); + auto features = m_plugin->getRemainingFeatures(); - for (int j = 0; j < (int)m_outputNos.size(); ++j) { - for (int fi = 0; fi < (int)features[m_outputNos[j]].size(); ++fi) { - Vamp::Plugin::Feature feature = features[m_outputNos[j]][fi]; + for (int j = 0; in_range_for(m_outputNos, j); ++j) { + for (int fi = 0; in_range_for(features[m_outputNos[j]], fi); ++fi) { + auto feature = features[m_outputNos[j]][fi]; addFeature(j, blockFrame, feature); + if (m_abandoned) { + break; + } } } } @@ -863,8 +918,10 @@ startFrame = 0; } - DenseTimeValueModel *input = getConformingInput(); - if (!input) return; + auto input = ModelById::getAs(getInputModel()); + if (!input) { + return; + } sv_frame_t got = 0; @@ -907,7 +964,10 @@ sv_frame_t blockFrame, const Vamp::Plugin::Feature &feature) { - sv_samplerate_t inputRate = m_input.getModel()->getSampleRate(); + auto input = ModelById::get(getInputModel()); + if (!input) return; + + sv_samplerate_t inputRate = input->getSampleRate(); // cerr << "FeatureExtractionModelTransformer::addFeature: blockFrame = " // << blockFrame << ", hasTimestamp = " << feature.hasTimestamp @@ -917,7 +977,7 @@ sv_frame_t frame = blockFrame; - if (m_descriptors[n]->sampleType == + if (m_descriptors[n].sampleType == Vamp::Plugin::OutputDescriptor::VariableSampleRate) { if (!feature.hasTimestamp) { @@ -933,10 +993,10 @@ // cerr << "variable sample rate: timestamp = " << feature.timestamp // << " at input rate " << inputRate << " -> " << frame << endl; - } else if (m_descriptors[n]->sampleType == + } else if (m_descriptors[n].sampleType == Vamp::Plugin::OutputDescriptor::FixedSampleRate) { - sv_samplerate_t rate = m_descriptors[n]->sampleRate; + sv_samplerate_t rate = m_descriptors[n].sampleRate; if (rate <= 0.0) { rate = inputRate; } @@ -949,7 +1009,7 @@ } // cerr << "m_fixedRateFeatureNo = " << m_fixedRateFeatureNos[n] -// << ", m_descriptor->sampleRate = " << m_descriptors[n]->sampleRate +// << ", m_descriptor->sampleRate = " << m_descriptors[n].sampleRate // << ", inputRate = " << inputRate // << " giving frame = "; frame = lrint((double(m_fixedRateFeatureNos[n]) / rate) * inputRate); @@ -971,21 +1031,20 @@ // to, we instead test what sort of model the constructor decided // to create. - if (isOutput(n)) { + ModelId outputId = m_outputs[n]; - SparseOneDimensionalModel *model = - getConformingOutput(n); + if (isOutputType(n)) { + + auto model = ModelById::getAs(outputId); + if (!model) return; + model->add(Event(frame, feature.label.c_str())); + + } else if (isOutputType(n)) { + + auto model = ModelById::getAs(outputId); if (!model) return; - model->add(Event(frame, feature.label.c_str())); - - } else if (isOutput(n)) { - - SparseTimeValueModel *model = - getConformingOutput(n); - if (!model) return; - - for (int i = 0; i < (int)feature.values.size(); ++i) { + for (int i = 0; in_range_for(feature.values, i); ++i) { float value = feature.values[i]; @@ -994,20 +1053,19 @@ label = QString("[%1] %2").arg(i+1).arg(label); } - SparseTimeValueModel *targetModel = model; + auto targetModel = model; if (m_needAdditionalModels[n] && i > 0) { - targetModel = getAdditionalModel(n, i); + targetModel = ModelById::getAs + (getAdditionalModel(n, i)); if (!targetModel) targetModel = model; -// std::cerr << "adding point to model " << targetModel -// << " for output " << n << " bin " << i << std::endl; } targetModel->add(Event(frame, value, label)); } - } else if (isOutput(n) || isOutput(n)) { - + } else if (isOutputType(n) || isOutputType(n)) { + int index = 0; float value = 0.0; @@ -1024,7 +1082,8 @@ } } - if (isOutput(n)) { + auto noteModel = ModelById::getAs(outputId); + if (noteModel) { float velocity = 100; if ((int)feature.values.size() > index) { @@ -1032,62 +1091,58 @@ } if (velocity < 0) velocity = 127; if (velocity > 127) velocity = 127; + + noteModel->add(Event(frame, value, // value is pitch + duration, + velocity / 127.f, + feature.label.c_str())); + } - NoteModel *model = getConformingOutput(n); - if (!model) return; - model->add(Event(frame, value, // value is pitch - duration, - velocity / 127.f, - feature.label.c_str())); - } else { - - RegionModel *model = getConformingOutput(n); - if (!model) return; - + auto regionModel = ModelById::getAs(outputId); + if (regionModel) { + if (feature.hasDuration && !feature.values.empty()) { - - for (int i = 0; i < (int)feature.values.size(); ++i) { - + + for (int i = 0; in_range_for(feature.values, i); ++i) { + float value = feature.values[i]; - + QString label = feature.label.c_str(); if (feature.values.size() > 1) { label = QString("[%1] %2").arg(i+1).arg(label); } - - model->add(Event(frame, - value, - duration, - label)); + + regionModel->add(Event(frame, + value, + duration, + label)); } } else { - - model->add(Event(frame, - value, - duration, - feature.label.c_str())); + + regionModel->add(Event(frame, + value, + duration, + feature.label.c_str())); } } - - } else if (isOutput(n)) { + + } else if (isOutputType(n)) { + + auto model = ModelById::getAs + (outputId); + if (!model) return; DenseThreeDimensionalModel::Column values = feature.values; - EditableDenseThreeDimensionalModel *model = - getConformingOutput(n); - if (!model) return; - -// cerr << "(note: model resolution = " << model->getResolution() << ")" -// << endl; - if (!feature.hasTimestamp && m_fixedRateFeatureNos[n] >= 0) { model->setColumn(m_fixedRateFeatureNos[n], values); } else { model->setColumn(int(frame / model->getResolution()), values); } - } else { - SVDEBUG << "FeatureExtractionModelTransformer::addFeature: Unknown output model type!" << endl; + + SVDEBUG << "FeatureExtractionModelTransformer::addFeature: Unknown output model type - possibly a deleted model" << endl; + abandon(); } } @@ -1099,43 +1154,11 @@ << completion << ")" << endl; #endif - if (isOutput(n)) { - - SparseOneDimensionalModel *model = - getConformingOutput(n); - if (!model) return; - if (model->isAbandoning()) abandon(); - model->setCompletion(completion, true); - - } else if (isOutput(n)) { - - SparseTimeValueModel *model = - getConformingOutput(n); - if (!model) return; - if (model->isAbandoning()) abandon(); - model->setCompletion(completion, true); - - } else if (isOutput(n)) { - - NoteModel *model = getConformingOutput(n); - if (!model) return; - if (model->isAbandoning()) abandon(); - model->setCompletion(completion, true); - - } else if (isOutput(n)) { - - RegionModel *model = getConformingOutput(n); - if (!model) return; - if (model->isAbandoning()) abandon(); - model->setCompletion(completion, true); - - } else if (isOutput(n)) { - - EditableDenseThreeDimensionalModel *model = - getConformingOutput(n); - if (!model) return; - if (model->isAbandoning()) abandon(); - model->setCompletion(completion, true); //!!!m_context.updates); - } + (void) + (setOutputCompletion(n, completion) || + setOutputCompletion(n, completion) || + setOutputCompletion(n, completion) || + setOutputCompletion(n, completion) || + setOutputCompletion(n, completion)); } diff -r 649ac57c5a2d -r 85b9b466a59f transform/FeatureExtractionModelTransformer.h --- a/transform/FeatureExtractionModelTransformer.h Thu Jun 20 14:58:20 2019 +0100 +++ b/transform/FeatureExtractionModelTransformer.h Wed Jul 17 14:24:51 2019 +0100 @@ -38,9 +38,11 @@ FeatureExtractionModelTransformer(Input input, const Transform &transform); - // Obtain outputs for a set of transforms that all use the same - // plugin and input (but with different outputs). i.e. run the - // plugin once only and collect more than one output from it. + /** + * Obtain outputs for a set of transforms that all use the same + * plugin and input (but with different outputs). i.e. run the + * plugin once only and collect more than one output from it. + */ FeatureExtractionModelTransformer(Input input, const Transforms &relatedTransforms); @@ -57,16 +59,27 @@ void run() override; Vamp::Plugin *m_plugin; - std::vector m_descriptors; // per transform - std::vector m_fixedRateFeatureNos; // to assign times to FixedSampleRate features - std::vector m_outputNos; // list of plugin output indexes required for this group of transforms + + // descriptors per transform + std::vector m_descriptors; + + // to assign times to FixedSampleRate features + std::vector m_fixedRateFeatureNos; + + // list of plugin output indexes required for this group of transforms + std::vector m_outputNos; void createOutputModels(int n); - std::map m_needAdditionalModels; // transformNo -> necessity - typedef std::map > AdditionalModelMap; + // map from transformNo -> necessity + std::map m_needAdditionalModels; + + // map from transformNo -> binNo -> SparseTimeValueModel id + typedef std::map > AdditionalModelMap; + AdditionalModelMap m_additionalModels; - SparseTimeValueModel *getAdditionalModel(int transformNo, int binNo); + + ModelId getAdditionalModel(int transformNo, int binNo); void addFeature(int n, sv_frame_t blockFrame, @@ -82,24 +95,21 @@ QWaitCondition m_outputsCondition; void awaitOutputModels() override; - // just casts: - - DenseTimeValueModel *getConformingInput(); - - template bool isOutput(int n) { - return dynamic_cast(m_outputs[n]) != 0; + template bool isOutputType(int n) { + if (!ModelById::getAs(m_outputs[n])) { + return false; + } else { + return true; + } } - template ModelClass *getConformingOutput(int n) { - if ((int)m_outputs.size() > n) { - ModelClass *mc = dynamic_cast(m_outputs[n]); - if (!mc) { - std::cerr << "FeatureExtractionModelTransformer::getOutput: Output model not conformable" << std::endl; - } - return mc; + template bool setOutputCompletion(int n, int completion) { + auto model = ModelById::getAs(m_outputs[n]); + if (!model) { + return false; } else { - std::cerr << "FeatureExtractionModelTransformer::getOutput: No such output number " << n << std::endl; - return 0; + model->setCompletion(completion, true); + return true; } } }; diff -r 649ac57c5a2d -r 85b9b466a59f transform/ModelTransformer.cpp --- a/transform/ModelTransformer.cpp Thu Jun 20 14:58:20 2019 +0100 +++ b/transform/ModelTransformer.cpp Wed Jul 17 14:24:51 2019 +0100 @@ -17,8 +17,6 @@ ModelTransformer::ModelTransformer(Input input, const Transform &transform) : m_input(input), - m_detached(false), - m_detachedAdd(false), m_abandoned(false) { m_transforms.push_back(transform); @@ -27,8 +25,6 @@ ModelTransformer::ModelTransformer(Input input, const Transforms &transforms) : m_transforms(transforms), m_input(input), - m_detached(false), - m_detachedAdd(false), m_abandoned(false) { } @@ -37,13 +33,5 @@ { m_abandoned = true; wait(); - if (!m_detached) { - Models mine = getOutputModels(); - foreach (Model *m, mine) delete m; - } - if (!m_detachedAdd) { - Models mine = getAdditionalOutputModels(); - foreach (Model *m, mine) delete m; - } } diff -r 649ac57c5a2d -r 85b9b466a59f transform/ModelTransformer.h --- a/transform/ModelTransformer.h Thu Jun 20 14:58:20 2019 +0100 +++ b/transform/ModelTransformer.h Wed Jul 17 14:24:51 2019 +0100 @@ -34,36 +34,34 @@ * available to the user of the ModelTransformer immediately, but may * be initially empty until the background thread has populated it. */ - class ModelTransformer : public Thread { public: virtual ~ModelTransformer(); - typedef std::vector Models; + typedef std::vector Models; class Input { public: - Input(Model *m) : m_model(m), m_channel(-1) { } - Input(Model *m, int c) : m_model(m), m_channel(c) { } + Input(ModelId m) : m_model(m), m_channel(-1) { } + Input(ModelId m, int c) : m_model(m), m_channel(c) { } - Model *getModel() const { return m_model; } - void setModel(Model *m) { m_model = m; } + ModelId getModel() const { return m_model; } + void setModel(ModelId m) { m_model = m; } int getChannel() const { return m_channel; } void setChannel(int c) { m_channel = c; } protected: - Model *m_model; + ModelId m_model; int m_channel; }; /** * Hint to the processing thread that it should give up, for - * example because the process is going to exit or we want to get - * rid of the input model. Caller should still wait() and/or - * delete the transform before assuming its input and output - * models are no longer required. + * example because the process is going to exit or the + * model/document context is being replaced. Caller should still + * wait() to be sure that processing has ended. */ void abandon() { m_abandoned = true; } @@ -76,7 +74,7 @@ /** * Return the input model for the transform. */ - Model *getInputModel() { return m_input.getModel(); } + ModelId getInputModel() { return m_input.getModel(); } /** * Return the input channel spec for the transform. @@ -84,10 +82,11 @@ int getInputChannel() { return m_input.getChannel(); } /** - * Return the set of output models created by the transform or - * transforms. Returns an empty list if any transform could not - * be initialised; an error message may be available via - * getMessage() in this situation. + * Return the set of output model IDs created by the transform or + * transforms. Returns an empty list if any transform could not be + * initialised; an error message may be available via getMessage() + * in this situation. The returned models have been added to + * ModelById. */ Models getOutputModels() { awaitOutputModels(); @@ -95,17 +94,6 @@ } /** - * Return the set of output models, also detaching them from the - * transformer so that they will not be deleted when the - * transformer is. The caller takes ownership of the models. - */ - Models detachOutputModels() { - awaitOutputModels(); - m_detached = true; - return m_outputs; - } - - /** * Return any additional models that were created during * processing. This might happen if, for example, a transform was * configured to split a multi-bin output into separate single-bin @@ -122,15 +110,6 @@ virtual bool willHaveAdditionalOutputModels() { return false; } /** - * Return the set of additional models, also detaching them from - * the transformer. The caller takes ownership of the models. - */ - virtual Models detachAdditionalOutputModels() { - m_detachedAdd = true; - return getAdditionalOutputModels(); - } - - /** * Return a warning or error message. If getOutputModel returned * a null pointer, this should contain a fatal error message for * the transformer; otherwise it may contain a warning to show to @@ -145,10 +124,8 @@ virtual void awaitOutputModels() = 0; Transforms m_transforms; - Input m_input; // I don't own the model in this - Models m_outputs; // I own this, unless... - bool m_detached; // ... this is true. - bool m_detachedAdd; + Input m_input; + Models m_outputs; bool m_abandoned; QString m_message; }; diff -r 649ac57c5a2d -r 85b9b466a59f transform/ModelTransformerFactory.cpp --- a/transform/ModelTransformerFactory.cpp Thu Jun 20 14:58:20 2019 +0100 +++ b/transform/ModelTransformerFactory.cpp Wed Jul 17 14:24:51 2019 +0100 @@ -56,8 +56,8 @@ ModelTransformer::Input ModelTransformerFactory::getConfigurationForTransform(Transform &transform, - const vector &candidateInputModels, - Model *defaultInputModel, + vector candidateInputModels, + ModelId defaultInputModel, AudioPlaySource *source, sv_frame_t startFrame, sv_frame_t duration, @@ -65,26 +65,39 @@ { QMutexLocker locker(&m_mutex); - ModelTransformer::Input input(nullptr); + ModelTransformer::Input input({}); if (candidateInputModels.empty()) return input; //!!! This will need revision -- we'll have to have a callback //from the dialog for when the candidate input model is changed, //as we'll need to reinitialise the channel settings in the dialog - Model *inputModel = candidateInputModels[0]; + ModelId inputModel = candidateInputModels[0]; QStringList candidateModelNames; QString defaultModelName; - QMap modelMap; - for (int i = 0; i < (int)candidateInputModels.size(); ++i) { - QString modelName = candidateInputModels[i]->objectName(); + QMap modelMap; + + sv_samplerate_t defaultSampleRate; + { auto im = ModelById::get(inputModel); + if (!im) return input; + defaultSampleRate = im->getSampleRate(); + } + + for (int i = 0; in_range_for(candidateInputModels, i); ++i) { + + auto model = ModelById::get(candidateInputModels[i]); + if (!model) return input; + + QString modelName = model->objectName(); QString origModelName = modelName; int dupcount = 1; while (modelMap.contains(modelName)) { modelName = tr("%1 <%2>").arg(origModelName).arg(++dupcount); } + modelMap[modelName] = candidateInputModels[i]; candidateModelNames.push_back(modelName); + if (candidateInputModels[i] == defaultInputModel) { defaultModelName = modelName; } @@ -105,7 +118,7 @@ RealTimePluginFactory *factory = RealTimePluginFactory::instanceFor(id); - sv_samplerate_t sampleRate = inputModel->getSampleRate(); + sv_samplerate_t sampleRate = defaultSampleRate; int blockSize = 1024; int channels = 1; if (source) { @@ -125,7 +138,7 @@ Vamp::Plugin *vp = FeatureExtractionPluginFactory::instance()->instantiatePlugin - (id, float(inputModel->getSampleRate())); + (id, float(defaultSampleRate)); plugin = vp; } @@ -196,7 +209,7 @@ return transformer; } -Model * +ModelId ModelTransformerFactory::transform(const Transform &transform, const ModelTransformer::Input &input, QString &message, @@ -206,12 +219,12 @@ Transforms transforms; transforms.push_back(transform); - vector mm = transformMultiple(transforms, input, message, handler); - if (mm.empty()) return nullptr; + vector mm = transformMultiple(transforms, input, message, handler); + if (mm.empty()) return {}; else return mm[0]; } -vector +vector ModelTransformerFactory::transformMultiple(const Transforms &transforms, const ModelTransformer::Input &input, QString &message, @@ -220,9 +233,12 @@ SVDEBUG << "ModelTransformerFactory::transformMultiple: Constructing transformer with input model " << input.getModel() << endl; QMutexLocker locker(&m_mutex); + + auto inputModel = ModelById::get(input.getModel()); + if (!inputModel) return {}; ModelTransformer *t = createTransformer(transforms, input); - if (!t) return vector(); + if (!t) return {}; if (handler) { m_handlers[t] = handler; @@ -233,22 +249,24 @@ connect(t, SIGNAL(finished()), this, SLOT(transformerFinished())); t->start(); - vector models = t->detachOutputModels(); - + vector models = t->getOutputModels(); + if (!models.empty()) { - QString imn = input.getModel()->objectName(); + QString imn = inputModel->objectName(); QString trn = TransformFactory::getInstance()->getTransformFriendlyName (transforms[0].getIdentifier()); - for (int i = 0; i < (int)models.size(); ++i) { + for (int i = 0; in_range_for(models, i); ++i) { + auto model = ModelById::get(models[i]); + if (!model) continue; if (imn != "") { if (trn != "") { - models[i]->setObjectName(tr("%1: %2").arg(imn).arg(trn)); + model->setObjectName(tr("%1: %2").arg(imn).arg(trn)); } else { - models[i]->setObjectName(imn); + model->setObjectName(imn); } } else if (trn != "") { - models[i]->setObjectName(trn); + model->setObjectName(trn); } } } else { @@ -284,12 +302,12 @@ m_runningTransformers.erase(transformer); - map> toNotifyOfMore; + map> toNotifyOfMore; vector toNotifyOfNoMore; if (m_handlers.find(transformer) != m_handlers.end()) { if (transformer->willHaveAdditionalOutputModels()) { - vector mm = transformer->detachAdditionalOutputModels(); + vector mm = transformer->getAdditionalOutputModels(); toNotifyOfMore[m_handlers[transformer]] = mm; } else { toNotifyOfNoMore.push_back(m_handlers[transformer]); @@ -299,11 +317,9 @@ m_mutex.unlock(); - // These calls have to be made without the mutex held, as they may - // ultimately call back on us (e.g. we have one baroque situation - // where this could trigger a command to create a layer, which - // triggers the command history to clip the stack, which deletes a - // spare old model, which calls back on our modelAboutToBeDeleted) + // We make these calls without the mutex held, in case they + // ultimately call back on us - not such a concern as in the old + // model lifecycle but just in case for (const auto &i: toNotifyOfMore) { i.first->moreModelsAvailable(i.second); @@ -322,43 +338,6 @@ delete transformer; } -void -ModelTransformerFactory::modelAboutToBeDeleted(Model *m) -{ - TransformerSet affected; - - { - QMutexLocker locker(&m_mutex); - - for (TransformerSet::iterator i = m_runningTransformers.begin(); - i != m_runningTransformers.end(); ++i) { - - ModelTransformer *t = *i; - - if (t->getInputModel() == m) { - affected.insert(t); - } else { - vector mm = t->getOutputModels(); - for (int i = 0; i < (int)mm.size(); ++i) { - if (mm[i] == m) affected.insert(t); - } - } - } - } - - for (TransformerSet::iterator i = affected.begin(); - i != affected.end(); ++i) { - - ModelTransformer *t = *i; - - t->abandon(); - - t->wait(); // this should eventually call back on - // transformerFinished, which will remove from - // m_runningTransformers and delete. - } -} - bool ModelTransformerFactory::haveRunningTransformers() const { diff -r 649ac57c5a2d -r 85b9b466a59f transform/ModelTransformerFactory.h --- a/transform/ModelTransformerFactory.h Thu Jun 20 14:58:20 2019 +0100 +++ b/transform/ModelTransformerFactory.h Wed Jul 17 14:24:51 2019 +0100 @@ -46,11 +46,11 @@ virtual bool configure(ModelTransformer::Input &input, Transform &transform, Vamp::PluginBase *plugin, - Model *&inputModel, + ModelId &inputModel, AudioPlaySource *source, sv_frame_t startFrame, sv_frame_t duration, - const QMap &modelMap, + const QMap &modelMap, QStringList candidateModelNames, QString defaultModelName) = 0; }; @@ -59,14 +59,14 @@ * Fill out the configuration for the given transform (may include * asking the user by calling back on the UserConfigurator). * Returns the selected input model and channel if the transform - * is acceptable, or an input with a null model if the operation + * is acceptable, or an input with no model if the operation * should be cancelled. Audio play source may be used to audition * effects plugins, if provided. */ ModelTransformer::Input getConfigurationForTransform(Transform &transform, - const std::vector &candidateInputModels, - Model *defaultInputModel, + std::vector candidateInputModels, + ModelId defaultInputModel, AudioPlaySource *source = 0, sv_frame_t startFrame = 0, sv_frame_t duration = 0, @@ -77,7 +77,7 @@ virtual ~AdditionalModelHandler() { } // Exactly one of these functions will be called - virtual void moreModelsAvailable(std::vector models) = 0; + virtual void moreModelsAvailable(std::vector models) = 0; virtual void noMoreModelsAvailable() = 0; }; @@ -104,10 +104,10 @@ * The returned model is owned by the caller and must be deleted * when no longer needed. */ - Model *transform(const Transform &transform, - const ModelTransformer::Input &input, - QString &message, - AdditionalModelHandler *handler = 0); + ModelId transform(const Transform &transform, + const ModelTransformer::Input &input, + QString &message, + AdditionalModelHandler *handler = 0); /** * Return the multiple output models resulting from applying the @@ -141,7 +141,7 @@ * The returned models are owned by the caller and must be deleted * when no longer needed. */ - std::vector transformMultiple(const Transforms &transform, + std::vector transformMultiple(const Transforms &transform, const ModelTransformer::Input &input, QString &message, AdditionalModelHandler *handler = 0); @@ -154,8 +154,6 @@ protected slots: void transformerFinished(); - void modelAboutToBeDeleted(Model *); - protected: ModelTransformer *createTransformer(const Transforms &transforms, const ModelTransformer::Input &input); diff -r 649ac57c5a2d -r 85b9b466a59f transform/RealTimeEffectModelTransformer.cpp --- a/transform/RealTimeEffectModelTransformer.cpp Thu Jun 20 14:58:20 2019 +0100 +++ b/transform/RealTimeEffectModelTransformer.cpp Wed Jul 17 14:24:51 2019 +0100 @@ -58,8 +58,11 @@ return; } - DenseTimeValueModel *input = getConformingInput(); - if (!input) return; + auto input = ModelById::getAs(getInputModel()); + if (!input) { + SVCERR << "RealTimeEffectModelTransformer: Input is absent or of wrong type" << endl; + return; + } m_plugin = factory->instantiatePlugin(pluginId, 0, 0, input->getSampleRate(), @@ -87,19 +90,19 @@ outputChannels = input->getChannelCount(); } - WritableWaveFileModel *model = new WritableWaveFileModel + auto model = std::make_shared (input->getSampleRate(), outputChannels); - m_outputs.push_back(model); + m_outputs.push_back(ModelById::add(model)); } else { - SparseTimeValueModel *model = new SparseTimeValueModel - (input->getSampleRate(), transform.getBlockSize(), 0.0, 0.0, false); - + auto model = std::make_shared + (input->getSampleRate(), transform.getBlockSize(), + 0.0, 0.0, false); if (m_units != "") model->setScaleUnits(m_units); - m_outputs.push_back(model); + m_outputs.push_back(ModelById::add(model)); } } @@ -108,38 +111,58 @@ delete m_plugin; } -DenseTimeValueModel * -RealTimeEffectModelTransformer::getConformingInput() -{ - DenseTimeValueModel *dtvm = - dynamic_cast(getInputModel()); - if (!dtvm) { - SVDEBUG << "RealTimeEffectModelTransformer::getConformingInput: WARNING: Input model is not conformable to DenseTimeValueModel" << endl; - } - return dtvm; -} - void RealTimeEffectModelTransformer::run() { - DenseTimeValueModel *input = getConformingInput(); - if (!input) return; - - while (!input->isReady() && !m_abandoned) { - SVDEBUG << "RealTimeEffectModelTransformer::run: Waiting for input model to be ready..." << endl; - usleep(500000); - } - if (m_abandoned) { + if (m_outputs.empty()) { + abandon(); return; } - if (m_outputs.empty()) { + + bool ready = false; + while (!ready && !m_abandoned) { + { // scope so as to release input shared_ptr before sleeping + auto input = ModelById::getAs(getInputModel()); + if (!input) { + abandon(); + return; + } + ready = input->isReady(); + } + if (!ready) { + SVDEBUG << "RealTimeEffectModelTransformer::run: Waiting for input model to be ready..." << endl; + usleep(500000); + } + } + if (m_abandoned) return; + + auto input = ModelById::getAs(getInputModel()); + if (!input) { + abandon(); return; } - - SparseTimeValueModel *stvm = - dynamic_cast(m_outputs[0]); - WritableWaveFileModel *wwfm = - dynamic_cast(m_outputs[0]); + + sv_samplerate_t sampleRate; + int channelCount; + sv_frame_t startFrame; + sv_frame_t endFrame; + + { // scope so as not to have this borrowed pointer retained around + // the edges of the process loop + auto input = ModelById::getAs(getInputModel()); + if (!input) { + abandon(); + return; + } + + sampleRate = input->getSampleRate(); + channelCount = input->getChannelCount(); + startFrame = input->getStartFrame(); + endFrame = input->getEndFrame(); + } + + auto stvm = ModelById::getAs(m_outputs[0]); + auto wwfm = ModelById::getAs(m_outputs[0]); if (!stvm && !wwfm) { return; @@ -149,17 +172,12 @@ return; } - sv_samplerate_t sampleRate = input->getSampleRate(); - int channelCount = input->getChannelCount(); if (!wwfm && m_input.getChannel() != -1) channelCount = 1; sv_frame_t blockSize = m_plugin->getBufferSize(); float **inbufs = m_plugin->getAudioInputBuffers(); - sv_frame_t startFrame = m_input.getModel()->getStartFrame(); - sv_frame_t endFrame = m_input.getModel()->getEndFrame(); - Transform transform = m_transforms[0]; RealTime contextStartRT = transform.getStartTime(); @@ -201,6 +219,12 @@ sv_frame_t got = 0; + auto input = ModelById::getAs(getInputModel()); + if (!input) { + abandon(); + return; + } + if (channelCount == 1) { if (inbufs && inbufs[0]) { auto data = input->getData @@ -242,20 +266,6 @@ } } -/* - cerr << "Input for plugin: " << m_plugin->getAudioInputCount() << " channels "<< endl; - - for (int ch = 0; ch < m_plugin->getAudioInputCount(); ++ch) { - cerr << "Input channel " << ch << endl; - for (int i = 0; i < 100; ++i) { - cerr << inbufs[ch][i] << " "; - if (isnan(inbufs[ch][i])) { - cerr << "\n\nWARNING: NaN in audio input" << endl; - } - } - } -*/ - m_plugin->run(RealTime::frame2RealTime(blockFrame, sampleRate)); if (stvm) { diff -r 649ac57c5a2d -r 85b9b466a59f transform/RealTimeEffectModelTransformer.h --- a/transform/RealTimeEffectModelTransformer.h Thu Jun 20 14:58:20 2019 +0100 +++ b/transform/RealTimeEffectModelTransformer.h Wed Jul 17 14:24:51 2019 +0100 @@ -36,9 +36,6 @@ QString m_units; RealTimePluginInstance *m_plugin; int m_outputNo; - - // just casts - DenseTimeValueModel *getConformingInput(); }; #endif