# HG changeset patch
# User Chris Cannam
# Date 1221754094 0
# Node ID 288f455330411c24a71cc88ab67aa9d8f6c47e1f
# Parent 5746c559af158c22fb596aa06d039010e9207878
* Add region model and layer; improve assignment of model types to
feature extraction transforms with duration
diff -r 5746c559af15 -r 288f45533041 data/data.pro
--- a/data/data.pro Thu Sep 18 12:33:30 2008 +0000
+++ b/data/data.pro Thu Sep 18 16:08:14 2008 +0000
@@ -59,6 +59,7 @@
model/PowerOfSqrtTwoZoomConstraint.h \
model/PowerOfTwoZoomConstraint.h \
model/RangeSummarisableTimeValueModel.h \
+ model/RegionModel.h \
model/SparseModel.h \
model/SparseOneDimensionalModel.h \
model/SparseTimeValueModel.h \
diff -r 5746c559af15 -r 288f45533041 data/fileio/QuickTimeFileReader.cpp
--- a/data/fileio/QuickTimeFileReader.cpp Thu Sep 18 12:33:30 2008 +0000
+++ b/data/fileio/QuickTimeFileReader.cpp Thu Sep 18 16:08:14 2008 +0000
@@ -203,6 +203,7 @@
if (m_d->err) {
m_error = QString("Error in QuickTime decoder property set: code %1").arg(m_d->err);
+ m_channelCount = 0;
return;
}
m_d->buffer.mNumberBuffers = 1;
diff -r 5746c559af15 -r 288f45533041 data/model/NoteModel.h
--- a/data/model/NoteModel.h Thu Sep 18 12:33:30 2008 +0000
+++ b/data/model/NoteModel.h Thu Sep 18 16:08:14 2008 +0000
@@ -21,12 +21,16 @@
#include "base/PlayParameterRepository.h"
/**
- * Note type for use in a SparseModel or SparseValueModel. All we
- * mean by a "note" is something that has an onset time, a single
- * value, and a duration. Like other points, it can also have a
- * label. With this point type, the model can be thought of as
- * representing a simple MIDI-type piano roll, except that the y
- * coordinates (values) do not have to be discrete integers.
+ * NoteModel -- a concrete IntervalModel for notes.
+ */
+
+/**
+ * Note type for use in a sparse model. All we mean by a "note" is
+ * something that has an onset time, a single value, a duration, and a
+ * level. Like other points, it can also have a label. With this
+ * point type, the model can be thought of as representing a simple
+ * MIDI-type piano roll, except that the y coordinates (values) do not
+ * have to be discrete integers.
*/
struct Note
diff -r 5746c559af15 -r 288f45533041 data/model/RegionModel.h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/data/model/RegionModel.h Thu Sep 18 16:08:14 2008 +0000
@@ -0,0 +1,204 @@
+/* -*- 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 2006 Chris Cannam.
+
+ 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 _REGION_MODEL_H_
+#define _REGION_MODEL_H_
+
+#include "IntervalModel.h"
+#include "base/RealTime.h"
+
+/**
+ * RegionModel -- a concrete IntervalModel for intervals associated
+ * with a value, which we call regions for no very compelling reason.
+ */
+
+/**
+ * Region "point" type. A region is something that has an onset time,
+ * a single value, and a duration. Like other points, it can also
+ * have a label.
+ *
+ * This is called RegionRec instead of Region to avoid name collisions
+ * with the X11 Region struct. Bah.
+ */
+
+struct RegionRec
+{
+public:
+ RegionRec(long _frame) : frame(_frame), value(0.0f), duration(0) { }
+ RegionRec(long _frame, float _value, size_t _duration, QString _label) :
+ frame(_frame), value(_value), duration(_duration), label(_label) { }
+
+ int getDimensions() const { return 3; }
+
+ long frame;
+ float value;
+ size_t duration;
+ QString label;
+
+ QString getLabel() const { return label; }
+
+ void toXml(QTextStream &stream,
+ QString indent = "",
+ QString extraAttributes = "") const
+ {
+ stream <<
+ QString("%1\n")
+ .arg(indent).arg(frame).arg(value).arg(duration).arg(label).arg(extraAttributes);
+ }
+
+ QString toDelimitedDataString(QString delimiter, size_t sampleRate) const
+ {
+ QStringList list;
+ list << RealTime::frame2RealTime(frame, sampleRate).toString().c_str();
+ list << QString("%1").arg(value);
+ list << RealTime::frame2RealTime(duration, sampleRate).toString().c_str();
+ if (label != "") list << label;
+ return list.join(delimiter);
+ }
+
+ struct Comparator {
+ bool operator()(const RegionRec &p1,
+ const RegionRec &p2) const {
+ if (p1.frame != p2.frame) return p1.frame < p2.frame;
+ if (p1.value != p2.value) return p1.value < p2.value;
+ if (p1.duration != p2.duration) return p1.duration < p2.duration;
+ return p1.label < p2.label;
+ }
+ };
+
+ struct OrderComparator {
+ bool operator()(const RegionRec &p1,
+ const RegionRec &p2) const {
+ return p1.frame < p2.frame;
+ }
+ };
+};
+
+
+class RegionModel : public IntervalModel
+{
+ Q_OBJECT
+
+public:
+ RegionModel(size_t sampleRate, size_t resolution,
+ bool notifyOnAdd = true) :
+ IntervalModel(sampleRate, resolution, notifyOnAdd),
+ m_valueQuantization(0)
+ {
+ }
+
+ RegionModel(size_t sampleRate, size_t resolution,
+ float valueMinimum, float valueMaximum,
+ bool notifyOnAdd = true) :
+ IntervalModel(sampleRate, resolution,
+ valueMinimum, valueMaximum,
+ notifyOnAdd),
+ m_valueQuantization(0)
+ {
+ }
+
+ virtual ~RegionModel()
+ {
+ }
+
+ float getValueQuantization() const { return m_valueQuantization; }
+ void setValueQuantization(float q) { m_valueQuantization = q; }
+
+ QString getTypeName() const { return tr("Region"); }
+
+ virtual void toXml(QTextStream &out,
+ QString indent = "",
+ QString extraAttributes = "") const
+ {
+ std::cerr << "RegionModel::toXml: extraAttributes = \""
+ << extraAttributes.toStdString() << std::endl;
+
+ IntervalModel::toXml
+ (out,
+ indent,
+ QString("%1 valueQuantization=\"%2\"")
+ .arg(extraAttributes).arg(m_valueQuantization));
+ }
+
+ /**
+ * TabularModel methods.
+ */
+
+ virtual int getColumnCount() const
+ {
+ return 6;
+ }
+
+ virtual QString getHeading(int column) const
+ {
+ switch (column) {
+ case 0: return tr("Time");
+ case 1: return tr("Frame");
+ case 2: return tr("Value");
+ case 3: return tr("Duration");
+ case 4: return tr("Label");
+ default: return tr("Unknown");
+ }
+ }
+
+ virtual QVariant getData(int row, int column, int role) const
+ {
+ if (column < 4) {
+ return IntervalModel::getData(row, column, role);
+ }
+
+ PointListIterator i = getPointListIteratorForRow(row);
+ if (i == m_points.end()) return QVariant();
+
+ switch (column) {
+ case 4: return i->label;
+ default: return QVariant();
+ }
+ }
+
+ virtual Command *getSetDataCommand(int row, int column, const QVariant &value, int role)
+ {
+ if (column < 4) {
+ return IntervalModel::getSetDataCommand
+ (row, column, value, role);
+ }
+
+ if (role != Qt::EditRole) return false;
+ PointListIterator i = getPointListIteratorForRow(row);
+ if (i == m_points.end()) return false;
+ EditCommand *command = new EditCommand(this, tr("Edit Data"));
+
+ Point point(*i);
+ command->deletePoint(point);
+
+ switch (column) {
+ case 4: point.label = value.toString(); break;
+ }
+
+ command->addPoint(point);
+ return command->finish();
+ }
+
+ virtual SortType getSortType(int column) const
+ {
+ if (column == 5) return SortAlphabetical;
+ return SortNumeric;
+ }
+
+protected:
+ float m_valueQuantization;
+};
+
+#endif
diff -r 5746c559af15 -r 288f45533041 transform/FeatureExtractionModelTransformer.cpp
--- a/transform/FeatureExtractionModelTransformer.cpp Thu Sep 18 12:33:30 2008 +0000
+++ b/transform/FeatureExtractionModelTransformer.cpp Thu Sep 18 16:08:14 2008 +0000
@@ -27,6 +27,7 @@
#include "data/model/EditableDenseThreeDimensionalModel.h"
#include "data/model/DenseTimeValueModel.h"
#include "data/model/NoteModel.h"
+#include "data/model/RegionModel.h"
#include "data/model/FFTModel.h"
#include "data/model/WaveFileModel.h"
@@ -154,8 +155,7 @@
if (m_transform.getOutput() == "" ||
outputs[i].identifier == m_transform.getOutput().toStdString()) {
m_outputFeatureNo = i;
- m_descriptor = new Vamp::Plugin::OutputDescriptor
- (outputs[i]);
+ m_descriptor = new Vamp::Plugin::OutputDescriptor(outputs[i]);
break;
}
}
@@ -207,12 +207,92 @@
break;
}
- if (binCount == 0) {
+ bool preDurationPlugin = (m_plugin->getVampApiVersion() < 2);
+
+ if (binCount == 0 &&
+ (preDurationPlugin || !m_descriptor->hasDuration)) {
m_output = new SparseOneDimensionalModel(modelRate, modelResolution,
false);
- } else if (binCount == 1) {
+ } else if ((preDurationPlugin && binCount > 1 &&
+ (m_descriptor->sampleType ==
+ Vamp::Plugin::OutputDescriptor::VariableSampleRate)) ||
+ (!preDurationPlugin && m_descriptor->hasDuration)) {
+
+ // For plugins using the old v1 API without explicit duration,
+ // we treat anything that has multiple bins (i.e. that has the
+ // potential to have value and duration) and a variable sample
+ // rate as a note model, taking its values as pitch, duration
+ // and velocity (if present) respectively. This is the same
+ // behaviour as always applied by SV to these plugins in the
+ // past.
+
+ // For plugins with the newer API, we treat anything with
+ // duration as either a note model with pitch and velocity, or
+ // a region model.
+
+ // How do we know whether it's an interval or note model?
+ // What's the essential difference? Is a note model any
+ // interval model using a Hz or "MIDI pitch" scale? There
+ // isn't really a reliable test for "MIDI pitch"... Does a
+ // note model always have velocity? This is a good question
+ // to be addressed by accompanying RDF, but for the moment we
+ // will do the following...
+
+ bool isNoteModel = false;
+
+ // Regions have only value (and duration -- we can't extract a
+ // region model from an old-style plugin that doesn't support
+ // duration)
+ if (binCount > 1) isNoteModel = true;
+
+ // Regions do not have units of Hz (a sweeping assumption!)
+ if (m_descriptor->unit == "Hz") isNoteModel = true;
+
+ // If we had a "sparse 3D model", we would have the additional
+ // problem of determining whether to use that here (if bin
+ // count > 1). But we don't.
+
+ if (isNoteModel) {
+
+ NoteModel *model;
+ if (haveExtents) {
+ model = new NoteModel
+ (modelRate, modelResolution, minValue, maxValue, false);
+ } else {
+ model = new NoteModel
+ (modelRate, modelResolution, false);
+ }
+ model->setScaleUnits(m_descriptor->unit.c_str());
+ m_output = model;
+
+ } else {
+
+ RegionModel *model;
+ if (haveExtents) {
+ model = new RegionModel
+ (modelRate, modelResolution, minValue, maxValue, false);
+ } else {
+ model = new RegionModel
+ (modelRate, modelResolution, false);
+ }
+ model->setScaleUnits(m_descriptor->unit.c_str());
+ m_output = model;
+ }
+
+ } else if (binCount == 1 ||
+ (m_descriptor->sampleType ==
+ Vamp::Plugin::OutputDescriptor::VariableSampleRate)) {
+
+ // Anything that is not a 1D, note, or interval model and that
+ // has only one value per result must be a sparse time value
+ // model.
+
+ // Anything that is not a 1D, note, or interval model and that
+ // has a variable sample rate is also treated as a sparse time
+ // value model regardless of its bin count, because we lack a
+ // sparse 3D model.
SparseTimeValueModel *model;
if (haveExtents) {
@@ -226,30 +306,11 @@
m_output = model;
- } else if (m_descriptor->sampleType ==
- Vamp::Plugin::OutputDescriptor::VariableSampleRate) {
+ } else {
- // We don't have a sparse 3D model, so interpret this as a
- // note model. There's nothing to define which values to use
- // as which parameters of the note -- for the moment let's
- // treat the first as pitch, second as duration in frames,
- // third (if present) as velocity. (Our note model doesn't
- // yet store velocity.)
- //!!! todo: ask the user!
-
- NoteModel *model;
- if (haveExtents) {
- model = new NoteModel
- (modelRate, modelResolution, minValue, maxValue, false);
- } else {
- model = new NoteModel
- (modelRate, modelResolution, false);
- }
- model->setScaleUnits(outputs[m_outputFeatureNo].unit.c_str());
-
- m_output = model;
-
- } else {
+ // Anything that is not a 1D, note, or interval model and that
+ // has a fixed sample rate and more than one value per result
+ // must be a dense 3D model.
EditableDenseThreeDimensionalModel *model =
new EditableDenseThreeDimensionalModel
@@ -541,15 +602,21 @@
}
}
- if (binCount == 0) {
+ // Rather than repeat the complicated tests from the constructor
+ // to determine what sort of model we must be adding the features
+ // to, we instead test what sort of model the constructor decided
+ // to create.
- SparseOneDimensionalModel *model =
+ if (isOutput()) {
+
+ SparseOneDimensionalModel *model =
getConformingOutput();
if (!model) return;
- model->addPoint(SparseOneDimensionalModel::Point(frame, feature.label.c_str()));
+ model->addPoint(SparseOneDimensionalModel::Point
+ (frame, feature.label.c_str()));
- } else if (binCount == 1) {
+ } else if (isOutput()) {
float value = 0.0;
if (feature.values.size() > 0) value = feature.values[0];
@@ -558,32 +625,52 @@
getConformingOutput();
if (!model) return;
- model->addPoint(SparseTimeValueModel::Point(frame, value, feature.label.c_str()));
-// std::cerr << "SparseTimeValueModel::addPoint(" << frame << ", " << value << "), " << feature.label.c_str() << std::endl;
+ model->addPoint(SparseTimeValueModel::Point
+ (frame, value, feature.label.c_str()));
- } else if (m_descriptor->sampleType ==
- Vamp::Plugin::OutputDescriptor::VariableSampleRate) {
+ } else if (isOutput() || isOutput()) {
- float pitch = 0.0;
- if (feature.values.size() > 0) pitch = feature.values[0];
+ int index = 0;
+
+ float value = 0.0;
+ if (feature.values.size() > index) {
+ value = feature.values[index++];
+ }
float duration = 1;
- if (feature.values.size() > 1) duration = feature.values[1];
+ if (feature.hasDuration) {
+ duration = Vamp::RealTime::realTime2Frame(feature.duration, inputRate);
+ } else {
+ if (feature.values.size() > index) {
+ duration = feature.values[index++];
+ }
+ }
- float velocity = 100;
- if (feature.values.size() > 2) velocity = feature.values[2];
- if (velocity < 0) velocity = 127;
- if (velocity > 127) velocity = 127;
+ if (isOutput()) {
- NoteModel *model = getConformingOutput();
- if (!model) return;
+ float velocity = 100;
+ if (feature.values.size() > index) {
+ velocity = feature.values[index++];
+ }
+ if (velocity < 0) velocity = 127;
+ if (velocity > 127) velocity = 127;
- model->addPoint(NoteModel::Point(frame, pitch,
- lrintf(duration),
- velocity / 127.f,
- feature.label.c_str()));
+ NoteModel *model = getConformingOutput();
+ if (!model) return;
+ model->addPoint(NoteModel::Point(frame, value, // value is pitch
+ lrintf(duration),
+ velocity / 127.f,
+ feature.label.c_str()));
+ } else {
+ RegionModel *model = getConformingOutput();
+ if (model) {
+ model->addPoint(RegionModel::Point(frame, value,
+ lrintf(duration),
+ feature.label.c_str()));
+ } else return;
+ }
- } else {
+ } else if (isOutput()) {
DenseThreeDimensionalModel::Column values = feature.values;
@@ -592,6 +679,9 @@
if (!model) return;
model->setColumn(frame / model->getResolution(), values);
+
+ } else {
+ std::cerr << "FeatureExtractionModelTransformer::addFeature: Unknown output model type!" << std::endl;
}
}
@@ -606,29 +696,33 @@
// std::cerr << "FeatureExtractionModelTransformer::setCompletion("
// << completion << ")" << std::endl;
- if (binCount == 0) {
+ if (isOutput()) {
SparseOneDimensionalModel *model =
getConformingOutput();
if (!model) return;
- model->setCompletion(completion, true); //!!!m_context.updates);
+ model->setCompletion(completion, true);
- } else if (binCount == 1) {
+ } else if (isOutput()) {
SparseTimeValueModel *model =
getConformingOutput();
if (!model) return;
- model->setCompletion(completion, true); //!!!m_context.updates);
+ model->setCompletion(completion, true);
- } else if (m_descriptor->sampleType ==
- Vamp::Plugin::OutputDescriptor::VariableSampleRate) {
+ } else if (isOutput()) {
- NoteModel *model =
- getConformingOutput();
+ NoteModel *model = getConformingOutput();
if (!model) return;
- model->setCompletion(completion, true); //!!!m_context.updates);
+ model->setCompletion(completion, true);
- } else {
+ } else if (isOutput()) {
+
+ RegionModel *model = getConformingOutput();
+ if (!model) return;
+ model->setCompletion(completion, true);
+
+ } else if (isOutput()) {
EditableDenseThreeDimensionalModel *model =
getConformingOutput();
diff -r 5746c559af15 -r 288f45533041 transform/FeatureExtractionModelTransformer.h
--- a/transform/FeatureExtractionModelTransformer.h Thu Sep 18 12:33:30 2008 +0000
+++ b/transform/FeatureExtractionModelTransformer.h Thu Sep 18 16:08:14 2008 +0000
@@ -51,7 +51,13 @@
float **buffer);
// just casts
+
DenseTimeValueModel *getConformingInput();
+
+ template bool isOutput() {
+ return dynamic_cast(m_output) != 0;
+ }
+
template ModelClass *getConformingOutput() {
ModelClass *mc = dynamic_cast(m_output);
if (!mc) {