Mercurial > hg > svcore
changeset 903:1b05ba6af360 tony_integration
Merge from tonioni branch
author | Chris Cannam |
---|---|
date | Wed, 07 May 2014 15:11:56 +0100 |
parents | c3656035240f (diff) 68e880e0b857 (current diff) |
children | aa2257796931 |
files | |
diffstat | 19 files changed, 1136 insertions(+), 797 deletions(-) [+] |
line wrap: on
line diff
--- a/base/Pitch.cpp Sat Apr 26 22:22:19 2014 +0100 +++ b/base/Pitch.cpp Wed May 07 15:11:56 2014 +0100 @@ -101,7 +101,13 @@ float centsOffset, bool useFlats) { - int octave = -2; + int baseOctave = Preferences::getInstance()->getOctaveOfLowestMIDINote(); + int octave = baseOctave; + + // Note, this only gets the right octave number at octave + // boundaries because Cb is enharmonic with B (not B#) and B# is + // enharmonic with C (not Cb). So neither B# nor Cb will be + // spelled from a MIDI pitch + flats flag in isolation. if (midiPitch < 0) { while (midiPitch < 0) { @@ -109,7 +115,7 @@ --octave; } } else { - octave = midiPitch / 12 - 2; + octave = midiPitch / 12 + baseOctave; } QString plain = (useFlats ? flatNotes : notes)[midiPitch % 12].arg(octave);
--- a/base/Pitch.h Sat Apr 26 22:22:19 2014 +0100 +++ b/base/Pitch.h Wed May 07 15:11:56 2014 +0100 @@ -71,9 +71,10 @@ /** * Return a string describing the given MIDI pitch, with optional - * cents offset. This consists of the note name, octave number - * (with MIDI pitch 0 having octave number -2), and optional - * cents. + * cents offset. This consists of the note name, octave number, + * and optional cents. The octave numbering system is based on the + * application preferences (default is C4 = middle C, though in + * previous SV releases that was C3). * * For example, "A#3" (A# in octave 3) or "C2-12c" (C in octave 2, * minus 12 cents).
--- a/base/Preferences.cpp Sat Apr 26 22:22:19 2014 +0100 +++ b/base/Preferences.cpp Wed May 07 15:11:56 2014 +0100 @@ -47,6 +47,7 @@ m_viewFontSize(10), m_backgroundMode(BackgroundFromTheme), m_timeToTextMode(TimeToTextMs), + m_octave(4), m_showSplash(true) { QSettings settings; @@ -66,6 +67,7 @@ (settings.value("background-mode", int(BackgroundFromTheme)).toInt()); m_timeToTextMode = TimeToTextMode (settings.value("time-to-text-mode", int(TimeToTextMs)).toInt()); + m_octave = (settings.value("octave-of-middle-c", 4)).toInt(); m_viewFontSize = settings.value("view-font-size", 10).toInt(); m_showSplash = settings.value("show-splash", true).toBool(); settings.endGroup(); @@ -94,6 +96,7 @@ props.push_back("Temporary Directory Root"); props.push_back("Background Mode"); props.push_back("Time To Text Mode"); + props.push_back("Octave Numbering System"); props.push_back("View Font Size"); props.push_back("Show Splash Screen"); return props; @@ -135,6 +138,9 @@ if (name == "Time To Text Mode") { return tr("Time display format"); } + if (name == "Octave Numbering System") { + return tr("Label middle C as"); + } if (name == "View Font Size") { return tr("Font size for text overlays"); } @@ -181,6 +187,9 @@ if (name == "Time To Text Mode") { return ValueProperty; } + if (name == "Octave Numbering System") { + return ValueProperty; + } if (name == "View Font Size") { return RangeProperty; } @@ -248,6 +257,16 @@ return int(m_timeToTextMode); } + if (name == "Octave Numbering System") { + // we don't support arbitrary octaves in the gui, because we + // want to be able to label what the octave system comes + // from. so we support 0, 3, 4 and 5. + if (min) *min = 0; + if (max) *max = 3; + if (deflt) *deflt = 2; + return int(getSystemWithMiddleCInOctave(m_octave)); + } + if (name == "View Font Size") { if (min) *min = 3; if (max) *max = 48; @@ -322,6 +341,14 @@ case TimeToText60Frame: return tr("60 FPS"); } } + if (name == "Octave Numbering System") { + switch (value) { + case C0_Centre: return tr("C0 - middle of octave scale"); + case C3_Logic: return tr("C3 - common MIDI sequencer convention"); + case C4_ASA: return tr("C4 - ASA American standard"); + case C5_Sonar: return tr("C5 - used in Cakewalk and others"); + } + } return ""; } @@ -359,6 +386,9 @@ setBackgroundMode(BackgroundMode(value)); } else if (name == "Time To Text Mode") { setTimeToTextMode(TimeToTextMode(value)); + } else if (name == "Octave Numbering System") { + setOctaveOfMiddleC(getOctaveOfMiddleCInSystem + (OctaveNumberingSystem(value))); } else if (name == "View Font Size") { setViewFontSize(value); } else if (name == "Show Splash Screen") { @@ -525,6 +555,45 @@ } void +Preferences::setOctaveOfMiddleC(int oct) +{ + if (m_octave != oct) { + + m_octave = oct; + + QSettings settings; + settings.beginGroup("Preferences"); + settings.setValue("octave-of-middle-c", int(oct)); + settings.endGroup(); + emit propertyChanged("Octave Numbering System"); + } +} + +int +Preferences::getOctaveOfMiddleCInSystem(OctaveNumberingSystem s) +{ + switch (s) { + case C0_Centre: return 0; + case C3_Logic: return 3; + case C4_ASA: return 4; + case C5_Sonar: return 5; + default: return 4; + } +} + +Preferences::OctaveNumberingSystem +Preferences::getSystemWithMiddleCInOctave(int o) +{ + switch (o) { + case 0: return C0_Centre; + case 3: return C3_Logic; + case 4: return C4_ASA; + case 5: return C5_Sonar; + default: return C4_ASA; + } +} + +void Preferences::setViewFontSize(int size) { if (m_viewFontSize != size) {
--- a/base/Preferences.h Sat Apr 26 22:22:19 2014 +0100 +++ b/base/Preferences.h Wed May 07 15:11:56 2014 +0100 @@ -86,6 +86,14 @@ }; TimeToTextMode getTimeToTextMode() const { return m_timeToTextMode; } + int getOctaveOfMiddleC() const { + // weed out unsupported octaves + return getOctaveOfMiddleCInSystem(getSystemWithMiddleCInOctave(m_octave)); + } + int getOctaveOfLowestMIDINote() const { + return getOctaveOfMiddleC() - 5; + } + bool getShowSplash() const { return m_showSplash; } public slots: @@ -102,6 +110,7 @@ void setResampleOnLoad(bool); void setBackgroundMode(BackgroundMode mode); void setTimeToTextMode(TimeToTextMode mode); + void setOctaveOfMiddleC(int oct); void setViewFontSize(int size); void setShowSplash(bool); @@ -111,6 +120,19 @@ static Preferences *m_instance; + // We don't support arbitrary octaves in the gui, because we want + // to be able to label what the octave system comes from. These + // are the ones we support. (But we save and load as octave + // numbers, so as not to make the prefs format really confusing) + enum OctaveNumberingSystem { + C0_Centre, + C3_Logic, + C4_ASA, + C5_Sonar + }; + static int getOctaveOfMiddleCInSystem(OctaveNumberingSystem s); + static OctaveNumberingSystem getSystemWithMiddleCInOctave(int o); + SpectrogramSmoothing m_spectrogramSmoothing; SpectrogramXSmoothing m_spectrogramXSmoothing; float m_tuningFrequency; @@ -123,6 +145,7 @@ int m_viewFontSize; BackgroundMode m_backgroundMode; TimeToTextMode m_timeToTextMode; + int m_octave; bool m_showSplash; };
--- a/base/RangeMapper.cpp Sat Apr 26 22:22:19 2014 +0100 +++ b/base/RangeMapper.cpp Wed May 07 15:11:56 2014 +0100 @@ -38,28 +38,38 @@ int LinearRangeMapper::getPositionForValue(float value) const { + int position = getPositionForValueUnclamped(value); + if (position < m_minpos) position = m_minpos; + if (position > m_maxpos) position = m_maxpos; + return position; +} + +int +LinearRangeMapper::getPositionForValueUnclamped(float value) const +{ int position = m_minpos + lrintf(((value - m_minval) / (m_maxval - m_minval)) * (m_maxpos - m_minpos)); - if (position < m_minpos) position = m_minpos; - if (position > m_maxpos) position = m_maxpos; -// SVDEBUG << "LinearRangeMapper::getPositionForValue: " << value << " -> " -// << position << " (minpos " << m_minpos << ", maxpos " << m_maxpos << ", minval " << m_minval << ", maxval " << m_maxval << ")" << endl; - if (m_inverted) return m_maxpos - position; + if (m_inverted) return m_maxpos - (position - m_minpos); else return position; } float LinearRangeMapper::getValueForPosition(int position) const { - if (m_inverted) position = m_maxpos - position; + if (position < m_minpos) position = m_minpos; + if (position > m_maxpos) position = m_maxpos; + float value = getValueForPositionUnclamped(position); + return value; +} + +float +LinearRangeMapper::getValueForPositionUnclamped(int position) const +{ + if (m_inverted) position = m_maxpos - (position - m_minpos); float value = m_minval + ((float(position - m_minpos) / float(m_maxpos - m_minpos)) * (m_maxval - m_minval)); - if (value < m_minval) value = m_minval; - if (value > m_maxval) value = m_maxval; -// SVDEBUG << "LinearRangeMapper::getValueForPosition: " << position << " -> " -// << value << " (minpos " << m_minpos << ", maxpos " << m_maxpos << ", minval " << m_minval << ", maxval " << m_maxval << ")" << endl; return value; } @@ -73,14 +83,16 @@ { convertMinMax(minpos, maxpos, minval, maxval, m_minlog, m_ratio); - cerr << "LogRangeMapper: minpos " << minpos << ", maxpos " - << maxpos << ", minval " << minval << ", maxval " - << maxval << ", minlog " << m_minlog << ", ratio " << m_ratio - << ", unit " << unit << endl; +// cerr << "LogRangeMapper: minpos " << minpos << ", maxpos " +// << maxpos << ", minval " << minval << ", maxval " +// << maxval << ", minlog " << m_minlog << ", ratio " << m_ratio +// << ", unit " << unit << endl; assert(m_maxpos != m_minpos); m_maxlog = (m_maxpos - m_minpos) / m_ratio + m_minlog; + +// cerr << "LogRangeMapper: maxlog = " << m_maxlog << endl; } void @@ -106,22 +118,223 @@ int LogRangeMapper::getPositionForValue(float value) const { - int position = (log10(value) - m_minlog) * m_ratio + m_minpos; + int position = getPositionForValueUnclamped(value); if (position < m_minpos) position = m_minpos; if (position > m_maxpos) position = m_maxpos; -// SVDEBUG << "LogRangeMapper::getPositionForValue: " << value << " -> " -// << position << " (minpos " << m_minpos << ", maxpos " << m_maxpos << ", ratio " << m_ratio << ", minlog " << m_minlog << ")" << endl; - if (m_inverted) return m_maxpos - position; + return position; +} + +int +LogRangeMapper::getPositionForValueUnclamped(float value) const +{ + static float thresh = powf(10, -10); + if (value < thresh) value = thresh; + int position = lrintf((log10(value) - m_minlog) * m_ratio) + m_minpos; + if (m_inverted) return m_maxpos - (position - m_minpos); else return position; } float LogRangeMapper::getValueForPosition(int position) const { - if (m_inverted) position = m_maxpos - position; - float value = powf(10, (position - m_minpos) / m_ratio + m_minlog); -// SVDEBUG << "LogRangeMapper::getValueForPosition: " << position << " -> " -// << value << " (minpos " << m_minpos << ", maxpos " << m_maxpos << ", ratio " << m_ratio << ", minlog " << m_minlog << ")" << endl; + if (position < m_minpos) position = m_minpos; + if (position > m_maxpos) position = m_maxpos; + float value = getValueForPositionUnclamped(position); return value; } +float +LogRangeMapper::getValueForPositionUnclamped(int position) const +{ + if (m_inverted) position = m_maxpos - (position - m_minpos); + float value = powf(10, (position - m_minpos) / m_ratio + m_minlog); + return value; +} + +InterpolatingRangeMapper::InterpolatingRangeMapper(CoordMap pointMappings, + QString unit) : + m_mappings(pointMappings), + m_unit(unit) +{ + for (CoordMap::const_iterator i = m_mappings.begin(); + i != m_mappings.end(); ++i) { + m_reverse[i->second] = i->first; + } +} + +int +InterpolatingRangeMapper::getPositionForValue(float value) const +{ + int pos = getPositionForValueUnclamped(value); + CoordMap::const_iterator i = m_mappings.begin(); + if (pos < i->second) pos = i->second; + i = m_mappings.end(); --i; + if (pos > i->second) pos = i->second; + return pos; +} + +int +InterpolatingRangeMapper::getPositionForValueUnclamped(float value) const +{ + float p = interpolate(&m_mappings, value); + return lrintf(p); +} + +float +InterpolatingRangeMapper::getValueForPosition(int position) const +{ + float val = getValueForPositionUnclamped(position); + CoordMap::const_iterator i = m_mappings.begin(); + if (val < i->first) val = i->first; + i = m_mappings.end(); --i; + if (val > i->first) val = i->first; + return val; +} + +float +InterpolatingRangeMapper::getValueForPositionUnclamped(int position) const +{ + return interpolate(&m_reverse, position); +} + +template <typename T> +float +InterpolatingRangeMapper::interpolate(T *mapping, float value) const +{ + // lower_bound: first element which does not compare less than value + typename T::const_iterator i = mapping->lower_bound(value); + + if (i == mapping->begin()) { + // value is less than or equal to first element, so use the + // gradient from first to second and extend it + ++i; + } + + if (i == mapping->end()) { + // value is off the end, so use the gradient from penultimate + // to ultimate and extend it + --i; + } + + typename T::const_iterator j = i; + --j; + + float gradient = float(i->second - j->second) / float(i->first - j->first); + + return j->second + (value - j->first) * gradient; +} + +AutoRangeMapper::AutoRangeMapper(CoordMap pointMappings, + QString unit) : + m_mappings(pointMappings), + m_unit(unit) +{ + m_type = chooseMappingTypeFor(m_mappings); + + CoordMap::const_iterator first = m_mappings.begin(); + CoordMap::const_iterator last = m_mappings.end(); + --last; + + switch (m_type) { + case StraightLine: + m_mapper = new LinearRangeMapper(first->second, last->second, + first->first, last->first, + unit, false); + break; + case Logarithmic: + m_mapper = new LogRangeMapper(first->second, last->second, + first->first, last->first, + unit, false); + break; + case Interpolating: + m_mapper = new InterpolatingRangeMapper(m_mappings, unit); + break; + } +} + +AutoRangeMapper::~AutoRangeMapper() +{ + delete m_mapper; +} + +AutoRangeMapper::MappingType +AutoRangeMapper::chooseMappingTypeFor(const CoordMap &mappings) +{ + // how do we work out whether a linear/log mapping is "close enough"? + + CoordMap::const_iterator first = mappings.begin(); + CoordMap::const_iterator last = mappings.end(); + --last; + + LinearRangeMapper linm(first->second, last->second, + first->first, last->first, + "", false); + + bool inadequate = false; + + for (CoordMap::const_iterator i = mappings.begin(); + i != mappings.end(); ++i) { + int candidate = linm.getPositionForValue(i->first); + int diff = candidate - i->second; + if (diff < 0) diff = -diff; + if (diff > 1) { +// cerr << "AutoRangeMapper::chooseMappingTypeFor: diff = " << diff +// << ", straight-line mapping inadequate" << endl; + inadequate = true; + break; + } + } + + if (!inadequate) { + return StraightLine; + } + + LogRangeMapper logm(first->second, last->second, + first->first, last->first, + "", false); + + inadequate = false; + + for (CoordMap::const_iterator i = mappings.begin(); + i != mappings.end(); ++i) { + int candidate = logm.getPositionForValue(i->first); + int diff = candidate - i->second; + if (diff < 0) diff = -diff; + if (diff > 1) { +// cerr << "AutoRangeMapper::chooseMappingTypeFor: diff = " << diff +// << ", log mapping inadequate" << endl; + inadequate = true; + break; + } + } + + if (!inadequate) { + return Logarithmic; + } + + return Interpolating; +} + +int +AutoRangeMapper::getPositionForValue(float value) const +{ + return m_mapper->getPositionForValue(value); +} + +float +AutoRangeMapper::getValueForPosition(int position) const +{ + return m_mapper->getValueForPosition(position); +} + +int +AutoRangeMapper::getPositionForValueUnclamped(float value) const +{ + return m_mapper->getPositionForValueUnclamped(value); +} + +float +AutoRangeMapper::getValueForPositionUnclamped(int position) const +{ + return m_mapper->getValueForPositionUnclamped(position); +}
--- a/base/RangeMapper.h Sat Apr 26 22:22:19 2014 +0100 +++ b/base/RangeMapper.h Wed May 07 15:11:56 2014 +0100 @@ -19,14 +19,48 @@ #include <QString> #include "Debug.h" - +#include <map> class RangeMapper { public: virtual ~RangeMapper() { } + + /** + * Return the position that maps to the given value, rounding to + * the nearest position and clamping to the minimum and maximum + * extents of the mapper's positional range. + */ virtual int getPositionForValue(float value) const = 0; + + /** + * Return the position that maps to the given value, rounding to + * the nearest position, without clamping. That is, whatever + * mapping function is in use will be projected even outside the + * minimum and maximum extents of the mapper's positional + * range. (The mapping outside that range is not guaranteed to be + * exact, except if the mapper is a linear one.) + */ + virtual int getPositionForValueUnclamped(float value) const = 0; + + /** + * Return the value mapped from the given position, clamping to + * the minimum and maximum extents of the mapper's value range. + */ virtual float getValueForPosition(int position) const = 0; + + /** + * Return the value mapped from the given positionq, without + * clamping. That is, whatever mapping function is in use will be + * projected even outside the minimum and maximum extents of the + * mapper's value range. (The mapping outside that range is not + * guaranteed to be exact, except if the mapper is a linear one.) + */ + virtual float getValueForPositionUnclamped(int position) const = 0; + + /** + * Get the unit of the mapper's value range. + */ virtual QString getUnit() const { return ""; } }; @@ -34,12 +68,21 @@ class LinearRangeMapper : public RangeMapper { public: + /** + * Map values in range minval->maxval linearly into integer range + * minpos->maxpos. minval and minpos must be less than maxval and + * maxpos respectively. If inverted is true, the range will be + * mapped "backwards" (minval to maxpos and maxval to minpos). + */ LinearRangeMapper(int minpos, int maxpos, float minval, float maxval, QString unit = "", bool inverted = false); virtual int getPositionForValue(float value) const; + virtual int getPositionForValueUnclamped(float value) const; + virtual float getValueForPosition(int position) const; + virtual float getValueForPositionUnclamped(int position) const; virtual QString getUnit() const { return m_unit; } @@ -52,10 +95,17 @@ bool m_inverted; }; - class LogRangeMapper : public RangeMapper { public: + /** + * Map values in range minval->maxval into integer range + * minpos->maxpos such that logs of the values are mapped + * linearly. minval must be greater than zero, and minval and + * minpos must be less than maxval and maxpos respectively. If + * inverted is true, the range will be mapped "backwards" (minval + * to maxpos and maxval to minpos). + */ LogRangeMapper(int minpos, int maxpos, float minval, float maxval, QString m_unit = "", bool inverted = false); @@ -69,7 +119,10 @@ float &ratio, float &minlog); virtual int getPositionForValue(float value) const; + virtual int getPositionForValueUnclamped(float value) const; + virtual float getValueForPosition(int position) const; + virtual float getValueForPositionUnclamped(int position) const; virtual QString getUnit() const { return m_unit; } @@ -83,5 +136,120 @@ bool m_inverted; }; +class InterpolatingRangeMapper : public RangeMapper +{ +public: + typedef std::map<float, int> CoordMap; + + /** + * Given a series of (value, position) coordinate mappings, + * construct a range mapper that maps arbitrary values, in the + * range between minimum and maximum of the provided values, onto + * coordinates using linear interpolation between the supplied + * points. + * + *!!! todo: Cubic -- more generally useful than linear interpolation + *!!! todo: inverted flag + * + * The set of provided mappings must contain at least two + * coordinates. + * + * It is expected that the values and positions in the coordinate + * mappings will both be monotonically increasing (i.e. no + * inflections in the mapping curve). Behaviour is undefined if + * this is not the case. + */ + InterpolatingRangeMapper(CoordMap pointMappings, + QString unit); + + virtual int getPositionForValue(float value) const; + virtual int getPositionForValueUnclamped(float value) const; + + virtual float getValueForPosition(int position) const; + virtual float getValueForPositionUnclamped(int position) const; + + virtual QString getUnit() const { return m_unit; } + +protected: + CoordMap m_mappings; + std::map<int, float> m_reverse; + QString m_unit; + + template <typename T> + float interpolate(T *mapping, float v) const; +}; + +class AutoRangeMapper : public RangeMapper +{ +public: + enum MappingType { + Interpolating, + StraightLine, + Logarithmic, + }; + + typedef std::map<float, int> CoordMap; + + /** + * Given a series of (value, position) coordinate mappings, + * construct a range mapper that maps arbitrary values, in the + * range between minimum and maximum of the provided values, onto + * coordinates. + * + * The mapping used may be + * + * Interpolating -- an InterpolatingRangeMapper will be used + * + * StraightLine -- a LinearRangeMapper from the minimum to + * maximum value coordinates will be used, ignoring all other + * supplied coordinate mappings + * + * Logarithmic -- a LogRangeMapper from the minimum to + * maximum value coordinates will be used, ignoring all other + * supplied coordinate mappings + * + * The mapping will be chosen automatically by looking at the + * supplied coordinates. If the supplied coordinates fall on a + * straight line, a StraightLine mapping will be used; if they + * fall on a log curve, a Logarithmic mapping will be used; + * otherwise an Interpolating mapping will be used. + * + *!!! todo: inverted flag + * + * The set of provided mappings must contain at least two + * coordinates, or at least three if the points are not supposed + * to be in a straight line. + * + * It is expected that the values and positions in the coordinate + * mappings will both be monotonically increasing (i.e. no + * inflections in the mapping curve). Behaviour is undefined if + * this is not the case. + */ + AutoRangeMapper(CoordMap pointMappings, + QString unit); + + ~AutoRangeMapper(); + + /** + * Return the mapping type in use. + */ + MappingType getType() const { return m_type; } + + virtual int getPositionForValue(float value) const; + virtual int getPositionForValueUnclamped(float value) const; + + virtual float getValueForPosition(int position) const; + virtual float getValueForPositionUnclamped(int position) const; + + virtual QString getUnit() const { return m_unit; } + +protected: + MappingType m_type; + CoordMap m_mappings; + QString m_unit; + RangeMapper *m_mapper; + + MappingType chooseMappingTypeFor(const CoordMap &); +}; #endif
--- a/base/RealTime.cpp Sat Apr 26 22:22:19 2014 +0100 +++ b/base/RealTime.cpp Wed May 07 15:11:56 2014 +0100 @@ -442,7 +442,7 @@ { if (time < zeroTime) return -realTime2Frame(-time, sampleRate); double s = time.sec + double(time.nsec + 1) / 1000000000.0; - return long(s * sampleRate); + return long(s * double(sampleRate)); } RealTime
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/test/TestPitch.h Wed May 07 15:11:56 2014 +0100 @@ -0,0 +1,97 @@ +/* -*- 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 TEST_PITCH_H +#define TEST_PITCH_H + +#include "../Pitch.h" +#include "../Preferences.h" + +#include <QObject> +#include <QtTest> +#include <QDir> + +#include <iostream> + +using namespace std; + +class TestPitch : public QObject +{ + Q_OBJECT + +private slots: + void init() { + Preferences::getInstance()->setOctaveOfMiddleC(4); + Preferences::getInstance()->setTuningFrequency(440); + } + + void pitchLabel() + { + QCOMPARE(Pitch::getPitchLabel(60, 0, false), QString("C4")); + QCOMPARE(Pitch::getPitchLabel(69, 0, false), QString("A4")); + QCOMPARE(Pitch::getPitchLabel(61, 0, false), QString("C#4")); + QCOMPARE(Pitch::getPitchLabel(61, 0, true), QString("Db4")); + QCOMPARE(Pitch::getPitchLabel(59, 0, false), QString("B3")); + QCOMPARE(Pitch::getPitchLabel(59, 0, true), QString("B3")); + QCOMPARE(Pitch::getPitchLabel(0, 0, false), QString("C-1")); + + QCOMPARE(Pitch::getPitchLabel(60, -40, false), QString("C4-40c")); + QCOMPARE(Pitch::getPitchLabel(60, 40, false), QString("C4+40c")); + QCOMPARE(Pitch::getPitchLabel(58, 4, false), QString("A#3+4c")); + + Preferences::getInstance()->setOctaveOfMiddleC(3); + + QCOMPARE(Pitch::getPitchLabel(60, 0, false), QString("C3")); + QCOMPARE(Pitch::getPitchLabel(69, 0, false), QString("A3")); + QCOMPARE(Pitch::getPitchLabel(61, 0, false), QString("C#3")); + QCOMPARE(Pitch::getPitchLabel(61, 0, true), QString("Db3")); + QCOMPARE(Pitch::getPitchLabel(59, 0, false), QString("B2")); + QCOMPARE(Pitch::getPitchLabel(59, 0, true), QString("B2")); + QCOMPARE(Pitch::getPitchLabel(0, 0, false), QString("C-2")); + + QCOMPARE(Pitch::getPitchLabel(60, -40, false), QString("C3-40c")); + QCOMPARE(Pitch::getPitchLabel(60, 40, false), QString("C3+40c")); + QCOMPARE(Pitch::getPitchLabel(58, 4, false), QString("A#2+4c")); + } + + void pitchLabelForFrequency() + { + QCOMPARE(Pitch::getPitchLabelForFrequency(440, 440, false), QString("A4")); + QCOMPARE(Pitch::getPitchLabelForFrequency(440, 220, false), QString("A5")); + QCOMPARE(Pitch::getPitchLabelForFrequency(261.63, 440, false), QString("C4")); + } + +#define MIDDLE_C 261.6255653f + + void frequencyForPitch() + { + QCOMPARE(Pitch::getFrequencyForPitch(60, 0), MIDDLE_C); + QCOMPARE(Pitch::getFrequencyForPitch(69, 0), 440.f); + QCOMPARE(Pitch::getFrequencyForPitch(60, 0, 220), MIDDLE_C / 2.f); + QCOMPARE(Pitch::getFrequencyForPitch(69, 0, 220), 220.f); + } + + void pitchForFrequency() + { + float centsOffset = 0.f; + QCOMPARE(Pitch::getPitchForFrequency(MIDDLE_C, ¢sOffset), 60); + QCOMPARE(centsOffset, 0.f); + QCOMPARE(Pitch::getPitchForFrequency(261.0, ¢sOffset), 60); + QCOMPARE(int(centsOffset), -4); + QCOMPARE(Pitch::getPitchForFrequency(440.0, ¢sOffset), 69); + QCOMPARE(centsOffset, 0.f); + } +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/test/TestRangeMapper.h Wed May 07 15:11:56 2014 +0100 @@ -0,0 +1,284 @@ +/* -*- 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 TEST_RANGE_MAPPER_H +#define TEST_RANGE_MAPPER_H + +#include "../RangeMapper.h" + +#include <QObject> +#include <QtTest> +#include <QDir> + +#include <iostream> + +using namespace std; + +class TestRangeMapper : public QObject +{ + Q_OBJECT + +private slots: + void linearUpForward() + { + LinearRangeMapper rm(1, 8, 0.5, 4.0, "x", false); + QCOMPARE(rm.getUnit(), QString("x")); + QCOMPARE(rm.getPositionForValue(0.5), 1); + QCOMPARE(rm.getPositionForValue(4.0), 8); + QCOMPARE(rm.getPositionForValue(3.0), 6); + QCOMPARE(rm.getPositionForValue(3.1), 6); + QCOMPARE(rm.getPositionForValue(3.4), 7); + QCOMPARE(rm.getPositionForValue(0.2), 1); + QCOMPARE(rm.getPositionForValue(-12), 1); + QCOMPARE(rm.getPositionForValue(6.1), 8); + QCOMPARE(rm.getPositionForValueUnclamped(3.0), 6); + QCOMPARE(rm.getPositionForValueUnclamped(0.2), 0); + QCOMPARE(rm.getPositionForValueUnclamped(-12), -24); + QCOMPARE(rm.getPositionForValueUnclamped(6.1), 12); + } + + void linearDownForward() + { + LinearRangeMapper rm(1, 8, 0.5, 4.0, "x", true); + QCOMPARE(rm.getUnit(), QString("x")); + QCOMPARE(rm.getPositionForValue(0.5), 8); + QCOMPARE(rm.getPositionForValue(4.0), 1); + QCOMPARE(rm.getPositionForValue(3.0), 3); + QCOMPARE(rm.getPositionForValue(3.1), 3); + QCOMPARE(rm.getPositionForValue(3.4), 2); + QCOMPARE(rm.getPositionForValue(0.2), 8); + QCOMPARE(rm.getPositionForValue(-12), 8); + QCOMPARE(rm.getPositionForValue(6.1), 1); + QCOMPARE(rm.getPositionForValueUnclamped(3.0), 3); + QCOMPARE(rm.getPositionForValueUnclamped(0.2), 9); + QCOMPARE(rm.getPositionForValueUnclamped(-12), 33); + QCOMPARE(rm.getPositionForValueUnclamped(6.1), -3); + } + + void linearUpBackward() + { + LinearRangeMapper rm(1, 8, 0.5, 4.0, "x", false); + QCOMPARE(rm.getUnit(), QString("x")); + QCOMPARE(rm.getValueForPosition(1), 0.5); + QCOMPARE(rm.getValueForPosition(8), 4.0); + QCOMPARE(rm.getValueForPosition(6), 3.0); + QCOMPARE(rm.getValueForPosition(7), 3.5); + QCOMPARE(rm.getValueForPosition(0), rm.getValueForPosition(1)); + QCOMPARE(rm.getValueForPosition(9), rm.getValueForPosition(8)); + QCOMPARE(rm.getValueForPositionUnclamped(6), 3.0); + QCOMPARE(rm.getValueForPositionUnclamped(0), 0.0); + QCOMPARE(rm.getValueForPositionUnclamped(-24), -12.0); + QCOMPARE(rm.getValueForPositionUnclamped(12), 6.0); + } + + void linearDownBackward() + { + LinearRangeMapper rm(1, 8, 0.5, 4.0, "x", true); + QCOMPARE(rm.getUnit(), QString("x")); + QCOMPARE(rm.getValueForPosition(8), 0.5); + QCOMPARE(rm.getValueForPosition(1), 4.0); + QCOMPARE(rm.getValueForPosition(3), 3.0); + QCOMPARE(rm.getValueForPosition(2), 3.5); + QCOMPARE(rm.getValueForPosition(0), rm.getValueForPosition(1)); + QCOMPARE(rm.getValueForPosition(9), rm.getValueForPosition(8)); + QCOMPARE(rm.getValueForPositionUnclamped(3), 3.0); + QCOMPARE(rm.getValueForPositionUnclamped(9), 0.0); + QCOMPARE(rm.getValueForPositionUnclamped(33), -12.0); + QCOMPARE(rm.getValueForPositionUnclamped(-3), 6.0); + } + + void logUpForward() + { + LogRangeMapper rm(3, 7, 10, 100000, "x", false); + QCOMPARE(rm.getUnit(), QString("x")); + QCOMPARE(rm.getPositionForValue(10.0), 3); + QCOMPARE(rm.getPositionForValue(100000.0), 7); + QCOMPARE(rm.getPositionForValue(1.0), 3); + QCOMPARE(rm.getPositionForValue(1000000.0), 7); + QCOMPARE(rm.getPositionForValue(1000.0), 5); + QCOMPARE(rm.getPositionForValue(900.0), 5); + QCOMPARE(rm.getPositionForValue(20000), 6); + QCOMPARE(rm.getPositionForValueUnclamped(1.0), 2); + QCOMPARE(rm.getPositionForValueUnclamped(1000000.0), 8); + QCOMPARE(rm.getPositionForValueUnclamped(1000.0), 5); + } + + void logDownForward() + { + LogRangeMapper rm(3, 7, 10, 100000, "x", true); + QCOMPARE(rm.getUnit(), QString("x")); + QCOMPARE(rm.getPositionForValue(10.0), 7); + QCOMPARE(rm.getPositionForValue(100000.0), 3); + QCOMPARE(rm.getPositionForValue(1.0), 7); + QCOMPARE(rm.getPositionForValue(1000000.0), 3); + QCOMPARE(rm.getPositionForValue(1000.0), 5); + QCOMPARE(rm.getPositionForValue(900.0), 5); + QCOMPARE(rm.getPositionForValue(20000), 4); + QCOMPARE(rm.getPositionForValueUnclamped(1.0), 8); + QCOMPARE(rm.getPositionForValueUnclamped(1000000.0), 2); + QCOMPARE(rm.getPositionForValueUnclamped(1000.0), 5); + } + + void logUpBackward() + { + LogRangeMapper rm(3, 7, 10, 100000, "x", false); + QCOMPARE(rm.getUnit(), QString("x")); + QCOMPARE(rm.getValueForPosition(3), 10.0); + QCOMPARE(rm.getValueForPosition(7), 100000.0); + QCOMPARE(rm.getValueForPosition(5), 1000.0); + QCOMPARE(rm.getValueForPosition(6), 10000.0); + QCOMPARE(rm.getValueForPosition(0), rm.getValueForPosition(3)); + QCOMPARE(rm.getValueForPosition(9), rm.getValueForPosition(7)); + QCOMPARE(rm.getValueForPositionUnclamped(2), 1.0); + QCOMPARE(rm.getValueForPositionUnclamped(8), 1000000.0); + QCOMPARE(rm.getValueForPositionUnclamped(5), 1000.0); + } + + void logDownBackward() + { + LogRangeMapper rm(3, 7, 10, 100000, "x", true); + QCOMPARE(rm.getUnit(), QString("x")); + QCOMPARE(rm.getValueForPosition(7), 10.0); + QCOMPARE(rm.getValueForPosition(3), 100000.0); + QCOMPARE(rm.getValueForPosition(5), 1000.0); + QCOMPARE(rm.getValueForPosition(4), 10000.0); + QCOMPARE(rm.getValueForPosition(0), rm.getValueForPosition(3)); + QCOMPARE(rm.getValueForPosition(9), rm.getValueForPosition(7)); + QCOMPARE(rm.getValueForPositionUnclamped(8), 1.0); + QCOMPARE(rm.getValueForPositionUnclamped(2), 1000000.0); + QCOMPARE(rm.getValueForPositionUnclamped(5), 1000.0); + } + + void interpolatingForward() + { + InterpolatingRangeMapper::CoordMap mappings; + mappings[1] = 10; + mappings[3] = 30; + mappings[5] = 70; + InterpolatingRangeMapper rm(mappings, "x"); + QCOMPARE(rm.getUnit(), QString("x")); + QCOMPARE(rm.getPositionForValue(1.0), 10); + QCOMPARE(rm.getPositionForValue(0.0), 10); + QCOMPARE(rm.getPositionForValue(5.0), 70); + QCOMPARE(rm.getPositionForValue(6.0), 70); + QCOMPARE(rm.getPositionForValue(3.0), 30); + QCOMPARE(rm.getPositionForValue(2.5), 25); + QCOMPARE(rm.getPositionForValue(4.5), 60); + QCOMPARE(rm.getPositionForValueUnclamped(0.0), 0); + QCOMPARE(rm.getPositionForValueUnclamped(2.5), 25); + QCOMPARE(rm.getPositionForValueUnclamped(6.0), 90); + } + + void interpolatingBackward() + { + InterpolatingRangeMapper::CoordMap mappings; + mappings[1] = 10; + mappings[3] = 30; + mappings[5] = 70; + InterpolatingRangeMapper rm(mappings, "x"); + QCOMPARE(rm.getUnit(), QString("x")); + QCOMPARE(rm.getValueForPosition(10), 1.0); + QCOMPARE(rm.getValueForPosition(9), 1.0); + QCOMPARE(rm.getValueForPosition(70), 5.0); + QCOMPARE(rm.getValueForPosition(80), 5.0); + QCOMPARE(rm.getValueForPosition(30), 3.0); + QCOMPARE(rm.getValueForPosition(25), 2.5); + QCOMPARE(rm.getValueForPosition(60), 4.5); + } + + void autoLinearForward() + { + AutoRangeMapper::CoordMap mappings; + mappings[0.5] = 1; + mappings[4.0] = 8; + AutoRangeMapper rm1(mappings, "x"); + QCOMPARE(rm1.getUnit(), QString("x")); + QCOMPARE(rm1.getType(), AutoRangeMapper::StraightLine); + QCOMPARE(rm1.getPositionForValue(0.1), 1); + QCOMPARE(rm1.getPositionForValue(0.5), 1); + QCOMPARE(rm1.getPositionForValue(4.0), 8); + QCOMPARE(rm1.getPositionForValue(4.5), 8); + QCOMPARE(rm1.getPositionForValue(3.0), 6); + QCOMPARE(rm1.getPositionForValue(3.1), 6); + QCOMPARE(rm1.getPositionForValueUnclamped(0.1), 0); + QCOMPARE(rm1.getPositionForValueUnclamped(3.1), 6); + QCOMPARE(rm1.getPositionForValueUnclamped(4.5), 9); + mappings[3.0] = 6; + mappings[3.5] = 7; + AutoRangeMapper rm2(mappings, "x"); + QCOMPARE(rm2.getUnit(), QString("x")); + QCOMPARE(rm2.getType(), AutoRangeMapper::StraightLine); + QCOMPARE(rm2.getPositionForValue(0.5), 1); + QCOMPARE(rm2.getPositionForValue(4.0), 8); + QCOMPARE(rm2.getPositionForValue(3.0), 6); + QCOMPARE(rm2.getPositionForValue(3.1), 6); + } + + void autoLogForward() + { + AutoRangeMapper::CoordMap mappings; + mappings[10] = 3; + mappings[1000] = 5; + mappings[100000] = 7; + AutoRangeMapper rm1(mappings, "x"); + QCOMPARE(rm1.getUnit(), QString("x")); + QCOMPARE(rm1.getType(), AutoRangeMapper::Logarithmic); + QCOMPARE(rm1.getPositionForValue(10.0), 3); + QCOMPARE(rm1.getPositionForValue(100000.0), 7); + QCOMPARE(rm1.getPositionForValue(1.0), 3); + QCOMPARE(rm1.getPositionForValue(1000000.0), 7); + QCOMPARE(rm1.getPositionForValue(1000.0), 5); + QCOMPARE(rm1.getPositionForValue(900.0), 5); + QCOMPARE(rm1.getPositionForValue(20000), 6); + QCOMPARE(rm1.getPositionForValueUnclamped(1.0), 2); + QCOMPARE(rm1.getPositionForValueUnclamped(900.0), 5); + QCOMPARE(rm1.getPositionForValueUnclamped(1000000.0), 8); + mappings[100] = 4; + AutoRangeMapper rm2(mappings, "x"); + QCOMPARE(rm2.getUnit(), QString("x")); + QCOMPARE(rm2.getType(), AutoRangeMapper::Logarithmic); + QCOMPARE(rm2.getPositionForValue(10.0), 3); + QCOMPARE(rm2.getPositionForValue(100000.0), 7); + QCOMPARE(rm2.getPositionForValue(1.0), 3); + QCOMPARE(rm2.getPositionForValue(1000000.0), 7); + QCOMPARE(rm2.getPositionForValue(1000.0), 5); + QCOMPARE(rm2.getPositionForValue(900.0), 5); + QCOMPARE(rm2.getPositionForValue(20000), 6); + } + + void autoInterpolatingForward() + { + AutoRangeMapper::CoordMap mappings; + mappings[1] = 10; + mappings[3] = 30; + mappings[5] = 70; + AutoRangeMapper rm(mappings, "x"); + QCOMPARE(rm.getUnit(), QString("x")); + QCOMPARE(rm.getType(), AutoRangeMapper::Interpolating); + QCOMPARE(rm.getPositionForValue(1.0), 10); + QCOMPARE(rm.getPositionForValue(0.0), 10); + QCOMPARE(rm.getPositionForValue(5.0), 70); + QCOMPARE(rm.getPositionForValue(6.0), 70); + QCOMPARE(rm.getPositionForValue(3.0), 30); + QCOMPARE(rm.getPositionForValue(2.5), 25); + QCOMPARE(rm.getPositionForValue(4.5), 60); + QCOMPARE(rm.getPositionForValueUnclamped(0.0), 0); + QCOMPARE(rm.getPositionForValueUnclamped(5.0), 70); + QCOMPARE(rm.getPositionForValueUnclamped(6.0), 90); + } +}; + +#endif + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/test/main.cpp Wed May 07 15:11:56 2014 +0100 @@ -0,0 +1,47 @@ +/* -*- 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 "TestRangeMapper.h" +#include "TestPitch.h" + +#include <QtTest> + +#include <iostream> + +int main(int argc, char *argv[]) +{ + int good = 0, bad = 0; + + QCoreApplication app(argc, argv); + app.setOrganizationName("Sonic Visualiser"); + app.setApplicationName("test-svcore-base"); + + { + TestRangeMapper t; + if (QTest::qExec(&t, argc, argv) == 0) ++good; + else ++bad; + } + { + TestPitch t; + if (QTest::qExec(&t, argc, argv) == 0) ++good; + else ++bad; + } + + if (bad > 0) { + cerr << "\n********* " << bad << " test suite(s) failed!\n" << endl; + return 1; + } else { + cerr << "All tests passed" << endl; + return 0; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/test/test.pro Wed May 07 15:11:56 2014 +0100 @@ -0,0 +1,53 @@ + +TEMPLATE = app + +LIBS += -L../.. -L../../../dataquay -L../../release -L../../../dataquay/release -lsvcore -ldataquay + +win32-g++ { + INCLUDEPATH += ../../../sv-dependency-builds/win32-mingw/include + LIBS += -L../../../sv-dependency-builds/win32-mingw/lib +} + +exists(../../config.pri) { + include(../../config.pri) +} + +win* { + !exists(../../config.pri) { + DEFINES += HAVE_BZ2 HAVE_FFTW3 HAVE_FFTW3F HAVE_SNDFILE HAVE_SAMPLERATE HAVE_VAMP HAVE_VAMPHOSTSDK HAVE_RUBBERBAND HAVE_DATAQUAY HAVE_LIBLO HAVE_MAD HAVE_ID3TAG HAVE_PORTAUDIO_2_0 + LIBS += -lbz2 -lrubberband -lvamp-hostsdk -lfftw3 -lfftw3f -lsndfile -lFLAC -logg -lvorbis -lvorbisenc -lvorbisfile -logg -lmad -lid3tag -lportaudio -lsamplerate -llo -lz -lsord-0 -lserd-0 -lwinmm -lws2_32 + } +} + +CONFIG += qt thread warn_on stl rtti exceptions console +QT += network xml testlib +QT -= gui + +TARGET = svcore-base-test + +DEPENDPATH += ../.. +INCLUDEPATH += ../.. +OBJECTS_DIR = o +MOC_DIR = o + +HEADERS += TestRangeMapper.h TestPitch.h +SOURCES += main.cpp + +win* { +//PRE_TARGETDEPS += ../../svcore.lib +} +!win* { +PRE_TARGETDEPS += ../../libsvcore.a +} + +!win32 { + !macx* { + QMAKE_POST_LINK=./$${TARGET} + } + macx* { + QMAKE_POST_LINK=./$${TARGET}.app/Contents/MacOS/$${TARGET} + } +} + +win32:QMAKE_POST_LINK=./release/$${TARGET}.exe +
--- a/data/fileio/test/main.cpp Sat Apr 26 22:22:19 2014 +0100 +++ b/data/fileio/test/main.cpp Wed May 07 15:11:56 2014 +0100 @@ -1,5 +1,16 @@ /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ -/* Copyright Chris Cannam - All Rights Reserved */ +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2013 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. +*/ #include "AudioFileReaderTest.h"
--- a/data/fileio/test/test.pro Sat Apr 26 22:22:19 2014 +0100 +++ b/data/fileio/test/test.pro Wed May 07 15:11:56 2014 +0100 @@ -23,7 +23,7 @@ QT += network xml testlib QT -= gui -TARGET = svcore-test +TARGET = svcore-data-fileio-test DEPENDPATH += ../../.. INCLUDEPATH += ../../..
--- a/data/model/DenseThreeDimensionalModel.h Sat Apr 26 22:22:19 2014 +0100 +++ b/data/model/DenseThreeDimensionalModel.h Wed May 07 15:11:56 2014 +0100 @@ -83,6 +83,25 @@ virtual QString getBinName(size_t n) const = 0; /** + * Return true if the bins have values as well as names. If this + * returns true, getBinValue() may be used to retrieve the values. + */ + virtual bool hasBinValues() const { return false; } + + /** + * Return the value of bin n, if any. This is a "vertical scale" + * value which does not vary from one column to the next. This is + * only meaningful if hasBinValues() returns true. + */ + virtual float getBinValue(size_t n) const { return n; } + + /** + * Obtain the name of the unit of the values returned from + * getBinValue(), if any. + */ + virtual QString getBinValueUnit() const { return ""; } + + /** * Estimate whether a logarithmic scale might be appropriate for * the value scale. */
--- a/data/model/EditableDenseThreeDimensionalModel.cpp Sat Apr 26 22:22:19 2014 +0100 +++ b/data/model/EditableDenseThreeDimensionalModel.cpp Wed May 07 15:11:56 2014 +0100 @@ -410,6 +410,37 @@ } bool +EditableDenseThreeDimensionalModel::hasBinValues() const +{ + return !m_binValues.empty(); +} + +float +EditableDenseThreeDimensionalModel::getBinValue(size_t n) const +{ + if (n < m_binValues.size()) return m_binValues[n]; + else return 0.f; +} + +void +EditableDenseThreeDimensionalModel::setBinValues(std::vector<float> values) +{ + m_binValues = values; +} + +QString +EditableDenseThreeDimensionalModel::getBinValueUnit() const +{ + return m_binValueUnit; +} + +void +EditableDenseThreeDimensionalModel::setBinValueUnit(QString unit) +{ + m_binValueUnit = unit; +} + +bool EditableDenseThreeDimensionalModel::shouldUseLogValueScale() const { QReadLocker locker(&m_lock);
--- a/data/model/EditableDenseThreeDimensionalModel.h Sat Apr 26 22:22:19 2014 +0100 +++ b/data/model/EditableDenseThreeDimensionalModel.h Wed May 07 15:11:56 2014 +0100 @@ -127,10 +127,61 @@ */ virtual void setColumn(size_t x, const Column &values); + /** + * Return the name of bin n. This is a single label per bin that + * does not vary from one column to the next. + */ virtual QString getBinName(size_t n) const; + + /** + * Set the name of bin n. + */ virtual void setBinName(size_t n, QString); + + /** + * Set the names of all bins. + */ virtual void setBinNames(std::vector<QString> names); + /** + * Return true if the bins have values as well as names. (The + * values may have been derived from the names, e.g. by parsing + * numbers from them.) If this returns true, getBinValue() may be + * used to retrieve the values. + */ + virtual bool hasBinValues() const; + + /** + * Return the value of bin n, if any. This is a "vertical scale" + * value which does not vary from one column to the next. This is + * only meaningful if hasBinValues() returns true. + */ + virtual float getBinValue(size_t n) const; + + /** + * Set the values of all bins (separate from their labels). These + * are "vertical scale" values which do not vary from one column + * to the next. + */ + virtual void setBinValues(std::vector<float> values); + + /** + * Obtain the name of the unit of the values returned from + * getBinValue(), if any. + */ + virtual QString getBinValueUnit() const; + + /** + * Set the name of the unit of the values return from + * getBinValue() if any. + */ + virtual void setBinValueUnit(QString unit); + + /** + * Return true if the distribution of values in the bins is such + * as to suggest a log scale (mapping to colour etc) may be better + * than a linear one. + */ bool shouldUseLogValueScale() const; virtual void setCompletion(int completion, bool update = true); @@ -162,6 +213,8 @@ Column expandAndRetrieve(size_t index) const; std::vector<QString> m_binNames; + std::vector<float> m_binValues; + QString m_binValueUnit; size_t m_startFrame; size_t m_sampleRate;
--- a/rdf/SimpleSPARQLQuery.cpp Sat Apr 26 22:22:19 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,639 +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 2008 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. -*/ - -#include "SimpleSPARQLQuery.h" -#include "base/ProgressReporter.h" -#include "base/Profiler.h" - -#include <QMutex> -#include <QMutexLocker> -#include <QRegExp> - -#include <set> - -// Rather than including <redland.h> -- for some reason redland.h -// includes <rasqal.h>, while the rasqal header actually gets -// installed as <rasqal/rasqal.h> which breaks the inclusion all over -// the place unless a very clever include path is set -#include <rasqal/rasqal.h> -#include <librdf.h> - -//#define DEBUG_SIMPLE_SPARQL_QUERY 1 - -#include <iostream> - -using cerr; -using endl; - -class WredlandWorldWrapper -{ -public: - WredlandWorldWrapper(); - ~WredlandWorldWrapper(); - - bool isOK() const; - - bool loadUriIntoDefaultModel(QString uriString, QString &errorString); - - librdf_world *getWorld() { return m_world; } - const librdf_world *getWorld() const { return m_world; } - - librdf_model *getDefaultModel() { return m_defaultModel; } - const librdf_model *getDefaultModel() const { return m_defaultModel; } - - librdf_model *getModel(QString fromUri); - void freeModel(QString forUri); - -private: - QMutex m_mutex; - - librdf_world *m_world; - librdf_storage *m_defaultStorage; - librdf_model *m_defaultModel; - - std::set<QString> m_defaultModelUris; - - std::map<QString, librdf_storage *> m_ownStorageUris; - std::map<QString, librdf_model *> m_ownModelUris; - - bool loadUri(librdf_model *model, QString uri, QString &errorString); -}; - -WredlandWorldWrapper::WredlandWorldWrapper() : - m_world(0), m_defaultStorage(0), m_defaultModel(0) -{ - m_world = librdf_new_world(); - if (!m_world) { - cerr << "SimpleSPARQLQuery: ERROR: Failed to create LIBRDF world!" << endl; - return; - } - librdf_world_open(m_world); - - m_defaultStorage = librdf_new_storage(m_world, "trees", NULL, NULL); - if (!m_defaultStorage) { - cerr << "SimpleSPARQLQuery: WARNING: Failed to initialise Redland trees datastore, falling back to memory store" << endl; - m_defaultStorage = librdf_new_storage(m_world, NULL, NULL, NULL); - if (!m_defaultStorage) { - cerr << "SimpleSPARQLQuery: ERROR: Failed to initialise Redland memory datastore" << endl; - return; - } - } - m_defaultModel = librdf_new_model(m_world, m_defaultStorage, NULL); - if (!m_defaultModel) { - cerr << "SimpleSPARQLQuery: ERROR: Failed to initialise Redland data model" << endl; - return; - } -} - -WredlandWorldWrapper::~WredlandWorldWrapper() -{ -/*!!! This is a static singleton; destroying it while there are - queries outstanding can be problematic, it appears, and since - the storage is non-persistent there shouldn't be any need to - destroy it explicitly, except for the sake of tidiness. - - while (!m_ownModelUris.empty()) { - librdf_free_model(m_ownModelUris.begin()->second); - m_ownModelUris.erase(m_ownModelUris.begin()); - } - while (!m_ownStorageUris.empty()) { - librdf_free_storage(m_ownStorageUris.begin()->second); - m_ownStorageUris.erase(m_ownStorageUris.begin()); - } - if (m_defaultModel) librdf_free_model(m_defaultModel); - if (m_defaultStorage) librdf_free_storage(m_defaultStorage); - if (m_world) librdf_free_world(m_world); -*/ -} - -bool -WredlandWorldWrapper::isOK() const { - return (m_defaultModel != 0); -} - -bool -WredlandWorldWrapper::loadUriIntoDefaultModel(QString uriString, QString &errorString) -{ - QMutexLocker locker(&m_mutex); - - if (m_defaultModelUris.find(uriString) != m_defaultModelUris.end()) { - return true; - } - - if (loadUri(m_defaultModel, uriString, errorString)) { - m_defaultModelUris.insert(uriString); - return true; - } else { - return false; - } -} - -librdf_model * -WredlandWorldWrapper::getModel(QString fromUri) -{ - QMutexLocker locker(&m_mutex); - if (fromUri == "") { - return getDefaultModel(); - } - if (m_ownModelUris.find(fromUri) != m_ownModelUris.end()) { - return m_ownModelUris[fromUri]; - } - librdf_storage *storage = librdf_new_storage(m_world, "trees", NULL, NULL); - if (!storage) { // don't warn here, we probably already did it in main ctor - storage = librdf_new_storage(m_world, NULL, NULL, NULL); - } - librdf_model *model = librdf_new_model(m_world, storage, NULL); - if (!model) { - cerr << "SimpleSPARQLQuery: ERROR: Failed to create new model" << endl; - librdf_free_storage(storage); - return 0; - } - QString err; - if (!loadUri(model, fromUri, err)) { - cerr << "SimpleSPARQLQuery: ERROR: Failed to parse into new model: " << err << endl; - librdf_free_model(model); - librdf_free_storage(storage); - m_ownModelUris[fromUri] = 0; - return 0; - } - m_ownModelUris[fromUri] = model; - m_ownStorageUris[fromUri] = storage; - return model; -} - -void -WredlandWorldWrapper::freeModel(QString forUri) -{ -#ifdef DEBUG_SIMPLE_SPARQL_QUERY - SVDEBUG << "SimpleSPARQLQuery::freeModel: Model uri = \"" << forUri << "\"" << endl; -#endif - - QMutexLocker locker(&m_mutex); - if (forUri == "") { - SVDEBUG << "SimpleSPARQLQuery::freeModel: ERROR: Can't free default model" << endl; - return; - } - if (m_ownModelUris.find(forUri) == m_ownModelUris.end()) { -#ifdef DEBUG_SIMPLE_SPARQL_QUERY - SVDEBUG << "SimpleSPARQLQuery::freeModel: NOTE: Unknown or already-freed model (uri = \"" << forUri << "\")" << endl; -#endif - return; - } - - librdf_model *model = m_ownModelUris[forUri]; - if (model) librdf_free_model(model); - m_ownModelUris.erase(forUri); - - if (m_ownStorageUris.find(forUri) != m_ownStorageUris.end()) { - librdf_storage *storage = m_ownStorageUris[forUri]; - if (storage) librdf_free_storage(storage); - m_ownStorageUris.erase(forUri); - } -} - -bool -WredlandWorldWrapper::loadUri(librdf_model *model, QString uri, QString &errorString) -{ - librdf_uri *luri = librdf_new_uri - (m_world, (const unsigned char *)uri.toUtf8().data()); - if (!luri) { - errorString = "Failed to construct librdf_uri!"; - return false; - } - - librdf_parser *parser = librdf_new_parser(m_world, "guess", NULL, NULL); - if (!parser) { - errorString = "Failed to initialise Redland parser"; - return false; - } - -#ifdef DEBUG_SIMPLE_SPARQL_QUERY - cerr << "About to parse \"" << uri << "\"" << endl; -#endif - - Profiler p("SimpleSPARQLQuery: Parse URI into LIBRDF model"); - - if (librdf_parser_parse_into_model(parser, luri, NULL, model)) { - - errorString = QString("Failed to parse RDF from URI \"%1\"") - .arg(uri); - librdf_free_parser(parser); - return false; - - } else { - - librdf_free_parser(parser); - return true; - } -} - - -class SimpleSPARQLQuery::Impl -{ -public: - Impl(SimpleSPARQLQuery::QueryType, QString query); - ~Impl(); - - static bool addSourceToModel(QString sourceUri); - static void closeSingleSource(QString sourceUri); - - void setProgressReporter(ProgressReporter *reporter) { m_reporter = reporter; } - bool wasCancelled() const { return m_cancelled; } - - ResultList execute(); - - bool isOK() const; - QString getErrorString() const; - -protected: - static QMutex m_mutex; - - static WredlandWorldWrapper *m_redland; - - ResultList executeDirectParser(); - ResultList executeDatastore(); - ResultList executeFor(QString modelUri); - - QueryType m_type; - QString m_query; - QString m_errorString; - ProgressReporter *m_reporter; - bool m_cancelled; -}; - -WredlandWorldWrapper *SimpleSPARQLQuery::Impl::m_redland = 0; - -QMutex SimpleSPARQLQuery::Impl::m_mutex; - -SimpleSPARQLQuery::SimpleSPARQLQuery(QueryType type, QString query) : - m_impl(new Impl(type, query)) -{ -} - -SimpleSPARQLQuery::~SimpleSPARQLQuery() -{ - delete m_impl; -} - -void -SimpleSPARQLQuery::setProgressReporter(ProgressReporter *reporter) -{ - m_impl->setProgressReporter(reporter); -} - -bool -SimpleSPARQLQuery::wasCancelled() const -{ - return m_impl->wasCancelled(); -} - -SimpleSPARQLQuery::ResultList -SimpleSPARQLQuery::execute() -{ - return m_impl->execute(); -} - -bool -SimpleSPARQLQuery::isOK() const -{ - return m_impl->isOK(); -} - -QString -SimpleSPARQLQuery::getErrorString() const -{ - return m_impl->getErrorString(); -} - -bool -SimpleSPARQLQuery::addSourceToModel(QString sourceUri) -{ - return SimpleSPARQLQuery::Impl::addSourceToModel(sourceUri); -} - -void -SimpleSPARQLQuery::closeSingleSource(QString sourceUri) -{ - SimpleSPARQLQuery::Impl::closeSingleSource(sourceUri); -} - -SimpleSPARQLQuery::Impl::Impl(QueryType type, QString query) : - m_type(type), - m_query(query), - m_reporter(0), - m_cancelled(false) -{ -} - -SimpleSPARQLQuery::Impl::~Impl() -{ -} - -bool -SimpleSPARQLQuery::Impl::isOK() const -{ - return (m_errorString == ""); -} - -QString -SimpleSPARQLQuery::Impl::getErrorString() const -{ - return m_errorString; -} - -SimpleSPARQLQuery::ResultList -SimpleSPARQLQuery::Impl::execute() -{ - ResultList list; - - QMutexLocker locker(&m_mutex); - - if (!m_redland) { - m_redland = new WredlandWorldWrapper(); - } - - if (!m_redland->isOK()) { - cerr << "ERROR: SimpleSPARQLQuery::execute: Failed to initialise Redland datastore" << endl; - return list; - } - - if (m_type == QueryFromSingleSource) { - return executeDirectParser(); - } else { - return executeDatastore(); - } - -#ifdef DEBUG_SIMPLE_SPARQL_QUERY - if (m_errorString != "") { - cerr << "SimpleSPARQLQuery::execute: error returned: \"" - << m_errorString << "\"" << endl; - } -#endif -} - -SimpleSPARQLQuery::ResultList -SimpleSPARQLQuery::Impl::executeDirectParser() -{ -#ifdef DEBUG_SIMPLE_SPARQL_QUERY - SVDEBUG << "SimpleSPARQLQuery::executeDirectParser: Query is: \"" << m_query << "\"" << endl; -#endif - - ResultList list; - - Profiler profiler("SimpleSPARQLQuery::executeDirectParser"); - - static QRegExp fromRE("from\\s+<([^>]+)>", Qt::CaseInsensitive); - QString fromUri; - - if (fromRE.indexIn(m_query) < 0) { - SVDEBUG << "SimpleSPARQLQuery::executeDirectParser: Query contains no FROM clause, nothing to parse from" << endl; - return list; - } else { - fromUri = fromRE.cap(1); -#ifdef DEBUG_SIMPLE_SPARQL_QUERY - SVDEBUG << "SimpleSPARQLQuery::executeDirectParser: FROM URI is <" - << fromUri << ">" << endl; -#endif - } - - return executeFor(fromUri); -} - -SimpleSPARQLQuery::ResultList -SimpleSPARQLQuery::Impl::executeDatastore() -{ -#ifdef DEBUG_SIMPLE_SPARQL_QUERY - SVDEBUG << "SimpleSPARQLQuery::executeDatastore: Query is: \"" << m_query << "\"" << endl; -#endif - - ResultList list; - - Profiler profiler("SimpleSPARQLQuery::executeDatastore"); - - return executeFor(""); -} - -SimpleSPARQLQuery::ResultList -SimpleSPARQLQuery::Impl::executeFor(QString modelUri) -{ - ResultList list; - librdf_query *query; - -#ifdef DEBUG_SIMPLE_SPARQL_QUERY - static std::map<QString, int> counter; - if (counter.find(m_query) == counter.end()) counter[m_query] = 1; - else ++counter[m_query]; - cerr << "Counter for this query: " << counter[m_query] << endl; - cerr << "Base URI is: \"" << modelUri << "\"" << endl; -#endif - - { - Profiler p("SimpleSPARQLQuery: Prepare LIBRDF query"); - query = librdf_new_query - (m_redland->getWorld(), "sparql", NULL, - (const unsigned char *)m_query.toUtf8().data(), NULL); - } - - if (!query) { - m_errorString = "Failed to construct query"; - return list; - } - - librdf_query_results *results; - { - Profiler p("SimpleSPARQLQuery: Execute LIBRDF query"); - results = librdf_query_execute(query, m_redland->getModel(modelUri)); - } - - if (!results) { - m_errorString = "RDF query failed"; - librdf_free_query(query); - return list; - } - - if (!librdf_query_results_is_bindings(results)) { - m_errorString = "RDF query returned non-bindings results"; - librdf_free_query_results(results); - librdf_free_query(query); - return list; - } - - int resultCount = 0; - int resultTotal = librdf_query_results_get_count(results); // probably wrong - m_cancelled = false; - - while (!librdf_query_results_finished(results)) { - - int count = librdf_query_results_get_bindings_count(results); - - KeyValueMap resultmap; - - for (int i = 0; i < count; ++i) { - - const char *name = - librdf_query_results_get_binding_name(results, i); - - if (!name) { - cerr << "WARNING: Result " << i << " of query has no name" << endl; - continue; - } - - librdf_node *node = - librdf_query_results_get_binding_value(results, i); - - QString key = (const char *)name; - - if (!node) { -#ifdef DEBUG_SIMPLE_SPARQL_QUERY - cerr << i << ". " << key << " -> (nil)" << endl; -#endif - resultmap[key] = Value(); - continue; - } - - ValueType type = LiteralValue; - QString text; - - if (librdf_node_is_resource(node)) { - - type = URIValue; - librdf_uri *uri = librdf_node_get_uri(node); - const char *us = (const char *)librdf_uri_as_string(uri); - - if (!us) { - cerr << "WARNING: Result " << i << " of query claims URI type, but has null URI" << endl; - } else { - text = us; - } - - } else if (librdf_node_is_literal(node)) { - - type = LiteralValue; - - const char *lit = (const char *)librdf_node_get_literal_value(node); - if (!lit) { - cerr << "WARNING: Result " << i << " of query claims literal type, but has no literal" << endl; - } else { - text = lit; - } - - } else if (librdf_node_is_blank(node)) { - - type = BlankValue; - - const char *lit = (const char *)librdf_node_get_literal_value(node); - if (lit) text = lit; - - } else { - - cerr << "SimpleSPARQLQuery: LIBRDF query returned unknown node type (not resource, literal, or blank)" << endl; - } - -#ifdef DEBUG_SIMPLE_SPARQL_QUERY - cerr << i << ". " << key << " -> " << text << " (type " << type << ")" << endl; -#endif - - resultmap[key] = Value(type, text); - -// librdf_free_node(node); - } - - list.push_back(resultmap); - - librdf_query_results_next(results); - - resultCount++; - - if (m_reporter) { - if (resultCount >= resultTotal) { - if (m_reporter->isDefinite()) m_reporter->setDefinite(false); - m_reporter->setProgress(resultCount); - } else { - m_reporter->setProgress((resultCount * 100) / resultTotal); - } - - if (m_reporter->wasCancelled()) { - m_cancelled = true; - break; - } - } - } - - librdf_free_query_results(results); - librdf_free_query(query); - -#ifdef DEBUG_SIMPLE_SPARQL_QUERY - SVDEBUG << "SimpleSPARQLQuery::executeDatastore: All results retrieved (" << resultCount << " of them)" << endl; -#endif - - return list; -} - -bool -SimpleSPARQLQuery::Impl::addSourceToModel(QString sourceUri) -{ - QString err; - - QMutexLocker locker(&m_mutex); - - if (!m_redland) { - m_redland = new WredlandWorldWrapper(); - } - - if (!m_redland->isOK()) { - cerr << "SimpleSPARQLQuery::addSourceToModel: Failed to initialise Redland datastore" << endl; - return false; - } - - if (!m_redland->loadUriIntoDefaultModel(sourceUri, err)) { - cerr << "SimpleSPARQLQuery::addSourceToModel: Failed to add source URI \"" << sourceUri << ": " << err << endl; - return false; - } - return true; -} - -void -SimpleSPARQLQuery::Impl::closeSingleSource(QString sourceUri) -{ - QMutexLocker locker(&m_mutex); - - m_redland->freeModel(sourceUri); -} - -SimpleSPARQLQuery::Value -SimpleSPARQLQuery::singleResultQuery(QueryType type, - QString query, QString binding) -{ - SimpleSPARQLQuery q(type, query); - ResultList results = q.execute(); - if (!q.isOK()) { - cerr << "SimpleSPARQLQuery::singleResultQuery: ERROR: " - << q.getErrorString() << endl; - return Value(); - } - if (results.empty()) { - return Value(); - } - for (int i = 0; i < results.size(); ++i) { - if (results[i].find(binding) != results[i].end() && - results[i][binding].type != NoValue) { - return results[i][binding]; - } - } - return Value(); -} - - -
--- a/rdf/SimpleSPARQLQuery.h Sat Apr 26 22:22:19 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,117 +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 2008 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 _SIMPLE_SPARQL_QUERY_H_ -#define _SIMPLE_SPARQL_QUERY_H_ - -#ifdef NOT_DEFINED - -#include <QString> -#include <map> -#include <vector> - -#include "base/Debug.h" - -class ProgressReporter; - -class SimpleSPARQLQuery -{ -public: - enum ValueType { NoValue, URIValue, LiteralValue, BlankValue }; - - struct Value { - Value() : type(NoValue), value() { } - Value(ValueType t, QString v) : type(t), value(v) { } - ValueType type; - QString value; - }; - - typedef std::map<QString, Value> KeyValueMap; - typedef std::vector<KeyValueMap> ResultList; - - /** - * QueryType specifies the context in which the query will be - * evaluated. SimpleSPARQLQuery maintains a general global data - * model, into which data can be loaded using addSourceToModel(), - * as well as permitting one-time queries directly on data sources - * identified by URL. - * - * The query type QueryFromModel indicates a query to be evaluated - * over the general global model; the query type - * QueryFromSingleSource indicates that the query should be - * evaluated in the context of a model generated solely by parsing - * the FROM url found in the query. - * - * Even in QueryFromSingleSource mode, the parsed data remains in - * memory and will be reused in subsequent queries with the same - * mode and FROM url. To release data loaded in this way once all - * queries across it are complete, pass the said FROM url to - * closeSingleSource(). - */ - enum QueryType { - QueryFromModel, - QueryFromSingleSource - }; - - /** - * Construct a query of the given type (indicating the data model - * context for the query) using the given SPARQL query content. - */ - SimpleSPARQLQuery(QueryType type, QString query); - ~SimpleSPARQLQuery(); - - /** - * Add the given URI to the general global model used for - * QueryFromModel queries. - */ - static bool addSourceToModel(QString sourceUri); - - /** - * Release any data that has been loaded from the given source as - * part of a QueryFromSingleSource query with this source in the - * FROM clause. Note this will not prevent any subsequent queries - * on the source from working -- it will just make them slower as - * the data will need to be re-parsed. - */ - static void closeSingleSource(QString sourceUri); - - void setProgressReporter(ProgressReporter *reporter); - bool wasCancelled() const; - - ResultList execute(); - - bool isOK() const; - QString getErrorString() const; - - /** - * Construct and execute a query, and return the first result - * value for the given binding. - */ - static Value singleResultQuery(QueryType type, - QString query, - QString binding); - -protected: - class Impl; - Impl *m_impl; - -private: - SimpleSPARQLQuery(const SimpleSPARQLQuery &); // not provided - SimpleSPARQLQuery &operator=(const SimpleSPARQLQuery &); // not provided -}; - -#endif - -#endif
--- a/transform/FeatureExtractionModelTransformer.cpp Sat Apr 26 22:22:19 2014 +0100 +++ b/transform/FeatureExtractionModelTransformer.cpp Wed May 07 15:11:56 2014 +0100 @@ -278,12 +278,11 @@ //!!! the model rate to be the input model's rate, and adjust //!!! the resolution appropriately. We can't properly display //!!! data with a higher resolution than the base model at all -// modelRate = size_t(m_descriptors[n]->sampleRate + 0.001); if (m_descriptors[n]->sampleRate > input->getSampleRate()) { modelResolution = 1; } else { - modelResolution = size_t(input->getSampleRate() / - m_descriptors[n]->sampleRate); + modelResolution = size_t(round(input->getSampleRate() / + m_descriptors[n]->sampleRate)); } break; } @@ -822,7 +821,7 @@ binCount = m_descriptors[n]->binCount; } - size_t frame = blockFrame; + int frame = blockFrame; if (m_descriptors[n]->sampleType == Vamp::Plugin::OutputDescriptor::VariableSampleRate) { @@ -847,11 +846,25 @@ m_fixedRateFeatureNos[n] = lrint(ts.toDouble() * m_descriptors[n]->sampleRate); } - + +// cerr << "m_fixedRateFeatureNo = " << m_fixedRateFeatureNo +// << ", m_descriptor->sampleRate = " << m_descriptor->sampleRate +// << ", inputRate = " << inputRate +// << " giving frame = "; frame = lrintf((m_fixedRateFeatureNos[n] / m_descriptors[n]->sampleRate) - * inputRate); + * int(inputRate)); } - + + if (frame < 0) { + cerr + << "WARNING: FeatureExtractionModelTransformer::addFeature: " + << "Negative frame counts are not supported (frame = " << frame + << " from timestamp " << feature.timestamp + << "), dropping feature" + << endl; + return; + } + // 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 @@ -912,7 +925,7 @@ } } - if (isOutput<FlexiNoteModel>(n)) { // GF: added for flexi note model + if (isOutput<FlexiNoteModel>(n)) { // GF: added for flexi note model float velocity = 100; if (feature.values.size() > index) { @@ -980,7 +993,14 @@ getConformingOutput<EditableDenseThreeDimensionalModel>(n); if (!model) return; - model->setColumn(frame / model->getResolution(), values); +// cerr << "(note: model resolution = " << model->getResolution() << ")" +// << endl; + + if (!feature.hasTimestamp && m_fixedRateFeatureNos[n] >= 0) { + model->setColumn(m_fixedRateFeatureNos[n], values); + } else { + model->setColumn(frame / model->getResolution(), values); + } } else { SVDEBUG << "FeatureExtractionModelTransformer::addFeature: Unknown output model type!" << endl;