From a11f6800da4415ffe63ec1ab763e27993280f4d3 Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 9 Jan 2020 21:36:36 -0500 Subject: [PATCH] Add support for size zero KBM maps (#1463) I didn't even know Size 0 KBM maps were a thing! But they are. They mean exactly what you'd expect (note-for-note). So add them here with regtests to show they work rather than crash. Addresses #1041 --- src/common/SurgeStorage.cpp | 19 +++++++--- src/common/Tunings.cpp | 23 +++++++++++++ src/common/Tunings.h | 2 ++ src/headless/UnitTests.cpp | 63 ++++++++++++++++++++++++++++++++++ test-data/scl/empty-note61.kbm | 17 +++++++++ test-data/scl/empty-note69.kbm | 17 +++++++++ 6 files changed, 137 insertions(+), 4 deletions(-) create mode 100644 test-data/scl/empty-note61.kbm create mode 100644 test-data/scl/empty-note69.kbm diff --git a/src/common/SurgeStorage.cpp b/src/common/SurgeStorage.cpp index dc09ad89d0e..6d22925592a 100644 --- a/src/common/SurgeStorage.cpp +++ b/src/common/SurgeStorage.cpp @@ -1296,7 +1296,10 @@ bool SurgeStorage::retuneToScale(const Surge::Storage::Scale& s) float pitchMod = log(scaleConstantPitch())/log(2) - 1; int scalePositionOfStartNote = 0; - int scalePositionOfTuningNote = currentMapping.keys[currentMapping.tuningConstantNote - currentMapping.middleNote]; + int scalePositionOfTuningNote = currentMapping.tuningConstantNote - currentMapping.middleNote; + if( currentMapping.count > 0 ) + scalePositionOfTuningNote = currentMapping.keys[scalePositionOfTuningNote]; + float tuningCenterPitchOffset; if( scalePositionOfTuningNote == 0 ) tuningCenterPitchOffset = 0; @@ -1335,7 +1338,7 @@ bool SurgeStorage::retuneToScale(const Surge::Storage::Scale& s) int rounds; int thisRound; int disable = false; - if( currentMapping.isStandardMapping ) + if( currentMapping.isStandardMapping || ( currentMapping.count == 0 ) ) { rounds = (distanceFromScale0-1) / s.count; thisRound = (distanceFromScale0-1) % s.count; @@ -1439,8 +1442,16 @@ bool SurgeStorage::remapToKeyboard(const Surge::Storage::KeyboardMapping& k) tuningPitch = k.tuningFrequency / 8.175798915; tuningPitchInv = 1.0 / tuningPitch; } - // The mapping will change all the cached pitches - retuneToScale(currentScale); + // The mapping will change all the cached pitches. + if( ! currentScale.isValid() ) + { + // We need to set the current scale to a default scale + retuneToScale(Surge::Storage::Scale::evenTemprament12NoteScale()); + } + else + { + retuneToScale(currentScale); + } return true; } diff --git a/src/common/Tunings.cpp b/src/common/Tunings.cpp index d05d6ecf501..f779693e71e 100644 --- a/src/common/Tunings.cpp +++ b/src/common/Tunings.cpp @@ -118,6 +118,29 @@ Surge::Storage::Scale Surge::Storage::parseSCLData(const std::string &d) return res; } +Surge::Storage::Scale Surge::Storage::Scale::evenTemprament12NoteScale() +{ + auto data = R"SCL(! even.scl +! +12 note even temprament + 12 +! + 100.0 + 200.0 + 300.0 + 400.0 + 500.0 + 600.0 + 700.0 + 800.0 + 900.0 + 1000.0 + 1100.0 + 2/1 +)SCL"; + return parseSCLData(data); +} + Surge::Storage::KeyboardMapping keyboardMappingFromStream(std::istream &inf) { std::string line; diff --git a/src/common/Tunings.h b/src/common/Tunings.h index 9b6e0410488..098849cc58d 100644 --- a/src/common/Tunings.h +++ b/src/common/Tunings.h @@ -43,6 +43,8 @@ struct Scale bool isValid() const; std::string toHtml(SurgeStorage *storage); + + static Scale evenTemprament12NoteScale(); }; struct KeyboardMapping diff --git a/src/headless/UnitTests.cpp b/src/headless/UnitTests.cpp index b7f24a1d477..1ae7a5699ae 100644 --- a/src/headless/UnitTests.cpp +++ b/src/headless/UnitTests.cpp @@ -505,6 +505,69 @@ TEST_CASE( "Non-uniform keyboard mapping", "[tun]" ) } } +TEST_CASE( "Zero Size Maps", "[tun]" ) +{ + auto surge = surgeOnSine(); + REQUIRE( surge.get() ); + + SECTION( "Note 61" ) + { + auto f60std = frequencyForNote( surge, 60 ); + auto f61std = frequencyForNote( surge, 61 ); + REQUIRE( f60std == Approx( 261.63 ).margin( 0.1 ) ); + + auto k61 = Surge::Storage::readKBMFile( "test-data/scl/empty-note61.kbm" ); + REQUIRE( !k61.isStandardMapping ); + REQUIRE( k61.count == 0 ); + surge->storage.remapToKeyboard( k61 ); + + auto f60map = frequencyForNote( surge, 60 ); + auto f61map = frequencyForNote( surge, 61 ); + REQUIRE( frequencyForNote( surge, 61 ) == Approx( 280 ).margin( 0.1 ) ); + REQUIRE( f61std / f60std == Approx( f61map / f60map ).margin( 0.001 ) ); + } + + SECTION( "Note 69" ) + { + auto f60std = frequencyForNote( surge, 60 ); + auto f69std = frequencyForNote( surge, 69 ); + REQUIRE( f60std == Approx( 261.63 ).margin( 0.1 ) ); + REQUIRE( f69std == Approx( 440.0 ).margin( 0.1 ) ); + + auto k69 = Surge::Storage::readKBMFile( "test-data/scl/empty-note69.kbm" ); + REQUIRE( !k69.isStandardMapping ); + REQUIRE( k69.count == 0 ); + surge->storage.remapToKeyboard( k69 ); + + auto f60map = frequencyForNote( surge, 60 ); + auto f69map = frequencyForNote( surge, 69 ); + REQUIRE( frequencyForNote( surge, 69 ) == Approx( 452 ).margin( 0.1 ) ); + REQUIRE( f69std / f60std == Approx( f69map / f60map ).margin( 0.001 ) ); + } + + SECTION( "Note 69 with Tuning" ) + { + Surge::Storage::Scale s = Surge::Storage::readSCLFile("test-data/scl/marvel12.scl" ); + surge->storage.retuneToScale(s); + auto f60std = frequencyForNote( surge, 60 ); + auto f69std = frequencyForNote( surge, 69 ); + REQUIRE( f60std == Approx( 261.63 ).margin( 0.1 ) ); + REQUIRE( f69std != Approx( 440.0 ).margin( 0.1 ) ); + + + auto k69 = Surge::Storage::readKBMFile( "test-data/scl/empty-note69.kbm" ); + REQUIRE( !k69.isStandardMapping ); + REQUIRE( k69.count == 0 ); + surge->storage.remapToKeyboard( k69 ); + + auto f60map = frequencyForNote( surge, 60 ); + auto f69map = frequencyForNote( surge, 69 ); + REQUIRE( frequencyForNote( surge, 69 ) == Approx( 452 ).margin( 0.1 ) ); + REQUIRE( f69std / f60std == Approx( f69map / f60map ).margin( 0.001 ) ); + } + +} + TEST_CASE( "Simple Single Oscillator is Constant", "[dsp]" ) { SurgeSynthesizer* surge = Surge::Headless::createSurge(44100); diff --git a/test-data/scl/empty-note61.kbm b/test-data/scl/empty-note61.kbm new file mode 100644 index 00000000000..02685f7e3c3 --- /dev/null +++ b/test-data/scl/empty-note61.kbm @@ -0,0 +1,17 @@ +! 61-277-61 Concert C#, Db.kbm +! +! Size of map: +0 +! First MIDI note number to retune: +0 +! Last MIDI note number to retune: +127 +! Middle note where the first entry in the mapping is mapped to: +61 +! Reference note for which frequency is given: +61 +! Frequency to tune the above note to (floating point e.g. 440.0): +280.0 +! Scale degree to consider as formal octave: +0 +! Mapping. diff --git a/test-data/scl/empty-note69.kbm b/test-data/scl/empty-note69.kbm new file mode 100644 index 00000000000..54a65017f80 --- /dev/null +++ b/test-data/scl/empty-note69.kbm @@ -0,0 +1,17 @@ +! 61-277-61 Concert C#, Db.kbm +! +! Size of map: +0 +! First MIDI note number to retune: +0 +! Last MIDI note number to retune: +127 +! Middle note where the first entry in the mapping is mapped to: +60 +! Reference note for which frequency is given: +69 +! Frequency to tune the above note to (floating point e.g. 440.0): +452 +! Scale degree to consider as formal octave: +0 +! Mapping.