diff --git a/src/common/SurgeStorage.cpp b/src/common/SurgeStorage.cpp index 4943672229d..dc09ad89d0e 100644 --- a/src/common/SurgeStorage.cpp +++ b/src/common/SurgeStorage.cpp @@ -1291,20 +1291,85 @@ bool SurgeStorage::retuneToScale(const Surge::Storage::Scale& s) isStandardTuning = false; float pitches[512]; - int pos0 = 256 + scaleConstantNote(); + int posPitch0 = 256 + scaleConstantNote(); + int posScale0 = 256 + currentMapping.middleNote; float pitchMod = log(scaleConstantPitch())/log(2) - 1; - pitches[pos0] = 1.0; + + int scalePositionOfStartNote = 0; + int scalePositionOfTuningNote = currentMapping.keys[currentMapping.tuningConstantNote - currentMapping.middleNote]; + float tuningCenterPitchOffset; + if( scalePositionOfTuningNote == 0 ) + tuningCenterPitchOffset = 0; + else + tuningCenterPitchOffset = s.tones[scalePositionOfTuningNote-1].floatValue - 1.0; + + pitches[posPitch0] = 1.0; for (int i=0; i<512; ++i) { - int distanceFromScale0 = i - pos0; + // TODO: ScaleCenter and PitchCenter are now two different notes. + int distanceFromPitch0 = i - posPitch0; + int distanceFromScale0 = i - posScale0; - if( distanceFromScale0 == 0 ) + if( distanceFromPitch0 == 0 ) { + table_pitch[i] = pow( 2.0, pitches[i] + pitchMod ); +#if DEBUG_SCALES + if( i > 296 && i < 340 ) + std::cout << "PITCH: i=" << i << " n=" << i - 256 + << " p=" << pitches[i] + << " tp=" << table_pitch[i] + << " fr=" << table_pitch[i] * 8.175798915 + << std::endl; +#endif } else { + /* + We used to have this which assumed 1-12 + Now we have our note number, our distance from the + center note, and the key remapping int rounds = (distanceFromScale0-1) / s.count; int thisRound = (distanceFromScale0-1) % s.count; + */ + + int rounds; + int thisRound; + int disable = false; + if( currentMapping.isStandardMapping ) + { + rounds = (distanceFromScale0-1) / s.count; + thisRound = (distanceFromScale0-1) % s.count; + } + else + { + /* + ** Now we have this situation. We are at note i so we + ** are m away from the center note which is distanceFromScale0 + ** + ** If we mod that by the mapping size we know which note we are on + */ + int mappingKey = distanceFromScale0 % currentMapping.count; + if( mappingKey < 0 ) + mappingKey += currentMapping.count; + int cm = currentMapping.keys[mappingKey]; + int push = 0; + if( cm < 0 ) + { + disable = true; + } + else + { + push = mappingKey - cm; + } + rounds = (distanceFromScale0 - push - 1) / s.count; + thisRound = (distanceFromScale0 - push - 1) % s.count; +#ifdef DEBUG_SCALES + if( i > 296 && i < 340 ) + std::cout << "MAPPING n=" << i - 256 << " pushes ds0=" << distanceFromScale0 << " cmc=" << currentMapping.count << " tr=" << thisRound << " r=" << rounds << " mk=" << mappingKey << " cm=" << cm << " push=" << push << " dis=" << disable << " mk-p-1=" << mappingKey - push - 1 << std::endl; +#endif + + + } if( thisRound < 0 ) { @@ -1312,17 +1377,27 @@ bool SurgeStorage::retuneToScale(const Surge::Storage::Scale& s) rounds -= 1; } float mul = pow( s.tones[s.count-1].floatValue, rounds); - pitches[i] = s.tones[thisRound].floatValue + rounds * (s.tones[s.count - 1].floatValue - 1.0); + if( disable ) + pitches[i] = 0; + else + pitches[i] = s.tones[thisRound].floatValue + rounds * (s.tones[s.count - 1].floatValue - 1.0) - tuningCenterPitchOffset; + float otp = table_pitch[i]; table_pitch[i] = pow( 2.0, pitches[i] + pitchMod ); #if DEBUG_SCALES if( i > 296 && i < 340 ) - std::cout << "PITCH: i=" << i << " n=" << i - 256 << " r=" << rounds << " t=" << thisRound + std::cout << "PITCH: i=" << i << " n=" << i - 256 + << " ds0=" << distanceFromScale0 + << " dp0=" << distanceFromPitch0 + << " r=" << rounds << " t=" << thisRound << " p=" << pitches[i] - << " t=" << s.tones[thisRound].floatValue + << " t=" << s.tones[thisRound].floatValue << " " << s.tones[thisRound ] + << " dis=" << disable << " tp=" << table_pitch[i] + << " fr=" << table_pitch[i] * 8.175798915 << " otp=" << otp + << " tcpo=" << tuningCenterPitchOffset << " diff=" << table_pitch[i] - otp //<< " l2p=" << log(otp)/log(2.0) @@ -1345,6 +1420,30 @@ bool SurgeStorage::retuneToScale(const Surge::Storage::Scale& s) return true; } +bool SurgeStorage::remapToStandardKeyboard() +{ + return remapToKeyboard(Surge::Storage::KeyboardMapping()); +} + +bool SurgeStorage::remapToKeyboard(const Surge::Storage::KeyboardMapping& k) +{ + currentMapping = k; + isStandardMapping = k.isStandardMapping; + if( isStandardMapping ) + { + tuningPitch = 32.0; + tuningPitchInv = 1.0 / 32.0; + } + else + { + tuningPitch = k.tuningFrequency / 8.175798915; + tuningPitchInv = 1.0 / tuningPitch; + } + // The mapping will change all the cached pitches + retuneToScale(currentScale); + return true; +} + #if TARGET_LV2 bool SurgeStorage::skipLoadWtAndPatch = false; #endif diff --git a/src/common/SurgeStorage.h b/src/common/SurgeStorage.h index b0a05d3b2ac..b225c98a3b4 100644 --- a/src/common/SurgeStorage.h +++ b/src/common/SurgeStorage.h @@ -608,12 +608,18 @@ class alignas(16) SurgeStorage void note_to_omega(float, float&, float&); bool retuneToScale(const Surge::Storage::Scale& s); - inline int scaleConstantNote() { return 60; } - inline float scaleConstantPitch() { return 32.0; } - inline float scaleConstantPitchInv() { return 0.03125; } // Obviously that's the inverse of the above + bool remapToKeyboard(const Surge::Storage::KeyboardMapping &k); + bool remapToStandardKeyboard(); + inline int scaleConstantNote() { return currentMapping.tuningConstantNote; } + inline float scaleConstantPitch() { return tuningPitch; } + inline float scaleConstantPitchInv() { return tuningPitchInv; } // Obviously that's the inverse of the above Surge::Storage::Scale currentScale; bool isStandardTuning; + + Surge::Storage::KeyboardMapping currentMapping; + bool isStandardMapping = true; + float tuningPitch = 32.0f, tuningPitchInv = 0.03125f; private: TiXmlDocument snapshotloader; diff --git a/src/common/Tunings.cpp b/src/common/Tunings.cpp index 9209f43bb12..d05d6ecf501 100644 --- a/src/common/Tunings.cpp +++ b/src/common/Tunings.cpp @@ -118,6 +118,97 @@ Surge::Storage::Scale Surge::Storage::parseSCLData(const std::string &d) return res; } +Surge::Storage::KeyboardMapping keyboardMappingFromStream(std::istream &inf) +{ + std::string line; + const int read_header = 0, read_count = 1, read_note = 2; + + Surge::Storage::KeyboardMapping res; + std::ostringstream rawOSS; + res.isStandardMapping = false; + res.keys.clear(); + + enum parsePosition { + map_size = 0, + first_midi, + last_midi, + middle, + reference, + freq, + degree, + keys + }; + parsePosition state = map_size; + + while (std::getline(inf, line)) + { + rawOSS << line << "\n"; + if (line[0] == '!') + { + continue; + } + + if( line == "x" ) line = "-1"; + + int i = std::atoi(line.c_str()); + float v = std::atof(line.c_str()); + + switch (state) + { + case map_size: + res.count = i; + break; + case first_midi: + res.firstMidi = i; + break; + case last_midi: + res.lastMidi = i; + break; + case middle: + res.middleNote = i; + break; + case reference: + res.tuningConstantNote = i; + break; + case freq: + res.tuningFrequency = v; + break; + case degree: + res.octaveDegrees = i; + break; + case keys: + res.keys.push_back(i); + break; + } + if( state != keys ) state = (parsePosition)(state + 1); + } + + res.rawText = rawOSS.str(); + return res; +} + +Surge::Storage::KeyboardMapping Surge::Storage::readKBMFile(std::string fname) +{ + std::ifstream inf; + inf.open(fname); + if (!inf.is_open()) + { + return KeyboardMapping(); + } + + auto res = keyboardMappingFromStream(inf); + res.name = fname; + return res; +} + +Surge::Storage::KeyboardMapping Surge::Storage::parseKBMData(const std::string &d) +{ + std::istringstream iss(d); + auto res = keyboardMappingFromStream(iss); + res.name = "Mapping from Patch"; + return res; +} + std::ostream& Surge::Storage::operator<<(std::ostream& os, const Surge::Storage::Tone& t) { os << (t.type == Tone::kToneCents ? "cents" : "ratio") << " "; diff --git a/src/common/Tunings.h b/src/common/Tunings.h index af522d55948..9b6e0410488 100644 --- a/src/common/Tunings.h +++ b/src/common/Tunings.h @@ -45,10 +45,49 @@ struct Scale std::string toHtml(SurgeStorage *storage); }; +struct KeyboardMapping +{ + bool isValid; + bool isStandardMapping; + int count; + int firstMidi, lastMidi; + int middleNote; + int tuningConstantNote; + float tuningFrequency; + int octaveDegrees; + std::vector keys; // rather than an 'x' we use a '-1' for skipped keys + + std::string rawText; + std::string name; + + KeyboardMapping() : isValid(true), + isStandardMapping(true), + count(12), + firstMidi(0), + lastMidi(127), + middleNote(60), + tuningConstantNote(60), + tuningFrequency(8.175798915 * 32), + octaveDegrees(12), + rawText( "" ), + name( "" ) + { + for( int i=0; i<12; ++i ) + keys.push_back(i); + } + + // TODO + // std::string toHtml(); +}; + std::ostream& operator<<(std::ostream& os, const Tone& sc); std::ostream& operator<<(std::ostream& os, const Scale& sc); - +//TODO +//std::ostream& operator<<(std::ostream& os, const KeyboardMapping& kbm); + Scale readSCLFile(std::string fname); Scale parseSCLData(const std::string &sclContents); +KeyboardMapping readKBMFile(std::string fname); +KeyboardMapping parseKBMData(const std::string &kbmContents); } // namespace Storage } // namespace Surge diff --git a/src/common/gui/SurgeGUIEditor.cpp b/src/common/gui/SurgeGUIEditor.cpp index fe5fb2091c4..f53fdf1ab17 100644 --- a/src/common/gui/SurgeGUIEditor.cpp +++ b/src/common/gui/SurgeGUIEditor.cpp @@ -3460,6 +3460,15 @@ VSTGUI::COptionMenu *SurgeGUIEditor::makeTuningMenu(VSTGUI::CRect &menuRect) ); st->setEnabled(! this->synth->storage.isStandardTuning); tid++; + + auto *kst = addCallbackMenu(tuningSubMenu, "Set to Standard Keyboard Mapping", + [this]() + { + this->synth->storage.remapToStandardKeyboard(); + } + ); + kst->setEnabled(! this->synth->storage.currentMapping.isStandardMapping); + tid++; addCallbackMenu(tuningSubMenu, "Apply .scl file tuning", [this]() @@ -3491,6 +3500,36 @@ VSTGUI::COptionMenu *SurgeGUIEditor::makeTuningMenu(VSTGUI::CRect &menuRect) ); tid++; + addCallbackMenu(tuningSubMenu, "Apply .kbm keyboard mapping", + [this]() + { + auto cb = [this](std::string sf) + { + std::string sfx = ".kbm"; + if( sf.length() >= sfx.length()) + { + if( sf.compare(sf.length() - sfx.length(), sfx.length(), sfx) != 0 ) + { + Surge::UserInteractions::promptError( "Please only select .kbm files", "Invalid Choice" ); + std::cout << "FILE is [" << sf << "]" << std::endl; + return; + } + } + auto kb = Surge::Storage::readKBMFile(sf); + + if (!this->synth->storage.remapToKeyboard(kb) ) + { + Surge::UserInteractions::promptError( "This .kbm file is not valid", "File format error" ); + return; + } + }; + Surge::UserInteractions::promptFileOpenDialog(this->synth->storage.userDataPath, + ".scl", + cb); + } + ); + tid++; + auto *sct = addCallbackMenu(tuningSubMenu, "Show current tuning", [this]() { diff --git a/src/headless/UnitTests.cpp b/src/headless/UnitTests.cpp index 3cb1864fd9f..b7f24a1d477 100644 --- a/src/headless/UnitTests.cpp +++ b/src/headless/UnitTests.cpp @@ -251,6 +251,260 @@ TEST_CASE( "Notes at Appropriate Frequencies", "[tun]" ) } +TEST_CASE( "KBM File Parsing", "[tun]" ) +{ + SECTION( "Default Keyboard is Default" ) + { + auto k = Surge::Storage::KeyboardMapping(); + REQUIRE( k.isValid ); + REQUIRE( k.isStandardMapping ); + REQUIRE( k.count == 12 ); + REQUIRE( k.firstMidi == 0 ); + REQUIRE( k.lastMidi == 127 ); + REQUIRE( k.middleNote == 60 ); + REQUIRE( k.tuningConstantNote == 60 ); + REQUIRE( k.tuningFrequency == Approx( 261.62558 ) ); + REQUIRE( k.octaveDegrees == 12 ); + for( auto i=0; i values = { 0, 1, -1, 2, -1, 3, 4, -1, 5, -1, 6 }; + for( int i=0; istorage.retuneToScale(s); + auto f60 = frequencyForNote( surge, 60 ); + auto f72 = frequencyForNote( surge, 72 ); + auto f69 = frequencyForNote( surge, 69 ); + + REQUIRE( f60 == Approx( 261.63 ).margin( .1 ) ); + REQUIRE( f72 == Approx( 261.63 * 2 ).margin( .1 ) ); + REQUIRE( f69 == Approx( 448.2 ).margin( .1 ) ); + unmapped[0] = f60; + unmapped[1] = f72; + unmapped[2] = f69; + } + + SECTION( "And remap to 440" ) + { + Surge::Storage::Scale s = Surge::Storage::readSCLFile("test-data/scl/marvel12.scl" ); + auto k = Surge::Storage::readKBMFile( "test-data/scl/mapping-a440-constant.kbm" ); + + surge->storage.retuneToScale(s); + surge->storage.remapToKeyboard(k); + + auto f60 = frequencyForNote( surge, 60 ); + auto f72 = frequencyForNote( surge, 72 ); + auto f69 = frequencyForNote( surge, 69 ); + REQUIRE( f69 == Approx( 440.0 ).margin(.1) ); + REQUIRE( unmapped[2]/440.0 == Approx( unmapped[0] / f60 ).margin(.001) ); + REQUIRE( unmapped[2]/440.0 == Approx( unmapped[1] / f72 ).margin(.001) ); + } + + // and back and then back again + SECTION( "Can Map and ReMap consistently" ) + { + Surge::Storage::Scale s = Surge::Storage::readSCLFile("test-data/scl/marvel12.scl" ); + auto k440 = Surge::Storage::readKBMFile( "test-data/scl/mapping-a440-constant.kbm" ); + + surge->storage.retuneToScale(s); + surge->storage.remapToStandardKeyboard(); + + auto f60std = frequencyForNote( surge, 60 ); + auto f69std = frequencyForNote( surge, 69 ); + + surge->storage.remapToKeyboard( k440 ); + auto f60map = frequencyForNote( surge, 60 ); + auto f69map = frequencyForNote( surge, 69 ); + + REQUIRE( f60std == Approx( 261.63 ).margin(0.1) ); + REQUIRE( f69map == Approx( 440.0 ).margin(0.1) ); + REQUIRE( f69std/f60std == Approx( f69map/f60map ).margin(.001 ) ); + + for( int i=0; i<50; ++i ) + { + auto fr = 1.0f * rand() / RAND_MAX; + if( fr > 0 ) + { + surge->storage.remapToKeyboard(k440); + auto f60 = frequencyForNote( surge, 60 ); + auto f69 = frequencyForNote( surge, 69 ); + REQUIRE( f60 == f60map ); + REQUIRE( f69 == f69map ); + } + else + { + surge->storage.remapToStandardKeyboard(); + auto f60 = frequencyForNote( surge, 60 ); + auto f69 = frequencyForNote( surge, 69 ); + REQUIRE( f60 == f60std ); + REQUIRE( f69 == f69std ); + } + } + } + + SECTION( "Scale Ratio is Unch" ) + { + Surge::Storage::Scale s = Surge::Storage::readSCLFile("test-data/scl/marvel12.scl" ); + auto k440 = Surge::Storage::readKBMFile( "test-data/scl/mapping-a440-constant.kbm" ); + + surge->storage.retuneToScale(s); + surge->storage.remapToStandardKeyboard(); + auto f60 = frequencyForNote( surge, 60 ); + REQUIRE( f60 == Approx( 261.63 ).margin( .1 ) ); + + std::vector ratios; + for( int i=61; i<72; ++i ) + ratios.push_back( frequencyForNote( surge, i ) / f60 ); + + surge->storage.remapToStandardKeyboard(); + auto f60map = frequencyForNote( surge, 60 ); + for( int i=61; i<72; ++i ) + { + auto fi = frequencyForNote( surge, i ); + REQUIRE( fi / f60map == Approx( ratios[i-61] ).margin( 0.001 ) ); + } + } +} + +TEST_CASE( "Non-uniform keyboard mapping", "[tun]" ) +{ + auto surge = surgeOnSine(); + REQUIRE( surge.get() ); + + auto mt = [](float c) { + auto t = Surge::Storage::Tone(); + t.type = Surge::Storage::Tone::kToneCents; + t.cents = c; + t.floatValue = c / 1200.0 + 1.0; + return t; + }; + // This is the "white keys" scale + Surge::Storage::Scale s; + s.count = 7; + s.tones.push_back( mt( 200 ) ); + s.tones.push_back( mt( 400 ) ); + s.tones.push_back( mt( 500 ) ); + s.tones.push_back( mt( 700 ) ); + s.tones.push_back( mt( 900 ) ); + s.tones.push_back( mt( 1100 ) ); + s.tones.push_back( mt( 1200 ) ); + + Surge::Storage::Scale sWonky; + sWonky.count = 7; + sWonky.tones.push_back( mt( 220 ) ); + sWonky.tones.push_back( mt( 390 ) ); + sWonky.tones.push_back( mt( 517 ) ); + sWonky.tones.push_back( mt( 682 ) ); + sWonky.tones.push_back( mt( 941 ) ); + sWonky.tones.push_back( mt( 1141 ) ); + sWonky.tones.push_back( mt( 1200 ) ); + + SECTION( "7 Note Scale" ) + { + std::vector frequencies; + // When I map it directly I get 440 at note 65 (since I skipped 4 black keys) + surge->storage.retuneToScale(s); + for( int i=0; i<8; ++i ) + { + frequencies.push_back( frequencyForNote( surge, 60+i ) ); + } + REQUIRE( frequencies[0] == Approx( 261.63 ).margin( 0.1 ) ); + REQUIRE( frequencies[5] == Approx( 440.00 ).margin( 0.1 ) ); + + auto k = Surge::Storage::readKBMFile( "test-data/scl/mapping-whitekeys-c261.kbm" ); + REQUIRE( ! k.isStandardMapping ); + REQUIRE( k.count == 12 ); + REQUIRE( k.octaveDegrees == 7 ); + surge->storage.remapToKeyboard(k); + auto f60 = frequencyForNote( surge, 60 ); + auto f69 = frequencyForNote( surge, 69 ); + + REQUIRE( f60 == Approx( 261.63 ).margin( 0.1 ) ); + REQUIRE( f69 == Approx( 440 ).margin( 0.1 ) ); + + // If we have remapped to white keys this will be true + REQUIRE( frequencyForNote( surge, 60 ) == Approx( frequencies[0] ).margin(0.1) ); + REQUIRE( frequencyForNote( surge, 62 ) == Approx( frequencies[1] ).margin(0.1) ); + REQUIRE( frequencyForNote( surge, 64 ) == Approx( frequencies[2] ).margin(0.1) ); + REQUIRE( frequencyForNote( surge, 65 ) == Approx( frequencies[3] ).margin(0.1) ); + REQUIRE( frequencyForNote( surge, 67 ) == Approx( frequencies[4] ).margin(0.1) ); + REQUIRE( frequencyForNote( surge, 69 ) == Approx( frequencies[5] ).margin(0.1) ); + REQUIRE( frequencyForNote( surge, 71 ) == Approx( frequencies[6] ).margin(0.1) ); + } + + SECTION( "7 Note Scale with Tuning Centers" ) + { + auto k261 = Surge::Storage::readKBMFile( "test-data/scl/mapping-whitekeys-c261.kbm" ); + auto k440 = Surge::Storage::readKBMFile( "test-data/scl/mapping-whitekeys-a440.kbm" ); + REQUIRE( ! k261.isStandardMapping ); + REQUIRE( ! k440.isStandardMapping ); + + surge->storage.retuneToScale(sWonky); + surge->storage.remapToKeyboard(k261); + auto f60 = frequencyForNote( surge, 60 ); + auto f69 = frequencyForNote( surge, 69 ); + + REQUIRE( f60 == Approx( 261.63 ).margin( 0.1 ) ); + REQUIRE( f69 != Approx( 440.0 ).margin( 0.1 ) ); + + surge->storage.retuneToScale(sWonky); + surge->storage.remapToKeyboard(k440); + auto f60_440 = frequencyForNote( surge, 60 ); + auto f69_440 = frequencyForNote( surge, 69 ); + + REQUIRE( f60_440 != Approx( 261.63 ).margin( 0.1 ) ); + REQUIRE( f69_440 == Approx( 440.0 ).margin( 0.1 ) ); + + REQUIRE( f69_440/f60_440 == Approx( f69/f60 ).margin( 0.001 ) ); + } +} + TEST_CASE( "Simple Single Oscillator is Constant", "[dsp]" ) { SurgeSynthesizer* surge = Surge::Headless::createSurge(44100); diff --git a/test-data/scl/mapping-a440-constant.kbm b/test-data/scl/mapping-a440-constant.kbm new file mode 100644 index 00000000000..80046ec4576 --- /dev/null +++ b/test-data/scl/mapping-a440-constant.kbm @@ -0,0 +1,33 @@ +! Template for a keyboard mapping +! +! Size of map. The pattern repeats every so many keys: +12 +! First MIDI note number to retune: +0 +! Last MIDI note number to retune: +127 +! Middle note where the first entry of 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): +440.0 +! Scale degree to consider as formal octave (determines difference in pitch +! between adjacent mapping patterns): +12 +! Mapping. +! The numbers represent scale degrees mapped to keys. The first entry is for +! the given middle note, the next for subsequent higher keys. +! For an unmapped key, put in an "x". At the end, unmapped keys may be left out. +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 \ No newline at end of file diff --git a/test-data/scl/mapping-a442-7-to-12.kbm b/test-data/scl/mapping-a442-7-to-12.kbm new file mode 100644 index 00000000000..4c452d9fc72 --- /dev/null +++ b/test-data/scl/mapping-a442-7-to-12.kbm @@ -0,0 +1,32 @@ +! Template for a keyboard mapping +! +! Size of map. The pattern repeats every so many keys: +12 +! First MIDI note number to retune: +0 +! Last MIDI note number to retune: +127 +! Middle note where the first entry of the mapping is mapped to. Tune on b +59 +! Reference note for which frequency is given. g# is 442 here +68 +! Frequency to tune the above note to (floating point e.g. 440.0): +442.0 +! Scale degree to consider as formal octave (determines difference in pitch +! between adjacent mapping patterns): +7 +! Mapping. +! The numbers represent scale degrees mapped to keys. The first entry is for +! the given middle note, the next for subsequent higher keys. +! For an unmapped key, put in an "x". At the end, unmapped keys may be left out. +0 +1 +x +2 +x +3 +4 +x +5 +x +6 \ No newline at end of file diff --git a/test-data/scl/mapping-whitekeys-a440.kbm b/test-data/scl/mapping-whitekeys-a440.kbm new file mode 100644 index 00000000000..15b923ef6d5 --- /dev/null +++ b/test-data/scl/mapping-whitekeys-a440.kbm @@ -0,0 +1,33 @@ +! Template for a keyboard mapping +! +! Size of map. The pattern repeats every so many keys: +12 +! First MIDI note number to retune: +0 +! Last MIDI note number to retune: +127 +! Middle note where the first entry of 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): +440.0 +! Scale degree to consider as formal octave (determines difference in pitch +! between adjacent mapping patterns): +7 +! Mapping. +! The numbers represent scale degrees mapped to keys. The first entry is for +! the given middle note, the next for subsequent higher keys. +! For an unmapped key, put in an "x". At the end, unmapped keys may be left out. +0 +x +1 +x +2 +3 +x +4 +x +5 +x +6 diff --git a/test-data/scl/mapping-whitekeys-c261.kbm b/test-data/scl/mapping-whitekeys-c261.kbm new file mode 100644 index 00000000000..a6db8572181 --- /dev/null +++ b/test-data/scl/mapping-whitekeys-c261.kbm @@ -0,0 +1,33 @@ +! Template for a keyboard mapping +! +! Size of map. The pattern repeats every so many keys: +12 +! First MIDI note number to retune: +0 +! Last MIDI note number to retune: +127 +! Middle note where the first entry of the mapping is mapped to: +60 +! Reference note for which frequency is given: +60 +! Frequency to tune the above note to (floating point e.g. 440.0): +261.625565280 +! Scale degree to consider as formal octave (determines difference in pitch +! between adjacent mapping patterns): +7 +! Mapping. +! The numbers represent scale degrees mapped to keys. The first entry is for +! the given middle note, the next for subsequent higher keys. +! For an unmapped key, put in an "x". At the end, unmapped keys may be left out. +0 +x +1 +x +2 +3 +x +4 +x +5 +x +6 \ No newline at end of file diff --git a/test-data/scl/marvel12.scl b/test-data/scl/marvel12.scl new file mode 100755 index 00000000000..51f389c90ae --- /dev/null +++ b/test-data/scl/marvel12.scl @@ -0,0 +1,34 @@ +! marvel12.scl +Marvel[12] hobbit in 197-tET +12 +! +115.73604 +201.01523 +316.75127 +383.75635 +499.49239 +584.77157 +700.50761 +816.24365 +931.97970 +968.52792 +1084.26396 +2/1 +! +! ! premarvel12.scl +! ! +! Premarvel[12] hobbit 5-limit transversal = diadie2 = pump9 +! 12 +! ! +! 16/15 +! 9/8 +! 6/5 +! 5/4 +! 4/3 +! 45/32 +! 3/2 +! 8/5 +! 128/75 +! 225/128 +! 15/8 +! 2/1