# HG changeset patch # User Chris Cannam # Date 1399472057 -3600 # Node ID aa2257796931a5b4368000618b5cc928d6936218 # Parent 1b05ba6af360bb358daa077ef473ff87e493d86f# Parent 16c48a3db2a71eca4f0907637bbcb5d0da3644d2 Merge diff -r 16c48a3db2a7 -r aa2257796931 base/Clipboard.cpp --- a/base/Clipboard.cpp Sat Apr 26 23:05:32 2014 +0100 +++ b/base/Clipboard.cpp Wed May 07 15:14:17 2014 +0100 @@ -123,6 +123,15 @@ return m_frame; } +Clipboard::Point +Clipboard::Point::withFrame(long frame) const +{ + Point p(*this); + p.m_haveFrame = true; + p.m_frame = frame; + return p; +} + bool Clipboard::Point::haveValue() const { @@ -135,6 +144,15 @@ return m_value; } +Clipboard::Point +Clipboard::Point::withValue(float value) const +{ + Point p(*this); + p.m_haveValue = true; + p.m_value = value; + return p; +} + bool Clipboard::Point::haveDuration() const { @@ -147,6 +165,15 @@ return m_duration; } +Clipboard::Point +Clipboard::Point::withDuration(size_t duration) const +{ + Point p(*this); + p.m_haveDuration = true; + p.m_duration = duration; + return p; +} + bool Clipboard::Point::haveLabel() const { @@ -159,6 +186,15 @@ return m_label; } +Clipboard::Point +Clipboard::Point::withLabel(QString label) const +{ + Point p(*this); + p.m_haveLabel = true; + p.m_label = label; + return p; +} + bool Clipboard::Point::haveLevel() const { @@ -171,6 +207,15 @@ return m_level; } +Clipboard::Point +Clipboard::Point::withLevel(float level) const +{ + Point p(*this); + p.m_haveLevel = true; + p.m_level = level; + return p; +} + bool Clipboard::Point::haveReferenceFrame() const { diff -r 16c48a3db2a7 -r aa2257796931 base/Clipboard.h --- a/base/Clipboard.h Sat Apr 26 23:05:32 2014 +0100 +++ b/base/Clipboard.h Wed May 07 15:14:17 2014 +0100 @@ -34,18 +34,23 @@ bool haveFrame() const; long getFrame() const; + Point withFrame(long frame) const; bool haveValue() const; float getValue() const; + Point withValue(float value) const; bool haveDuration() const; size_t getDuration() const; + Point withDuration(size_t duration) const; bool haveLabel() const; QString getLabel() const; + Point withLabel(QString label) const; bool haveLevel() const; float getLevel() const; + Point withLevel(float level) const; bool haveReferenceFrame() const; bool referenceFrameDiffers() const; // from point frame diff -r 16c48a3db2a7 -r aa2257796931 base/Debug.h --- a/base/Debug.h Sat Apr 26 23:05:32 2014 +0100 +++ b/base/Debug.h Wed May 07 15:14:17 2014 +0100 @@ -19,6 +19,8 @@ #include #include +#include + #include #include @@ -39,6 +41,11 @@ #define SVDEBUG getSVDebug() +inline QDebug &operator<<(QDebug &d, const Vamp::RealTime &rt) { + d << rt.toString(); + return d; +} + template inline QDebug &operator<<(QDebug &d, const T &t) { QString s; diff -r 16c48a3db2a7 -r aa2257796931 base/Pitch.cpp --- a/base/Pitch.cpp Sat Apr 26 23:05:32 2014 +0100 +++ b/base/Pitch.cpp Wed May 07 15:14:17 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); diff -r 16c48a3db2a7 -r aa2257796931 base/Pitch.h --- a/base/Pitch.h Sat Apr 26 23:05:32 2014 +0100 +++ b/base/Pitch.h Wed May 07 15:14:17 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). diff -r 16c48a3db2a7 -r aa2257796931 base/PlayParameterRepository.cpp --- a/base/PlayParameterRepository.cpp Sat Apr 26 23:05:32 2014 +0100 +++ b/base/PlayParameterRepository.cpp Wed May 07 15:14:17 2014 +0100 @@ -35,36 +35,29 @@ void PlayParameterRepository::addPlayable(const Playable *playable) { -// cerr << "PlayParameterRepository:addPlayable " << playable << endl; + cerr << "PlayParameterRepository:addPlayable playable = " << playable << endl; if (!getPlayParameters(playable)) { // Give all playables the same type of play parameters for the // moment -// cerr << "PlayParameterRepository: Adding play parameters for " << playable << endl; + cerr << "PlayParameterRepository:addPlayable: Adding play parameters for " << playable << endl; PlayParameters *params = new PlayParameters; m_playParameters[playable] = params; - params->setPlayPluginId - (playable->getDefaultPlayPluginId()); - - params->setPlayPluginConfiguration - (playable->getDefaultPlayPluginConfiguration()); + params->setPlayClipId + (playable->getDefaultPlayClipId()); connect(params, SIGNAL(playParametersChanged()), this, SLOT(playParametersChanged())); - connect(params, SIGNAL(playPluginIdChanged(QString)), - this, SLOT(playPluginIdChanged(QString))); + connect(params, SIGNAL(playClipIdChanged(QString)), + this, SLOT(playClipIdChanged(QString))); - connect(params, SIGNAL(playPluginConfigurationChanged(QString)), - this, SLOT(playPluginConfigurationChanged(QString))); - -// cerr << "Connected play parameters " << params << " for playable " -// << playable << " to this " << this << endl; - + cerr << "Connected play parameters " << params << " for playable " + << playable << " to this " << this << endl; } } @@ -108,27 +101,13 @@ } void -PlayParameterRepository::playPluginIdChanged(QString id) +PlayParameterRepository::playClipIdChanged(QString id) { PlayParameters *params = dynamic_cast(sender()); for (PlayableParameterMap::iterator i = m_playParameters.begin(); i != m_playParameters.end(); ++i) { if (i->second == params) { - emit playPluginIdChanged(i->first, id); - return; - } - } -} - -void -PlayParameterRepository::playPluginConfigurationChanged(QString config) -{ - PlayParameters *params = dynamic_cast(sender()); -// SVDEBUG << "PlayParameterRepository::playPluginConfigurationChanged" << endl; - for (PlayableParameterMap::iterator i = m_playParameters.begin(); - i != m_playParameters.end(); ++i) { - if (i->second == params) { - emit playPluginConfigurationChanged(i->first, config); + emit playClipIdChanged(i->first, id); return; } } @@ -176,15 +155,9 @@ } void -PlayParameterRepository::EditCommand::setPlayPluginId(QString id) +PlayParameterRepository::EditCommand::setPlayClipId(QString id) { - m_to.setPlayPluginId(id); -} - -void -PlayParameterRepository::EditCommand::setPlayPluginConfiguration(QString conf) -{ - m_to.setPlayPluginConfiguration(conf); + m_to.setPlayClipId(id); } void @@ -222,13 +195,8 @@ if (++changed > 1) return multiname; } - if (m_to.getPlayPluginId() != m_from.getPlayPluginId()) { - name = tr("Change Playback Plugin"); - if (++changed > 1) return multiname; - } - - if (m_to.getPlayPluginConfiguration() != m_from.getPlayPluginConfiguration()) { - name = tr("Configure Playback Plugin"); + if (m_to.getPlayClipId() != m_from.getPlayClipId()) { + name = tr("Change Playback Sample"); if (++changed > 1) return multiname; } diff -r 16c48a3db2a7 -r aa2257796931 base/PlayParameterRepository.h --- a/base/PlayParameterRepository.h Sat Apr 26 23:05:32 2014 +0100 +++ b/base/PlayParameterRepository.h Wed May 07 15:14:17 2014 +0100 @@ -51,8 +51,7 @@ void setPlayAudible(bool); void setPlayPan(float); void setPlayGain(float); - void setPlayPluginId(QString); - void setPlayPluginConfiguration(QString); + void setPlayClipId(QString); void execute(); void unexecute(); QString getName() const; @@ -65,13 +64,11 @@ signals: void playParametersChanged(PlayParameters *); - void playPluginIdChanged(const Playable *, QString); - void playPluginConfigurationChanged(const Playable *, QString); + void playClipIdChanged(const Playable *, QString); protected slots: void playParametersChanged(); - void playPluginIdChanged(QString); - void playPluginConfigurationChanged(QString); + void playClipIdChanged(QString); protected: typedef std::map PlayableParameterMap; diff -r 16c48a3db2a7 -r aa2257796931 base/PlayParameters.cpp --- a/base/PlayParameters.cpp Sat Apr 26 23:05:32 2014 +0100 +++ b/base/PlayParameters.cpp Wed May 07 15:14:17 2014 +0100 @@ -43,15 +43,9 @@ changed = true; } - if (m_playPluginId != pp->getPlayPluginId()) { - m_playPluginId = pp->getPlayPluginId(); - emit playPluginIdChanged(m_playPluginId); - changed = true; - } - - if (m_playPluginConfiguration != pp->getPlayPluginConfiguration()) { - m_playPluginConfiguration = pp->getPlayPluginConfiguration(); - emit playPluginConfigurationChanged(m_playPluginConfiguration); + if (m_playClipId != pp->getPlayClipId()) { + m_playClipId = pp->getPlayClipId(); + emit playClipIdChanged(m_playClipId); changed = true; } @@ -64,18 +58,24 @@ QString extraAttributes) const { stream << indent; - stream << QString("\n " << indent << m_playPluginConfiguration - << "\n" << indent << "\n"; - } else { - stream << "/>\n"; + + stream << ">\n"; + + if (m_playClipId != "") { + // for backward compatibility + stream << indent << " "; + stream << QString("\n") + .arg("sample_player") + .arg(m_playClipId); } + + stream << indent << "\n"; } void @@ -118,24 +118,11 @@ } void -PlayParameters::setPlayPluginId(QString id) +PlayParameters::setPlayClipId(QString id) { - if (m_playPluginId != id) { - m_playPluginId = id; - emit playPluginIdChanged(id); + if (m_playClipId != id) { + m_playClipId = id; + emit playClipIdChanged(id); emit playParametersChanged(); } } - -void -PlayParameters::setPlayPluginConfiguration(QString configuration) -{ - if (m_playPluginConfiguration != configuration) { - m_playPluginConfiguration = configuration; -// cerr << "PlayParameters(" << this << "): setPlayPluginConfiguration to \"" << configuration << "\"" << endl; - emit playPluginConfigurationChanged(configuration); - emit playParametersChanged(); - } -} - - diff -r 16c48a3db2a7 -r aa2257796931 base/PlayParameters.h --- a/base/PlayParameters.h Sat Apr 26 23:05:32 2014 +0100 +++ b/base/PlayParameters.h Wed May 07 15:14:17 2014 +0100 @@ -32,8 +32,7 @@ virtual float getPlayPan() const { return m_playPan; } // -1.0 -> 1.0 virtual float getPlayGain() const { return m_playGain; } - virtual QString getPlayPluginId() const { return m_playPluginId; } - virtual QString getPlayPluginConfiguration() const { return m_playPluginConfiguration; } + virtual QString getPlayClipId() const { return m_playClipId; } virtual void copyFrom(const PlayParameters *); @@ -46,8 +45,7 @@ virtual void setPlayAudible(bool nonMuted); virtual void setPlayPan(float pan); virtual void setPlayGain(float gain); - virtual void setPlayPluginId(QString id); - virtual void setPlayPluginConfiguration(QString configuration); + virtual void setPlayClipId(QString id); signals: void playParametersChanged(); @@ -55,15 +53,13 @@ void playAudibleChanged(bool); void playPanChanged(float); void playGainChanged(float); - void playPluginIdChanged(QString); - void playPluginConfigurationChanged(QString); + void playClipIdChanged(QString); protected: bool m_playMuted; float m_playPan; float m_playGain; - QString m_playPluginId; - QString m_playPluginConfiguration; + QString m_playClipId; private: PlayParameters(const PlayParameters &); diff -r 16c48a3db2a7 -r aa2257796931 base/Playable.h --- a/base/Playable.h Sat Apr 26 23:05:32 2014 +0100 +++ b/base/Playable.h Wed May 07 15:14:17 2014 +0100 @@ -24,8 +24,7 @@ virtual ~Playable() { } virtual bool canPlay() const { return false; } - virtual QString getDefaultPlayPluginId() const { return ""; } - virtual QString getDefaultPlayPluginConfiguration() const { return ""; } + virtual QString getDefaultPlayClipId() const { return ""; } }; #endif diff -r 16c48a3db2a7 -r aa2257796931 base/Preferences.cpp --- a/base/Preferences.cpp Sat Apr 26 23:05:32 2014 +0100 +++ b/base/Preferences.cpp Wed May 07 15:14:17 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) { diff -r 16c48a3db2a7 -r aa2257796931 base/Preferences.h --- a/base/Preferences.h Sat Apr 26 23:05:32 2014 +0100 +++ b/base/Preferences.h Wed May 07 15:14:17 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; }; diff -r 16c48a3db2a7 -r aa2257796931 base/test/TestPitch.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/test/TestPitch.h Wed May 07 15:14:17 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 +#include +#include + +#include + +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 diff -r 16c48a3db2a7 -r aa2257796931 base/test/main.cpp --- a/base/test/main.cpp Sat Apr 26 23:05:32 2014 +0100 +++ b/base/test/main.cpp Wed May 07 15:14:17 2014 +0100 @@ -12,6 +12,7 @@ */ #include "TestRangeMapper.h" +#include "TestPitch.h" #include @@ -30,6 +31,11 @@ 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; diff -r 16c48a3db2a7 -r aa2257796931 base/test/test.pro --- a/base/test/test.pro Sat Apr 26 23:05:32 2014 +0100 +++ b/base/test/test.pro Wed May 07 15:14:17 2014 +0100 @@ -30,7 +30,7 @@ OBJECTS_DIR = o MOC_DIR = o -HEADERS += TestRangeMapper.h +HEADERS += TestRangeMapper.h TestPitch.h SOURCES += main.cpp win* { diff -r 16c48a3db2a7 -r aa2257796931 data/fft/FFTDataServer.cpp --- a/data/fft/FFTDataServer.cpp Sat Apr 26 23:05:32 2014 +0100 +++ b/data/fft/FFTDataServer.cpp Wed May 07 15:14:17 2014 +0100 @@ -191,7 +191,7 @@ if (server->getFillCompletion() < 50) distance += 100; #ifdef DEBUG_FFT_SERVER - SVDEBUG << "FFTDataServer::getFuzzyInstance: Distance for server " << server << " is " << distance << ", best is " << bestdist << endl; + std::cerr << "FFTDataServer::getFuzzyInstance: Distance for server " << server << " is " << distance << ", best is " << bestdist << std::endl; #endif if (bestdist == -1 || distance < bestdist) { @@ -204,7 +204,7 @@ if (bestdist >= 0) { FFTDataServer *server = best->second.first; #ifdef DEBUG_FFT_SERVER - SVDEBUG << "FFTDataServer::getFuzzyInstance: We like server " << server << " (with distance " << bestdist << ")" << endl; + std::cerr << "FFTDataServer::getFuzzyInstance: We like server " << server << " (with distance " << bestdist << ")" << std::endl; #endif claimInstance(server, false); return server; @@ -228,7 +228,7 @@ FFTDataServer::findServer(QString n) { #ifdef DEBUG_FFT_SERVER - SVDEBUG << "FFTDataServer::findServer(\"" << n << "\")" << endl; + std::cerr << "FFTDataServer::findServer(\"" << n << "\")" << std::endl; #endif if (m_servers.find(n) != m_servers.end()) { @@ -236,7 +236,7 @@ FFTDataServer *server = m_servers[n].first; #ifdef DEBUG_FFT_SERVER - SVDEBUG << "FFTDataServer::findServer(\"" << n << "\"): found " << server << endl; + std::cerr << "FFTDataServer::findServer(\"" << n << "\"): found " << server << std::endl; #endif claimInstance(server, false); @@ -245,7 +245,7 @@ } #ifdef DEBUG_FFT_SERVER - SVDEBUG << "FFTDataServer::findServer(\"" << n << "\"): not found" << endl; + std::cerr << "FFTDataServer::findServer(\"" << n << "\"): not found" << std::endl; #endif return 0; @@ -264,7 +264,7 @@ "FFTDataServer::claimInstance::m_serverMapMutex"); #ifdef DEBUG_FFT_SERVER - SVDEBUG << "FFTDataServer::claimInstance(" << server << ")" << endl; + std::cerr << "FFTDataServer::claimInstance(" << server << ")" << std::endl; #endif for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) { @@ -275,7 +275,7 @@ if (*j == server) { #ifdef DEBUG_FFT_SERVER - SVDEBUG << "FFTDataServer::claimInstance: found in released server list, removing from it" << endl; + std::cerr << "FFTDataServer::claimInstance: found in released server list, removing from it" << std::endl; #endif m_releasedServers.erase(j); break; @@ -285,7 +285,7 @@ ++i->second.second; #ifdef DEBUG_FFT_SERVER - SVDEBUG << "FFTDataServer::claimInstance: new refcount is " << i->second.second << endl; + std::cerr << "FFTDataServer::claimInstance: new refcount is " << i->second.second << std::endl; #endif return; @@ -309,7 +309,7 @@ "FFTDataServer::releaseInstance::m_serverMapMutex"); #ifdef DEBUG_FFT_SERVER - SVDEBUG << "FFTDataServer::releaseInstance(" << server << ")" << endl; + std::cerr << "FFTDataServer::releaseInstance(" << server << ")" << std::endl; #endif // -- if ref count > 0, decrement and return @@ -332,18 +332,18 @@ /*!!! if (server->m_lastUsedCache == -1) { // never used #ifdef DEBUG_FFT_SERVER - SVDEBUG << "FFTDataServer::releaseInstance: instance " + std::cerr << "FFTDataServer::releaseInstance: instance " << server << " has never been used, erasing" - << endl; + << std::endl; #endif delete server; m_servers.erase(i); } else { */ #ifdef DEBUG_FFT_SERVER - SVDEBUG << "FFTDataServer::releaseInstance: instance " + std::cerr << "FFTDataServer::releaseInstance: instance " << server << " no longer in use, marking for possible collection" - << endl; + << std::endl; #endif bool found = false; for (ServerQueue::iterator j = m_releasedServers.begin(); @@ -361,9 +361,9 @@ //!!! } } else { #ifdef DEBUG_FFT_SERVER - SVDEBUG << "FFTDataServer::releaseInstance: instance " + std::cerr << "FFTDataServer::releaseInstance: instance " << server << " now has refcount " << i->second.second - << endl; + << std::endl; #endif } return; @@ -378,8 +378,8 @@ FFTDataServer::purgeLimbo(int maxSize) { #ifdef DEBUG_FFT_SERVER - SVDEBUG << "FFTDataServer::purgeLimbo(" << maxSize << "): " - << m_releasedServers.size() << " candidates" << endl; + std::cerr << "FFTDataServer::purgeLimbo(" << maxSize << "): " + << m_releasedServers.size() << " candidates" << std::endl; #endif while (int(m_releasedServers.size()) > maxSize) { @@ -389,8 +389,8 @@ bool found = false; #ifdef DEBUG_FFT_SERVER - SVDEBUG << "FFTDataServer::purgeLimbo: considering candidate " - << server << endl; + std::cerr << "FFTDataServer::purgeLimbo: considering candidate " + << server << std::endl; #endif for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) { @@ -405,8 +405,8 @@ break; } #ifdef DEBUG_FFT_SERVER - SVDEBUG << "FFTDataServer::purgeLimbo: looks OK, erasing it" - << endl; + std::cerr << "FFTDataServer::purgeLimbo: looks OK, erasing it" + << std::endl; #endif m_servers.erase(i); @@ -426,8 +426,8 @@ } #ifdef DEBUG_FFT_SERVER - SVDEBUG << "FFTDataServer::purgeLimbo(" << maxSize << "): " - << m_releasedServers.size() << " remain" << endl; + std::cerr << "FFTDataServer::purgeLimbo(" << maxSize << "): " + << m_releasedServers.size() << " remain" << std::endl; #endif } @@ -439,8 +439,8 @@ "FFTDataServer::modelAboutToBeDeleted::m_serverMapMutex"); #ifdef DEBUG_FFT_SERVER - SVDEBUG << "FFTDataServer::modelAboutToBeDeleted(" << model << ")" - << endl; + std::cerr << "FFTDataServer::modelAboutToBeDeleted(" << model << ")" + << std::endl; #endif for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) { @@ -450,8 +450,8 @@ if (server->getModel() == model) { #ifdef DEBUG_FFT_SERVER - SVDEBUG << "FFTDataServer::modelAboutToBeDeleted: server is " - << server << endl; + std::cerr << "FFTDataServer::modelAboutToBeDeleted: server is " + << server << std::endl; #endif if (i->second.second > 0) { @@ -463,14 +463,14 @@ j != m_releasedServers.end(); ++j) { if (*j == server) { #ifdef DEBUG_FFT_SERVER - SVDEBUG << "FFTDataServer::modelAboutToBeDeleted: erasing from released servers" << endl; + std::cerr << "FFTDataServer::modelAboutToBeDeleted: erasing from released servers" << std::endl; #endif m_releasedServers.erase(j); break; } } #ifdef DEBUG_FFT_SERVER - SVDEBUG << "FFTDataServer::modelAboutToBeDeleted: erasing server" << endl; + std::cerr << "FFTDataServer::modelAboutToBeDeleted: erasing server" << std::endl; #endif m_servers.erase(i); delete server; @@ -841,7 +841,7 @@ // preconditions: m_caches[c] exists and contains a file writer; // m_cacheVectorLock is not locked by this thread #ifdef DEBUG_FFT_SERVER - SVDEBUG << "FFTDataServer::makeCacheReader(" << c << ")" << endl; + std::cerr << "FFTDataServer::makeCacheReader(" << c << ")" << std::endl; #endif QThread *me = QThread::currentThread(); @@ -875,7 +875,7 @@ cb = m_caches.at(deleteCandidate); if (cb && cb->fileCacheReader.find(me) != cb->fileCacheReader.end()) { #ifdef DEBUG_FFT_SERVER - SVDEBUG << "FFTDataServer::makeCacheReader: Deleting probably unpopular reader " << deleteCandidate << " for this thread (as I create reader " << c << ")" << endl; + std::cerr << "FFTDataServer::makeCacheReader: Deleting probably unpopular reader " << deleteCandidate << " for this thread (as I create reader " << c << ")" << std::endl; #endif delete cb->fileCacheReader[me]; cb->fileCacheReader.erase(me); @@ -901,8 +901,8 @@ if (!cache->haveSetColumnAt(col)) { Profiler profiler("FFTDataServer::getMagnitudeAt: filling"); #ifdef DEBUG_FFT_SERVER - SVDEBUG << "FFTDataServer::getMagnitudeAt: calling fillColumn(" - << x << ")" << endl; + std::cerr << "FFTDataServer::getMagnitudeAt: calling fillColumn(" + << x << ")" << std::endl; #endif fillColumn(x); } @@ -1130,7 +1130,7 @@ if (!cache->haveSetColumnAt(col)) { Profiler profiler("FFTDataServer::getValuesAt: filling"); #ifdef DEBUG_FFT_SERVER - SVDEBUG << "FFTDataServer::getValuesAt(" << x << ", " << y << "): filling" << endl; + std::cerr << "FFTDataServer::getValuesAt(" << x << ", " << y << "): filling" << std::endl; #endif fillColumn(x); } @@ -1189,7 +1189,7 @@ /*!!! if (m_lastUsedCache == -1) { if (m_suspended) { - SVDEBUG << "FFTDataServer::isColumnReady(" << x << "): no cache, calling resume" << endl; + std::cerr << "FFTDataServer::isColumnReady(" << x << "): no cache, calling resume" << std::endl; resume(); } m_fillThread->start(); @@ -1258,12 +1258,12 @@ endFrame -= winsize / 2; #ifdef DEBUG_FFT_SERVER_FILL - SVDEBUG << "FFTDataServer::fillColumn: requesting frames " + std::cerr << "FFTDataServer::fillColumn: requesting frames " << startFrame + pfx << " -> " << endFrame << " ( = " << endFrame - (startFrame + pfx) << ") at index " << off + pfx << " in buffer of size " << m_fftSize << " with window size " << m_windowSize - << " from channel " << m_channel << endl; + << " from channel " << m_channel << std::endl; #endif QMutexLocker locker(&m_fftBuffersLock); @@ -1370,7 +1370,7 @@ } if (m_suspended) { -// SVDEBUG << "FFTDataServer::fillColumn(" << x << "): calling resume" << endl; +// std::cerr << "FFTDataServer::fillColumn(" << x << "): calling resume" << std::endl; // resume(); } } @@ -1446,7 +1446,7 @@ FFTDataServer::FillThread::run() { #ifdef DEBUG_FFT_SERVER_FILL - SVDEBUG << "FFTDataServer::FillThread::run()" << endl; + std::cerr << "FFTDataServer::FillThread::run()" << std::endl; #endif m_extent = 0; @@ -1454,7 +1454,7 @@ while (!m_server.m_model->isReady() && !m_server.m_exiting) { #ifdef DEBUG_FFT_SERVER_FILL - SVDEBUG << "FFTDataServer::FillThread::run(): waiting for model " << m_server.m_model << " to be ready" << endl; + std::cerr << "FFTDataServer::FillThread::run(): waiting for model " << m_server.m_model << " to be ready" << std::endl; #endif sleep(1); } @@ -1476,7 +1476,7 @@ try { m_server.fillColumn(int((f - start) / m_server.m_windowIncrement)); } catch (std::exception &e) { - SVDEBUG << "FFTDataServer::FillThread::run: exception: " << e.what() << endl; + std::cerr << "FFTDataServer::FillThread::run: exception: " << e.what() << std::endl; m_error = e.what(); m_server.fillComplete(); m_completion = 100; @@ -1525,7 +1525,7 @@ try { m_server.fillColumn(int((f - start) / m_server.m_windowIncrement)); } catch (std::exception &e) { - SVDEBUG << "FFTDataServer::FillThread::run: exception: " << e.what() << endl; + std::cerr << "FFTDataServer::FillThread::run: exception: " << e.what() << std::endl; m_error = e.what(); m_server.fillComplete(); m_completion = 100; @@ -1567,7 +1567,7 @@ m_extent = end; #ifdef DEBUG_FFT_SERVER - SVDEBUG << "FFTDataServer::FillThread::run exiting" << endl; + std::cerr << "FFTDataServer::FillThread::run exiting" << std::endl; #endif } diff -r 16c48a3db2a7 -r aa2257796931 data/fileio/CoreAudioFileReader.cpp --- a/data/fileio/CoreAudioFileReader.cpp Sat Apr 26 23:05:32 2014 +0100 +++ b/data/fileio/CoreAudioFileReader.cpp Wed May 07 15:14:17 2014 +0100 @@ -89,16 +89,16 @@ //!!! how do we find out if the file open fails because of DRM protection? -#if (MACOSX_DEPLOYMENT_TARGET <= 1040 && MAC_OS_X_VERSION_MIN_REQUIRED <= 1040) - FSRef fsref; - if (!CFURLGetFSRef(url, &fsref)) { // returns Boolean, not error code - m_error = "CoreAudioReadStream: Error looking up FS ref (file not found?)"; - return; - } - m_d->err = ExtAudioFileOpen(&fsref, &m_d->file); -#else +//#if (MACOSX_DEPLOYMENT_TARGET <= 1040 && MAC_OS_X_VERSION_MIN_REQUIRED <= 1040) +// FSRef fsref; +// if (!CFURLGetFSRef(url, &fsref)) { // returns Boolean, not error code +// m_error = "CoreAudioReadStream: Error looking up FS ref (file not found?)"; +// return; +// } +// m_d->err = ExtAudioFileOpen(&fsref, &m_d->file); +//#else m_d->err = ExtAudioFileOpenURL(url, &m_d->file); -#endif +//#endif CFRelease(url); diff -r 16c48a3db2a7 -r aa2257796931 data/fileio/FileFinder.h --- a/data/fileio/FileFinder.h Sat Apr 26 23:05:32 2014 +0100 +++ b/data/fileio/FileFinder.h Wed May 07 15:14:17 2014 +0100 @@ -30,6 +30,8 @@ ImageFile, AnyFile, CSVFile, + LayerFileNonSV, + LayerFileNoMidiNonSV, }; virtual QString getOpenFileName(FileType type, QString fallbackLocation = "") = 0; diff -r 16c48a3db2a7 -r aa2257796931 data/fileio/MIDIFileWriter.cpp --- a/data/fileio/MIDIFileWriter.cpp Sat Apr 26 23:05:32 2014 +0100 +++ b/data/fileio/MIDIFileWriter.cpp Wed May 07 15:14:17 2014 +0100 @@ -23,8 +23,7 @@ #include "MIDIFileWriter.h" #include "data/midi/MIDIEvent.h" - -#include "model/NoteModel.h" +#include "model/NoteData.h" #include "base/Pitch.h" @@ -37,14 +36,13 @@ using namespace MIDIConstants; -MIDIFileWriter::MIDIFileWriter(QString path, NoteModel *model, float tempo) : +MIDIFileWriter::MIDIFileWriter(QString path, const NoteExportable *exportable, + int sampleRate, float tempo) : m_path(path), - m_model(model), - m_modelUsesHz(false), + m_exportable(exportable), + m_sampleRate(sampleRate), m_tempo(tempo) { - if (model->getScaleUnits().toLower() == "hz") m_modelUsesHz = true; - if (!convert()) { m_error = "Conversion from model to internal MIDI format failed"; } @@ -342,42 +340,28 @@ // Omit time signature - const NoteModel::PointList ¬es = - static_cast *>(m_model)->getPoints(); + NoteList notes = m_exportable->getNotes(); - for (NoteModel::PointList::const_iterator i = notes.begin(); - i != notes.end(); ++i) { + for (NoteList::const_iterator i = notes.begin(); i != notes.end(); ++i) { - long frame = i->frame; - float value = i->value; + size_t frame = i->start; size_t duration = i->duration; - - int pitch; - - if (m_modelUsesHz) { - pitch = Pitch::getPitchForFrequency(value); - } else { - pitch = lrintf(value); - } + int pitch = i->midiPitch; + int velocity = i->velocity; if (pitch < 0) pitch = 0; if (pitch > 127) pitch = 127; // Convert frame to MIDI time - double seconds = double(frame) / double(m_model->getSampleRate()); + double seconds = double(frame) / double(m_sampleRate); double quarters = (seconds * m_tempo) / 60.0; - unsigned long midiTime = lrint(quarters * m_timingDivision); - - int velocity = 100; - if (i->level > 0.f && i->level <= 1.f) { - velocity = lrintf(i->level * 127.f); - } + unsigned long midiTime = int(quarters * m_timingDivision + 0.5); // Get the sounding time for the matching NOTE_OFF - seconds = double(frame + duration) / double(m_model->getSampleRate()); + seconds = double(frame + duration) / double(m_sampleRate); quarters = (seconds * m_tempo) / 60.0; - unsigned long endTime = lrint(quarters * m_timingDivision); + unsigned long endTime = int(quarters * m_timingDivision + 0.5); // At this point all the notes we insert have absolute times // in the delta time fields. We resolve these into delta diff -r 16c48a3db2a7 -r aa2257796931 data/fileio/MIDIFileWriter.h --- a/data/fileio/MIDIFileWriter.h Sat Apr 26 23:05:32 2014 +0100 +++ b/data/fileio/MIDIFileWriter.h Wed May 07 15:14:17 2014 +0100 @@ -32,7 +32,7 @@ #include class MIDIEvent; -class NoteModel; +class NoteExportable; /** * Write a MIDI file. This includes file write code for generic @@ -43,7 +43,10 @@ class MIDIFileWriter { public: - MIDIFileWriter(QString path, NoteModel *model, float tempo = 120.f); + MIDIFileWriter(QString path, + const NoteExportable *exportable, + int sampleRate, // used to convert exportable sample timings + float tempo = 120.f); virtual ~MIDIFileWriter(); virtual bool isOK() const; @@ -74,18 +77,18 @@ bool convert(); - QString m_path; - NoteModel *m_model; - bool m_modelUsesHz; - float m_tempo; - int m_timingDivision; // pulses per quarter note - MIDIFileFormatType m_format; - unsigned int m_numberOfTracks; + QString m_path; + const NoteExportable *m_exportable; + int m_sampleRate; + float m_tempo; + int m_timingDivision; // pulses per quarter note + MIDIFileFormatType m_format; + unsigned int m_numberOfTracks; - MIDIComposition m_midiComposition; + MIDIComposition m_midiComposition; - std::ofstream *m_midiFile; - QString m_error; + std::ofstream *m_midiFile; + QString m_error; }; #endif diff -r 16c48a3db2a7 -r aa2257796931 data/model/DenseTimeValueModel.h --- a/data/model/DenseTimeValueModel.h Sat Apr 26 23:05:32 2014 +0100 +++ b/data/model/DenseTimeValueModel.h Wed May 07 15:14:17 2014 +0100 @@ -22,7 +22,9 @@ /** * Base class for models containing dense two-dimensional data (value - * against time). For example, audio waveform data. + * against time). For example, audio waveform data. Other time-value + * plot data, especially if editable, will normally go into a + * SparseTimeValueModel instead even if regularly sampled. */ class DenseTimeValueModel : public Model @@ -83,8 +85,7 @@ float **buffers) const = 0; virtual bool canPlay() const { return true; } - virtual QString getDefaultPlayPluginId() const { return ""; } - virtual QString getDefaultPlayPluginConfiguration() const { return ""; } + virtual QString getDefaultPlayClipId() const { return ""; } virtual QString toDelimitedDataString(QString delimiter, size_t f0, size_t f1) const; diff -r 16c48a3db2a7 -r aa2257796931 data/model/FlexiNoteModel.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/FlexiNoteModel.h Wed May 07 15:14:17 2014 +0100 @@ -0,0 +1,271 @@ +/* -*- 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 _FLEXINOTE_MODEL_H_ +#define _FLEXINOTE_MODEL_H_ + +#include "IntervalModel.h" +#include "NoteData.h" +#include "base/RealTime.h" +#include "base/Pitch.h" +#include "base/PlayParameterRepository.h" + +/** + * FlexiNoteModel -- a concrete IntervalModel for notes. + */ + +/** + * Extension of the NoteModel for more flexible note interaction. + * The original NoteModel rationale is given below, will need to be + * updated for FlexiNoteModel: + * + * 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 FlexiNote +{ +public: + FlexiNote(long _frame) : frame(_frame), value(0.0f), duration(0), level(1.f) { } + FlexiNote(long _frame, float _value, size_t _duration, float _level, QString _label) : + frame(_frame), value(_value), duration(_duration), level(_level), label(_label) { } + + int getDimensions() const { return 3; } + + long frame; + float value; + size_t duration; + float level; + 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(level) + .arg(XmlExportable::encodeEntities(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(); + list << QString("%1").arg(level); + if (label != "") list << label; + return list.join(delimiter); + } + + struct Comparator { + bool operator()(const FlexiNote &p1, + const FlexiNote &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; + if (p1.level != p2.level) return p1.level < p2.level; + return p1.label < p2.label; + } + }; + + struct OrderComparator { + bool operator()(const FlexiNote &p1, + const FlexiNote &p2) const { + return p1.frame < p2.frame; + } + }; +}; + + +class FlexiNoteModel : public IntervalModel, public NoteExportable +{ + Q_OBJECT + +public: + FlexiNoteModel(size_t sampleRate, size_t resolution, + bool notifyOnAdd = true) : + IntervalModel(sampleRate, resolution, notifyOnAdd), + m_valueQuantization(0) + { + PlayParameterRepository::getInstance()->addPlayable(this); + } + + FlexiNoteModel(size_t sampleRate, size_t resolution, + float valueMinimum, float valueMaximum, + bool notifyOnAdd = true) : + IntervalModel(sampleRate, resolution, + valueMinimum, valueMaximum, + notifyOnAdd), + m_valueQuantization(0) + { + PlayParameterRepository::getInstance()->addPlayable(this); + } + + virtual ~FlexiNoteModel() + { + PlayParameterRepository::getInstance()->removePlayable(this); + } + + float getValueQuantization() const { return m_valueQuantization; } + void setValueQuantization(float q) { m_valueQuantization = q; } + float getValueMinimum() const { return 33; } + float getValueMaximum() const { return 88; } + + QString getTypeName() const { return tr("FlexiNote"); } + + virtual bool canPlay() const { return true; } + + virtual QString getDefaultPlayClipId() const + { + return "elecpiano"; + } + + virtual void toXml(QTextStream &out, + QString indent = "", + QString extraAttributes = "") const + { + std::cerr << "FlexiNoteModel::toXml: extraAttributes = \"" + << extraAttributes.toStdString() << std::endl; + + IntervalModel::toXml + (out, + indent, + QString("%1 subtype=\"flexinote\" 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("Pitch"); + case 3: return tr("Duration"); + case 4: return tr("Level"); + case 5: 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); + } + + PointListConstIterator i = getPointListIteratorForRow(row); + if (i == m_points.end()) return QVariant(); + + switch (column) { + case 4: return i->level; + case 5: 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 0; + PointListConstIterator i = getPointListIteratorForRow(row); + if (i == m_points.end()) return 0; + EditCommand *command = new EditCommand(this, tr("Edit Data")); + + Point point(*i); + command->deletePoint(point); + + switch (column) { + case 4: point.level = value.toDouble(); break; + case 5: point.label = value.toString(); break; + } + + command->addPoint(point); + return command->finish(); + } + + virtual SortType getSortType(int column) const + { + if (column == 5) return SortAlphabetical; + return SortNumeric; + } + + /** + * NoteExportable methods. + */ + + NoteList getNotes() const { + return getNotes(getStartFrame(), getEndFrame()); + } + + NoteList getNotes(size_t startFrame, size_t endFrame) const { + + PointList points = getPoints(startFrame, endFrame); + NoteList notes; + + for (PointList::iterator pli = + points.begin(); pli != points.end(); ++pli) { + + size_t duration = pli->duration; + if (duration == 0 || duration == 1) { + duration = getSampleRate() / 20; + } + + int pitch = lrintf(pli->value); + + int velocity = 100; + if (pli->level > 0.f && pli->level <= 1.f) { + velocity = lrintf(pli->level * 127); + } + + NoteData note(pli->frame, duration, pitch, velocity); + + if (getScaleUnits() == "Hz") { + note.frequency = pli->value; + note.midiPitch = Pitch::getPitchForFrequency(note.frequency); + note.isMidiPitchQuantized = false; + } + + notes.push_back(note); + } + + return notes; + } + +protected: + float m_valueQuantization; +}; + +#endif diff -r 16c48a3db2a7 -r aa2257796931 data/model/Model.h --- a/data/model/Model.h Sat Apr 26 23:05:32 2014 +0100 +++ b/data/model/Model.h Wed May 07 15:14:17 2014 +0100 @@ -216,7 +216,7 @@ virtual QString toDelimitedDataString(QString delimiter) const { return toDelimitedDataString(delimiter, getStartFrame(), getEndFrame()); } - virtual QString toDelimitedDataString(QString, size_t f0, size_t f1) const { + virtual QString toDelimitedDataString(QString, size_t /* f0 */, size_t /* f1 */) const { return ""; } diff -r 16c48a3db2a7 -r aa2257796931 data/model/NoteData.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/NoteData.h Wed May 07 15:14:17 2014 +0100 @@ -0,0 +1,53 @@ +/* -*- 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 NOTE_DATA_H +#define NOTE_DATA_H + +#include + +#include "base/Pitch.h" + +struct NoteData +{ + NoteData(size_t _start, size_t _dur, int _mp, int _vel) : + start(_start), duration(_dur), midiPitch(_mp), frequency(0), + isMidiPitchQuantized(true), velocity(_vel) { }; + + size_t start; // audio sample frame + size_t duration; // in audio sample frames + int midiPitch; // 0-127 + float frequency; // Hz, to be used if isMidiPitchQuantized false + bool isMidiPitchQuantized; + int velocity; // MIDI-style 0-127 + + float getFrequency() const { + if (isMidiPitchQuantized) { + return Pitch::getFrequencyForPitch(midiPitch); + } else { + return frequency; + } + } +}; + +typedef std::vector NoteList; + +class NoteExportable +{ +public: + virtual NoteList getNotes() const = 0; + virtual NoteList getNotes(size_t startFrame, size_t endFrame) const = 0; +}; + +#endif diff -r 16c48a3db2a7 -r aa2257796931 data/model/NoteModel.h --- a/data/model/NoteModel.h Sat Apr 26 23:05:32 2014 +0100 +++ b/data/model/NoteModel.h Wed May 07 15:14:17 2014 +0100 @@ -17,8 +17,10 @@ #define _NOTE_MODEL_H_ #include "IntervalModel.h" +#include "NoteData.h" #include "base/RealTime.h" #include "base/PlayParameterRepository.h" +#include "base/Pitch.h" /** * NoteModel -- a concrete IntervalModel for notes. @@ -91,7 +93,7 @@ }; -class NoteModel : public IntervalModel +class NoteModel : public IntervalModel, public NoteExportable { Q_OBJECT @@ -127,14 +129,9 @@ virtual bool canPlay() const { return true; } - virtual QString getDefaultPlayPluginId() const + virtual QString getDefaultPlayClipId() const { - return "dssi:_builtin:sample_player"; - } - - virtual QString getDefaultPlayPluginConfiguration() const - { - return ""; + return "piano"; } virtual void toXml(QTextStream &out, @@ -219,6 +216,48 @@ return SortNumeric; } + /** + * NoteExportable methods. + */ + + NoteList getNotes() const { + return getNotes(getStartFrame(), getEndFrame()); + } + + NoteList getNotes(size_t startFrame, size_t endFrame) const { + + PointList points = getPoints(startFrame, endFrame); + NoteList notes; + + for (PointList::iterator pli = + points.begin(); pli != points.end(); ++pli) { + + size_t duration = pli->duration; + if (duration == 0 || duration == 1) { + duration = getSampleRate() / 20; + } + + int pitch = lrintf(pli->value); + + int velocity = 100; + if (pli->level > 0.f && pli->level <= 1.f) { + velocity = lrintf(pli->level * 127); + } + + NoteData note(pli->frame, duration, pitch, velocity); + + if (getScaleUnits() == "Hz") { + note.frequency = pli->value; + note.midiPitch = Pitch::getPitchForFrequency(note.frequency); + note.isMidiPitchQuantized = false; + } + + notes.push_back(note); + } + + return notes; + } + protected: float m_valueQuantization; }; diff -r 16c48a3db2a7 -r aa2257796931 data/model/SparseOneDimensionalModel.h --- a/data/model/SparseOneDimensionalModel.h Sat Apr 26 23:05:32 2014 +0100 +++ b/data/model/SparseOneDimensionalModel.h Wed May 07 15:14:17 2014 +0100 @@ -17,6 +17,7 @@ #define _SPARSE_ONE_DIMENSIONAL_MODEL_H_ #include "SparseModel.h" +#include "NoteData.h" #include "base/PlayParameterRepository.h" #include "base/RealTime.h" @@ -69,7 +70,8 @@ }; -class SparseOneDimensionalModel : public SparseModel +class SparseOneDimensionalModel : public SparseModel, + public NoteExportable { Q_OBJECT @@ -88,14 +90,9 @@ virtual bool canPlay() const { return true; } - virtual QString getDefaultPlayPluginId() const + virtual QString getDefaultPlayClipId() const { - return "dssi:_builtin:sample_player"; - } - - virtual QString getDefaultPlayPluginConfiguration() const - { - return ""; + return "tap"; } int getIndexOf(const Point &point) @@ -181,6 +178,32 @@ if (column == 2) return SortAlphabetical; return SortNumeric; } + + /** + * NoteExportable methods. + */ + + NoteList getNotes() const { + return getNotes(getStartFrame(), getEndFrame()); + } + + NoteList getNotes(size_t startFrame, size_t endFrame) const { + + PointList points = getPoints(startFrame, endFrame); + NoteList notes; + + for (PointList::iterator pli = + points.begin(); pli != points.end(); ++pli) { + + notes.push_back + (NoteData(pli->frame, + getSampleRate() / 6, // arbitrary short duration + 64, // default pitch + 100)); // default velocity + } + + return notes; + } }; #endif diff -r 16c48a3db2a7 -r aa2257796931 data/model/SparseTimeValueModel.h --- a/data/model/SparseTimeValueModel.h Sat Apr 26 23:05:32 2014 +0100 +++ b/data/model/SparseTimeValueModel.h Wed May 07 15:14:17 2014 +0100 @@ -86,7 +86,9 @@ SparseValueModel(sampleRate, resolution, notifyOnAdd) { - // not yet playable + // Model is playable, but may not sound (if units not Hz or + // range unsuitable) + PlayParameterRepository::getInstance()->addPlayable(this); } SparseTimeValueModel(size_t sampleRate, size_t resolution, @@ -96,11 +98,20 @@ valueMinimum, valueMaximum, notifyOnAdd) { - // not yet playable + // Model is playable, but may not sound (if units not Hz or + // range unsuitable) + PlayParameterRepository::getInstance()->addPlayable(this); + } + + virtual ~SparseTimeValueModel() + { + PlayParameterRepository::getInstance()->removePlayable(this); } QString getTypeName() const { return tr("Sparse Time-Value"); } + virtual bool canPlay() const { return true; } + /** * TabularModel methods. */ diff -r 16c48a3db2a7 -r aa2257796931 plugin/DSSIPluginInstance.cpp --- a/plugin/DSSIPluginInstance.cpp Sat Apr 26 23:05:32 2014 +0100 +++ b/plugin/DSSIPluginInstance.cpp Wed May 07 15:14:17 2014 +0100 @@ -34,7 +34,7 @@ #endif //#define DEBUG_DSSI 1 -//#define DEBUG_DSSI_PROCESS 1 +#define DEBUG_DSSI_PROCESS 1 #define EVENT_BUFFER_SIZE 1023 diff -r 16c48a3db2a7 -r aa2257796931 plugin/plugins/SamplePlayer.cpp --- a/plugin/plugins/SamplePlayer.cpp Sat Apr 26 23:05:32 2014 +0100 +++ b/plugin/plugins/SamplePlayer.cpp Wed May 07 15:14:17 2014 +0100 @@ -157,6 +157,7 @@ } SamplePlayer *player = new SamplePlayer(rate); + // std::cerr << "Instantiated sample player " << std::endl; if (hostDescriptor->request_non_rt_thread(player, workThreadCallback)) { SVDEBUG << "SamplePlayer::instantiate: Host rejected request_non_rt_thread call, not instantiating" << endl; diff -r 16c48a3db2a7 -r aa2257796931 svcore.pro --- a/svcore.pro Sat Apr 26 23:05:32 2014 +0100 +++ b/svcore.pro Wed May 07 15:14:17 2014 +0100 @@ -4,9 +4,28 @@ 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 +!exists(config.pri) { + + CONFIG += release + DEFINES += NDEBUG BUILD_RELEASE NO_TIMING + + win32-g++ { + INCLUDEPATH += ../sv-dependency-builds/win32-mingw/include + LIBS += -L../sv-dependency-builds/win32-mingw/lib + } + win32-msvc* { + INCLUDEPATH += ../sv-dependency-builds/win32-msvc/include + LIBS += -L../sv-dependency-builds/win32-msvc/lib + } + macx* { + INCLUDEPATH += ../sv-dependency-builds/osx/include + LIBS += -L../sv-dependency-builds/osx/lib + } + + DEFINES += HAVE_BZ2 HAVE_FFTW3 HAVE_FFTW3F HAVE_SNDFILE HAVE_SAMPLERATE HAVE_VAMP HAVE_VAMPHOSTSDK HAVE_RUBBERBAND HAVE_LIBLO HAVE_MAD HAVE_ID3TAG + + macx* { + DEFINES += HAVE_COREAUDIO } } @@ -21,13 +40,6 @@ OBJECTS_DIR = o MOC_DIR = o -win32-g++ { - INCLUDEPATH += ../sv-dependency-builds/win32-mingw/include -} -win32-msvc* { - INCLUDEPATH += ../sv-dependency-builds/win32-msvc/include -} - # Doesn't work with this library, which contains C99 as well as C++ PRECOMPILED_HEADER = @@ -155,6 +167,7 @@ data/model/Model.h \ data/model/ModelDataTableModel.h \ data/model/NoteModel.h \ + data/model/FlexiNoteModel.h \ data/model/PathModel.h \ data/model/PowerOfSqrtTwoZoomConstraint.h \ data/model/PowerOfTwoZoomConstraint.h \ diff -r 16c48a3db2a7 -r aa2257796931 transform/FeatureExtractionModelTransformer.cpp --- a/transform/FeatureExtractionModelTransformer.cpp Sat Apr 26 23:05:32 2014 +0100 +++ b/transform/FeatureExtractionModelTransformer.cpp Wed May 07 15:14:17 2014 +0100 @@ -27,6 +27,7 @@ #include "data/model/EditableDenseThreeDimensionalModel.h" #include "data/model/DenseTimeValueModel.h" #include "data/model/NoteModel.h" +#include "data/model/FlexiNoteModel.h" #include "data/model/RegionModel.h" #include "data/model/FFTModel.h" #include "data/model/WaveFileModel.h" @@ -36,43 +37,80 @@ #include +#include + FeatureExtractionModelTransformer::FeatureExtractionModelTransformer(Input in, const Transform &transform) : ModelTransformer(in, transform), - m_plugin(0), - m_descriptor(0), - m_outputNo(0), - m_fixedRateFeatureNo(-1) // we increment before use + m_plugin(0) { // SVDEBUG << "FeatureExtractionModelTransformer::FeatureExtractionModelTransformer: plugin " << pluginId << ", outputName " << m_transform.getOutput() << endl; - QString pluginId = transform.getPluginIdentifier(); + initialise(); +} + +FeatureExtractionModelTransformer::FeatureExtractionModelTransformer(Input in, + const Transforms &transforms) : + ModelTransformer(in, transforms), + m_plugin(0) +{ +// SVDEBUG << "FeatureExtractionModelTransformer::FeatureExtractionModelTransformer: plugin " << pluginId << ", outputName " << m_transform.getOutput() << endl; + + initialise(); +} + +static bool +areTransformsSimilar(const Transform &t1, const Transform &t2) +{ + Transform t2o(t2); + t2o.setOutput(t1.getOutput()); + return t1 == t2o; +} + +bool +FeatureExtractionModelTransformer::initialise() +{ + // All transforms must use the same plugin, parameters, and + // inputs: they can differ only in choice of plugin output. So we + // initialise based purely on the first transform in the list (but + // first check that they are actually similar as promised) + + for (int j = 1; j < (int)m_transforms.size(); ++j) { + if (!areTransformsSimilar(m_transforms[0], m_transforms[j])) { + m_message = tr("Transforms supplied to a single FeatureExtractionModelTransformer instance must be similar in every respect except plugin output"); + return false; + } + } + + Transform primaryTransform = m_transforms[0]; + + QString pluginId = primaryTransform.getPluginIdentifier(); FeatureExtractionPluginFactory *factory = FeatureExtractionPluginFactory::instanceFor(pluginId); if (!factory) { m_message = tr("No factory available for feature extraction plugin id \"%1\" (unknown plugin type, or internal error?)").arg(pluginId); - return; + return false; } DenseTimeValueModel *input = getConformingInput(); if (!input) { m_message = tr("Input model for feature extraction plugin \"%1\" is of wrong type (internal error?)").arg(pluginId); - return; + return false; } m_plugin = factory->instantiatePlugin(pluginId, input->getSampleRate()); if (!m_plugin) { m_message = tr("Failed to instantiate plugin \"%1\"").arg(pluginId); - return; + return false; } TransformFactory::getInstance()->makeContextConsistentWithPlugin - (m_transform, m_plugin); + (primaryTransform, m_plugin); TransformFactory::getInstance()->setPluginParameters - (m_transform, m_plugin); + (primaryTransform, m_plugin); size_t channelCount = input->getChannelCount(); if (m_plugin->getMaxChannelCount() < channelCount) { @@ -84,34 +122,35 @@ .arg(m_plugin->getMinChannelCount()) .arg(m_plugin->getMaxChannelCount()) .arg(input->getChannelCount()); - return; + return false; } SVDEBUG << "Initialising feature extraction plugin with channels = " - << channelCount << ", step = " << m_transform.getStepSize() - << ", block = " << m_transform.getBlockSize() << endl; + << channelCount << ", step = " << primaryTransform.getStepSize() + << ", block = " << primaryTransform.getBlockSize() << endl; if (!m_plugin->initialise(channelCount, - m_transform.getStepSize(), - m_transform.getBlockSize())) { + primaryTransform.getStepSize(), + primaryTransform.getBlockSize())) { - size_t pstep = m_transform.getStepSize(); - size_t pblock = m_transform.getBlockSize(); + size_t pstep = primaryTransform.getStepSize(); + size_t pblock = primaryTransform.getBlockSize(); - m_transform.setStepSize(0); - m_transform.setBlockSize(0); +///!!! hang on, this isn't right -- we're modifying a copy + primaryTransform.setStepSize(0); + primaryTransform.setBlockSize(0); TransformFactory::getInstance()->makeContextConsistentWithPlugin - (m_transform, m_plugin); + (primaryTransform, m_plugin); - if (m_transform.getStepSize() != pstep || - m_transform.getBlockSize() != pblock) { + if (primaryTransform.getStepSize() != pstep || + primaryTransform.getBlockSize() != pblock) { if (!m_plugin->initialise(channelCount, - m_transform.getStepSize(), - m_transform.getBlockSize())) { + primaryTransform.getStepSize(), + primaryTransform.getBlockSize())) { m_message = tr("Failed to initialise feature extraction plugin \"%1\"").arg(pluginId); - return; + return false; } else { @@ -119,22 +158,22 @@ .arg(pluginId) .arg(pstep) .arg(pblock) - .arg(m_transform.getStepSize()) - .arg(m_transform.getBlockSize()); + .arg(primaryTransform.getStepSize()) + .arg(primaryTransform.getBlockSize()); } } else { m_message = tr("Failed to initialise feature extraction plugin \"%1\"").arg(pluginId); - return; + return false; } } - if (m_transform.getPluginVersion() != "") { + if (primaryTransform.getPluginVersion() != "") { QString pv = QString("%1").arg(m_plugin->getPluginVersion()); - if (pv != m_transform.getPluginVersion()) { + if (pv != primaryTransform.getPluginVersion()) { QString vm = tr("Transform was configured for version %1 of plugin \"%2\", but the plugin being used is version %3") - .arg(m_transform.getPluginVersion()) + .arg(primaryTransform.getPluginVersion()) .arg(pluginId) .arg(pv); if (m_message != "") { @@ -149,77 +188,88 @@ if (outputs.empty()) { m_message = tr("Plugin \"%1\" has no outputs").arg(pluginId); - return; - } - - for (size_t i = 0; i < outputs.size(); ++i) { -// SVDEBUG << "comparing output " << i << " name \"" << outputs[i].identifier << "\" with expected \"" << m_transform.getOutput() << "\"" << endl; - if (m_transform.getOutput() == "" || - outputs[i].identifier == m_transform.getOutput().toStdString()) { - m_outputNo = i; - m_descriptor = new Vamp::Plugin::OutputDescriptor(outputs[i]); - break; - } + return false; } - if (!m_descriptor) { - m_message = tr("Plugin \"%1\" has no output named \"%2\"") - .arg(pluginId) - .arg(m_transform.getOutput()); - return; + for (int j = 0; j < (int)m_transforms.size(); ++j) { + + for (int i = 0; i < (int)outputs.size(); ++i) { +// SVDEBUG << "comparing output " << i << " name \"" << outputs[i].identifier << "\" with expected \"" << m_transform.getOutput() << "\"" << endl; + if (m_transforms[j].getOutput() == "" || + outputs[i].identifier == m_transforms[j].getOutput().toStdString()) { + m_outputNos.push_back(i); + m_descriptors.push_back(new Vamp::Plugin::OutputDescriptor(outputs[i])); + m_fixedRateFeatureNos.push_back(-1); // we increment before use + break; + } + } + + if (m_descriptors.size() <= j) { + m_message = tr("Plugin \"%1\" has no output named \"%2\"") + .arg(pluginId) + .arg(m_transforms[j].getOutput()); + return false; + } } - createOutputModel(); + for (int j = 0; j < (int)m_transforms.size(); ++j) { + createOutputModels(j); + } + + return true; } void -FeatureExtractionModelTransformer::createOutputModel() +FeatureExtractionModelTransformer::createOutputModels(int n) { DenseTimeValueModel *input = getConformingInput(); // cerr << "FeatureExtractionModelTransformer::createOutputModel: sample type " << m_descriptor->sampleType << ", rate " << m_descriptor->sampleRate << endl; - PluginRDFDescription description(m_transform.getPluginIdentifier()); - QString outputId = m_transform.getOutput(); + PluginRDFDescription description(m_transforms[n].getPluginIdentifier()); + QString outputId = m_transforms[n].getOutput(); int binCount = 1; float minValue = 0.0, maxValue = 0.0; bool haveExtents = false; - - if (m_descriptor->hasFixedBinCount) { - binCount = m_descriptor->binCount; + bool haveBinCount = m_descriptors[n]->hasFixedBinCount; + + if (haveBinCount) { + binCount = m_descriptors[n]->binCount; } + m_needAdditionalModels[n] = false; + // cerr << "FeatureExtractionModelTransformer: output bin count " // << binCount << endl; - if (binCount > 0 && m_descriptor->hasKnownExtents) { - minValue = m_descriptor->minValue; - maxValue = m_descriptor->maxValue; + if (binCount > 0 && m_descriptors[n]->hasKnownExtents) { + minValue = m_descriptors[n]->minValue; + maxValue = m_descriptors[n]->maxValue; haveExtents = true; } size_t modelRate = input->getSampleRate(); size_t modelResolution = 1; - if (m_descriptor->sampleType != + if (m_descriptors[n]->sampleType != Vamp::Plugin::OutputDescriptor::OneSamplePerStep) { - if (m_descriptor->sampleRate > input->getSampleRate()) { + if (m_descriptors[n]->sampleRate > input->getSampleRate()) { cerr << "WARNING: plugin reports output sample rate as " - << m_descriptor->sampleRate << " (can't display features with finer resolution than the input rate of " << input->getSampleRate() << ")" << endl; + << m_descriptors[n]->sampleRate << " (can't display features with finer resolution than the input rate of " << input->getSampleRate() << ")" << endl; } } - switch (m_descriptor->sampleType) { + switch (m_descriptors[n]->sampleType) { case Vamp::Plugin::OutputDescriptor::VariableSampleRate: - if (m_descriptor->sampleRate != 0.0) { - modelResolution = size_t(modelRate / m_descriptor->sampleRate + 0.001); + if (m_descriptors[n]->sampleRate != 0.0) { + modelResolution = size_t(modelRate / m_descriptors[n]->sampleRate + 0.001); } break; case Vamp::Plugin::OutputDescriptor::OneSamplePerStep: - modelResolution = m_transform.getStepSize(); + modelResolution = m_transforms[n].getStepSize(); break; case Vamp::Plugin::OutputDescriptor::FixedSampleRate: @@ -228,32 +278,32 @@ //!!! 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 - if (m_descriptor->sampleRate > input->getSampleRate()) { + if (m_descriptors[n]->sampleRate > input->getSampleRate()) { modelResolution = 1; } else { modelResolution = size_t(round(input->getSampleRate() / - m_descriptor->sampleRate)); + m_descriptors[n]->sampleRate)); } break; } bool preDurationPlugin = (m_plugin->getVampApiVersion() < 2); + Model *out = 0; + if (binCount == 0 && - (preDurationPlugin || !m_descriptor->hasDuration)) { + (preDurationPlugin || !m_descriptors[n]->hasDuration)) { // Anything with no value and no duration is an instant - m_output = new SparseOneDimensionalModel(modelRate, modelResolution, - false); - + out = new SparseOneDimensionalModel(modelRate, modelResolution, false); QString outputEventTypeURI = description.getOutputEventTypeURI(outputId); - m_output->setRDFTypeURI(outputEventTypeURI); + out->setRDFTypeURI(outputEventTypeURI); } else if ((preDurationPlugin && binCount > 1 && - (m_descriptor->sampleType == + (m_descriptors[n]->sampleType == Vamp::Plugin::OutputDescriptor::VariableSampleRate)) || - (!preDurationPlugin && m_descriptor->hasDuration)) { + (!preDurationPlugin && m_descriptors[n]->hasDuration)) { // For plugins using the old v1 API without explicit duration, // we treat anything that has multiple bins (i.e. that has the @@ -284,9 +334,9 @@ // Regions do not have units of Hz or MIDI things (a sweeping // assumption!) - if (m_descriptor->unit == "Hz" || - m_descriptor->unit.find("MIDI") != std::string::npos || - m_descriptor->unit.find("midi") != std::string::npos) { + if (m_descriptors[n]->unit == "Hz" || + m_descriptors[n]->unit.find("MIDI") != std::string::npos || + m_descriptors[n]->unit.find("midi") != std::string::npos) { isNoteModel = true; } @@ -294,7 +344,14 @@ // problem of determining whether to use that here (if bin // count > 1). But we don't. - if (isNoteModel) { + QSettings settings; + settings.beginGroup("Transformer"); + bool flexi = settings.value("use-flexi-note-model", false).toBool(); + settings.endGroup(); + + cerr << "flexi = " << flexi << endl; + + if (isNoteModel && !flexi) { NoteModel *model; if (haveExtents) { @@ -304,8 +361,21 @@ model = new NoteModel (modelRate, modelResolution, false); } - model->setScaleUnits(m_descriptor->unit.c_str()); - m_output = model; + model->setScaleUnits(m_descriptors[n]->unit.c_str()); + out = model; + + } else if (isNoteModel && flexi) { + + FlexiNoteModel *model; + if (haveExtents) { + model = new FlexiNoteModel + (modelRate, modelResolution, minValue, maxValue, false); + } else { + model = new FlexiNoteModel + (modelRate, modelResolution, false); + } + model->setScaleUnits(m_descriptors[n]->unit.c_str()); + out = model; } else { @@ -317,15 +387,15 @@ model = new RegionModel (modelRate, modelResolution, false); } - model->setScaleUnits(m_descriptor->unit.c_str()); - m_output = model; + model->setScaleUnits(m_descriptors[n]->unit.c_str()); + out = model; } QString outputEventTypeURI = description.getOutputEventTypeURI(outputId); - m_output->setRDFTypeURI(outputEventTypeURI); + out->setRDFTypeURI(outputEventTypeURI); } else if (binCount == 1 || - (m_descriptor->sampleType == + (m_descriptors[n]->sampleType == Vamp::Plugin::OutputDescriptor::VariableSampleRate)) { // Anything that is not a 1D, note, or interval model and that @@ -333,10 +403,27 @@ // 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 + // has a variable sample rate is treated as a set of sparse + // time value models, one per output bin, because we lack a // sparse 3D model. + // Anything that is not a 1D, note, or interval model and that + // has a fixed sample rate but an unknown number of values per + // result is also treated as a set of sparse time value models. + + // For sets of sparse time value models, we create a single + // model first as the "standard" output and then create models + // for bins 1+ in the additional model map (mapping the output + // descriptor to a list of models indexed by bin-1). But we + // don't create the additional models yet, as this case has to + // work even if the number of bins is unknown at this point -- + // we create an additional model (copying its parameters from + // the default one) each time a new bin is encountered. + + if (!haveBinCount || binCount > 1) { + m_needAdditionalModels[n] = true; + } + SparseTimeValueModel *model; if (haveExtents) { model = new SparseTimeValueModel @@ -347,12 +434,12 @@ } Vamp::Plugin::OutputList outputs = m_plugin->getOutputDescriptors(); - model->setScaleUnits(outputs[m_outputNo].unit.c_str()); + model->setScaleUnits(outputs[m_outputNos[n]].unit.c_str()); - m_output = model; + out = model; QString outputEventTypeURI = description.getOutputEventTypeURI(outputId); - m_output->setRDFTypeURI(outputEventTypeURI); + out->setRDFTypeURI(outputEventTypeURI); } else { @@ -366,28 +453,95 @@ EditableDenseThreeDimensionalModel::BasicMultirateCompression, false); - if (!m_descriptor->binNames.empty()) { + if (!m_descriptors[n]->binNames.empty()) { std::vector names; - for (size_t i = 0; i < m_descriptor->binNames.size(); ++i) { - names.push_back(m_descriptor->binNames[i].c_str()); + for (size_t i = 0; i < m_descriptors[n]->binNames.size(); ++i) { + names.push_back(m_descriptors[n]->binNames[i].c_str()); } model->setBinNames(names); } - m_output = model; + out = model; QString outputSignalTypeURI = description.getOutputSignalTypeURI(outputId); - m_output->setRDFTypeURI(outputSignalTypeURI); + out->setRDFTypeURI(outputSignalTypeURI); } - if (m_output) m_output->setSourceModel(input); + if (out) { + out->setSourceModel(input); + m_outputs.push_back(out); + } } FeatureExtractionModelTransformer::~FeatureExtractionModelTransformer() { // SVDEBUG << "FeatureExtractionModelTransformer::~FeatureExtractionModelTransformer()" << endl; delete m_plugin; - delete m_descriptor; + for (int j = 0; j < m_descriptors.size(); ++j) { + delete m_descriptors[j]; + } +} + +FeatureExtractionModelTransformer::Models +FeatureExtractionModelTransformer::getAdditionalOutputModels() +{ + Models mm; + for (AdditionalModelMap::iterator i = m_additionalModels.begin(); + i != m_additionalModels.end(); ++i) { + for (std::map::iterator j = + i->second.begin(); + j != i->second.end(); ++j) { + SparseTimeValueModel *m = j->second; + if (m) mm.push_back(m); + } + } + return mm; +} + +bool +FeatureExtractionModelTransformer::willHaveAdditionalOutputModels() +{ + for (std::map::const_iterator i = + m_needAdditionalModels.begin(); + i != m_needAdditionalModels.end(); ++i) { + if (i->second) return true; + } + return false; +} + +SparseTimeValueModel * +FeatureExtractionModelTransformer::getAdditionalModel(int n, int binNo) +{ +// std::cerr << "getAdditionalModel(" << n << ", " << binNo << ")" << std::endl; + + if (binNo == 0) { + std::cerr << "Internal error: binNo == 0 in getAdditionalModel (should be using primary model)" << std::endl; + return 0; + } + + if (!m_needAdditionalModels[n]) return 0; + if (!isOutput(n)) return 0; + if (m_additionalModels[n][binNo]) return m_additionalModels[n][binNo]; + + std::cerr << "getAdditionalModel(" << n << ", " << binNo << "): creating" << std::endl; + + SparseTimeValueModel *baseModel = getConformingOutput(n); + if (!baseModel) return 0; + + std::cerr << "getAdditionalModel(" << n << ", " << binNo << "): (from " << baseModel << ")" << std::endl; + + SparseTimeValueModel *additional = + new SparseTimeValueModel(baseModel->getSampleRate(), + baseModel->getResolution(), + baseModel->getValueMinimum(), + baseModel->getValueMaximum(), + false); + + additional->setScaleUnits(baseModel->getScaleUnits()); + additional->setRDFTypeURI(baseModel->getRDFTypeURI()); + + m_additionalModels[n][binNo] = additional; + return additional; } DenseTimeValueModel * @@ -409,10 +563,12 @@ DenseTimeValueModel *input = getConformingInput(); if (!input) return; - if (!m_output) return; + if (m_outputs.empty()) return; + + Transform primaryTransform = m_transforms[0]; while (!input->isReady() && !m_abandoned) { - SVDEBUG << "FeatureExtractionModelTransformer::run: Waiting for input model to be ready..." << endl; + cerr << "FeatureExtractionModelTransformer::run: Waiting for input model to be ready..." << endl; usleep(500000); } if (m_abandoned) return; @@ -426,11 +582,11 @@ float **buffers = new float*[channelCount]; for (size_t ch = 0; ch < channelCount; ++ch) { - buffers[ch] = new float[m_transform.getBlockSize() + 2]; + buffers[ch] = new float[primaryTransform.getBlockSize() + 2]; } - size_t stepSize = m_transform.getStepSize(); - size_t blockSize = m_transform.getBlockSize(); + size_t stepSize = primaryTransform.getStepSize(); + size_t blockSize = primaryTransform.getBlockSize(); bool frequencyDomain = (m_plugin->getInputDomain() == Vamp::Plugin::FrequencyDomain); @@ -441,7 +597,7 @@ FFTModel *model = new FFTModel (getConformingInput(), channelCount == 1 ? m_input.getChannel() : ch, - m_transform.getWindowType(), + primaryTransform.getWindowType(), blockSize, stepSize, blockSize, @@ -449,7 +605,9 @@ StorageAdviser::PrecisionCritical); if (!model->isOK()) { delete model; - setCompletion(100); + for (int j = 0; j < (int)m_outputNos.size(); ++j) { + setCompletion(j, 100); + } //!!! need a better way to handle this -- previously we were using a QMessageBox but that isn't an appropriate thing to do here either throw AllocationFailed("Failed to create the FFT model for this feature extraction model transformer"); } @@ -461,8 +619,8 @@ long startFrame = m_input.getModel()->getStartFrame(); long endFrame = m_input.getModel()->getEndFrame(); - RealTime contextStartRT = m_transform.getStartTime(); - RealTime contextDurationRT = m_transform.getDuration(); + RealTime contextStartRT = primaryTransform.getStartTime(); + RealTime contextDurationRT = primaryTransform.getDuration(); long contextStart = RealTime::realTime2Frame(contextStartRT, sampleRate); @@ -485,7 +643,9 @@ long prevCompletion = 0; - setCompletion(0); + for (int j = 0; j < (int)m_outputNos.size(); ++j) { + setCompletion(j, 0); + } float *reals = 0; float *imaginaries = 0; @@ -542,13 +702,17 @@ if (m_abandoned) break; - for (size_t fi = 0; fi < features[m_outputNo].size(); ++fi) { - Vamp::Plugin::Feature feature = features[m_outputNo][fi]; - addFeature(blockFrame, feature); - } + for (int j = 0; j < (int)m_outputNos.size(); ++j) { + for (size_t fi = 0; fi < features[m_outputNos[j]].size(); ++fi) { + Vamp::Plugin::Feature feature = features[m_outputNos[j]][fi]; + addFeature(j, blockFrame, feature); + } + } if (blockFrame == contextStart || completion > prevCompletion) { - setCompletion(completion); + for (int j = 0; j < (int)m_outputNos.size(); ++j) { + setCompletion(j, completion); + } prevCompletion = completion; } @@ -558,13 +722,17 @@ if (!m_abandoned) { Vamp::Plugin::FeatureSet features = m_plugin->getRemainingFeatures(); - for (size_t fi = 0; fi < features[m_outputNo].size(); ++fi) { - Vamp::Plugin::Feature feature = features[m_outputNo][fi]; - addFeature(blockFrame, feature); + for (int j = 0; j < (int)m_outputNos.size(); ++j) { + for (size_t fi = 0; fi < features[m_outputNos[j]].size(); ++fi) { + Vamp::Plugin::Feature feature = features[m_outputNos[j]][fi]; + addFeature(j, blockFrame, feature); + } } } - setCompletion(100); + for (int j = 0; j < (int)m_outputNos.size(); ++j) { + setCompletion(j, 100); + } if (frequencyDomain) { for (size_t ch = 0; ch < channelCount; ++ch) { @@ -636,7 +804,8 @@ } void -FeatureExtractionModelTransformer::addFeature(size_t blockFrame, +FeatureExtractionModelTransformer::addFeature(int n, + size_t blockFrame, const Vamp::Plugin::Feature &feature) { size_t inputRate = m_input.getModel()->getSampleRate(); @@ -648,13 +817,13 @@ // << endl; int binCount = 1; - if (m_descriptor->hasFixedBinCount) { - binCount = m_descriptor->binCount; + if (m_descriptors[n]->hasFixedBinCount) { + binCount = m_descriptors[n]->binCount; } int frame = blockFrame; - if (m_descriptor->sampleType == + if (m_descriptors[n]->sampleType == Vamp::Plugin::OutputDescriptor::VariableSampleRate) { if (!feature.hasTimestamp) { @@ -667,26 +836,23 @@ frame = Vamp::RealTime::realTime2Frame(feature.timestamp, inputRate); } - } else if (m_descriptor->sampleType == + } else if (m_descriptors[n]->sampleType == Vamp::Plugin::OutputDescriptor::FixedSampleRate) { if (!feature.hasTimestamp) { - ++m_fixedRateFeatureNo; + ++m_fixedRateFeatureNos[n]; } else { RealTime ts(feature.timestamp.sec, feature.timestamp.nsec); - m_fixedRateFeatureNo = - lrint(ts.toDouble() * m_descriptor->sampleRate); + 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_fixedRateFeatureNo / m_descriptor->sampleRate) + frame = lrintf((m_fixedRateFeatureNos[n] / m_descriptors[n]->sampleRate) * int(inputRate)); - -// cerr << frame << endl; } if (frame < 0) { @@ -704,19 +870,19 @@ // to, we instead test what sort of model the constructor decided // to create. - if (isOutput()) { + if (isOutput(n)) { SparseOneDimensionalModel *model = - getConformingOutput(); + getConformingOutput(n); if (!model) return; model->addPoint(SparseOneDimensionalModel::Point (frame, feature.label.c_str())); - } else if (isOutput()) { + } else if (isOutput(n)) { SparseTimeValueModel *model = - getConformingOutput(); + getConformingOutput(n); if (!model) return; for (int i = 0; i < feature.values.size(); ++i) { @@ -728,10 +894,20 @@ label = QString("[%1] %2").arg(i+1).arg(label); } - model->addPoint(SparseTimeValueModel::Point(frame, value, label)); + SparseTimeValueModel *targetModel = model; + + if (m_needAdditionalModels[n] && i > 0) { + targetModel = getAdditionalModel(n, i); + if (!targetModel) targetModel = model; +// std::cerr << "adding point to model " << targetModel +// << " for output " << n << " bin " << i << std::endl; + } + + targetModel->addPoint + (SparseTimeValueModel::Point(frame, value, label)); } - } else if (isOutput() || isOutput()) { + } else if (isOutput(n) || isOutput(n) || isOutput(n)) { //GF: Added Note Model int index = 0; @@ -748,8 +924,8 @@ duration = feature.values[index++]; } } - - if (isOutput()) { + + if (isOutput(n)) { // GF: added for flexi note model float velocity = 100; if (feature.values.size() > index) { @@ -758,14 +934,31 @@ if (velocity < 0) velocity = 127; if (velocity > 127) velocity = 127; - NoteModel *model = getConformingOutput(); + FlexiNoteModel *model = getConformingOutput(n); + if (!model) return; + model->addPoint(FlexiNoteModel::Point(frame, value, // value is pitch + lrintf(duration), + velocity / 127.f, + feature.label.c_str())); + // GF: end -- added for flexi note model + } else if (isOutput(n)) { + + float velocity = 100; + if (feature.values.size() > index) { + velocity = feature.values[index++]; + } + if (velocity < 0) velocity = 127; + if (velocity > 127) velocity = 127; + + NoteModel *model = getConformingOutput(n); 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(); + + RegionModel *model = getConformingOutput(n); if (!model) return; if (feature.hasDuration && !feature.values.empty()) { @@ -791,20 +984,20 @@ } } - } else if (isOutput()) { + } else if (isOutput(n)) { DenseThreeDimensionalModel::Column values = DenseThreeDimensionalModel::Column::fromStdVector(feature.values); EditableDenseThreeDimensionalModel *model = - getConformingOutput(); + getConformingOutput(n); if (!model) return; // cerr << "(note: model resolution = " << model->getResolution() << ")" // << endl; - if (!feature.hasTimestamp && m_fixedRateFeatureNo >= 0) { - model->setColumn(m_fixedRateFeatureNo, values); + if (!feature.hasTimestamp && m_fixedRateFeatureNos[n] >= 0) { + model->setColumn(m_fixedRateFeatureNos[n], values); } else { model->setColumn(frame / model->getResolution(), values); } @@ -815,46 +1008,47 @@ } void -FeatureExtractionModelTransformer::setCompletion(int completion) +FeatureExtractionModelTransformer::setCompletion(int n, int completion) { - int binCount = 1; - if (m_descriptor->hasFixedBinCount) { - binCount = m_descriptor->binCount; - } - // SVDEBUG << "FeatureExtractionModelTransformer::setCompletion(" // << completion << ")" << endl; - if (isOutput()) { + if (isOutput(n)) { SparseOneDimensionalModel *model = - getConformingOutput(); + getConformingOutput(n); if (!model) return; model->setCompletion(completion, true); - } else if (isOutput()) { + } else if (isOutput(n)) { SparseTimeValueModel *model = - getConformingOutput(); + getConformingOutput(n); if (!model) return; model->setCompletion(completion, true); - } else if (isOutput()) { + } else if (isOutput(n)) { - NoteModel *model = getConformingOutput(); + NoteModel *model = getConformingOutput(n); + if (!model) return; + model->setCompletion(completion, true); + + } else if (isOutput(n)) { + + FlexiNoteModel *model = getConformingOutput(n); if (!model) return; model->setCompletion(completion, true); - } else if (isOutput()) { + } else if (isOutput(n)) { - RegionModel *model = getConformingOutput(); + RegionModel *model = getConformingOutput(n); if (!model) return; model->setCompletion(completion, true); - } else if (isOutput()) { + } else if (isOutput(n)) { EditableDenseThreeDimensionalModel *model = - getConformingOutput(); + getConformingOutput(n); if (!model) return; model->setCompletion(completion, true); //!!!m_context.updates); } diff -r 16c48a3db2a7 -r aa2257796931 transform/FeatureExtractionModelTransformer.h --- a/transform/FeatureExtractionModelTransformer.h Sat Apr 26 23:05:32 2014 +0100 +++ b/transform/FeatureExtractionModelTransformer.h Wed May 07 15:14:17 2014 +0100 @@ -13,8 +13,8 @@ COPYING included with this distribution for more information. */ -#ifndef _FEATURE_EXTRACTION_PLUGIN_TRANSFORMER_H_ -#define _FEATURE_EXTRACTION_PLUGIN_TRANSFORMER_H_ +#ifndef _FEATURE_EXTRACTION_MODEL_TRANSFORMER_H_ +#define _FEATURE_EXTRACTION_MODEL_TRANSFORMER_H_ #include "ModelTransformer.h" @@ -23,8 +23,10 @@ #include #include +#include class DenseTimeValueModel; +class SparseTimeValueModel; class FeatureExtractionModelTransformer : public ModelTransformer { @@ -33,22 +35,41 @@ public: FeatureExtractionModelTransformer(Input input, const Transform &transform); + + // Obtain outputs for a set of transforms that all use the same + // plugin and input (but with different outputs). i.e. run the + // plugin once only and collect more than one output from it. + FeatureExtractionModelTransformer(Input input, + const Transforms &relatedTransforms); + virtual ~FeatureExtractionModelTransformer(); + // ModelTransformer method, retrieve the additional models + Models getAdditionalOutputModels(); + bool willHaveAdditionalOutputModels(); + protected: + bool initialise(); + virtual void run(); Vamp::Plugin *m_plugin; - Vamp::Plugin::OutputDescriptor *m_descriptor; - int m_fixedRateFeatureNo; // to assign times to FixedSampleRate features - int m_outputNo; + std::vector m_descriptors; // per transform + std::vector m_fixedRateFeatureNos; // to assign times to FixedSampleRate features + std::vector m_outputNos; // list of plugin output indexes required for this group of transforms - void createOutputModel(); + void createOutputModels(int n); - void addFeature(size_t blockFrame, + std::map m_needAdditionalModels; // transformNo -> necessity + typedef std::map > AdditionalModelMap; + AdditionalModelMap m_additionalModels; + SparseTimeValueModel *getAdditionalModel(int transformNo, int binNo); + + void addFeature(int n, + size_t blockFrame, const Vamp::Plugin::Feature &feature); - void setCompletion(int); + void setCompletion(int, int); void getFrames(int channelCount, long startFrame, long size, float **buffer); @@ -57,16 +78,21 @@ DenseTimeValueModel *getConformingInput(); - template bool isOutput() { - return dynamic_cast(m_output) != 0; + template bool isOutput(int n) { + return dynamic_cast(m_outputs[n]) != 0; } - template ModelClass *getConformingOutput() { - ModelClass *mc = dynamic_cast(m_output); - if (!mc) { - std::cerr << "FeatureExtractionModelTransformer::getOutput: Output model not conformable" << std::endl; - } - return mc; + template ModelClass *getConformingOutput(int n) { + if ((int)m_outputs.size() > n) { + ModelClass *mc = dynamic_cast(m_outputs[n]); + if (!mc) { + std::cerr << "FeatureExtractionModelTransformer::getOutput: Output model not conformable" << std::endl; + } + return mc; + } else { + std::cerr << "FeatureExtractionModelTransformer::getOutput: No such output number " << n << std::endl; + return 0; + } } }; diff -r 16c48a3db2a7 -r aa2257796931 transform/ModelTransformer.cpp --- a/transform/ModelTransformer.cpp Sat Apr 26 23:05:32 2014 +0100 +++ b/transform/ModelTransformer.cpp Wed May 07 15:14:17 2014 +0100 @@ -16,10 +16,19 @@ #include "ModelTransformer.h" ModelTransformer::ModelTransformer(Input input, const Transform &transform) : - m_transform(transform), m_input(input), - m_output(0), m_detached(false), + m_detachedAdd(false), + m_abandoned(false) +{ + m_transforms.push_back(transform); +} + +ModelTransformer::ModelTransformer(Input input, const Transforms &transforms) : + m_transforms(transforms), + m_input(input), + m_detached(false), + m_detachedAdd(false), m_abandoned(false) { } @@ -28,6 +37,13 @@ { m_abandoned = true; wait(); - if (!m_detached) delete m_output; + if (!m_detached) { + Models mine = getOutputModels(); + foreach (Model *m, mine) delete m; + } + if (!m_detachedAdd) { + Models mine = getAdditionalOutputModels(); + foreach (Model *m, mine) delete m; + } } diff -r 16c48a3db2a7 -r aa2257796931 transform/ModelTransformer.h --- a/transform/ModelTransformer.h Sat Apr 26 23:05:32 2014 +0100 +++ b/transform/ModelTransformer.h Wed May 07 15:14:17 2014 +0100 @@ -40,6 +40,8 @@ public: virtual ~ModelTransformer(); + typedef std::vector Models; + class Input { public: Input(Model *m) : m_model(m), m_channel(-1) { } @@ -76,18 +78,47 @@ int getInputChannel() { return m_input.getChannel(); } /** - * Return the output model created by the transform. Returns a - * null model if the transform could not be initialised; an error - * message may be available via getMessage() in this situation. + * Return the set of output models created by the transform or + * transforms. Returns an empty list if any transform could not + * be initialised; an error message may be available via + * getMessage() in this situation. */ - Model *getOutputModel() { return m_output; } + Models getOutputModels() { return m_outputs; } /** - * Return the output model, also detaching it from the transformer - * so that it will not be deleted when the transformer is. The - * caller takes ownership of the model. + * Return the set of output models, also detaching them from the + * transformer so that they will not be deleted when the + * transformer is. The caller takes ownership of the models. */ - Model *detachOutputModel() { m_detached = true; return m_output; } + Models detachOutputModels() { + m_detached = true; + return getOutputModels(); + } + + /** + * Return any additional models that were created during + * processing. This might happen if, for example, a transform was + * configured to split a multi-bin output into separate single-bin + * models as it processed. These should not be queried until after + * the transform has completed. + */ + virtual Models getAdditionalOutputModels() { return Models(); } + + /** + * Return true if the current transform is one that may produce + * additional models (to be retrieved through + * getAdditionalOutputModels above). + */ + virtual bool willHaveAdditionalOutputModels() { return false; } + + /** + * Return the set of additional models, also detaching them from + * the transformer. The caller takes ownership of the models. + */ + virtual Models detachAdditionalOutputModels() { + m_detachedAdd = true; + return getAdditionalOutputModels(); + } /** * Return a warning or error message. If getOutputModel returned @@ -99,11 +130,13 @@ protected: ModelTransformer(Input input, const Transform &transform); + ModelTransformer(Input input, const Transforms &transforms); - Transform m_transform; + Transforms m_transforms; Input m_input; // I don't own the model in this - Model *m_output; // I own this, unless... + Models m_outputs; // I own this, unless... bool m_detached; // ... this is true. + bool m_detachedAdd; bool m_abandoned; QString m_message; }; diff -r 16c48a3db2a7 -r aa2257796931 transform/ModelTransformerFactory.cpp --- a/transform/ModelTransformerFactory.cpp Sat Apr 26 23:05:32 2014 +0100 +++ b/transform/ModelTransformerFactory.cpp Wed May 07 15:14:17 2014 +0100 @@ -35,6 +35,8 @@ #include +using std::vector; + ModelTransformerFactory * ModelTransformerFactory::m_instance = new ModelTransformerFactory; @@ -163,63 +165,85 @@ } ModelTransformer * -ModelTransformerFactory::createTransformer(const Transform &transform, +ModelTransformerFactory::createTransformer(const Transforms &transforms, const ModelTransformer::Input &input) { ModelTransformer *transformer = 0; - QString id = transform.getPluginIdentifier(); + QString id = transforms[0].getPluginIdentifier(); if (FeatureExtractionPluginFactory::instanceFor(id)) { transformer = - new FeatureExtractionModelTransformer(input, transform); + new FeatureExtractionModelTransformer(input, transforms); } else if (RealTimePluginFactory::instanceFor(id)) { transformer = - new RealTimeEffectModelTransformer(input, transform); + new RealTimeEffectModelTransformer(input, transforms[0]); } else { SVDEBUG << "ModelTransformerFactory::createTransformer: Unknown transform \"" - << transform.getIdentifier() << "\"" << endl; + << transforms[0].getIdentifier() << "\"" << endl; return transformer; } - if (transformer) transformer->setObjectName(transform.getIdentifier()); + if (transformer) transformer->setObjectName(transforms[0].getIdentifier()); return transformer; } Model * ModelTransformerFactory::transform(const Transform &transform, const ModelTransformer::Input &input, - QString &message) + QString &message, + AdditionalModelHandler *handler) { SVDEBUG << "ModelTransformerFactory::transform: Constructing transformer with input model " << input.getModel() << endl; - ModelTransformer *t = createTransformer(transform, input); - if (!t) return 0; + Transforms transforms; + transforms.push_back(transform); + vector mm = transformMultiple(transforms, input, message, handler); + if (mm.empty()) return 0; + else return mm[0]; +} + +vector +ModelTransformerFactory::transformMultiple(const Transforms &transforms, + const ModelTransformer::Input &input, + QString &message, + AdditionalModelHandler *handler) +{ + SVDEBUG << "ModelTransformerFactory::transformMultiple: Constructing transformer with input model " << input.getModel() << endl; + + ModelTransformer *t = createTransformer(transforms, input); + if (!t) return vector(); + + if (handler) { + m_handlers[t] = handler; + } + + m_runningTransformers.insert(t); connect(t, SIGNAL(finished()), this, SLOT(transformerFinished())); - m_runningTransformers.insert(t); + t->start(); + vector models = t->detachOutputModels(); - t->start(); - Model *model = t->detachOutputModel(); - - if (model) { + if (!models.empty()) { QString imn = input.getModel()->objectName(); QString trn = TransformFactory::getInstance()->getTransformFriendlyName - (transform.getIdentifier()); - if (imn != "") { - if (trn != "") { - model->setObjectName(tr("%1: %2").arg(imn).arg(trn)); - } else { - model->setObjectName(imn); + (transforms[0].getIdentifier()); + for (int i = 0; i < models.size(); ++i) { + if (imn != "") { + if (trn != "") { + models[i]->setObjectName(tr("%1: %2").arg(imn).arg(trn)); + } else { + models[i]->setObjectName(imn); + } + } else if (trn != "") { + models[i]->setObjectName(trn); } - } else if (trn != "") { - model->setObjectName(trn); } } else { t->wait(); @@ -227,7 +251,7 @@ message = t->getMessage(); - return model; + return models; } void @@ -252,6 +276,16 @@ m_runningTransformers.erase(transformer); + if (m_handlers.find(transformer) != m_handlers.end()) { + if (transformer->willHaveAdditionalOutputModels()) { + vector mm = transformer->detachAdditionalOutputModels(); + m_handlers[transformer]->moreModelsAvailable(mm); + } else { + m_handlers[transformer]->noMoreModelsAvailable(); + } + m_handlers.erase(transformer); + } + transformer->wait(); // unnecessary but reassuring delete transformer; } @@ -266,8 +300,13 @@ ModelTransformer *t = *i; - if (t->getInputModel() == m || t->getOutputModel() == m) { + if (t->getInputModel() == m) { affected.insert(t); + } else { + vector mm = t->getOutputModels(); + for (int i = 0; i < (int)mm.size(); ++i) { + if (mm[i] == m) affected.insert(t); + } } } diff -r 16c48a3db2a7 -r aa2257796931 transform/ModelTransformerFactory.h --- a/transform/ModelTransformerFactory.h Sat Apr 26 23:05:32 2014 +0100 +++ b/transform/ModelTransformerFactory.h Wed May 07 15:14:17 2014 +0100 @@ -18,6 +18,7 @@ #include "Transform.h" #include "TransformDescription.h" +#include "FeatureExtractionModelTransformer.h" #include "ModelTransformer.h" @@ -26,6 +27,7 @@ #include #include #include +#include class AudioPlaySource; @@ -68,6 +70,15 @@ size_t startFrame = 0, size_t duration = 0, UserConfigurator *configurator = 0); + + class AdditionalModelHandler { + public: + virtual ~AdditionalModelHandler() { } + + // Exactly one of these functions will be called + virtual void moreModelsAvailable(std::vector models) = 0; + virtual void noMoreModelsAvailable() = 0; + }; /** * Return the output model resulting from applying the named @@ -80,12 +91,54 @@ * problem occurs, return 0. Set message if there is any error or * warning to report. * + * Some transforms may return additional models at the end of + * processing. (For example, a transform that splits an output + * into multiple one-per-bin models.) If an additionalModelHandler + * is provided here, its moreModelsAvailable method will be called + * when those models become available, and ownership of those + * models will be transferred to the handler. Otherwise (if the + * handler is null) any such models will be discarded. + * * The returned model is owned by the caller and must be deleted * when no longer needed. */ Model *transform(const Transform &transform, const ModelTransformer::Input &input, - QString &message); + QString &message, + AdditionalModelHandler *handler = 0); + + /** + * Return the multiple output models resulting from applying the + * named transforms to the given input model. The transforms may + * differ only in output identifier for the plugin: they must all + * use the same plugin, parameters, and programs. The plugin will + * be run once only, but more than one output will be harvested + * (as appropriate). Models will be returned in the same order as + * the transforms were given. The plugin may still be working in + * the background when the model is returned; check the output + * models' isReady completion statuses for more details. + * + * If a transform is unknown or the transforms are insufficiently + * closely related or the input model is not an appropriate type + * for the given transform, or if some other problem occurs, + * return 0. Set message if there is any error or warning to + * report. + * + * Some transforms may return additional models at the end of + * processing. (For example, a transform that splits an output + * into multiple one-per-bin models.) If an additionalModelHandler + * is provided here, its moreModelsAvailable method will be called + * when those models become available, and ownership of those + * models will be transferred to the handler. Otherwise (if the + * handler is null) any such models will be discarded. + * + * The returned models are owned by the caller and must be deleted + * when no longer needed. + */ + std::vector transformMultiple(const Transforms &transform, + const ModelTransformer::Input &input, + QString &message, + AdditionalModelHandler *handler = 0); protected slots: void transformerFinished(); @@ -93,7 +146,7 @@ void modelAboutToBeDeleted(Model *); protected: - ModelTransformer *createTransformer(const Transform &transform, + ModelTransformer *createTransformer(const Transforms &transforms, const ModelTransformer::Input &input); typedef std::map TransformerConfigurationMap; @@ -102,6 +155,9 @@ typedef std::set TransformerSet; TransformerSet m_runningTransformers; + typedef std::map HandlerMap; + HandlerMap m_handlers; + static ModelTransformerFactory *m_instance; }; diff -r 16c48a3db2a7 -r aa2257796931 transform/RealTimeEffectModelTransformer.cpp --- a/transform/RealTimeEffectModelTransformer.cpp Sat Apr 26 23:05:32 2014 +0100 +++ b/transform/RealTimeEffectModelTransformer.cpp Wed May 07 15:14:17 2014 +0100 @@ -30,10 +30,16 @@ #include RealTimeEffectModelTransformer::RealTimeEffectModelTransformer(Input in, - const Transform &transform) : - ModelTransformer(in, transform), + const Transform &t) : + ModelTransformer(in, t), m_plugin(0) { + Transform transform(t); + if (!transform.getBlockSize()) { + transform.setBlockSize(1024); + m_transforms[0] = transform; + } + m_units = TransformFactory::getInstance()->getTransformUnits (transform.getIdentifier()); m_outputNo = @@ -41,8 +47,6 @@ QString pluginId = transform.getPluginIdentifier(); - if (!m_transform.getBlockSize()) m_transform.setBlockSize(1024); - // SVDEBUG << "RealTimeEffectModelTransformer::RealTimeEffectModelTransformer: plugin " << pluginId << ", output " << output << endl; RealTimePluginFactory *factory = @@ -59,16 +63,16 @@ m_plugin = factory->instantiatePlugin(pluginId, 0, 0, input->getSampleRate(), - m_transform.getBlockSize(), + transform.getBlockSize(), input->getChannelCount()); if (!m_plugin) { cerr << "RealTimeEffectModelTransformer: Failed to instantiate plugin \"" - << pluginId << "\"" << endl; + << pluginId << "\"" << endl; return; } - TransformFactory::getInstance()->setPluginParameters(m_transform, m_plugin); + TransformFactory::getInstance()->setPluginParameters(transform, m_plugin); if (m_outputNo >= 0 && m_outputNo >= int(m_plugin->getControlOutputCount())) { @@ -86,16 +90,16 @@ WritableWaveFileModel *model = new WritableWaveFileModel (input->getSampleRate(), outputChannels); - m_output = model; + m_outputs.push_back(model); } else { SparseTimeValueModel *model = new SparseTimeValueModel - (input->getSampleRate(), m_transform.getBlockSize(), 0.0, 0.0, false); + (input->getSampleRate(), transform.getBlockSize(), 0.0, 0.0, false); if (m_units != "") model->setScaleUnits(m_units); - m_output = model; + m_outputs.push_back(model); } } @@ -127,8 +131,8 @@ } if (m_abandoned) return; - SparseTimeValueModel *stvm = dynamic_cast(m_output); - WritableWaveFileModel *wwfm = dynamic_cast(m_output); + SparseTimeValueModel *stvm = dynamic_cast(m_outputs[0]); + WritableWaveFileModel *wwfm = dynamic_cast(m_outputs[0]); if (!stvm && !wwfm) return; if (stvm && (m_outputNo >= int(m_plugin->getControlOutputCount()))) return; @@ -143,9 +147,11 @@ long startFrame = m_input.getModel()->getStartFrame(); long endFrame = m_input.getModel()->getEndFrame(); + + Transform transform = m_transforms[0]; - RealTime contextStartRT = m_transform.getStartTime(); - RealTime contextDurationRT = m_transform.getDuration(); + RealTime contextStartRT = transform.getStartTime(); + RealTime contextDurationRT = transform.getDuration(); long contextStart = RealTime::realTime2Frame(contextStartRT, sampleRate); diff -r 16c48a3db2a7 -r aa2257796931 transform/RealTimeEffectModelTransformer.h --- a/transform/RealTimeEffectModelTransformer.h Sat Apr 26 23:05:32 2014 +0100 +++ b/transform/RealTimeEffectModelTransformer.h Wed May 07 15:14:17 2014 +0100 @@ -13,8 +13,8 @@ COPYING included with this distribution for more information. */ -#ifndef _REAL_TIME_PLUGIN_TRANSFORMER_H_ -#define _REAL_TIME_PLUGIN_TRANSFORMER_H_ +#ifndef _REAL_TIME_EFFECT_TRANSFORMER_H_ +#define _REAL_TIME_EFFECT_TRANSFORMER_H_ #include "ModelTransformer.h" #include "plugin/RealTimePluginInstance.h" diff -r 16c48a3db2a7 -r aa2257796931 transform/Transform.h --- a/transform/Transform.h Sat Apr 26 23:05:32 2014 +0100 +++ b/transform/Transform.h Wed May 07 15:14:17 2014 +0100 @@ -25,6 +25,7 @@ #include #include +#include typedef QString TransformId; @@ -196,5 +197,7 @@ float m_sampleRate; }; +typedef std::vector Transforms; + #endif