changeset 906:654990320867

Merge from 899:bc0ff66102a8
author Chris Cannam
date Wed, 07 May 2014 15:17:51 +0100
parents 1f94f3776158 (current diff) bc0ff66102a8 (diff)
children a589d2201a0c
files
diffstat 12 files changed, 246 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/base/Pitch.cpp	Wed May 07 15:17:43 2014 +0100
+++ b/base/Pitch.cpp	Wed May 07 15:17:51 2014 +0100
@@ -101,7 +101,13 @@
 		     float centsOffset,
 		     bool useFlats)
 {
-    int octave = -2;
+    int baseOctave = Preferences::getInstance()->getOctaveOfLowestMIDINote();
+    int octave = baseOctave;
+
+    // Note, this only gets the right octave number at octave
+    // boundaries because Cb is enharmonic with B (not B#) and B# is
+    // enharmonic with C (not Cb). So neither B# nor Cb will be
+    // spelled from a MIDI pitch + flats flag in isolation.
 
     if (midiPitch < 0) {
 	while (midiPitch < 0) {
@@ -109,7 +115,7 @@
 	    --octave;
 	}
     } else {
-	octave = midiPitch / 12 - 2;
+	octave = midiPitch / 12 + baseOctave;
     }
 
     QString plain = (useFlats ? flatNotes : notes)[midiPitch % 12].arg(octave);
--- a/base/Pitch.h	Wed May 07 15:17:43 2014 +0100
+++ b/base/Pitch.h	Wed May 07 15:17:51 2014 +0100
@@ -71,9 +71,10 @@
 
     /**
      * Return a string describing the given MIDI pitch, with optional
-     * cents offset.  This consists of the note name, octave number
-     * (with MIDI pitch 0 having octave number -2), and optional
-     * cents.
+     * cents offset.  This consists of the note name, octave number,
+     * and optional cents. The octave numbering system is based on the
+     * application preferences (default is C4 = middle C, though in
+     * previous SV releases that was C3).
      *
      * For example, "A#3" (A# in octave 3) or "C2-12c" (C in octave 2,
      * minus 12 cents).
--- a/base/Preferences.cpp	Wed May 07 15:17:43 2014 +0100
+++ b/base/Preferences.cpp	Wed May 07 15:17:51 2014 +0100
@@ -47,6 +47,7 @@
     m_viewFontSize(10),
     m_backgroundMode(BackgroundFromTheme),
     m_timeToTextMode(TimeToTextMs),
+    m_octave(4),
     m_showSplash(true)
 {
     QSettings settings;
@@ -66,6 +67,7 @@
         (settings.value("background-mode", int(BackgroundFromTheme)).toInt());
     m_timeToTextMode = TimeToTextMode
         (settings.value("time-to-text-mode", int(TimeToTextMs)).toInt());
+    m_octave = (settings.value("octave-of-middle-c", 4)).toInt();
     m_viewFontSize = settings.value("view-font-size", 10).toInt();
     m_showSplash = settings.value("show-splash", true).toBool();
     settings.endGroup();
@@ -94,6 +96,7 @@
     props.push_back("Temporary Directory Root");
     props.push_back("Background Mode");
     props.push_back("Time To Text Mode");
+    props.push_back("Octave Numbering System");
     props.push_back("View Font Size");
     props.push_back("Show Splash Screen");
     return props;
@@ -135,6 +138,9 @@
     if (name == "Time To Text Mode") {
         return tr("Time display format");
     }
+    if (name == "Octave Numbering System") {
+        return tr("Label middle C as");
+    }
     if (name == "View Font Size") {
         return tr("Font size for text overlays");
     }
@@ -181,6 +187,9 @@
     if (name == "Time To Text Mode") {
         return ValueProperty;
     }
+    if (name == "Octave Numbering System") {
+        return ValueProperty;
+    }
     if (name == "View Font Size") {
         return RangeProperty;
     }
@@ -248,6 +257,16 @@
         return int(m_timeToTextMode);
     }        
 
+    if (name == "Octave Numbering System") {
+        // we don't support arbitrary octaves in the gui, because we
+        // want to be able to label what the octave system comes
+        // from. so we support 0, 3, 4 and 5.
+        if (min) *min = 0;
+        if (max) *max = 3;
+        if (deflt) *deflt = 2;
+        return int(getSystemWithMiddleCInOctave(m_octave));
+    }
+
     if (name == "View Font Size") {
         if (min) *min = 3;
         if (max) *max = 48;
@@ -322,6 +341,14 @@
         case TimeToText60Frame: return tr("60 FPS");
         }
     }
+    if (name == "Octave Numbering System") {
+        switch (value) {
+        case C0_Centre: return tr("C0 - middle of octave scale");
+        case C3_Logic: return tr("C3 - common MIDI sequencer convention");
+        case C4_ASA: return tr("C4 - ASA American standard");
+        case C5_Sonar: return tr("C5 - used in Cakewalk and others");
+        }
+    }
             
     return "";
 }
@@ -359,6 +386,9 @@
         setBackgroundMode(BackgroundMode(value));
     } else if (name == "Time To Text Mode") {
         setTimeToTextMode(TimeToTextMode(value));
+    } else if (name == "Octave Numbering System") {
+        setOctaveOfMiddleC(getOctaveOfMiddleCInSystem
+                           (OctaveNumberingSystem(value)));
     } else if (name == "View Font Size") {
         setViewFontSize(value);
     } else if (name == "Show Splash Screen") {
@@ -525,6 +555,45 @@
 }
 
 void
+Preferences::setOctaveOfMiddleC(int oct)
+{
+    if (m_octave != oct) {
+
+        m_octave = oct;
+
+        QSettings settings;
+        settings.beginGroup("Preferences");
+        settings.setValue("octave-of-middle-c", int(oct));
+        settings.endGroup();
+        emit propertyChanged("Octave Numbering System");
+    }
+}
+
+int
+Preferences::getOctaveOfMiddleCInSystem(OctaveNumberingSystem s)
+{
+    switch (s) {
+    case C0_Centre: return 0;
+    case C3_Logic: return 3;
+    case C4_ASA: return 4;
+    case C5_Sonar: return 5;
+    default: return 4;
+    }
+}
+
+Preferences::OctaveNumberingSystem
+Preferences::getSystemWithMiddleCInOctave(int o)
+{
+    switch (o) {
+    case 0: return C0_Centre;
+    case 3: return C3_Logic;
+    case 4: return C4_ASA;
+    case 5: return C5_Sonar;
+    default: return C4_ASA;
+    }
+}
+
+void
 Preferences::setViewFontSize(int size)
 {
     if (m_viewFontSize != size) {
--- a/base/Preferences.h	Wed May 07 15:17:43 2014 +0100
+++ b/base/Preferences.h	Wed May 07 15:17:51 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;
 };
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/test/TestPitch.h	Wed May 07 15:17:51 2014 +0100
@@ -0,0 +1,97 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef TEST_PITCH_H
+#define TEST_PITCH_H
+
+#include "../Pitch.h"
+#include "../Preferences.h"
+
+#include <QObject>
+#include <QtTest>
+#include <QDir>
+
+#include <iostream>
+
+using namespace std;
+
+class TestPitch : public QObject
+{
+    Q_OBJECT
+
+private slots:
+    void init() {
+	Preferences::getInstance()->setOctaveOfMiddleC(4);
+	Preferences::getInstance()->setTuningFrequency(440);
+    }
+
+    void pitchLabel()
+    {
+	QCOMPARE(Pitch::getPitchLabel(60, 0, false), QString("C4"));
+	QCOMPARE(Pitch::getPitchLabel(69, 0, false), QString("A4"));
+	QCOMPARE(Pitch::getPitchLabel(61, 0, false), QString("C#4"));
+	QCOMPARE(Pitch::getPitchLabel(61, 0, true), QString("Db4"));
+	QCOMPARE(Pitch::getPitchLabel(59, 0, false), QString("B3"));
+	QCOMPARE(Pitch::getPitchLabel(59, 0, true), QString("B3"));
+	QCOMPARE(Pitch::getPitchLabel(0, 0, false), QString("C-1"));
+
+	QCOMPARE(Pitch::getPitchLabel(60, -40, false), QString("C4-40c"));
+	QCOMPARE(Pitch::getPitchLabel(60, 40, false), QString("C4+40c"));
+	QCOMPARE(Pitch::getPitchLabel(58, 4, false), QString("A#3+4c"));
+
+	Preferences::getInstance()->setOctaveOfMiddleC(3);
+
+	QCOMPARE(Pitch::getPitchLabel(60, 0, false), QString("C3"));
+	QCOMPARE(Pitch::getPitchLabel(69, 0, false), QString("A3"));
+	QCOMPARE(Pitch::getPitchLabel(61, 0, false), QString("C#3"));
+	QCOMPARE(Pitch::getPitchLabel(61, 0, true), QString("Db3"));
+	QCOMPARE(Pitch::getPitchLabel(59, 0, false), QString("B2"));
+	QCOMPARE(Pitch::getPitchLabel(59, 0, true), QString("B2"));
+	QCOMPARE(Pitch::getPitchLabel(0, 0, false), QString("C-2"));
+
+	QCOMPARE(Pitch::getPitchLabel(60, -40, false), QString("C3-40c"));
+	QCOMPARE(Pitch::getPitchLabel(60, 40, false), QString("C3+40c"));
+	QCOMPARE(Pitch::getPitchLabel(58, 4, false), QString("A#2+4c"));
+    }
+
+    void pitchLabelForFrequency()
+    {
+	QCOMPARE(Pitch::getPitchLabelForFrequency(440, 440, false), QString("A4"));
+	QCOMPARE(Pitch::getPitchLabelForFrequency(440, 220, false), QString("A5"));
+	QCOMPARE(Pitch::getPitchLabelForFrequency(261.63, 440, false), QString("C4"));
+    }
+
+#define MIDDLE_C 261.6255653f
+
+    void frequencyForPitch()
+    {
+	QCOMPARE(Pitch::getFrequencyForPitch(60, 0), MIDDLE_C);
+	QCOMPARE(Pitch::getFrequencyForPitch(69, 0), 440.f);
+	QCOMPARE(Pitch::getFrequencyForPitch(60, 0, 220), MIDDLE_C / 2.f);
+	QCOMPARE(Pitch::getFrequencyForPitch(69, 0, 220), 220.f);
+    }
+
+    void pitchForFrequency()
+    {
+	float centsOffset = 0.f;
+	QCOMPARE(Pitch::getPitchForFrequency(MIDDLE_C, &centsOffset), 60);
+	QCOMPARE(centsOffset, 0.f);
+	QCOMPARE(Pitch::getPitchForFrequency(261.0, &centsOffset), 60);
+	QCOMPARE(int(centsOffset), -4);
+	QCOMPARE(Pitch::getPitchForFrequency(440.0, &centsOffset), 69);
+	QCOMPARE(centsOffset, 0.f);
+    }
+};
+
+#endif
--- a/base/test/main.cpp	Wed May 07 15:17:43 2014 +0100
+++ b/base/test/main.cpp	Wed May 07 15:17:51 2014 +0100
@@ -12,6 +12,7 @@
 */
 
 #include "TestRangeMapper.h"
+#include "TestPitch.h"
 
 #include <QtTest>
 
@@ -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;
--- a/base/test/test.pro	Wed May 07 15:17:43 2014 +0100
+++ b/base/test/test.pro	Wed May 07 15:17:51 2014 +0100
@@ -30,7 +30,7 @@
 OBJECTS_DIR = o
 MOC_DIR = o
 
-HEADERS += TestRangeMapper.h
+HEADERS += TestRangeMapper.h TestPitch.h
 SOURCES += main.cpp
 
 win* {
--- a/data/fft/FFTDataServer.cpp	Wed May 07 15:17:43 2014 +0100
+++ b/data/fft/FFTDataServer.cpp	Wed May 07 15:17:51 2014 +0100
@@ -868,7 +868,7 @@
     // and previous cache readers)
     int deleteCandidate = c - 2;
     if (deleteCandidate < 0) deleteCandidate = c + 2;
-    if (deleteCandidate >= m_caches.size()) {
+    if (deleteCandidate >= (int)m_caches.size()) {
         return true;
     }
 
--- a/data/fileio/CSVFileReader.cpp	Wed May 07 15:17:43 2014 +0100
+++ b/data/fileio/CSVFileReader.cpp	Wed May 07 15:17:51 2014 +0100
@@ -22,6 +22,7 @@
 #include "model/SparseTimeValueModel.h"
 #include "model/EditableDenseThreeDimensionalModel.h"
 #include "model/RegionModel.h"
+#include "model/NoteModel.h"
 #include "DataFileReaderFactory.h"
 
 #include <QFile>
@@ -85,7 +86,7 @@
                                 size_t windowSize) const
 {
     QRegExp nonNumericRx("[^0-9eE.,+-]");
-    unsigned int warnLimit = 10;
+    int warnLimit = 10;
 
     CSVFormat::TimeUnits timeUnits = m_format.getTimeUnits();
 
@@ -156,6 +157,7 @@
     SparseOneDimensionalModel *model1 = 0;
     SparseTimeValueModel *model2 = 0;
     RegionModel *model2a = 0;
+    NoteModel *model2b = 0;
     EditableDenseThreeDimensionalModel *model3 = 0;
     Model *model = 0;
 
@@ -173,6 +175,7 @@
 
     bool haveAnyValue = false;
     bool haveEndTime = false;
+    bool pitchLooksLikeMIDI = true;
 
     size_t startFrame = 0; // for calculation of dense model resolution
     bool firstEverValue = true;
@@ -202,7 +205,7 @@
         QString chunk = in.readLine();
         QStringList lines = chunk.split('\r', QString::SkipEmptyParts);
         
-        for (size_t li = 0; li < lines.size(); ++li) {
+        for (int li = 0; li < lines.size(); ++li) {
 
             QString line = lines[li];
 
@@ -228,6 +231,11 @@
                     model = model2a;
                     break;
 		
+                case CSVFormat::TwoDimensionalModelWithDurationAndPitch:
+                    model2b = new NoteModel(sampleRate, windowSize, false);
+                    model = model2b;
+                    break;
+		
                 case CSVFormat::ThreeDimensionalModel:
                     model3 = new EditableDenseThreeDimensionalModel
                         (sampleRate,
@@ -240,6 +248,7 @@
             }
 
             float value = 0.f;
+            float pitch = 0.f;
             QString label = "";
 
             duration = 0.f;
@@ -274,6 +283,13 @@
                     haveAnyValue = true;
                     break;
 
+                case CSVFormat::ColumnPitch:
+                    pitch = s.toFloat();
+                    if (pitch < 0.f || pitch > 127.f) {
+                        pitchLooksLikeMIDI = false;
+                    }
+                    break;
+
                 case CSVFormat::ColumnLabel:
                     label = s;
                     ++labelCountMap[label];
@@ -302,6 +318,12 @@
                 RegionModel::Point point(frameNo, value, duration, label);
                 model2a->addPoint(point);
 
+            } else if (modelType == CSVFormat::TwoDimensionalModelWithDurationAndPitch) {
+
+                float level = ((value >= 0.f && value <= 1.f) ? value : 1.f);
+                NoteModel::Point point(frameNo, pitch, duration, level, label);
+                model2b->addPoint(point);
+
             } else if (modelType == CSVFormat::ThreeDimensionalModel) {
 
                 DenseThreeDimensionalModel::Column values;
@@ -400,6 +422,14 @@
         }
     }
                 
+    if (model2b) {
+        if (pitchLooksLikeMIDI) {
+            model2b->setScaleUnits("MIDI Pitch");
+        } else {
+            model2b->setScaleUnits("Hz");
+        }
+    }
+
     if (modelType == CSVFormat::ThreeDimensionalModel) {
 	model3->setMinimumLevel(min);
 	model3->setMaximumLevel(max);
--- a/data/fileio/CSVFormat.cpp	Wed May 07 15:17:43 2014 +0100
+++ b/data/fileio/CSVFormat.cpp	Wed May 07 15:17:51 2014 +0100
@@ -66,7 +66,7 @@
         QString chunk = in.readLine();
         QStringList lines = chunk.split('\r', QString::SkipEmptyParts);
 
-        for (size_t li = 0; li < lines.size(); ++li) {
+        for (int li = 0; li < lines.size(); ++li) {
 
             QString line = lines[li];
             if (line.startsWith("#") || line == "") continue;
@@ -86,7 +86,7 @@
 CSVFormat::guessSeparator(QString line)
 {
     char candidates[] = { ',', '\t', ' ', '|', '/', ':' };
-    for (int i = 0; i < sizeof(candidates)/sizeof(candidates[0]); ++i) {
+    for (int i = 0; i < int(sizeof(candidates)/sizeof(candidates[0])); ++i) {
         if (StringBits::split(line, candidates[i], m_allowQuoting).size() >= 2) {
             m_separator = candidates[i];
             return;
--- a/data/fileio/CSVFormat.h	Wed May 07 15:17:43 2014 +0100
+++ b/data/fileio/CSVFormat.h	Wed May 07 15:17:51 2014 +0100
@@ -26,6 +26,7 @@
 	OneDimensionalModel,
 	TwoDimensionalModel,
         TwoDimensionalModelWithDuration,
+        TwoDimensionalModelWithDurationAndPitch,
 	ThreeDimensionalModel
     };
     
@@ -46,6 +47,7 @@
         ColumnEndTime,
         ColumnDuration,
         ColumnValue,
+        ColumnPitch,
         ColumnLabel
     };
 
--- a/data/model/Model.h	Wed May 07 15:17:43 2014 +0100
+++ b/data/model/Model.h	Wed May 07 15:17:51 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 "";
     }