# HG changeset patch # User Chris Cannam # Date 1395927176 0 # Node ID 451f7f3ab6e7a1efeb603fdab0b5b9c9434d1e5b # Parent 7f97a4d9d14f09517a4e25f172305d5a9c64b36a Make octave numbering configurable, and change default to C4 = middle C diff -r 7f97a4d9d14f -r 451f7f3ab6e7 base/Pitch.cpp --- a/base/Pitch.cpp Tue Mar 11 17:29:44 2014 +0000 +++ b/base/Pitch.cpp Thu Mar 27 13:32:56 2014 +0000 @@ -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 7f97a4d9d14f -r 451f7f3ab6e7 base/Pitch.h --- a/base/Pitch.h Tue Mar 11 17:29:44 2014 +0000 +++ b/base/Pitch.h Thu Mar 27 13:32:56 2014 +0000 @@ -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 7f97a4d9d14f -r 451f7f3ab6e7 base/Preferences.cpp --- a/base/Preferences.cpp Tue Mar 11 17:29:44 2014 +0000 +++ b/base/Preferences.cpp Thu Mar 27 13:32:56 2014 +0000 @@ -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 7f97a4d9d14f -r 451f7f3ab6e7 base/Preferences.h --- a/base/Preferences.h Tue Mar 11 17:29:44 2014 +0000 +++ b/base/Preferences.h Thu Mar 27 13:32:56 2014 +0000 @@ -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 7f97a4d9d14f -r 451f7f3ab6e7 base/test/TestPitch.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/test/TestPitch.h Thu Mar 27 13:32:56 2014 +0000 @@ -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 7f97a4d9d14f -r 451f7f3ab6e7 base/test/main.cpp --- a/base/test/main.cpp Tue Mar 11 17:29:44 2014 +0000 +++ b/base/test/main.cpp Thu Mar 27 13:32:56 2014 +0000 @@ -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 7f97a4d9d14f -r 451f7f3ab6e7 base/test/test.pro --- a/base/test/test.pro Tue Mar 11 17:29:44 2014 +0000 +++ b/base/test/test.pro Thu Mar 27 13:32:56 2014 +0000 @@ -30,7 +30,7 @@ OBJECTS_DIR = o MOC_DIR = o -HEADERS += TestRangeMapper.h +HEADERS += TestRangeMapper.h TestPitch.h SOURCES += main.cpp win* {