# HG changeset patch # User Chris Cannam # Date 1427105088 0 # Node ID c4898e57eea502546201700e773585e157641611 # Parent ba404199345fa3948e7c5aa33aaf7cdbd8904cd6# Parent 682d64f05e72d0d83ae0c75a7a4962c0eb3bbeee Merge from default branch diff -r ba404199345f -r c4898e57eea5 base/Pitch.cpp --- a/base/Pitch.cpp Mon Nov 10 09:19:49 2014 +0000 +++ b/base/Pitch.cpp Mon Mar 23 10:04:48 2015 +0000 @@ -19,45 +19,49 @@ #include -float +double Pitch::getFrequencyForPitch(int midiPitch, - float centsOffset, - float concertA) + double centsOffset, + double concertA) { if (concertA <= 0.0) { concertA = Preferences::getInstance()->getTuningFrequency(); } - float p = float(midiPitch) + (centsOffset / 100); - return concertA * powf(2.0, (p - 69.0) / 12.0); + double p = double(midiPitch) + (centsOffset / 100); + return concertA * pow(2.0, (p - 69.0) / 12.0); } int -Pitch::getPitchForFrequency(float frequency, - float *centsOffsetReturn, - float concertA) +Pitch::getPitchForFrequency(double frequency, + double *centsOffsetReturn, + double concertA) { if (concertA <= 0.0) { concertA = Preferences::getInstance()->getTuningFrequency(); } - float p = 12.0 * (log(frequency / (concertA / 2.0)) / log(2.0)) + 57.0; + double p = 12.0 * (log(frequency / (concertA / 2.0)) / log(2.0)) + 57.0; - int midiPitch = int(p + 0.00001); - float centsOffset = (p - midiPitch) * 100.0; + int midiPitch = int(round(p)); + double centsOffset = (p - midiPitch) * 100.0; if (centsOffset >= 50.0) { midiPitch = midiPitch + 1; centsOffset = -(100.0 - centsOffset); } + if (centsOffset < -50.0) { + midiPitch = midiPitch - 1; + centsOffset = (100.0 + centsOffset); + } if (centsOffsetReturn) *centsOffsetReturn = centsOffset; return midiPitch; } int -Pitch::getPitchForFrequencyDifference(float frequencyA, - float frequencyB, - float *centsOffsetReturn, - float concertA) +Pitch::getPitchForFrequencyDifference(double frequencyA, + double frequencyB, + double *centsOffsetReturn, + double concertA) { if (concertA <= 0.0) { concertA = Preferences::getInstance()->getTuningFrequency(); @@ -67,13 +71,13 @@ std::swap(frequencyA, frequencyB); } - float pA = 12.0 * (log(frequencyA / (concertA / 2.0)) / log(2.0)) + 57.0; - float pB = 12.0 * (log(frequencyB / (concertA / 2.0)) / log(2.0)) + 57.0; + double pA = 12.0 * (log(frequencyA / (concertA / 2.0)) / log(2.0)) + 57.0; + double pB = 12.0 * (log(frequencyB / (concertA / 2.0)) / log(2.0)) + 57.0; - float p = pB - pA; + double p = pB - pA; int midiPitch = int(p + 0.00001); - float centsOffset = (p - midiPitch) * 100.0; + double centsOffset = (p - midiPitch) * 100.0; if (centsOffset >= 50.0) { midiPitch = midiPitch + 1; @@ -96,13 +100,19 @@ "Ab%1", "A%1", "Bb%1", "B%1" }; -QString -Pitch::getPitchLabel(int midiPitch, - float centsOffset, - bool useFlats) +int +Pitch::getPitchForNoteAndOctave(int note, int octave) { int baseOctave = Preferences::getInstance()->getOctaveOfLowestMIDINote(); - int octave = baseOctave; + return (octave - baseOctave) * 12 + note; +} + +void +Pitch::getNoteAndOctaveForPitch(int midiPitch, int ¬e, int &octave) +{ + int baseOctave = Preferences::getInstance()->getOctaveOfLowestMIDINote(); + + octave = baseOctave; // Note, this only gets the right octave number at octave // boundaries because Cb is enharmonic with B (not B#) and B# is @@ -118,44 +128,55 @@ octave = midiPitch / 12 + baseOctave; } - QString plain = (useFlats ? flatNotes : notes)[midiPitch % 12].arg(octave); + note = midiPitch % 12; +} - int ic = lrintf(centsOffset); +QString +Pitch::getPitchLabel(int midiPitch, + double centsOffset, + bool useFlats) +{ + int note, octave; + getNoteAndOctaveForPitch(midiPitch, note, octave); + + QString plain = (useFlats ? flatNotes : notes)[note].arg(octave); + + int ic = lrint(centsOffset); if (ic == 0) return plain; else if (ic > 0) return QString("%1+%2c").arg(plain).arg(ic); else return QString("%1%2c").arg(plain).arg(ic); } QString -Pitch::getPitchLabelForFrequency(float frequency, - float concertA, +Pitch::getPitchLabelForFrequency(double frequency, + double concertA, bool useFlats) { if (concertA <= 0.0) { concertA = Preferences::getInstance()->getTuningFrequency(); } - float centsOffset = 0.0; + double centsOffset = 0.0; int midiPitch = getPitchForFrequency(frequency, ¢sOffset, concertA); return getPitchLabel(midiPitch, centsOffset, useFlats); } QString -Pitch::getLabelForPitchRange(int semis, float cents) +Pitch::getLabelForPitchRange(int semis, double cents) { if (semis > 0) { - while (cents < 0.f) { + while (cents < 0.0) { --semis; - cents += 100.f; + cents += 100.0; } } if (semis < 0) { - while (cents > 0.f) { + while (cents > 0.0) { ++semis; - cents -= 100.f; + cents -= 100.0; } } - int ic = lrintf(cents); + int ic = lrint(cents); if (ic == 0) { if (semis >= 12) { @@ -181,10 +202,10 @@ } bool -Pitch::isFrequencyInMidiRange(float frequency, - float concertA) +Pitch::isFrequencyInMidiRange(double frequency, + double concertA) { - float centsOffset = 0.0; + double centsOffset = 0.0; int midiPitch = getPitchForFrequency(frequency, ¢sOffset, concertA); return (midiPitch >= 0 && midiPitch < 128); } diff -r ba404199345f -r c4898e57eea5 base/Pitch.h --- a/base/Pitch.h Mon Nov 10 09:19:49 2014 +0000 +++ b/base/Pitch.h Mon Mar 23 10:04:48 2015 +0000 @@ -30,9 +30,9 @@ * for the A at MIDI pitch 69; otherwise use the tuning frequency * specified in the application preferences (default 440Hz). */ - static float getFrequencyForPitch(int midiPitch, - float centsOffset = 0, - float concertA = 0.0); + static double getFrequencyForPitch(int midiPitch, + double centsOffset = 0, + double concertA = 0.0); /** * Return the nearest MIDI pitch to the given frequency. @@ -46,9 +46,22 @@ * for the A at MIDI pitch 69; otherwise use the tuning frequency * specified in the application preferences (default 440Hz). */ - static int getPitchForFrequency(float frequency, - float *centsOffsetReturn = 0, - float concertA = 0.0); + static int getPitchForFrequency(double frequency, + double *centsOffsetReturn = 0, + double concertA = 0.0); + + /** + * Compatibility version of getPitchForFrequency accepting float + * pointer argument. + */ + static int getPitchForFrequency(double frequency, + float *centsOffsetReturn, + double concertA = 0.0) { + double c; + int p = getPitchForFrequency(frequency, &c, concertA); + if (centsOffsetReturn) *centsOffsetReturn = float(c); + return p; + } /** * Return the nearest MIDI pitch range to the given frequency @@ -64,10 +77,41 @@ * for the A at MIDI pitch 69; otherwise use the tuning frequency * specified in the application preferences (default 440Hz). */ - static int getPitchForFrequencyDifference(float frequencyA, - float frequencyB, - float *centsOffsetReturn = 0, - float concertA = 0.0); + static int getPitchForFrequencyDifference(double frequencyA, + double frequencyB, + double *centsOffsetReturn = 0, + double concertA = 0.0); + + /** + * Compatibility version of getPitchForFrequencyDifference + * accepting float pointer argument. + */ + static int getPitchForFrequencyDifference(double frequencyA, + double frequencyB, + float *centsOffsetReturn, + double concertA = 0.0) { + double c; + int p = getPitchForFrequencyDifference(frequencyA, frequencyB, + &c, concertA); + if (centsOffsetReturn) *centsOffsetReturn = float(c); + return p; + } + + /** + * Return the MIDI pitch for the given note number (0-12 where 0 + * is C) and octave number. The octave numbering system is based + * on the application preferences (default is C4 = middle C, + * though in previous SV releases that was C3). + */ + static int getPitchForNoteAndOctave(int note, int octave); + + /** + * Return the note number (0-12 where 0 is C) and octave number + * for the given MIDI pitch. The octave numbering system is based + * on the application preferences (default is C4 = middle C, + * though in previous SV releases that was C3). + */ + static void getNoteAndOctaveForPitch(int midiPitch, int ¬e, int &octave); /** * Return a string describing the given MIDI pitch, with optional @@ -83,9 +127,9 @@ * e.g. Bb3 instead of A#3. */ static QString getPitchLabel(int midiPitch, - float centsOffset = 0, + double centsOffset = 0, bool useFlats = false); - + /** * Return a string describing the nearest MIDI pitch to the given * frequency, with cents offset. @@ -97,15 +141,15 @@ * If useFlats is true, spell notes with flats instead of sharps, * e.g. Bb3 instead of A#3. */ - static QString getPitchLabelForFrequency(float frequency, - float concertA = 0.0, + static QString getPitchLabelForFrequency(double frequency, + double concertA = 0.0, bool useFlats = false); /** * Return a string describing the given pitch range in octaves, * semitones and cents. This is in the form e.g. "1'2+4c". */ - static QString getLabelForPitchRange(int semis, float cents = 0); + static QString getLabelForPitchRange(int semis, double cents = 0); /** * Return true if the given frequency falls within the range of @@ -118,8 +162,8 @@ * for the A at MIDI pitch 69; otherwise use the tuning frequency * specified in the application preferences (default 440Hz). */ - static bool isFrequencyInMidiRange(float frequency, - float concertA = 0.0); + static bool isFrequencyInMidiRange(double frequency, + double concertA = 0.0); }; diff -r ba404199345f -r c4898e57eea5 base/Preferences.cpp --- a/base/Preferences.cpp Mon Nov 10 09:19:49 2014 +0000 +++ b/base/Preferences.cpp Mon Mar 23 10:04:48 2015 +0000 @@ -49,6 +49,7 @@ m_viewFontSize(10), m_backgroundMode(BackgroundFromTheme), m_timeToTextMode(TimeToTextMs), + m_showHMS(true), m_octave(4), m_showSplash(true) { @@ -71,6 +72,7 @@ (settings.value("background-mode", int(BackgroundFromTheme)).toInt()); m_timeToTextMode = TimeToTextMode (settings.value("time-to-text-mode", int(TimeToTextMs)).toInt()); + m_showHMS = (settings.value("show-hours-minutes-seconds", true)).toBool(); 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(); @@ -102,6 +104,7 @@ props.push_back("Temporary Directory Root"); props.push_back("Background Mode"); props.push_back("Time To Text Mode"); + props.push_back("Show Hours And Minutes"); props.push_back("Octave Numbering System"); props.push_back("View Font Size"); props.push_back("Show Splash Screen"); @@ -148,7 +151,10 @@ return tr("Background colour preference"); } if (name == "Time To Text Mode") { - return tr("Time display format"); + return tr("Time display precision"); + } + if (name == "Show Hours And Minutes") { + return tr("Use hours:minutes:seconds format"); } if (name == "Octave Numbering System") { return tr("Label middle C as"); @@ -205,6 +211,9 @@ if (name == "Time To Text Mode") { return ValueProperty; } + if (name == "Show Hours And Minutes") { + return ToggleProperty; + } if (name == "Octave Numbering System") { return ValueProperty; } @@ -259,6 +268,7 @@ if (name == "Omit Temporaries from Recent Files") { if (deflt) *deflt = 1; + return m_omitRecentTemps ? 1 : 0; } if (name == "Background Mode") { @@ -275,6 +285,11 @@ return int(m_timeToTextMode); } + if (name == "Show Hours And Minutes") { + if (deflt) *deflt = 1; + return m_showHMS ? 1 : 0; + } + 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 @@ -294,6 +309,7 @@ if (name == "Show Splash Screen") { if (deflt) *deflt = 1; + return m_showSplash ? 1 : 0; } return 0; @@ -404,6 +420,8 @@ setBackgroundMode(BackgroundMode(value)); } else if (name == "Time To Text Mode") { setTimeToTextMode(TimeToTextMode(value)); + } else if (name == "Show Hours And Minutes") { + setShowHMS(value ? true : false); } else if (name == "Octave Numbering System") { setOctaveOfMiddleC(getOctaveOfMiddleCInSystem (OctaveNumberingSystem(value))); @@ -599,6 +617,21 @@ } void +Preferences::setShowHMS(bool show) +{ + if (m_showHMS != show) { + + m_showHMS = show; + + QSettings settings; + settings.beginGroup("Preferences"); + settings.setValue("show-hours-minutes-seconds", show); + settings.endGroup(); + emit propertyChanged("Show Hours And Minutes"); + } +} + +void Preferences::setOctaveOfMiddleC(int oct) { if (m_octave != oct) { diff -r ba404199345f -r c4898e57eea5 base/Preferences.h --- a/base/Preferences.h Mon Nov 10 09:19:49 2014 +0000 +++ b/base/Preferences.h Mon Mar 23 10:04:48 2015 +0000 @@ -93,6 +93,8 @@ }; TimeToTextMode getTimeToTextMode() const { return m_timeToTextMode; } + bool getShowHMS() const { return m_showHMS; } + int getOctaveOfMiddleC() const { // weed out unsupported octaves return getOctaveOfMiddleCInSystem(getSystemWithMiddleCInOctave(m_octave)); @@ -119,6 +121,7 @@ void setNormaliseAudio(bool); void setBackgroundMode(BackgroundMode mode); void setTimeToTextMode(TimeToTextMode mode); + void setShowHMS(bool show); void setOctaveOfMiddleC(int oct); void setViewFontSize(int size); void setShowSplash(bool); @@ -156,6 +159,7 @@ int m_viewFontSize; BackgroundMode m_backgroundMode; TimeToTextMode m_timeToTextMode; + bool m_showHMS; int m_octave; bool m_showSplash; }; diff -r ba404199345f -r c4898e57eea5 base/RealTime.cpp --- a/base/RealTime.cpp Mon Nov 10 09:19:49 2014 +0000 +++ b/base/RealTime.cpp Mon Mar 23 10:04:48 2015 +0000 @@ -59,7 +59,11 @@ RealTime RealTime::fromSeconds(double sec) { - return RealTime(int(sec), int((sec - int(sec)) * ONE_BILLION + 0.5)); + if (sec >= 0) { + return RealTime(int(sec), int((sec - int(sec)) * ONE_BILLION + 0.5)); + } else { + return -fromSeconds(-sec); + } } RealTime @@ -270,19 +274,25 @@ std::stringstream out; - if (sec >= 3600) { - out << (sec / 3600) << ":"; + if (p->getShowHMS()) { + + if (sec >= 3600) { + out << (sec / 3600) << ":"; + } + + if (sec >= 60) { + out << (sec % 3600) / 60 << ":"; + } + + if (sec >= 10) { + out << ((sec % 60) / 10); + } + + out << (sec % 10); + + } else { + out << sec; } - - if (sec >= 60) { - out << (sec % 3600) / 60 << ":"; - } - - if (sec >= 10) { - out << ((sec % 60) / 10); - } - - out << (sec % 10); int ms = msec(); @@ -315,21 +325,29 @@ { if (*this < RealTime::zeroTime) return "-" + (-*this).toFrameText(fps); + Preferences *p = Preferences::getInstance(); + std::stringstream out; - if (sec >= 3600) { - out << (sec / 3600) << ":"; + if (p->getShowHMS()) { + + if (sec >= 3600) { + out << (sec / 3600) << ":"; + } + + if (sec >= 60) { + out << (sec % 3600) / 60 << ":"; + } + + if (sec >= 10) { + out << ((sec % 60) / 10); + } + + out << (sec % 10); + + } else { + out << sec; } - - if (sec >= 60) { - out << (sec % 3600) / 60 << ":"; - } - - if (sec >= 10) { - out << ((sec % 60) / 10); - } - - out << (sec % 10); int f = nsec / (ONE_BILLION / fps); diff -r ba404199345f -r c4898e57eea5 base/StringBits.cpp --- a/base/StringBits.cpp Mon Nov 10 09:19:49 2014 +0000 +++ b/base/StringBits.cpp Mon Mar 23 10:04:48 2015 +0000 @@ -20,6 +20,10 @@ #include "StringBits.h" +#include "Debug.h" + +using namespace std; + double StringBits::stringToDoubleLocaleFree(QString s, bool *ok) { @@ -73,6 +77,11 @@ QStringList tokens; QString tok; + // sep -> just seen a field separator (or start of line) + // unq -> in an unquoted field + // q1 -> in a single-quoted field + // q2 -> in a double-quoted field + enum { sep, unq, q1, q2 } mode = sep; for (int i = 0; i < s.length(); ++i) { @@ -83,14 +92,14 @@ switch (mode) { case sep: mode = q1; break; case unq: case q2: tok += c; break; - case q1: mode = sep; tokens << tok; tok = ""; break; + case q1: mode = unq; break; } } else if (c == '"') { switch (mode) { case sep: mode = q2; break; case unq: case q1: tok += c; break; - case q2: mode = sep; tokens << tok; tok = ""; break; + case q2: mode = unq; break; } } else if (c == separator || (separator == ' ' && c.isSpace())) { @@ -117,86 +126,19 @@ } } - if (tok != "" || mode != sep) tokens << tok; + if (tok != "" || mode != sep) { + if (mode == q1) { + tokens << ("'" + tok); // turns out it wasn't quoted after all + } else if (mode == q2) { + tokens << ("\"" + tok); + } else { + tokens << tok; + } + } + return tokens; } -/* - -void testSplit() -{ - QStringList tests; - tests << "a b c d"; - tests << "a \"b c\" d"; - tests << "a 'b c' d"; - tests << "a \"b c\\\" d\""; - tests << "a 'b c\\' d'"; - tests << "a \"b c' d\""; - tests << "a 'b c\" d'"; - tests << "aa 'bb cc\" dd'"; - tests << "a'a 'bb' \\\"cc\" dd\\\""; - tests << " a'a \\\' 'bb' \' \\\"cc\" ' dd\\\" '"; - - for (int j = 0; j < tests.size(); ++j) { - cout << endl; - cout << tests[j] << endl; - cout << "->" << endl << "("; - QStringList l = splitQuoted(tests[j], ' '); - for (int i = 0; i < l.size(); ++i) { - if (i > 0) cout << ";"; - cout << l[i]; - } - cout << ")" << endl; - } -} - -*/ - -/* - Results: - -a b c d --> -(a;b;c;d) - -a "b c" d --> -(a;b c;d) - -a 'b c' d --> -(a;b c;d) - -a "b c\" d" --> -(a;b c" d) - -a 'b c\' d' --> -(a;b c' d) - -a "b c' d" --> -(a;b c' d) - -a 'b c" d' --> -(a;b c" d) - -aa 'bb cc" dd' --> -(aa;bb cc" dd) - -a'a 'bb' \"cc" dd\" --> -(a'a;bb;"cc";dd") - - a'a \' 'bb' ' \"cc" ' dd\" ' --> -(a'a;';bb; "cc" ;dd";) - -*/ - QStringList StringBits::split(QString line, QChar separator, bool quoted) { diff -r ba404199345f -r c4898e57eea5 base/test/TestPitch.h --- a/base/test/TestPitch.h Mon Nov 10 09:19:49 2014 +0000 +++ b/base/test/TestPitch.h Mon Mar 23 10:04:48 2015 +0000 @@ -72,18 +72,29 @@ QCOMPARE(Pitch::getPitchLabelForFrequency(261.63, 440, false), QString("C4")); } -#define MIDDLE_C 261.6255653f +#define MIDDLE_C 261.6255653005986 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); + QCOMPARE(Pitch::getFrequencyForPitch(69, 0), 440.0); + QCOMPARE(Pitch::getFrequencyForPitch(60, 0, 220), MIDDLE_C / 2.0); + QCOMPARE(Pitch::getFrequencyForPitch(69, 0, 220), 220.0); } void pitchForFrequency() { + double centsOffset = 0.0; + QCOMPARE(Pitch::getPitchForFrequency(MIDDLE_C, ¢sOffset), 60); + QCOMPARE(centsOffset, 0.0); + QCOMPARE(Pitch::getPitchForFrequency(261.0, ¢sOffset), 60); + QCOMPARE(int(centsOffset), -4); + QCOMPARE(Pitch::getPitchForFrequency(440.0, ¢sOffset), 69); + QCOMPARE(centsOffset, 0.0); + } + + void pitchForFrequencyF() + { float centsOffset = 0.f; QCOMPARE(Pitch::getPitchForFrequency(MIDDLE_C, ¢sOffset), 60); QCOMPARE(centsOffset, 0.f); diff -r ba404199345f -r c4898e57eea5 base/test/TestRealTime.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/test/TestRealTime.h Mon Mar 23 10:04:48 2015 +0000 @@ -0,0 +1,301 @@ +/* -*- 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_REALTIME_H +#define TEST_REALTIME_H + +#include "../RealTime.h" + +#include +#include +#include + +#include + +using namespace std; + +class TestRealTime : public QObject +{ + Q_OBJECT + +private slots: + +#define ONE_MILLION 1000000 +#define ONE_BILLION 1000000000 + + void zero() + { + QCOMPARE(RealTime(0, 0), RealTime::zeroTime); + QCOMPARE(RealTime(0, 0).sec, 0); + QCOMPARE(RealTime(0, 0).nsec, 0); + QCOMPARE(RealTime(0, 0).msec(), 0); + QCOMPARE(RealTime(0, 0).usec(), 0); + } + + void ctor() + { + QCOMPARE(RealTime(0, 0), RealTime(0, 0)); + + // wraparounds + QCOMPARE(RealTime(0, ONE_BILLION/2), RealTime(1, -ONE_BILLION/2)); + QCOMPARE(RealTime(0, -ONE_BILLION/2), RealTime(-1, ONE_BILLION/2)); + + QCOMPARE(RealTime(1, ONE_BILLION), RealTime(2, 0)); + QCOMPARE(RealTime(1, -ONE_BILLION), RealTime(0, 0)); + QCOMPARE(RealTime(-1, ONE_BILLION), RealTime(0, 0)); + QCOMPARE(RealTime(-1, -ONE_BILLION), RealTime(-2, 0)); + + QCOMPARE(RealTime(2, -ONE_BILLION*2), RealTime(0, 0)); + QCOMPARE(RealTime(2, -ONE_BILLION/2), RealTime(1, ONE_BILLION/2)); + + QCOMPARE(RealTime(-2, ONE_BILLION*2), RealTime(0, 0)); + QCOMPARE(RealTime(-2, ONE_BILLION/2), RealTime(-1, -ONE_BILLION/2)); + + QCOMPARE(RealTime(0, 1).sec, 0); + QCOMPARE(RealTime(0, 1).nsec, 1); + QCOMPARE(RealTime(0, -1).sec, 0); + QCOMPARE(RealTime(0, -1).nsec, -1); + QCOMPARE(RealTime(1, -1).sec, 0); + QCOMPARE(RealTime(1, -1).nsec, ONE_BILLION-1); + QCOMPARE(RealTime(-1, 1).sec, 0); + QCOMPARE(RealTime(-1, 1).nsec, -ONE_BILLION+1); + QCOMPARE(RealTime(-1, -1).sec, -1); + QCOMPARE(RealTime(-1, -1).nsec, -1); + + QCOMPARE(RealTime(2, -ONE_BILLION*2).sec, 0); + QCOMPARE(RealTime(2, -ONE_BILLION*2).nsec, 0); + QCOMPARE(RealTime(2, -ONE_BILLION/2).sec, 1); + QCOMPARE(RealTime(2, -ONE_BILLION/2).nsec, ONE_BILLION/2); + + QCOMPARE(RealTime(-2, ONE_BILLION*2).sec, 0); + QCOMPARE(RealTime(-2, ONE_BILLION*2).nsec, 0); + QCOMPARE(RealTime(-2, ONE_BILLION/2).sec, -1); + QCOMPARE(RealTime(-2, ONE_BILLION/2).nsec, -ONE_BILLION/2); + } + + void fromSeconds() + { + QCOMPARE(RealTime::fromSeconds(0), RealTime(0, 0)); + + QCOMPARE(RealTime::fromSeconds(0.5).sec, 0); + QCOMPARE(RealTime::fromSeconds(0.5).nsec, ONE_BILLION/2); + QCOMPARE(RealTime::fromSeconds(0.5).usec(), ONE_MILLION/2); + QCOMPARE(RealTime::fromSeconds(0.5).msec(), 500); + + QCOMPARE(RealTime::fromSeconds(0.5), RealTime(0, ONE_BILLION/2)); + QCOMPARE(RealTime::fromSeconds(1), RealTime(1, 0)); + QCOMPARE(RealTime::fromSeconds(1.5), RealTime(1, ONE_BILLION/2)); + + QCOMPARE(RealTime::fromSeconds(-0.5).sec, 0); + QCOMPARE(RealTime::fromSeconds(-0.5).nsec, -ONE_BILLION/2); + QCOMPARE(RealTime::fromSeconds(-0.5).usec(), -ONE_MILLION/2); + QCOMPARE(RealTime::fromSeconds(-0.5).msec(), -500); + + QCOMPARE(RealTime::fromSeconds(-1.5).sec, -1); + QCOMPARE(RealTime::fromSeconds(-1.5).nsec, -ONE_BILLION/2); + QCOMPARE(RealTime::fromSeconds(-1.5).usec(), -ONE_MILLION/2); + QCOMPARE(RealTime::fromSeconds(-1.5).msec(), -500); + + QCOMPARE(RealTime::fromSeconds(-0.5), RealTime(0, -ONE_BILLION/2)); + QCOMPARE(RealTime::fromSeconds(-1), RealTime(-1, 0)); + QCOMPARE(RealTime::fromSeconds(-1.5), RealTime(-1, -ONE_BILLION/2)); + } + + void fromMilliseconds() + { + QCOMPARE(RealTime::fromMilliseconds(0), RealTime(0, 0)); + QCOMPARE(RealTime::fromMilliseconds(500), RealTime(0, ONE_BILLION/2)); + QCOMPARE(RealTime::fromMilliseconds(1000), RealTime(1, 0)); + QCOMPARE(RealTime::fromMilliseconds(1500), RealTime(1, ONE_BILLION/2)); + + QCOMPARE(RealTime::fromMilliseconds(-0), RealTime(0, 0)); + QCOMPARE(RealTime::fromMilliseconds(-500), RealTime(0, -ONE_BILLION/2)); + QCOMPARE(RealTime::fromMilliseconds(-1000), RealTime(-1, 0)); + QCOMPARE(RealTime::fromMilliseconds(-1500), RealTime(-1, -ONE_BILLION/2)); + } + + void fromTimeval() + { + struct timeval tv; + + tv.tv_sec = 0; tv.tv_usec = 0; + QCOMPARE(RealTime::fromTimeval(tv), RealTime(0, 0)); + tv.tv_sec = 0; tv.tv_usec = ONE_MILLION/2; + QCOMPARE(RealTime::fromTimeval(tv), RealTime(0, ONE_BILLION/2)); + tv.tv_sec = 1; tv.tv_usec = 0; + QCOMPARE(RealTime::fromTimeval(tv), RealTime(1, 0)); + tv.tv_sec = 1; tv.tv_usec = ONE_MILLION/2; + QCOMPARE(RealTime::fromTimeval(tv), RealTime(1, ONE_BILLION/2)); + + tv.tv_sec = 0; tv.tv_usec = -ONE_MILLION/2; + QCOMPARE(RealTime::fromTimeval(tv), RealTime(0, -ONE_BILLION/2)); + tv.tv_sec = -1; tv.tv_usec = 0; + QCOMPARE(RealTime::fromTimeval(tv), RealTime(-1, 0)); + tv.tv_sec = -1; tv.tv_usec = -ONE_MILLION/2; + QCOMPARE(RealTime::fromTimeval(tv), RealTime(-1, -ONE_BILLION/2)); + } + + void fromXsdDuration() + { + QCOMPARE(RealTime::fromXsdDuration("PT0"), RealTime::zeroTime); + QCOMPARE(RealTime::fromXsdDuration("PT0S"), RealTime::zeroTime); + QCOMPARE(RealTime::fromXsdDuration("PT10S"), RealTime(10, 0)); + QCOMPARE(RealTime::fromXsdDuration("PT10.5S"), RealTime(10, ONE_BILLION/2)); + QCOMPARE(RealTime::fromXsdDuration("PT1.5S").sec, 1); + QCOMPARE(RealTime::fromXsdDuration("PT1.5S").msec(), 500); + QCOMPARE(RealTime::fromXsdDuration("-PT1.5S").sec, -1); + QCOMPARE(RealTime::fromXsdDuration("-PT1.5S").msec(), -500); + QCOMPARE(RealTime::fromXsdDuration("PT1M30.5S"), RealTime(90, ONE_BILLION/2)); + QCOMPARE(RealTime::fromXsdDuration("PT1H2M30.5S"), RealTime(3750, ONE_BILLION/2)); + } + + void toDouble() + { + QCOMPARE(RealTime(0, 0).toDouble(), 0.0); + QCOMPARE(RealTime(0, ONE_BILLION/2).toDouble(), 0.5); + QCOMPARE(RealTime(1, 0).toDouble(), 1.0); + QCOMPARE(RealTime(1, ONE_BILLION/2).toDouble(), 1.5); + + QCOMPARE(RealTime(0, -ONE_BILLION/2).toDouble(), -0.5); + QCOMPARE(RealTime(-1, 0).toDouble(), -1.0); + QCOMPARE(RealTime(-1, -ONE_BILLION/2).toDouble(), -1.5); + } + + void assign() + { + RealTime r; + r = RealTime(0, 0); + QCOMPARE(r, RealTime::zeroTime); + r = RealTime(0, ONE_BILLION/2); + QCOMPARE(r.toDouble(), 0.5); + r = RealTime(-1, -ONE_BILLION/2); + QCOMPARE(r.toDouble(), -1.5); + } + + void plus() + { + QCOMPARE(RealTime(0, 0) + RealTime(0, 0), RealTime(0, 0)); + + QCOMPARE(RealTime(0, 0) + RealTime(0, ONE_BILLION/2), RealTime(0, ONE_BILLION/2)); + QCOMPARE(RealTime(0, ONE_BILLION/2) + RealTime(0, ONE_BILLION/2), RealTime(1, 0)); + QCOMPARE(RealTime(1, 0) + RealTime(0, ONE_BILLION/2), RealTime(1, ONE_BILLION/2)); + + QCOMPARE(RealTime(0, 0) + RealTime(0, -ONE_BILLION/2), RealTime(0, -ONE_BILLION/2)); + QCOMPARE(RealTime(0, -ONE_BILLION/2) + RealTime(0, -ONE_BILLION/2), RealTime(-1, 0)); + QCOMPARE(RealTime(-1, 0) + RealTime(0, -ONE_BILLION/2), RealTime(-1, -ONE_BILLION/2)); + + QCOMPARE(RealTime(1, 0) + RealTime(0, -ONE_BILLION/2), RealTime(0, ONE_BILLION/2)); + QCOMPARE(RealTime(1, 0) + RealTime(0, -ONE_BILLION/2) + RealTime(0, -ONE_BILLION/2), RealTime(0, 0)); + QCOMPARE(RealTime(1, 0) + RealTime(0, -ONE_BILLION/2) + RealTime(0, -ONE_BILLION/2) + RealTime(0, -ONE_BILLION/2), RealTime(0, -ONE_BILLION/2)); + + QCOMPARE(RealTime(0, ONE_BILLION/2) + RealTime(-1, 0), RealTime(0, -ONE_BILLION/2)); + QCOMPARE(RealTime(0, -ONE_BILLION/2) + RealTime(1, 0), RealTime(0, ONE_BILLION/2)); + } + + void minus() + { + QCOMPARE(RealTime(0, 0) - RealTime(0, 0), RealTime(0, 0)); + + QCOMPARE(RealTime(0, 0) - RealTime(0, ONE_BILLION/2), RealTime(0, -ONE_BILLION/2)); + QCOMPARE(RealTime(0, ONE_BILLION/2) - RealTime(0, ONE_BILLION/2), RealTime(0, 0)); + QCOMPARE(RealTime(1, 0) - RealTime(0, ONE_BILLION/2), RealTime(0, ONE_BILLION/2)); + + QCOMPARE(RealTime(0, 0) - RealTime(0, -ONE_BILLION/2), RealTime(0, ONE_BILLION/2)); + QCOMPARE(RealTime(0, -ONE_BILLION/2) - RealTime(0, -ONE_BILLION/2), RealTime(0, 0)); + QCOMPARE(RealTime(-1, 0) - RealTime(0, -ONE_BILLION/2), RealTime(0, -ONE_BILLION/2)); + + QCOMPARE(RealTime(1, 0) - RealTime(0, -ONE_BILLION/2), RealTime(1, ONE_BILLION/2)); + QCOMPARE(RealTime(1, 0) - RealTime(0, -ONE_BILLION/2) - RealTime(0, -ONE_BILLION/2), RealTime(2, 0)); + QCOMPARE(RealTime(1, 0) - RealTime(0, -ONE_BILLION/2) - RealTime(0, -ONE_BILLION/2) - RealTime(0, -ONE_BILLION/2), RealTime(2, ONE_BILLION/2)); + + QCOMPARE(RealTime(0, ONE_BILLION/2) - RealTime(-1, 0), RealTime(1, ONE_BILLION/2)); + QCOMPARE(RealTime(0, -ONE_BILLION/2) - RealTime(1, 0), RealTime(-1, -ONE_BILLION/2)); + } + + void negate() + { + QCOMPARE(-RealTime(0, 0), RealTime(0, 0)); + QCOMPARE(-RealTime(1, 0), RealTime(-1, 0)); + QCOMPARE(-RealTime(1, ONE_BILLION/2), RealTime(-1, -ONE_BILLION/2)); + QCOMPARE(-RealTime(-1, -ONE_BILLION/2), RealTime(1, ONE_BILLION/2)); + } + + void compare() + { + int sec, nsec; + for (sec = -2; sec <= 2; sec += 2) { + for (nsec = -1; nsec <= 1; nsec += 1) { + QCOMPARE(RealTime(sec, nsec) < RealTime(sec, nsec), false); + QCOMPARE(RealTime(sec, nsec) > RealTime(sec, nsec), false); + QCOMPARE(RealTime(sec, nsec) == RealTime(sec, nsec), true); + QCOMPARE(RealTime(sec, nsec) != RealTime(sec, nsec), false); + QCOMPARE(RealTime(sec, nsec) <= RealTime(sec, nsec), true); + QCOMPARE(RealTime(sec, nsec) >= RealTime(sec, nsec), true); + } + } + RealTime prev(-3, 0); + for (sec = -2; sec <= 2; sec += 2) { + for (nsec = -1; nsec <= 1; nsec += 1) { + + RealTime curr(sec, nsec); + + QCOMPARE(prev < curr, true); + QCOMPARE(prev > curr, false); + QCOMPARE(prev == curr, false); + QCOMPARE(prev != curr, true); + QCOMPARE(prev <= curr, true); + QCOMPARE(prev >= curr, false); + + QCOMPARE(curr < prev, false); + QCOMPARE(curr > prev, true); + QCOMPARE(curr == prev, false); + QCOMPARE(curr != prev, true); + QCOMPARE(curr <= prev, false); + QCOMPARE(curr >= prev, true); + + prev = curr; + } + } + } + + void frame() + { + int frames[] = { + 0, 1, 2047, 2048, 6656, 32767, 32768, 44100, 44101, 999999999 + }; + int n = sizeof(frames)/sizeof(frames[0]); + + int rates[] = { + 1, 2, 8000, 22050, 44100, 44101, 192000 + }; + int m = sizeof(rates)/sizeof(rates[0]); + + for (int i = 0; i < n; ++i) { + int frame = frames[i]; + for (int j = 0; j < m; ++j) { + int rate = rates[j]; + + RealTime rt = RealTime::frame2RealTime(frame, rate); + int conv = RealTime::realTime2Frame(rt, rate); + QCOMPARE(frame, conv); + + rt = RealTime::frame2RealTime(-frame, rate); + conv = RealTime::realTime2Frame(rt, rate); + QCOMPARE(-frame, conv); + } + } + } +}; + +#endif diff -r ba404199345f -r c4898e57eea5 base/test/TestStringBits.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/test/TestStringBits.h Mon Mar 23 10:04:48 2015 +0000 @@ -0,0 +1,203 @@ +/* -*- 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_STRINGBITS_H +#define TEST_STRINGBITS_H + +#include "../StringBits.h" + +#include +#include +#include + +#include + +using namespace std; + +class TestStringBits : public QObject +{ + Q_OBJECT + +private: + void testSplitQuoted(QString in, QStringList out) { + // Only suitable where the output strings do not have + // consecutive spaces in them + QCOMPARE(StringBits::splitQuoted(in, ' '), out); + QString in2(in); + in2.replace(' ', ','); + QStringList out2; + foreach (QString o, out) { + out2 << o.replace(' ', ','); + } + QCOMPARE(StringBits::splitQuoted(in2, ','), out2); + } + +private slots: + void simple() { + QString in = "a b c d"; + QStringList out; + out << "a" << "b" << "c" << "d"; + testSplitQuoted(in, out); + } + + void dquoted() { + QString in = "a \"b c\" d"; + QStringList out; + out << "a" << "b c" << "d"; + testSplitQuoted(in, out); + } + + void drunon() { + QString in = "a \"b c\"d e"; + QStringList out; + out << "a" << "b cd" << "e"; + testSplitQuoted(in, out); + } + + void squoted() { + QString in = "a 'b c' d"; + QStringList out; + out << "a" << "b c" << "d"; + testSplitQuoted(in, out); + } + + void srunon() { + QString in = "a 'b c'd e"; + QStringList out; + out << "a" << "b cd" << "e"; + testSplitQuoted(in, out); + } + + void dempty() { + QString in = "\"\" \"\" \"\""; + QStringList out; + out << "" << "" << ""; + testSplitQuoted(in, out); + } + + void sempty() { + QString in = "'' '' ''"; + QStringList out; + out << "" << "" << ""; + testSplitQuoted(in, out); + } + + void descaped() { + QString in = "a \"b c\\\" d\""; + QStringList out; + out << "a" << "b c\" d"; + testSplitQuoted(in, out); + } + + void sescaped() { + QString in = "a 'b c\\' d'"; + QStringList out; + out << "a" << "b c' d"; + testSplitQuoted(in, out); + } + + void dnested() { + QString in = "a \"b c' d\""; + QStringList out; + out << "a" << "b c' d"; + testSplitQuoted(in, out); + } + + void snested() { + QString in = "a 'b c\" d'"; + QStringList out; + out << "a" << "b c\" d"; + testSplitQuoted(in, out); + } + + void snested2() { + QString in = "aa 'bb cc\" dd'"; + QStringList out; + out << "aa" << "bb cc\" dd"; + testSplitQuoted(in, out); + } + + void qquoted() { + QString in = "a'a 'bb' \\\"cc\" dd\\\""; + QStringList out; + out << "a'a" << "bb" << "\"cc\"" << "dd\""; + testSplitQuoted(in, out); + } + + void multispace() { + QString in = " a'a \\' 'bb' ' \\\"cc\" ' dd\\\" '"; + QStringList out; + out << "a'a" << "'" << "bb" << " \"cc\" " << "dd\"" << "'"; + QCOMPARE(StringBits::splitQuoted(in, ' '), out); + + QString in2 = ",,a'a,\\',,,,,,,,,'bb',,,,',,,,,,\\\"cc\",',dd\\\",'"; + QStringList out2; + out2 << "" << "" << "a'a" << "'" << "" << "" << "" << "" << "" << "" + << "" << "" << "bb" << "" << "" << "" << ",,,,,,\"cc\"," + << "dd\"" << "'"; + QCOMPARE(StringBits::splitQuoted(in2, ','), out2); + } +}; + +#endif + +/* r928 +Config: Using QtTest library 5.3.2, Qt 5.3.2 +PASS : TestStringBits::initTestCase() +PASS : TestStringBits::simple() +PASS : TestStringBits::dquoted() +PASS : TestStringBits::squoted() +PASS : TestStringBits::descaped() +FAIL! : TestStringBits::sescaped() Compared lists have different sizes. + Actual (StringBits::splitQuoted(in, ' ')) size: 3 + Expected (out) size: 2 + Loc: [o/../TestStringBits.h(65)] +PASS : TestStringBits::dnested() +PASS : TestStringBits::snested() +PASS : TestStringBits::snested2() +PASS : TestStringBits::qquoted() +FAIL! : TestStringBits::multispace() Compared lists differ at index 1. + Actual (StringBits::splitQuoted(in, ' ')): " " + Expected (out): "'" + Loc: [o/../TestStringBits.h(100)] +FAIL! : TestStringBits::qcommas() Compared lists have different sizes. + Actual (StringBits::splitQuoted(in, ',')) size: 4 + Expected (out) size: 3 + Loc: [o/../TestStringBits.h(107)] +PASS : TestStringBits::cleanupTestCase() +Totals: 10 passed, 3 failed, 0 skipped +*/ + +/*curr +PASS : TestStringBits::initTestCase() +PASS : TestStringBits::simple() +PASS : TestStringBits::dquoted() +PASS : TestStringBits::squoted() +PASS : TestStringBits::descaped() +FAIL! : TestStringBits::sescaped() Compared lists have different sizes. + Actual (StringBits::splitQuoted(in, ' ')) size: 3 + Expected (out) size: 2 + Loc: [o/../TestStringBits.h(65)] +PASS : TestStringBits::dnested() +PASS : TestStringBits::snested() +PASS : TestStringBits::snested2() +PASS : TestStringBits::qquoted() +FAIL! : TestStringBits::multispace() Compared lists have different sizes. + Actual (StringBits::splitQuoted(in, ' ')) size: 5 + Expected (out) size: 6 + Loc: [o/../TestStringBits.h(100)] +PASS : TestStringBits::qcommas() +PASS : TestStringBits::cleanupTestCase() +Totals: 11 passed, 2 failed, 0 skipped +*/ diff -r ba404199345f -r c4898e57eea5 base/test/main.cpp --- a/base/test/main.cpp Mon Nov 10 09:19:49 2014 +0000 +++ b/base/test/main.cpp Mon Mar 23 10:04:48 2015 +0000 @@ -13,6 +13,8 @@ #include "TestRangeMapper.h" #include "TestPitch.h" +#include "TestRealTime.h" +#include "TestStringBits.h" #include @@ -36,6 +38,16 @@ if (QTest::qExec(&t, argc, argv) == 0) ++good; else ++bad; } + { + TestRealTime t; + if (QTest::qExec(&t, argc, argv) == 0) ++good; + else ++bad; + } + { + TestStringBits 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 ba404199345f -r c4898e57eea5 base/test/test.pro --- a/base/test/test.pro Mon Nov 10 09:19:49 2014 +0000 +++ b/base/test/test.pro Mon Mar 23 10:04:48 2015 +0000 @@ -49,7 +49,7 @@ OBJECTS_DIR = o MOC_DIR = o -HEADERS += TestRangeMapper.h TestPitch.h +HEADERS += TestRangeMapper.h TestPitch.h TestRealTime.h TestStringBits.h SOURCES += main.cpp win* { diff -r ba404199345f -r c4898e57eea5 data/fileio/AudioFileReader.h --- a/data/fileio/AudioFileReader.h Mon Nov 10 09:19:49 2014 +0000 +++ b/data/fileio/AudioFileReader.h Mon Mar 23 10:04:48 2015 +0000 @@ -62,6 +62,16 @@ */ virtual QString getMaker() const { return ""; } + /** + * Return the local file path of the audio data. This is the + * location most likely to contain readable audio data: it may be + * in a different place or format from the originally specified + * location, for example if the file has been retrieved and + * decoded. In some cases there may be no local file path, and + * this will return "" if there is none. + */ + virtual QString getLocalFilename() const { return ""; } + typedef std::map TagMap; virtual TagMap getTags() const { return TagMap(); } diff -r ba404199345f -r c4898e57eea5 data/fileio/CSVFileReader.cpp --- a/data/fileio/CSVFileReader.cpp Mon Nov 10 09:19:49 2014 +0000 +++ b/data/fileio/CSVFileReader.cpp Mon Mar 23 10:04:48 2015 +0000 @@ -26,6 +26,7 @@ #include "DataFileReaderFactory.h" #include +#include #include #include #include @@ -37,42 +38,55 @@ CSVFileReader::CSVFileReader(QString path, CSVFormat format, int mainModelSampleRate) : m_format(format), - m_file(0), + m_device(0), + m_ownDevice(true), m_warnings(0), m_mainModelSampleRate(mainModelSampleRate) { - m_file = new QFile(path); + QFile *file = new QFile(path); bool good = false; - if (!m_file->exists()) { + if (!file->exists()) { m_error = QFile::tr("File \"%1\" does not exist").arg(path); - } else if (!m_file->open(QIODevice::ReadOnly | QIODevice::Text)) { + } else if (!file->open(QIODevice::ReadOnly | QIODevice::Text)) { m_error = QFile::tr("Failed to open file \"%1\"").arg(path); } else { good = true; } - if (!good) { - delete m_file; - m_file = 0; + if (good) { + m_device = file; + m_filename = QFileInfo(path).fileName(); + } else { + delete file; } } +CSVFileReader::CSVFileReader(QIODevice *device, CSVFormat format, + int mainModelSampleRate) : + m_format(format), + m_device(device), + m_ownDevice(false), + m_warnings(0), + m_mainModelSampleRate(mainModelSampleRate) +{ +} + CSVFileReader::~CSVFileReader() { - SVDEBUG << "CSVFileReader::~CSVFileReader: file is " << m_file << endl; + SVDEBUG << "CSVFileReader::~CSVFileReader: device is " << m_device << endl; - if (m_file) { - SVDEBUG << "CSVFileReader::CSVFileReader: Closing file" << endl; - m_file->close(); + if (m_device && m_ownDevice) { + SVDEBUG << "CSVFileReader::CSVFileReader: Closing device" << endl; + m_device->close(); + delete m_device; } - delete m_file; } bool CSVFileReader::isOK() const { - return (m_file != 0); + return (m_device != 0); } QString @@ -136,7 +150,7 @@ Model * CSVFileReader::load() const { - if (!m_file) return 0; + if (!m_device) return 0; CSVFormat::ModelType modelType = m_format.getModelType(); CSVFormat::TimingType timingType = m_format.getTimingType(); @@ -168,8 +182,7 @@ EditableDenseThreeDimensionalModel *model3 = 0; Model *model = 0; - QTextStream in(m_file); - in.seek(0); + QTextStream in(m_device); unsigned int warnings = 0, warnLimit = 10; unsigned int lineno = 0; @@ -215,7 +228,7 @@ for (int li = 0; li < lines.size(); ++li) { QString line = lines[li]; - + if (line.startsWith("#")) continue; QStringList list = StringBits::split(line, separator, allowQuoting); @@ -252,6 +265,12 @@ model = model3; break; } + + if (model) { + if (m_filename != "") { + model->setObjectName(m_filename); + } + } } float value = 0.f; diff -r ba404199345f -r c4898e57eea5 data/fileio/CSVFileReader.h --- a/data/fileio/CSVFileReader.h Mon Nov 10 09:19:49 2014 +0000 +++ b/data/fileio/CSVFileReader.h Mon Mar 23 10:04:48 2015 +0000 @@ -28,16 +28,32 @@ class CSVFileReader : public DataFileReader { public: + /** + * Construct a CSVFileReader to read the CSV file at the given + * path, with the given format. + */ CSVFileReader(QString path, CSVFormat format, int mainModelSampleRate); + + /** + * Construct a CSVFileReader to read from the given + * QIODevice. Caller retains ownership of the QIODevice: the + * CSVFileReader will not close or delete it and it must outlive + * the CSVFileReader. + */ + CSVFileReader(QIODevice *device, CSVFormat format, int mainModelSampleRate); + virtual ~CSVFileReader(); virtual bool isOK() const; virtual QString getError() const; + virtual Model *load() const; protected: CSVFormat m_format; - QFile *m_file; + QIODevice *m_device; + bool m_ownDevice; + QString m_filename; QString m_error; mutable int m_warnings; int m_mainModelSampleRate; diff -r ba404199345f -r c4898e57eea5 data/fileio/CSVFormat.cpp --- a/data/fileio/CSVFormat.cpp Mon Nov 10 09:19:49 2014 +0000 +++ b/data/fileio/CSVFormat.cpp Mon Mar 23 10:04:48 2015 +0000 @@ -110,7 +110,7 @@ // something that indicates otherwise: ColumnQualities defaultQualities = - ColumnNumeric | ColumnIntegral | ColumnIncreasing; + ColumnNumeric | ColumnIntegral | ColumnIncreasing | ColumnNearEmpty; for (int i = 0; i < cols; ++i) { @@ -128,7 +128,12 @@ bool integral = (qualities & ColumnIntegral); bool increasing = (qualities & ColumnIncreasing); bool large = (qualities & ColumnLarge); // this one defaults to off + bool emptyish = (qualities & ColumnNearEmpty); + if (lineno > 1 && s.trimmed() != "") { + emptyish = false; + } + float value = 0.f; //!!! how to take into account headers? @@ -166,7 +171,8 @@ (numeric ? ColumnNumeric : 0) | (integral ? ColumnIntegral : 0) | (increasing ? ColumnIncreasing : 0) | - (large ? ColumnLarge : 0); + (large ? ColumnLarge : 0) | + (emptyish ? ColumnNearEmpty : 0); } if (lineno < 10) { @@ -190,11 +196,31 @@ m_timeUnits = CSVFormat::TimeWindows; int timingColumnCount = 0; + + // if our first column has zero or one entries in it and the rest + // have more, then we'll default to ignoring the first column and + // counting the next one as primary. (e.g. Sonic Annotator output + // with filename at start of first column.) + + int primaryColumnNo = 0; + + if (m_columnCount >= 2) { + if ( (m_columnQualities[0] & ColumnNearEmpty) && + !(m_columnQualities[1] & ColumnNearEmpty)) { + primaryColumnNo = 1; + } + } for (int i = 0; i < m_columnCount; ++i) { ColumnPurpose purpose = ColumnUnknown; - bool primary = (i == 0); + + if (i < primaryColumnNo) { + setColumnPurpose(i, purpose); + continue; + } + + bool primary = (i == primaryColumnNo); ColumnQualities qualities = m_columnQualities[i]; diff -r ba404199345f -r c4898e57eea5 data/fileio/CSVFormat.h --- a/data/fileio/CSVFormat.h Mon Nov 10 09:19:49 2014 +0000 +++ b/data/fileio/CSVFormat.h Mon Mar 23 10:04:48 2015 +0000 @@ -53,10 +53,11 @@ }; enum ColumnQuality { - ColumnNumeric = 0x1, - ColumnIntegral = 0x2, - ColumnIncreasing = 0x4, - ColumnLarge = 0x8 + ColumnNumeric = 1, + ColumnIntegral = 2, + ColumnIncreasing = 4, + ColumnLarge = 8, + ColumnNearEmpty = 16, }; typedef unsigned int ColumnQualities; @@ -100,8 +101,8 @@ void setTimingType(TimingType t) { m_timingType = t; } void setTimeUnits(TimeUnits t) { m_timeUnits = t; } void setSeparator(QChar s) { m_separator = s; } - void setSampleRate(int r) { m_sampleRate = r; } - void setWindowSize(int s) { m_windowSize = s; } + void setSampleRate(int r) { m_sampleRate = r; } + void setWindowSize(int s) { m_windowSize = s; } void setColumnCount(int c) { m_columnCount = c; } void setAllowQuoting(bool q) { m_allowQuoting = q; } diff -r ba404199345f -r c4898e57eea5 data/fileio/CodedAudioFileReader.h --- a/data/fileio/CodedAudioFileReader.h Mon Nov 10 09:19:49 2014 +0000 +++ b/data/fileio/CodedAudioFileReader.h Mon Mar 23 10:04:48 2015 +0000 @@ -43,6 +43,8 @@ virtual int getNativeRate() const { return m_fileRate; } + virtual QString getLocalFilename() const { return m_cacheFileName; } + /// Intermediate cache means all CodedAudioFileReaders are quickly seekable virtual bool isQuicklySeekable() const { return true; } diff -r ba404199345f -r c4898e57eea5 data/fileio/FileSource.cpp --- a/data/fileio/FileSource.cpp Mon Nov 10 09:19:49 2014 +0000 +++ b/data/fileio/FileSource.cpp Mon Mar 23 10:04:48 2015 +0000 @@ -32,7 +32,7 @@ #include -//#define DEBUG_FILE_SOURCE 1 +#define DEBUG_FILE_SOURCE 1 int FileSource::m_count = 0; @@ -51,8 +51,11 @@ #ifdef DEBUG_FILE_SOURCE static int extantCount = 0; +static int threadCount = 0; static std::map urlExtantCountMap; +static QMutex countMutex; static void incCount(QString url) { + QMutexLocker locker(&countMutex); ++extantCount; if (urlExtantCountMap.find(url) == urlExtantCountMap.end()) { urlExtantCountMap[url] = 1; @@ -62,10 +65,26 @@ cerr << "FileSource: Now " << urlExtantCountMap[url] << " for this url, " << extantCount << " total" << endl; } static void decCount(QString url) { + QMutexLocker locker(&countMutex); --extantCount; --urlExtantCountMap[url]; cerr << "FileSource: Now " << urlExtantCountMap[url] << " for this url, " << extantCount << " total" << endl; } +void +FileSource::debugReport() +{ + QMutexLocker locker(&countMutex); + cerr << "\nFileSource::debugReport: Have " << extantCount << " FileSource object(s) extant across " << threadCount << " thread(s)" << endl; + cerr << "URLs by extant count:" << endl; + cerr << "Count | URL" << endl; + for (std::map::const_iterator i = urlExtantCountMap.begin(); + i != urlExtantCountMap.end(); ++i) { + cerr << i->second << " | " << i->first << endl; + } + cerr << "FileSource::debugReport done\n" << endl; +} +#else +void FileSource::debugReport() { } #endif static QThreadStorage nms; @@ -268,13 +287,6 @@ void FileSource::init() { - { // check we have a QNetworkAccessManager - QMutexLocker locker(&m_mapMutex); - if (!nms.hasLocalData()) { - nms.setLocalData(new QNetworkAccessManager()); - } - } - if (isResource()) { #ifdef DEBUG_FILE_SOURCE cerr << "FileSource::init: Is a resource" << endl; @@ -463,6 +475,16 @@ QString("%1, */*").arg(m_preferredContentType).toLatin1()); } + { // check we have a QNetworkAccessManager + QMutexLocker locker(&m_mapMutex); + if (!nms.hasLocalData()) { +#ifdef DEBUG_FILE_SOURCE + ++threadCount; +#endif + nms.setLocalData(new QNetworkAccessManager()); + } + } + m_reply = nms.localData()->get(req); connect(m_reply, SIGNAL(readyRead()), diff -r ba404199345f -r c4898e57eea5 data/fileio/FileSource.h --- a/data/fileio/FileSource.h Mon Nov 10 09:19:49 2014 +0000 +++ b/data/fileio/FileSource.h Mon Mar 23 10:04:48 2015 +0000 @@ -195,6 +195,13 @@ */ static bool canHandleScheme(QUrl url); + /** + * Print some stats, if FileSource was compiled with debugging. + * It's safe to leave a call to this function in release code, as + * long as FileSource itself is compiled with release flags. + */ + static void debugReport(); + signals: /** * Emitted during URL retrieval, when the retrieval progress diff -r ba404199345f -r c4898e57eea5 data/fileio/MIDIFileReader.cpp --- a/data/fileio/MIDIFileReader.cpp Mon Nov 10 09:19:49 2014 +0000 +++ b/data/fileio/MIDIFileReader.cpp Mon Mar 23 10:04:48 2015 +0000 @@ -36,6 +36,7 @@ #include "model/NoteModel.h" #include +#include #include @@ -932,6 +933,7 @@ if (!model) { model = new NoteModel(m_mainModelSampleRate, 1, 0.0, 0.0, false); model->setValueQuantization(1.0); + model->setObjectName(QFileInfo(m_path).fileName()); } const MIDITrack &track = m_midiComposition.find(trackToLoad)->second; diff -r ba404199345f -r c4898e57eea5 data/fileio/OggVorbisFileReader.cpp --- a/data/fileio/OggVorbisFileReader.cpp Mon Nov 10 09:19:49 2014 +0000 +++ b/data/fileio/OggVorbisFileReader.cpp Mon Mar 23 10:04:48 2015 +0000 @@ -173,11 +173,11 @@ if (!reader->m_commentsRead) { const FishSoundComment *comment; - comment = fish_sound_comment_first_byname(fs, "TITLE"); + comment = fish_sound_comment_first_byname(fs, (char *)"TITLE"); if (comment && comment->value) { reader->m_title = QString::fromUtf8(comment->value); } - comment = fish_sound_comment_first_byname(fs, "ARTIST"); + comment = fish_sound_comment_first_byname(fs, (char *)"ARTIST"); if (comment && comment->value) { reader->m_maker = QString::fromUtf8(comment->value); } diff -r ba404199345f -r c4898e57eea5 data/fileio/WavFileReader.h --- a/data/fileio/WavFileReader.h Mon Nov 10 09:19:49 2014 +0000 +++ b/data/fileio/WavFileReader.h Mon Mar 23 10:04:48 2015 +0000 @@ -42,6 +42,8 @@ virtual QString getLocation() const { return m_source.getLocation(); } virtual QString getError() const { return m_error; } + virtual QString getLocalFilename() const { return m_path; } + virtual bool isQuicklySeekable() const { return m_seekable; } /** diff -r ba404199345f -r c4898e57eea5 data/model/AggregateWaveModel.cpp --- a/data/model/AggregateWaveModel.cpp Mon Nov 10 09:19:49 2014 +0000 +++ b/data/model/AggregateWaveModel.cpp Mon Mar 23 10:04:48 2015 +0000 @@ -118,14 +118,21 @@ } } - int sz = count; - + int longest = 0; + for (int c = ch0; c <= ch1; ++c) { - int szHere = + int here = m_components[c].model->getData(m_components[c].channel, start, count, readbuf); - if (szHere < sz) sz = szHere; + if (here > longest) { + longest = here; + } + if (here < count) { + for (int i = here; i < count; ++i) { + readbuf[i] = 0.f; + } + } if (mixing) { for (int i = 0; i < count; ++i) { buffer[i] += readbuf[i]; @@ -134,7 +141,7 @@ } if (mixing) delete[] readbuf; - return sz; + return longest; } int @@ -157,14 +164,21 @@ } } - int sz = count; + int longest = 0; for (int c = ch0; c <= ch1; ++c) { - int szHere = + int here = m_components[c].model->getData(m_components[c].channel, start, count, readbuf); - if (szHere < sz) sz = szHere; + if (here > longest) { + longest = here; + } + if (here < count) { + for (int i = here; i < count; ++i) { + readbuf[i] = 0.; + } + } if (mixing) { for (int i = 0; i < count; ++i) { buffer[i] += readbuf[i]; @@ -173,7 +187,7 @@ } if (mixing) delete[] readbuf; - return sz; + return longest; } int diff -r ba404199345f -r c4898e57eea5 data/model/AlignmentModel.cpp --- a/data/model/AlignmentModel.cpp Mon Nov 10 09:19:49 2014 +0000 +++ b/data/model/AlignmentModel.cpp Mon Mar 23 10:04:48 2015 +0000 @@ -112,10 +112,17 @@ if (completion) *completion = 0; return false; } - if (m_pathComplete || !m_rawPath) { + if (m_pathComplete) { if (completion) *completion = 100; return true; } + if (!m_rawPath) { + // lack of raw path could mean path is complete (in which case + // m_pathComplete true above) or else no alignment has been + // set at all yet (this case) + if (completion) *completion = 0; + return false; + } return m_rawPath->isReady(completion); } @@ -347,6 +354,31 @@ } void +AlignmentModel::setPathFrom(SparseTimeValueModel *rawpath) +{ + if (m_rawPath) m_rawPath->aboutToDelete(); + delete m_rawPath; + + m_rawPath = rawpath; + + connect(m_rawPath, SIGNAL(modelChanged()), + this, SLOT(pathChanged())); + + connect(m_rawPath, SIGNAL(modelChangedWithin(int, int)), + this, SLOT(pathChangedWithin(int, int))); + + connect(m_rawPath, SIGNAL(completionChanged()), + this, SLOT(pathCompletionChanged())); + + constructPath(); + constructReversePath(); + + if (m_rawPath->isReady()) { + pathCompletionChanged(); + } +} + +void AlignmentModel::setPath(PathModel *path) { if (m_path) m_path->aboutToDelete(); diff -r ba404199345f -r c4898e57eea5 data/model/AlignmentModel.h --- a/data/model/AlignmentModel.h Mon Nov 10 09:19:49 2014 +0000 +++ b/data/model/AlignmentModel.h Mon Mar 23 10:04:48 2015 +0000 @@ -32,7 +32,7 @@ public: AlignmentModel(Model *reference, Model *aligned, - Model *inputModel, // probably an AggregateWaveModel; I take ownership + Model *inputModel, // probably an AggregateWaveModel; may be null; I take ownership SparseTimeValueModel *path); // I take ownership ~AlignmentModel(); @@ -52,6 +52,7 @@ int toReference(int frame) const; int fromReference(int frame) const; + void setPathFrom(SparseTimeValueModel *rawpath); void setPath(PathModel *path); virtual void toXml(QTextStream &stream, diff -r ba404199345f -r c4898e57eea5 data/model/Model.cpp --- a/data/model/Model.cpp Mon Nov 10 09:19:49 2014 +0000 +++ b/data/model/Model.cpp Mon Mar 23 10:04:48 2015 +0000 @@ -85,9 +85,13 @@ m_alignment->aboutToDelete(); delete m_alignment; } + m_alignment = alignment; - connect(m_alignment, SIGNAL(completionChanged()), - this, SIGNAL(alignmentCompletionChanged())); + + if (m_alignment) { + connect(m_alignment, SIGNAL(completionChanged()), + this, SIGNAL(alignmentCompletionChanged())); + } } const AlignmentModel * diff -r ba404199345f -r c4898e57eea5 data/model/WaveFileModel.cpp --- a/data/model/WaveFileModel.cpp Mon Nov 10 09:19:49 2014 +0000 +++ b/data/model/WaveFileModel.cpp Mon Mar 23 10:04:48 2015 +0000 @@ -183,6 +183,13 @@ if (m_reader) return m_reader->getLocation(); return ""; } + +QString +WaveFileModel::getLocalFilename() const +{ + if (m_reader) return m_reader->getLocalFilename(); + return ""; +} int WaveFileModel::getData(int channel, int start, int count, diff -r ba404199345f -r c4898e57eea5 data/model/WaveFileModel.h --- a/data/model/WaveFileModel.h Mon Nov 10 09:19:49 2014 +0000 +++ b/data/model/WaveFileModel.h Mon Mar 23 10:04:48 2015 +0000 @@ -52,6 +52,8 @@ QString getMaker() const; QString getLocation() const; + QString getLocalFilename() const; + virtual Model *clone() const; float getValueMinimum() const { return -1.0f; } diff -r ba404199345f -r c4898e57eea5 rdf/RDFFeatureWriter.cpp --- a/rdf/RDFFeatureWriter.cpp Mon Nov 10 09:19:49 2014 +0000 +++ b/rdf/RDFFeatureWriter.cpp Mon Mar 23 10:04:48 2015 +0000 @@ -22,6 +22,7 @@ #include "PluginRDFIndexer.h" #include +#include #include #include #include @@ -160,7 +161,8 @@ // Need to select appropriate output file for our track/transform // combination - QTextStream *stream = getOutputStream(trackId, transform.getIdentifier()); + QTextStream *stream = getOutputStream(trackId, transform.getIdentifier(), + QTextCodec::codecForName("UTF-8")); if (!stream) { throw FailedToOpenOutputStream(trackId, transform.getIdentifier()); } diff -r ba404199345f -r c4898e57eea5 transform/CSVFeatureWriter.cpp --- a/transform/CSVFeatureWriter.cpp Mon Nov 10 09:19:49 2014 +0000 +++ b/transform/CSVFeatureWriter.cpp Mon Mar 23 10:04:48 2015 +0000 @@ -23,6 +23,7 @@ #include #include +#include using namespace std; using namespace Vamp; @@ -123,7 +124,9 @@ // Select appropriate output file for our track/transform // combination - QTextStream *sptr = getOutputStream(trackId, transformId); + QTextStream *sptr = getOutputStream(trackId, + transformId, + QTextCodec::codecForName("UTF-8")); if (!sptr) { throw FailedToOpenOutputStream(trackId, transformId); } @@ -169,7 +172,9 @@ i != m_pending.end(); ++i) { DataId tt = i->first; Plugin::Feature f = i->second; - QTextStream *sptr = getOutputStream(tt.first, tt.second.getIdentifier()); + QTextStream *sptr = getOutputStream(tt.first, + tt.second.getIdentifier(), + QTextCodec::codecForName("UTF-8")); if (!sptr) { throw FailedToOpenOutputStream(tt.first, tt.second.getIdentifier()); } diff -r ba404199345f -r c4898e57eea5 transform/FeatureExtractionModelTransformer.cpp --- a/transform/FeatureExtractionModelTransformer.cpp Mon Nov 10 09:19:49 2014 +0000 +++ b/transform/FeatureExtractionModelTransformer.cpp Mon Mar 23 10:04:48 2015 +0000 @@ -679,11 +679,17 @@ if (frequencyDomain) { for (int ch = 0; ch < channelCount; ++ch) { int column = (blockFrame - startFrame) / stepSize; - fftModels[ch]->getValuesAt(column, reals, imaginaries); - for (int i = 0; i <= blockSize/2; ++i) { - buffers[ch][i*2] = reals[i]; - buffers[ch][i*2+1] = imaginaries[i]; - } + if (fftModels[ch]->getValuesAt(column, reals, imaginaries)) { + for (int i = 0; i <= blockSize/2; ++i) { + buffers[ch][i*2] = reals[i]; + buffers[ch][i*2+1] = imaginaries[i]; + } + } else { + for (int i = 0; i <= blockSize/2; ++i) { + buffers[ch][i*2] = 0.f; + buffers[ch][i*2+1] = 0.f; + } + } error = fftModels[ch]->getError(); if (error != "") { cerr << "FeatureExtractionModelTransformer::run: Abandoning, error is " << error << endl; diff -r ba404199345f -r c4898e57eea5 transform/FileFeatureWriter.cpp --- a/transform/FileFeatureWriter.cpp Mon Nov 10 09:19:49 2014 +0000 +++ b/transform/FileFeatureWriter.cpp Mon Mar 23 10:04:48 2015 +0000 @@ -315,7 +315,8 @@ QTextStream *FileFeatureWriter::getOutputStream(QString trackId, - TransformId transformId) + TransformId transformId, + QTextCodec *codec) { QFile *file = getOutputFile(trackId, transformId); if (!file && !m_stdout) { @@ -328,6 +329,7 @@ } else { m_streams[file] = new QTextStream(file); } + m_streams[file]->setCodec(codec); } QTextStream *stream = m_streams[file]; diff -r ba404199345f -r c4898e57eea5 transform/FileFeatureWriter.h --- a/transform/FileFeatureWriter.h Mon Nov 10 09:19:49 2014 +0000 +++ b/transform/FileFeatureWriter.h Mon Mar 23 10:04:48 2015 +0000 @@ -32,6 +32,7 @@ using std::pair; class QTextStream; +class QTextCodec; class QFile; class FileFeatureWriter : public FeatureWriter @@ -55,7 +56,7 @@ }; FileFeatureWriter(int support, QString extension); - QTextStream *getOutputStream(QString, TransformId); + QTextStream *getOutputStream(QString, TransformId, QTextCodec *); typedef pair TrackTransformPair; typedef map FileNameMap;