diff --git a/lv2/sfizz.c b/lv2/sfizz.c index 69784a85..52815295 100644 --- a/lv2/sfizz.c +++ b/lv2/sfizz.c @@ -108,6 +108,7 @@ typedef struct const float *freewheel_port; const float *scala_root_key_port; const float *tuning_frequency_port; + const float *stretch_tuning_port; // Atom forge LV2_Atom_Forge forge; ///< Forge for writing atoms in run thread @@ -150,6 +151,7 @@ typedef struct int num_voices; unsigned int preload_size; sfizz_oversampling_factor_t oversampling; + float stretch_tuning; volatile bool check_modification; int max_block_size; int sample_counter; @@ -170,6 +172,7 @@ enum SFIZZ_FREEWHEELING = 8, SFIZZ_SCALA_ROOT_KEY = 9, SFIZZ_TUNING_FREQUENCY = 10, + SFIZZ_STRETCH_TUNING = 11, }; static void @@ -242,6 +245,9 @@ connect_port(LV2_Handle instance, case SFIZZ_TUNING_FREQUENCY: self->tuning_frequency_port = (const float *)data; break; + case SFIZZ_STRETCH_TUNING: + self->stretch_tuning_port = (const float *)data; + break; default: break; } @@ -302,6 +308,7 @@ instantiate(const LV2_Descriptor *descriptor, self->num_voices = DEFAULT_VOICES; self->oversampling = DEFAULT_OVERSAMPLING; self->preload_size = DEFAULT_PRELOAD; + self->stretch_tuning = 0.0f; self->check_modification = true; self->sample_counter = 0; @@ -654,6 +661,17 @@ sfizz_lv2_check_freewheeling(sfizz_plugin_t* self) } } +static void +sfizz_lv2_check_stretch_tuning(sfizz_plugin_t* self) +{ + float stretch_tuning = (float)*self->stretch_tuning_port; + if (stretch_tuning != self->stretch_tuning) + { + sfizz_load_stretch_tuning_by_ratio(self->synth, stretch_tuning); + self->stretch_tuning = stretch_tuning; + } +} + static void run(LV2_Handle instance, uint32_t sample_count) { @@ -719,6 +737,7 @@ run(LV2_Handle instance, uint32_t sample_count) sfizz_set_volume(self->synth, *(self->volume_port)); sfizz_set_scala_root_key(self->synth, *(self->scala_root_key_port)); sfizz_set_tuning_frequency(self->synth, *(self->tuning_frequency_port)); + sfizz_lv2_check_stretch_tuning(self); sfizz_lv2_check_preload_size(self); sfizz_lv2_check_oversampling(self); sfizz_lv2_check_num_voices(self); diff --git a/lv2/sfizz.ttl.in b/lv2/sfizz.ttl.in index 4e52bcfe..189849ba 100644 --- a/lv2/sfizz.ttl.in +++ b/lv2/sfizz.ttl.in @@ -280,4 +280,15 @@ midnam:update a lv2:Feature . lv2:minimum 300.0 ; lv2:maximum 500.0 ; units:unit units:hz + ] , [ + a lv2:InputPort, lv2:ControlPort ; + lv2:index 11 ; + lv2:symbol "stretched_tuning" ; + lv2:name "Stretched tuning", + "Accordage étiré"@fr ; + pg:group <@LV2PLUGIN_URI@#tuning> ; + lv2:default 0.0 ; + lv2:minimum 0.0 ; + lv2:maximum 1.0 ; + units:unit units:coef ] . diff --git a/src/sfizz.h b/src/sfizz.h index e0a0c0a2..69bff64b 100644 --- a/src/sfizz.h +++ b/src/sfizz.h @@ -137,6 +137,15 @@ SFIZZ_EXPORTED_API void sfizz_set_tuning_frequency(sfizz_synth_t* synth, float f */ SFIZZ_EXPORTED_API float sfizz_get_tuning_frequency(sfizz_synth_t* synth); +/** + * @brief Configure stretch tuning using a predefined parametric Railsback curve. + * A ratio 1/2 is supposed to match the average piano; 0 disables (the default). + * + * @param synth The sfizz synth. + * @param ratio The parameter in domain 0-1. + */ +SFIZZ_EXPORTED_API void sfizz_load_stretch_tuning_by_ratio(sfizz_synth_t* synth, float ratio); + /** * @brief Return the number of regions in the currently loaded SFZ file. * diff --git a/src/sfizz.hpp b/src/sfizz.hpp index a57a5a4d..8dbffc36 100644 --- a/src/sfizz.hpp +++ b/src/sfizz.hpp @@ -119,6 +119,14 @@ public: */ float getTuningFrequency() const; + /** + * @brief Configure stretch tuning using a predefined parametric Railsback curve. + * A ratio 1/2 is supposed to match the average piano; 0 disables (the default). + * + * @param ratio The parameter in domain 0-1. + */ + void loadStretchTuningByRatio(float ratio); + /** * @brief Return the current number of regions loaded. */ diff --git a/src/sfizz/Resources.h b/src/sfizz/Resources.h index c998eb3c..933dde27 100644 --- a/src/sfizz/Resources.h +++ b/src/sfizz/Resources.h @@ -13,6 +13,7 @@ #include "Wavetables.h" #include "Curve.h" #include "Tuning.h" +#include "absl/types/optional.h" namespace sfz { @@ -29,6 +30,7 @@ struct Resources EQPool eqPool { midiState }; WavetablePool wavePool; Tuning tuning; + absl::optional stretch; void setSampleRate(float samplerate) { diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 2718a1e1..50e9a569 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -516,6 +516,17 @@ float sfz::Synth::getTuningFrequency() const return resources.tuning.getTuningFrequency(); } +void sfz::Synth::loadStretchTuningByRatio(float ratio) +{ + CHECK(ratio >= 0.0f && ratio <= 1.0f); + ratio = clamp(ratio, 0.0f, 1.0f); + + if (ratio > 0.0f) + resources.stretch = StretchTuning::createRailsbackFromRatio(ratio); + else + resources.stretch.reset(); +} + sfz::Voice* sfz::Synth::findFreeVoice() noexcept { auto freeVoice = absl::c_find_if(voices, [](const std::unique_ptr& voice) { diff --git a/src/sfizz/Synth.h b/src/sfizz/Synth.h index 0819880e..84330734 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -150,6 +150,12 @@ public: * @return The frequency which indicates where standard tuning A4 is (default 440 Hz). */ float getTuningFrequency() const; + /** + * @brief Configure stretch tuning using a predefined parametric Railsback curve. + * + * @param ratio The parameter in domain 0-1. + */ + void loadStretchTuningByRatio(float ratio); /** * @brief Get the current number of regions loaded * diff --git a/src/sfizz/Tuning.cpp b/src/sfizz/Tuning.cpp index 4f14acb8..36a8fbcd 100644 --- a/src/sfizz/Tuning.cpp +++ b/src/sfizz/Tuning.cpp @@ -8,6 +8,7 @@ #include "Debug.h" #include "absl/types/optional.h" #include "Tunings.h" // Surge tuning library +#include #include #include #include @@ -200,7 +201,7 @@ bool Tuning::loadScalaString(const std::string& text) } if (scl.count <= 0) { - DBG("The scale file is empty: " << path); + DBG("Error loading scala string: " << text); return false; } @@ -248,5 +249,76 @@ bool Tuning::shouldReloadScala() return impl_->shouldReloadScala(); } +/// +float StretchTuning::getRatioForIntegralKey(int key) const noexcept +{ + return keyDetuneRatio_[std::max(0, std::min(127, key))]; +} + +float StretchTuning::getRatioForFractionalKey(float key) const noexcept +{ + int index1 = static_cast(key); + float mu = key - index1; + + index1 = std::max(0, std::min(127, index1)); + int index2 = std::min(127, index1 + 1); + + return keyDetuneRatio_[index1] * (1 - mu) + keyDetuneRatio_[index2] * mu; +} + +StretchTuning StretchTuning::createFromDetuneRatios(const float detune[128]) +{ + StretchTuning t; + std::copy(detune, detune + 128, t.keyDetuneRatio_); + return t; +} + +StretchTuning StretchTuning::createRailsbackFromRatio(float stretch) +{ + float data[128]; + + static constexpr float railsback21[128] = { + #include "sfizz/railsback/2-1.h" + }; + static constexpr float railsback41[128] = { + #include "sfizz/railsback/4-1.h" + }; + static constexpr float railsback42[128] = { + #include "sfizz/railsback/4-2.h" + }; + + // known curves and their matching knob positions + const float* curves[] = { railsback21, railsback41, railsback42 }; + const float points[] = { 0.25f, 0.5f, 1.0f }; + constexpr int num_curves = sizeof(curves) / sizeof(decltype(curves[0])); + + // + int index = -1; + while (index + 1 < num_curves && stretch >= points[index + 1]) + ++index; + + // + if (index < 0) { + float mu = std::max(0.0f, stretch / points[0]); + const float* c = curves[0]; + for (int i = 0; i < 128; ++i) + data[i] = mu * c[i] + (1 - mu); + } + else if (index + 1 < num_curves) { + float mu = (stretch - points[index]) / (points[index + 1] - points[index]); + const float* c1 = curves[index]; + const float* c2 = curves[index + 1]; + for (int i = 0; i < 128; ++i) + data[i] = mu * c2[i] + (1 - mu) * c1[i]; + } + else { + const float* c = curves[num_curves - 1]; + for (int i = 0; i < 128; ++i) + data[i] = c[i]; + } + + // + return createFromDetuneRatios(data); +} } // namespace sfz diff --git a/src/sfizz/Tuning.h b/src/sfizz/Tuning.h index 507bf44f..35793f0b 100644 --- a/src/sfizz/Tuning.h +++ b/src/sfizz/Tuning.h @@ -71,4 +71,16 @@ private: std::unique_ptr impl_; }; +class StretchTuning { +public: + float getRatioForIntegralKey(int key) const noexcept; + float getRatioForFractionalKey(float key) const noexcept; + + static StretchTuning createFromDetuneRatios(const float detune[128]); + static StretchTuning createRailsbackFromRatio(float stretch); + +private: + float keyDetuneRatio_[128] {}; +}; + } // namespace sfz diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 6ae90db2..9f6df3a3 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -87,6 +87,11 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, float value, const float numberRetuned = resources.tuning.getKeyFractional12TET(number); pitchRatio = region->getBasePitchVariation(numberRetuned, value); + + // apply stretch tuning if set + if (resources.stretch) + pitchRatio *= resources.stretch->getRatioForFractionalKey(numberRetuned); + baseVolumedB = region->getBaseVolumedB(number); baseGain = region->getBaseGain(); if (triggerType != TriggerType::CC) diff --git a/src/sfizz/railsback/2-1.h b/src/sfizz/railsback/2-1.h new file mode 100644 index 00000000..cc4a3e54 --- /dev/null +++ b/src/sfizz/railsback/2-1.h @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +/* Railsback curve 2-1 */ + 0.995011, /* key 0: -8.658702 cents (extrapolated) */ + 0.995085, /* key 1: -8.529617 cents (extrapolated) */ + 0.995162, /* key 2: -8.395871 cents (extrapolated) */ + 0.995242, /* key 3: -8.257465 cents (extrapolated) */ + 0.995324, /* key 4: -8.114399 cents (extrapolated) */ + 0.995409, /* key 5: -7.966672 cents (extrapolated) */ + 0.995496, /* key 6: -7.814285 cents (extrapolated) */ + 0.995587, /* key 7: -7.657238 cents (extrapolated) */ + 0.995680, /* key 8: -7.495530 cents (extrapolated) */ + 0.995775, /* key 9: -7.329161 cents (extrapolated) */ + 0.995874, /* key 10: -7.158133 cents (extrapolated) */ + 0.995975, /* key 11: -6.982444 cents (extrapolated) */ + 0.996079, /* key 12: -6.802095 cents (extrapolated) */ + 0.996185, /* key 13: -6.617085 cents (extrapolated) */ + 0.996294, /* key 14: -6.427415 cents (extrapolated) */ + 0.996406, /* key 15: -6.233084 cents (extrapolated) */ + 0.996521, /* key 16: -6.034094 cents (extrapolated) */ + 0.996638, /* key 17: -5.830443 cents (extrapolated) */ + 0.996758, /* key 18: -5.622131 cents (extrapolated) */ + 0.996880, /* key 19: -5.409159 cents (extrapolated) */ + 0.997006, /* key 20: -5.191527 cents (extrapolated) */ + 0.997134, /* key 21: -4.969234 cents (extrapolated) */ + 0.997264, /* key 22: -4.742281 cents (extrapolated) */ + 0.997398, /* key 23: -4.510668 cents (extrapolated) */ + 0.997534, /* key 24: -4.274394 cents (extrapolated) */ + 0.997673, /* key 25: -4.033460 cents (extrapolated) */ + 0.997814, /* key 26: -3.787866 cents (extrapolated) */ + 0.997959, /* key 27: -3.537611 cents */ + 0.998106, /* key 28: -3.282696 cents */ + 0.998255, /* key 29: -3.023120 cents */ + 0.998408, /* key 30: -2.758884 cents */ + 0.998562, /* key 31: -2.491368 cents */ + 0.998706, /* key 32: -2.241976 cents */ + 0.998835, /* key 33: -2.018026 cents */ + 0.998951, /* key 34: -1.817862 cents */ + 0.999066, /* key 35: -1.617934 cents */ + 0.999186, /* key 36: -1.410588 cents */ + 0.999308, /* key 37: -1.197763 cents */ + 0.999421, /* key 38: -1.002872 cents */ + 0.999519, /* key 39: -0.832780 cents */ + 0.999604, /* key 40: -0.686387 cents */ + 0.999683, /* key 41: -0.549253 cents */ + 0.999759, /* key 42: -0.416912 cents */ + 0.999833, /* key 43: -0.289964 cents */ + 0.999899, /* key 44: -0.175639 cents */ + 0.999956, /* key 45: -0.076060 cents */ + 1.000005, /* key 46: 0.008822 cents */ + 1.000046, /* key 47: 0.079500 cents */ + 1.000079, /* key 48: 0.136106 cents */ + 1.000103, /* key 49: 0.178641 cents */ + 1.000120, /* key 50: 0.207103 cents */ + 1.000128, /* key 51: 0.221494 cents */ + 1.000129, /* key 52: 0.222942 cents */ + 1.000129, /* key 53: 0.223104 cents */ + 1.000130, /* key 54: 0.225054 cents */ + 1.000132, /* key 55: 0.228792 cents */ + 1.000135, /* key 56: 0.234318 cents */ + 1.000140, /* key 57: 0.241632 cents */ + 1.000145, /* key 58: 0.250734 cents */ + 1.000151, /* key 59: 0.261624 cents */ + 1.000158, /* key 60: 0.274302 cents */ + 1.000167, /* key 61: 0.288924 cents */ + 1.000177, /* key 62: 0.306785 cents */ + 1.000190, /* key 63: 0.328176 cents */ + 1.000204, /* key 64: 0.353095 cents */ + 1.000220, /* key 65: 0.381543 cents */ + 1.000239, /* key 66: 0.413520 cents */ + 1.000261, /* key 67: 0.452181 cents */ + 1.000301, /* key 68: 0.520401 cents */ + 1.000360, /* key 69: 0.622735 cents */ + 1.000437, /* key 70: 0.756456 cents */ + 1.000522, /* key 71: 0.904153 cents */ + 1.000614, /* key 72: 1.062739 cents */ + 1.000711, /* key 73: 1.231072 cents */ + 1.000810, /* key 74: 1.401365 cents */ + 1.000909, /* key 75: 1.572155 cents */ + 1.001009, /* key 76: 1.745072 cents */ + 1.001119, /* key 77: 1.935654 cents */ + 1.001241, /* key 78: 2.147768 cents */ + 1.001376, /* key 79: 2.381211 cents */ + 1.001523, /* key 80: 2.634168 cents */ + 1.001680, /* key 81: 2.906211 cents */ + 1.001847, /* key 82: 3.193974 cents */ + 1.002007, /* key 83: 3.471247 cents */ + 1.002158, /* key 84: 3.732482 cents */ + 1.002305, /* key 85: 3.985981 cents */ + 1.002487, /* key 86: 4.301067 cents */ + 1.002715, /* key 87: 4.693299 cents */ + 1.002982, /* key 88: 5.154494 cents */ + 1.003252, /* key 89: 5.620512 cents */ + 1.003517, /* key 90: 6.077690 cents */ + 1.003779, /* key 91: 6.530662 cents */ + 1.004058, /* key 92: 7.011498 cents */ + 1.004357, /* key 93: 7.526323 cents */ + 1.004673, /* key 94: 8.071295 cents */ + 1.004991, /* key 95: 8.619646 cents */ + 1.005309, /* key 96: 9.166235 cents */ + 1.005630, /* key 97: 9.720017 cents */ + 1.005990, /* key 98: 10.339965 cents */ + 1.006395, /* key 99: 11.036832 cents */ + 1.006839, /* key 100: 11.800397 cents */ + 1.007285, /* key 101: 12.566840 cents */ + 1.007727, /* key 102: 13.325103 cents */ + 1.008163, /* key 103: 14.075186 cents */ + 1.008595, /* key 104: 14.817087 cents */ + 1.009023, /* key 105: 15.550807 cents (extrapolated) */ + 1.009446, /* key 106: 16.276347 cents (extrapolated) */ + 1.009864, /* key 107: 16.993705 cents (extrapolated) */ + 1.010278, /* key 108: 17.702883 cents (extrapolated) */ + 1.010687, /* key 109: 18.403880 cents (extrapolated) */ + 1.011092, /* key 110: 19.096696 cents (extrapolated) */ + 1.011492, /* key 111: 19.781331 cents (extrapolated) */ + 1.011887, /* key 112: 20.457786 cents (extrapolated) */ + 1.012278, /* key 113: 21.126059 cents (extrapolated) */ + 1.012664, /* key 114: 21.786151 cents (extrapolated) */ + 1.013045, /* key 115: 22.438063 cents (extrapolated) */ + 1.013422, /* key 116: 23.081794 cents (extrapolated) */ + 1.013794, /* key 117: 23.717344 cents (extrapolated) */ + 1.014161, /* key 118: 24.344713 cents (extrapolated) */ + 1.014524, /* key 119: 24.963901 cents (extrapolated) */ + 1.014882, /* key 120: 25.574908 cents (extrapolated) */ + 1.015236, /* key 121: 26.177735 cents (extrapolated) */ + 1.015585, /* key 122: 26.772380 cents (extrapolated) */ + 1.015929, /* key 123: 27.358845 cents (extrapolated) */ + 1.016268, /* key 124: 27.937129 cents (extrapolated) */ + 1.016603, /* key 125: 28.507232 cents (extrapolated) */ + 1.016933, /* key 126: 29.069154 cents (extrapolated) */ + 1.017258, /* key 127: 29.622895 cents (extrapolated) */ diff --git a/src/sfizz/railsback/4-1.h b/src/sfizz/railsback/4-1.h new file mode 100644 index 00000000..34d93725 --- /dev/null +++ b/src/sfizz/railsback/4-1.h @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +/* Railsback curve 4-1 */ + 0.939286, /* key 0: -108.436476 cents (extrapolated) */ + 0.942401, /* key 1: -102.705252 cents (extrapolated) */ + 0.945435, /* key 2: -97.139571 cents (extrapolated) */ + 0.948389, /* key 3: -91.739433 cents (extrapolated) */ + 0.951261, /* key 4: -86.504836 cents (extrapolated) */ + 0.954050, /* key 5: -81.435782 cents (extrapolated) */ + 0.956756, /* key 6: -76.532270 cents (extrapolated) */ + 0.959378, /* key 7: -71.794300 cents (extrapolated) */ + 0.961915, /* key 8: -67.221873 cents (extrapolated) */ + 0.964367, /* key 9: -62.814988 cents (extrapolated) */ + 0.966732, /* key 10: -58.573645 cents (extrapolated) */ + 0.969011, /* key 11: -54.497845 cents (extrapolated) */ + 0.971202, /* key 12: -50.587587 cents (extrapolated) */ + 0.973305, /* key 13: -46.842871 cents (extrapolated) */ + 0.975320, /* key 14: -43.263697 cents (extrapolated) */ + 0.977245, /* key 15: -39.850066 cents (extrapolated) */ + 0.979080, /* key 16: -36.601977 cents (extrapolated) */ + 0.980825, /* key 17: -33.519430 cents (extrapolated) */ + 0.982479, /* key 18: -30.602425 cents (extrapolated) */ + 0.984041, /* key 19: -27.850963 cents (extrapolated) */ + 0.985512, /* key 20: -25.265043 cents (extrapolated) */ + 0.986891, /* key 21: -22.844666 cents (extrapolated) */ + 0.988177, /* key 22: -20.589830 cents (extrapolated) */ + 0.989371, /* key 23: -18.500537 cents (extrapolated) */ + 0.990471, /* key 24: -16.576786 cents (extrapolated) */ + 0.991477, /* key 25: -14.818578 cents (extrapolated) */ + 0.992390, /* key 26: -13.225912 cents (extrapolated) */ + 0.993208, /* key 27: -11.798788 cents */ + 0.993932, /* key 28: -10.537206 cents */ + 0.994561, /* key 29: -9.441167 cents */ + 0.995096, /* key 30: -8.510670 cents */ + 0.995559, /* key 31: -7.706263 cents */ + 0.995995, /* key 32: -6.947307 cents */ + 0.996407, /* key 33: -6.231943 cents */ + 0.996789, /* key 34: -5.567941 cents */ + 0.997140, /* key 35: -4.957876 cents */ + 0.997460, /* key 36: -4.403642 cents */ + 0.997745, /* key 37: -3.908466 cents */ + 0.997997, /* key 38: -3.470683 cents */ + 0.998229, /* key 39: -3.068428 cents */ + 0.998444, /* key 40: -2.695061 cents */ + 0.998640, /* key 41: -2.355536 cents */ + 0.998811, /* key 42: -2.059656 cents */ + 0.998957, /* key 43: -1.805795 cents */ + 0.999095, /* key 44: -1.567663 cents */ + 0.999228, /* key 45: -1.336825 cents */ + 0.999357, /* key 46: -1.113643 cents */ + 0.999479, /* key 47: -0.902246 cents */ + 0.999594, /* key 48: -0.703804 cents */ + 0.999701, /* key 49: -0.518051 cents */ + 0.999803, /* key 50: -0.341900 cents */ + 0.999899, /* key 51: -0.174467 cents */ + 0.999990, /* key 52: -0.016924 cents */ + 1.000069, /* key 53: 0.119979 cents */ + 1.000135, /* key 54: 0.233645 cents */ + 1.000196, /* key 55: 0.338454 cents */ + 1.000261, /* key 56: 0.452623 cents */ + 1.000330, /* key 57: 0.571568 cents */ + 1.000398, /* key 58: 0.688154 cents */ + 1.000463, /* key 59: 0.801883 cents */ + 1.000525, /* key 60: 0.908646 cents */ + 1.000582, /* key 61: 1.007402 cents */ + 1.000635, /* key 62: 1.099408 cents */ + 1.000690, /* key 63: 1.194622 cents */ + 1.000748, /* key 64: 1.295185 cents */ + 1.000809, /* key 65: 1.400548 cents */ + 1.000871, /* key 66: 1.506439 cents */ + 1.000932, /* key 67: 1.611953 cents */ + 1.000998, /* key 68: 1.727714 cents */ + 1.001079, /* key 69: 1.867793 cents */ + 1.001173, /* key 70: 2.029542 cents */ + 1.001269, /* key 71: 2.195080 cents */ + 1.001365, /* key 72: 2.361143 cents */ + 1.001463, /* key 73: 2.530246 cents */ + 1.001572, /* key 74: 2.719392 cents */ + 1.001695, /* key 75: 2.931762 cents */ + 1.001831, /* key 76: 3.166801 cents */ + 1.001977, /* key 77: 3.418944 cents */ + 1.002132, /* key 78: 3.686750 cents */ + 1.002296, /* key 79: 3.970898 cents */ + 1.002474, /* key 80: 4.277766 cents */ + 1.002666, /* key 81: 4.608921 cents */ + 1.002873, /* key 82: 4.965874 cents */ + 1.003102, /* key 83: 5.361161 cents */ + 1.003354, /* key 84: 5.797580 cents */ + 1.003633, /* key 85: 6.277406 cents */ + 1.003948, /* key 86: 6.821544 cents */ + 1.004304, /* key 87: 7.435055 cents */ + 1.004696, /* key 88: 8.110190 cents */ + 1.005088, /* key 89: 8.786767 cents */ + 1.005475, /* key 90: 9.452064 cents */ + 1.005861, /* key 91: 10.116356 cents */ + 1.006287, /* key 92: 10.849495 cents */ + 1.006760, /* key 93: 11.664603 cents */ + 1.007286, /* key 94: 12.567428 cents */ + 1.007886, /* key 95: 13.598919 cents */ + 1.008566, /* key 96: 14.767105 cents */ + 1.009328, /* key 97: 16.073381 cents */ + 1.010176, /* key 98: 17.527215 cents */ + 1.011111, /* key 99: 19.130383 cents */ + 1.012135, /* key 100: 20.882887 cents */ + 1.013248, /* key 101: 22.784726 cents (extrapolated) */ + 1.014449, /* key 102: 24.835900 cents (extrapolated) */ + 1.015739, /* key 103: 27.036410 cents (extrapolated) */ + 1.017119, /* key 104: 29.386254 cents (extrapolated) */ + 1.018588, /* key 105: 31.885434 cents (extrapolated) */ + 1.020148, /* key 106: 34.533949 cents (extrapolated) */ + 1.021798, /* key 107: 37.331799 cents (extrapolated) */ + 1.023539, /* key 108: 40.278984 cents (extrapolated) */ + 1.025371, /* key 109: 43.375505 cents (extrapolated) */ + 1.027295, /* key 110: 46.621360 cents (extrapolated) */ + 1.029312, /* key 111: 50.016551 cents (extrapolated) */ + 1.031422, /* key 112: 53.561077 cents (extrapolated) */ + 1.033625, /* key 113: 57.254938 cents (extrapolated) */ + 1.035922, /* key 114: 61.098134 cents (extrapolated) */ + 1.038314, /* key 115: 65.090666 cents (extrapolated) */ + 1.040801, /* key 116: 69.232533 cents (extrapolated) */ + 1.043384, /* key 117: 73.523734 cents (extrapolated) */ + 1.046063, /* key 118: 77.964271 cents (extrapolated) */ + 1.048840, /* key 119: 82.554143 cents (extrapolated) */ + 1.051715, /* key 120: 87.293351 cents (extrapolated) */ + 1.054689, /* key 121: 92.181893 cents (extrapolated) */ + 1.057763, /* key 122: 97.219771 cents (extrapolated) */ + 1.060937, /* key 123: 102.406984 cents (extrapolated) */ + 1.064213, /* key 124: 107.743532 cents (extrapolated) */ + 1.067590, /* key 125: 113.229415 cents (extrapolated) */ + 1.071071, /* key 126: 118.864633 cents (extrapolated) */ + 1.074656, /* key 127: 124.649187 cents (extrapolated) */ diff --git a/src/sfizz/railsback/4-2.h b/src/sfizz/railsback/4-2.h new file mode 100644 index 00000000..52825f2c --- /dev/null +++ b/src/sfizz/railsback/4-2.h @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +/* Railsback curve 4-2 */ + 0.968133, /* key 0: -56.068174 cents (extrapolated) */ + 0.969216, /* key 1: -54.130975 cents (extrapolated) */ + 0.970284, /* key 2: -52.225304 cents (extrapolated) */ + 0.971335, /* key 3: -50.351161 cents (extrapolated) */ + 0.972369, /* key 4: -48.508548 cents (extrapolated) */ + 0.973387, /* key 5: -46.697464 cents (extrapolated) */ + 0.974388, /* key 6: -44.917908 cents (extrapolated) */ + 0.975372, /* key 7: -43.169881 cents (extrapolated) */ + 0.976340, /* key 8: -41.453383 cents (extrapolated) */ + 0.977291, /* key 9: -39.768414 cents (extrapolated) */ + 0.978225, /* key 10: -38.114974 cents (extrapolated) */ + 0.979141, /* key 11: -36.493062 cents (extrapolated) */ + 0.980041, /* key 12: -34.902679 cents (extrapolated) */ + 0.980924, /* key 13: -33.343826 cents (extrapolated) */ + 0.981790, /* key 14: -31.816500 cents (extrapolated) */ + 0.982639, /* key 15: -30.320704 cents (extrapolated) */ + 0.983470, /* key 16: -28.856437 cents (extrapolated) */ + 0.984284, /* key 17: -27.423698 cents (extrapolated) */ + 0.985081, /* key 18: -26.022489 cents (extrapolated) */ + 0.985861, /* key 19: -24.652808 cents (extrapolated) */ + 0.986623, /* key 20: -23.314656 cents (extrapolated) */ + 0.987368, /* key 21: -22.008032 cents (extrapolated) */ + 0.988096, /* key 22: -20.732938 cents (extrapolated) */ + 0.988806, /* key 23: -19.489372 cents (extrapolated) */ + 0.989498, /* key 24: -18.277335 cents (extrapolated) */ + 0.990173, /* key 25: -17.096827 cents (extrapolated) */ + 0.990830, /* key 26: -15.947848 cents (extrapolated) */ + 0.991470, /* key 27: -14.830398 cents (extrapolated) */ + 0.992092, /* key 28: -13.744476 cents */ + 0.992697, /* key 29: -12.690084 cents */ + 0.993283, /* key 30: -11.667220 cents */ + 0.993852, /* key 31: -10.675885 cents */ + 0.994403, /* key 32: -9.716079 cents */ + 0.994935, /* key 33: -8.791019 cents */ + 0.995443, /* key 34: -7.906923 cents */ + 0.995928, /* key 35: -7.063949 cents */ + 0.996389, /* key 36: -6.263628 cents */ + 0.996814, /* key 37: -5.524320 cents */ + 0.997202, /* key 38: -4.851382 cents */ + 0.997551, /* key 39: -4.244205 cents */ + 0.997868, /* key 40: -3.695396 cents */ + 0.998152, /* key 41: -3.202781 cents */ + 0.998405, /* key 42: -2.763831 cents */ + 0.998641, /* key 43: -2.354022 cents */ + 0.998864, /* key 44: -1.967171 cents */ + 0.999074, /* key 45: -1.603278 cents */ + 0.999267, /* key 46: -1.268779 cents */ + 0.999438, /* key 47: -0.973349 cents */ + 0.999586, /* key 48: -0.717082 cents */ + 0.999713, /* key 49: -0.497215 cents */ + 0.999837, /* key 50: -0.283052 cents */ + 0.999962, /* key 51: -0.066075 cents */ + 1.000087, /* key 52: 0.150991 cents */ + 1.000196, /* key 53: 0.340017 cents */ + 1.000285, /* key 54: 0.493590 cents */ + 1.000364, /* key 55: 0.630714 cents */ + 1.000450, /* key 56: 0.778589 cents */ + 1.000542, /* key 57: 0.937723 cents */ + 1.000641, /* key 58: 1.108577 cents */ + 1.000746, /* key 59: 1.291156 cents */ + 1.000860, /* key 60: 1.487486 cents */ + 1.000983, /* key 61: 1.700692 cents */ + 1.001116, /* key 62: 1.930806 cents */ + 1.001259, /* key 63: 2.177830 cents */ + 1.001419, /* key 64: 2.454501 cents */ + 1.001605, /* key 65: 2.777218 cents */ + 1.001819, /* key 66: 3.146046 cents */ + 1.002058, /* key 67: 3.558460 cents */ + 1.002311, /* key 68: 3.995879 cents */ + 1.002576, /* key 69: 4.454554 cents */ + 1.002854, /* key 70: 4.934013 cents */ + 1.003142, /* key 71: 5.431357 cents */ + 1.003441, /* key 72: 5.946093 cents */ + 1.003750, /* key 73: 6.480558 cents */ + 1.004081, /* key 74: 7.050077 cents */ + 1.004433, /* key 75: 7.657436 cents */ + 1.004808, /* key 76: 8.304413 cents */ + 1.005217, /* key 77: 9.008633 cents */ + 1.005662, /* key 78: 9.774607 cents */ + 1.006141, /* key 79: 10.598988 cents */ + 1.006636, /* key 80: 11.450574 cents */ + 1.007143, /* key 81: 12.321738 cents */ + 1.007662, /* key 82: 13.213585 cents */ + 1.008198, /* key 83: 14.135184 cents */ + 1.008754, /* key 84: 15.088542 cents */ + 1.009329, /* key 85: 16.076215 cents */ + 1.009939, /* key 86: 17.121007 cents */ + 1.010585, /* key 87: 18.228310 cents */ + 1.011265, /* key 88: 19.392518 cents */ + 1.011950, /* key 89: 20.566206 cents */ + 1.012636, /* key 90: 21.738618 cents */ + 1.013327, /* key 91: 22.919118 cents */ + 1.014061, /* key 92: 24.173747 cents */ + 1.014847, /* key 93: 25.515341 cents */ + 1.015679, /* key 94: 26.934203 cents */ + 1.016517, /* key 95: 28.360657 cents */ + 1.017351, /* key 96: 29.780936 cents */ + 1.018188, /* key 97: 31.204307 cents */ + 1.019062, /* key 98: 32.690667 cents */ + 1.019981, /* key 99: 34.250749 cents */ + 1.020944, /* key 100: 35.884552 cents (extrapolated) */ + 1.021952, /* key 101: 37.592078 cents (extrapolated) */ + 1.023004, /* key 102: 39.373326 cents (extrapolated) */ + 1.024100, /* key 103: 41.228296 cents (extrapolated) */ + 1.025242, /* key 104: 43.156987 cents (extrapolated) */ + 1.026428, /* key 105: 45.159401 cents (extrapolated) */ + 1.027660, /* key 106: 47.235537 cents (extrapolated) */ + 1.028937, /* key 107: 49.385395 cents (extrapolated) */ + 1.030259, /* key 108: 51.608975 cents (extrapolated) */ + 1.031627, /* key 109: 53.906277 cents (extrapolated) */ + 1.033041, /* key 110: 56.277301 cents (extrapolated) */ + 1.034501, /* key 111: 58.722047 cents (extrapolated) */ + 1.036007, /* key 112: 61.240515 cents (extrapolated) */ + 1.037559, /* key 113: 63.832705 cents (extrapolated) */ + 1.039158, /* key 114: 66.498617 cents (extrapolated) */ + 1.040804, /* key 115: 69.238251 cents (extrapolated) */ + 1.042497, /* key 116: 72.051607 cents (extrapolated) */ + 1.044237, /* key 117: 74.938686 cents (extrapolated) */ + 1.046024, /* key 118: 77.899486 cents (extrapolated) */ + 1.047859, /* key 119: 80.934008 cents (extrapolated) */ + 1.049742, /* key 120: 84.042253 cents (extrapolated) */ + 1.051673, /* key 121: 87.224219 cents (extrapolated) */ + 1.053653, /* key 122: 90.479907 cents (extrapolated) */ + 1.055681, /* key 123: 93.809318 cents (extrapolated) */ + 1.057759, /* key 124: 97.212450 cents (extrapolated) */ + 1.059885, /* key 125: 100.689305 cents (extrapolated) */ + 1.062061, /* key 126: 104.239881 cents (extrapolated) */ + 1.064287, /* key 127: 107.864180 cents (extrapolated) */ diff --git a/src/sfizz/sfizz.cpp b/src/sfizz/sfizz.cpp index 31047c77..b110b4e1 100644 --- a/src/sfizz/sfizz.cpp +++ b/src/sfizz/sfizz.cpp @@ -58,6 +58,11 @@ float sfz::Sfizz::getTuningFrequency() const return synth->getTuningFrequency(); } +void sfz::Sfizz::loadStretchTuningByRatio(float ratio) +{ + return synth->loadStretchTuningByRatio(ratio); +} + int sfz::Sfizz::getNumRegions() const noexcept { return synth->getNumRegions(); diff --git a/src/sfizz/sfizz_wrapper.cpp b/src/sfizz/sfizz_wrapper.cpp index 86ef1b67..15310942 100644 --- a/src/sfizz/sfizz_wrapper.cpp +++ b/src/sfizz/sfizz_wrapper.cpp @@ -67,6 +67,12 @@ float sfizz_get_tuning_frequency(sfizz_synth_t* synth) return self->getTuningFrequency(); } +void sfizz_load_stretch_tuning_by_ratio(sfizz_synth_t* synth, float ratio) +{ + auto self = reinterpret_cast(synth); + self->loadStretchTuningByRatio(ratio); +} + void sfizz_free(sfizz_synth_t* synth) { delete reinterpret_cast(synth); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index af4981dc..c71cee5b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -64,6 +64,10 @@ if(JACK_FOUND AND TARGET Qt5::Widgets) add_executable(sfizz_demo_parser DemoParser.cpp) target_link_libraries(sfizz_demo_parser PRIVATE sfizz_parser Qt5::Widgets) set_target_properties(sfizz_demo_parser PROPERTIES AUTOUIC ON) + + add_executable(sfizz_demo_stretch_tuning DemoStretchTuning.cpp) + target_link_libraries(sfizz_demo_stretch_tuning PRIVATE sfizz::sfizz Qt5::Widgets) + set_target_properties(sfizz_demo_stretch_tuning PROPERTIES AUTOUIC ON) endif() add_executable(eq_apply EQ.cpp) diff --git a/tests/DemoStretchTuning.cpp b/tests/DemoStretchTuning.cpp new file mode 100644 index 00000000..6604f151 --- /dev/null +++ b/tests/DemoStretchTuning.cpp @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#include "sfizz/Tuning.h" +#include "ui_DemoStretchTuning.h" +#include +#include +#include +#include +#include +#include +#include + +class DataPlotWidget; + +/// +class DemoApp : public QApplication { +public: + DemoApp(int& argc, char** argv); + void initWindow(); + +private: + void updateStretch(); + +private: + QMainWindow* fWindow = nullptr; + DataPlotWidget* fDataPlot = nullptr; + Ui::DemoStretchTuningWindow fUi; + + sfz::StretchTuning fTuning; +}; + +/// +class DataPlotWidget : public QWidget { +public: + explicit DataPlotWidget(QWidget* parent = nullptr); + void setYRange(float y1, float y2); + void setYFunction(std::function fun); + +protected: + void paintEvent(QPaintEvent* event) override; + + float yRange1_ = 0.0f; + float yRange2_ = 0.0f; + std::function yFunction_; +}; + +DataPlotWidget::DataPlotWidget(QWidget* parent) + : QWidget(parent) +{ +} + +void DataPlotWidget::setYRange(float y1, float y2) +{ + yRange1_ = y1; + yRange2_ = y2; +} + +void DataPlotWidget::setYFunction(std::function fun) +{ + yFunction_ = fun; + repaint(); +} + +void DataPlotWidget::paintEvent(QPaintEvent* event) +{ + Q_UNUSED(event); + + QPainter painter(this); + + painter.fillRect(rect(), Qt::white); + + const qreal yRange1 = yRange1_; + const qreal yRange2 = yRange2_; + std::function yFunction = yFunction_; + + if (yRange1 == yRange2 || !yFunction) + return; + + int w = width(); + int h = height(); + + auto yPointToCoord = [h, yRange1, yRange2](qreal y) { + qreal r = (y - yRange1) / (yRange2 - yRange1); + return (1 - r) * (h - 1); + }; + + QPainterPath path; + path.moveTo(0.0, yPointToCoord(yFunction(0.0))); + for (int x = 1; x < w; ++x) { + qreal wRatio = x * (qreal(1.0) / (w - 1)); + path.lineTo(x, yPointToCoord(yFunction(wRatio * 127))); + } + + painter.strokePath(path, QPen(Qt::red, 1.0)); +} + +/// +DemoApp::DemoApp(int& argc, char** argv) + : QApplication(argc, argv) +{ + setApplicationName(tr("Sfizz Stretch Tuning")); +} + +void DemoApp::initWindow() +{ + QMainWindow* window = new QMainWindow; + fWindow = window; + fUi.setupUi(window); + + QVBoxLayout* lPlot = new QVBoxLayout; + lPlot->setContentsMargins(QMargins()); + DataPlotWidget* dataPlot = new DataPlotWidget; + fDataPlot = dataPlot; + lPlot->addWidget(dataPlot); + fUi.frmPlot->setLayout(lPlot); + + dataPlot->setYRange(-50.0, +50.0); + + connect( + fUi.valStretch, &QSlider::valueChanged, + this, [this]() { updateStretch(); }); + + fUi.valStretch->setValue(fUi.valStretch->maximum() / 2); + + window->setWindowTitle(applicationDisplayName()); + window->show(); +} + +void DemoApp::updateStretch() +{ + qreal stretch = qreal(fUi.valStretch->value()) / fUi.valStretch->maximum(); + + sfz::StretchTuning& tuning = fTuning; + tuning = sfz::StretchTuning::createRailsbackFromRatio(stretch); + + fDataPlot->setYFunction([this](qreal x) -> qreal { + float ratio = fTuning.getRatioForFractionalKey(x); + float cents = 1200.0f * std::log2(ratio); + return cents; + }); +} + +int main(int argc, char *argv[]) +{ + DemoApp app(argc, argv); + + app.initWindow(); + + return app.exec(); +} diff --git a/tests/DemoStretchTuning.ui b/tests/DemoStretchTuning.ui new file mode 100644 index 00000000..126ac256 --- /dev/null +++ b/tests/DemoStretchTuning.ui @@ -0,0 +1,68 @@ + + + DemoStretchTuningWindow + + + + 0 + 0 + 800 + 600 + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Stretch + + + Qt::AlignCenter + + + + + + + 100 + + + Qt::Horizontal + + + + + + + + + + + + diff --git a/tests/TuningT.cpp b/tests/TuningT.cpp index 76c94d67..430c031c 100644 --- a/tests/TuningT.cpp +++ b/tests/TuningT.cpp @@ -15,3 +15,109 @@ TEST_CASE("[Tuning] Default tuning") for (int key = 0; key < 128; ++key) REQUIRE(defaultTuning.getFrequencyOfKey(key) == Approx(midiNoteFrequency(key))); } + +TEST_CASE("[Tuning] Railsback disabled") +{ + auto tuning = sfz::StretchTuning::createRailsbackFromRatio(0.0f); + for (int key = 0; key < 128; ++key) + REQUIRE(tuning.getRatioForIntegralKey(key) == 1.0f); +} + +TEST_CASE("[Tuning] Stretch integral == float") +{ + auto tuning = sfz::StretchTuning::createRailsbackFromRatio(0.25f); + for (int key = 0; key < 128; ++key) + REQUIRE(tuning.getRatioForIntegralKey(key) == tuning.getRatioForFractionalKey(static_cast(key))); +} + +TEST_CASE("[Tuning] Stretch definition points") +{ + SECTION("For 0.5f") + { + static constexpr float railsback[128] = { + #include "sfizz/railsback/4-1.h" + }; + auto tuning = sfz::StretchTuning::createRailsbackFromRatio(0.5f); + for (int key = 0; key < 128; ++key) + REQUIRE(tuning.getRatioForIntegralKey(key) == railsback[key]); + } + SECTION("For 1.0f") + { + static constexpr float railsback[128] = { + #include "sfizz/railsback/4-2.h" + }; + auto tuning = sfz::StretchTuning::createRailsbackFromRatio(1.0f); + for (int key = 0; key < 128; ++key) + REQUIRE(tuning.getRatioForIntegralKey(key) == railsback[key]); + } + + SECTION("For 0.25f") + { + static constexpr float railsback[128] = { + #include "sfizz/railsback/2-1.h" + }; + auto tuning = sfz::StretchTuning::createRailsbackFromRatio(0.25f); + for (int key = 0; key < 128; ++key) + REQUIRE(tuning.getRatioForIntegralKey(key) == railsback[key]); + } +} + +TEST_CASE("[Tuning] Stretch interpolation bounds") +{ + SECTION("Between 0 and 0.25f") + { + static constexpr float bound1[128] = { + #include "sfizz/railsback/2-1.h" + }; + auto tuning = sfz::StretchTuning::createRailsbackFromRatio(0.1f); + for (int key = 0; key < 128; ++key) { + if (bound1[key] < 1.0f) { + REQUIRE(tuning.getRatioForIntegralKey(key) >= bound1[key]); + REQUIRE(tuning.getRatioForIntegralKey(key) <= 1.0f); + } else { + REQUIRE(tuning.getRatioForIntegralKey(key) <= bound1[key]); + REQUIRE(tuning.getRatioForIntegralKey(key) >= 1.0f); + } + } + } + SECTION("Between 0.25f and 0.5f") + { + static constexpr float bound1[128] = { + #include "sfizz/railsback/2-1.h" + }; + static constexpr float bound2[128] = { + #include "sfizz/railsback/4-1.h" + }; + auto tuning = sfz::StretchTuning::createRailsbackFromRatio(0.3f); + for (int key = 0; key < 128; ++key) { + if (bound1[key] < bound2[key]) { + REQUIRE(tuning.getRatioForIntegralKey(key) >= bound1[key]); + REQUIRE(tuning.getRatioForIntegralKey(key) <= bound2[key]); + } else { + REQUIRE(tuning.getRatioForIntegralKey(key) <= bound1[key]); + REQUIRE(tuning.getRatioForIntegralKey(key) >= bound2[key]); + } + } + } + SECTION("Between 0.5f and 1.0f") + { + static constexpr float bound1[128] = { + #include "sfizz/railsback/4-1.h" + }; + static constexpr float bound2[128] = { + #include "sfizz/railsback/4-2.h" + }; + auto tuning = sfz::StretchTuning::createRailsbackFromRatio(0.7f); + for (int key = 0; key < 128; ++key) { + if (bound1[key] < bound2[key]) { + REQUIRE(tuning.getRatioForIntegralKey(key) >= bound1[key]); + REQUIRE(tuning.getRatioForIntegralKey(key) <= bound2[key]); + } else { + REQUIRE(tuning.getRatioForIntegralKey(key) <= bound1[key]); + REQUIRE(tuning.getRatioForIntegralKey(key) >= bound2[key]); + } + } + } +} + +