# HG changeset patch # User Chris Cannam # Date 1399472271 -3600 # Node ID 65499032086787a2e637131d7225f613c61e7f7b # Parent 1f94f3776158a22edad40124d865a4ec7d11e59d# Parent bc0ff66102a8c52cb2b8b296ce1006207812ad4b Merge from 899:bc0ff66102a8 diff -r 1f94f3776158 -r 654990320867 base/Pitch.cpp --- 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); diff -r 1f94f3776158 -r 654990320867 base/Pitch.h --- 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). diff -r 1f94f3776158 -r 654990320867 base/Preferences.cpp --- 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) { diff -r 1f94f3776158 -r 654990320867 base/Preferences.h --- 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; }; diff -r 1f94f3776158 -r 654990320867 base/test/TestPitch.h --- /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 +#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 1f94f3776158 -r 654990320867 base/test/main.cpp --- 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 @@ -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 1f94f3776158 -r 654990320867 base/test/test.pro --- 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* { diff -r 1f94f3776158 -r 654990320867 data/fft/FFTDataServer.cpp --- 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; } diff -r 1f94f3776158 -r 654990320867 data/fileio/CSVFileReader.cpp --- 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 @@ -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); diff -r 1f94f3776158 -r 654990320867 data/fileio/CSVFormat.cpp --- 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; diff -r 1f94f3776158 -r 654990320867 data/fileio/CSVFormat.h --- 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 }; diff -r 1f94f3776158 -r 654990320867 data/model/Model.h --- 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 ""; }