# 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) {