From 6b906192de7d8498e188ef90c4a17619b2be9c33 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Thu, 11 Jun 2020 20:39:04 +0200 Subject: [PATCH 01/19] Stretch tuning demo --- src/sfizz/Tuning.cpp | 73 +++++++++++++++++ src/sfizz/Tuning.h | 12 +++ src/sfizz/railsback/2-1.h | 135 +++++++++++++++++++++++++++++++ src/sfizz/railsback/4-1.h | 135 +++++++++++++++++++++++++++++++ src/sfizz/railsback/4-2.h | 135 +++++++++++++++++++++++++++++++ tests/CMakeLists.txt | 4 + tests/DemoStretchTuning.cpp | 154 ++++++++++++++++++++++++++++++++++++ tests/DemoStretchTuning.ui | 68 ++++++++++++++++ 8 files changed, 716 insertions(+) create mode 100644 src/sfizz/railsback/2-1.h create mode 100644 src/sfizz/railsback/4-1.h create mode 100644 src/sfizz/railsback/4-2.h create mode 100644 tests/DemoStretchTuning.cpp create mode 100644 tests/DemoStretchTuning.ui diff --git a/src/sfizz/Tuning.cpp b/src/sfizz/Tuning.cpp index 4f14acb8e..2d4b3d99a 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 @@ -248,5 +249,77 @@ 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 int num_curves = 3; + const float* curves[] = {railsback21, railsback41, railsback42}; + const float points[] = {0.25f, 0.5f, 1.0f}; + + // + 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 507bf44f9..35793f0b9 100644 --- a/src/sfizz/Tuning.h +++ b/src/sfizz/Tuning.h @@ -71,4 +71,16 @@ class Tuning { 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/railsback/2-1.h b/src/sfizz/railsback/2-1.h new file mode 100644 index 000000000..cc4a3e54c --- /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 000000000..34d93725b --- /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 000000000..52825f2cd --- /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/tests/CMakeLists.txt b/tests/CMakeLists.txt index af4981dc2..c71cee5b5 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 000000000..6604f1510 --- /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 000000000..126ac256e --- /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 + + + + + + + + + + + + From 1ba7e6deb23c229b7a4510e87bf28e86de5e5ef6 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Thu, 11 Jun 2020 22:22:47 +0200 Subject: [PATCH 02/19] Add stretch tuning API and implement --- src/sfizz.h | 9 +++++++++ src/sfizz.hpp | 8 ++++++++ src/sfizz/Resources.h | 2 ++ src/sfizz/Synth.cpp | 11 +++++++++++ src/sfizz/Synth.h | 6 ++++++ src/sfizz/Voice.cpp | 5 +++++ src/sfizz/sfizz.cpp | 5 +++++ src/sfizz/sfizz_wrapper.cpp | 6 ++++++ 8 files changed, 52 insertions(+) diff --git a/src/sfizz.h b/src/sfizz.h index e0a0c0a2f..69bff64b6 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 a57a5a4dc..8dbffc365 100644 --- a/src/sfizz.hpp +++ b/src/sfizz.hpp @@ -119,6 +119,14 @@ class SFIZZ_EXPORTED_API Sfizz */ 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 c998eb3cb..933dde27e 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 2718a1e10..50e9a569f 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 0819880e3..843307341 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -150,6 +150,12 @@ class Synth final : public Parser::Listener { * @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/Voice.cpp b/src/sfizz/Voice.cpp index 6ae90db2e..9f6df3a31 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/sfizz.cpp b/src/sfizz/sfizz.cpp index 31047c775..b110b4e1a 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 86ef1b67e..15310942a 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); From 2895a2bfee782ebb5d9c7a6879ffaff2f181fa0f Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Thu, 11 Jun 2020 22:37:08 +0200 Subject: [PATCH 03/19] Implement stretch tuning in LV2 --- lv2/sfizz.c | 19 +++++++++++++++++++ lv2/sfizz.ttl.in | 11 +++++++++++ 2 files changed, 30 insertions(+) diff --git a/lv2/sfizz.c b/lv2/sfizz.c index 69784a854..52815295c 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 4e52bcfe9..189849baf 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 ] . From f0f4c34ad8e52f2624b7db608d87c158d38aaee2 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Sun, 17 May 2020 21:42:57 +0200 Subject: [PATCH 04/19] Numbering of regions and voices --- src/sfizz/Region.h | 14 ++++++++++-- src/sfizz/Synth.cpp | 5 +++-- src/sfizz/Voice.cpp | 4 ++-- src/sfizz/Voice.h | 12 +++++++++- tests/RegionActivationT.cpp | 2 +- tests/RegionT.cpp | 4 ++-- tests/RegionTriggersT.cpp | 4 ++-- tests/RegionValueComputationsT.cpp | 36 +++++++++++++++--------------- 8 files changed, 51 insertions(+), 30 deletions(-) diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index 5ce49ff84..9b6e01b27 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -35,8 +35,8 @@ namespace sfz { * */ struct Region { - Region(const MidiState& midiState, absl::string_view defaultPath = "") - : midiState(midiState), defaultPath(std::move(defaultPath)) + Region(int regionNumber, const MidiState& midiState, absl::string_view defaultPath = "") + : regionNumber(regionNumber), midiState(midiState), defaultPath(std::move(defaultPath)) { ccSwitched.set(); @@ -46,6 +46,14 @@ struct Region { Region(const Region&) = default; ~Region() = default; + /** + * @brief Get the number which identifies this region, also its index + */ + int getIdNumber() const noexcept + { + return regionNumber; + } + /** * @brief Triggers on release? * @@ -238,6 +246,8 @@ struct Region { */ float getGainToEffectBus(unsigned number) const noexcept; + const int regionNumber {}; + // Sound source: sample playback FileId sampleId {}; // Sample int sampleQuality { Default::sampleQuality }; diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 2718a1e10..b46a18b6f 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -95,7 +95,8 @@ void sfz::Synth::onParseWarning(const SourceRange& range, const std::string& mes void sfz::Synth::buildRegion(const std::vector& regionOpcodes) { - auto lastRegion = absl::make_unique(resources.midiState, defaultPath); + int regionNumber = static_cast(regions.size()); + auto lastRegion = absl::make_unique(regionNumber, resources.midiState, defaultPath); auto parseOpcodes = [&](const std::vector& opcodes) { for (auto& opcode : opcodes) { @@ -1094,7 +1095,7 @@ void sfz::Synth::resetVoices(int numVoices) voices.reserve(numVoices); for (int i = 0; i < numVoices; ++i) - voices.push_back(absl::make_unique(resources)); + voices.push_back(absl::make_unique(i, resources)); voiceViewArray.clear(); voiceViewArray.reserve(numVoices); diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 6ae90db2e..2f3ebe05f 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -14,8 +14,8 @@ #include "Interpolators.h" #include "absl/algorithm/container.h" -sfz::Voice::Voice(sfz::Resources& resources) -: resources(resources) +sfz::Voice::Voice(int voiceNumber, sfz::Resources& resources) +: voiceNumber(voiceNumber), resources(resources) { filters.reserve(config::filtersPerVoice); equalizers.reserve(config::eqsPerVoice); diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index a709eb58d..0c7937c78 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -32,14 +32,22 @@ class Voice { /** * @brief Construct a new voice with the midistate singleton * + * @param voiceNumber * @param midiState */ - Voice(Resources& resources); + Voice(int voiceNumber, Resources& resources); enum class TriggerType { NoteOn, NoteOff, CC }; + /** + * @brief Get the number which identifies this voice, its index + */ + int getIdNumber() const noexcept + { + return voiceNumber; + } /** * @brief Change the sample rate of the voice. This is used to compute all * pitch related transformations so it needs to be propagated from the synth @@ -270,6 +278,8 @@ class Voice { void setupOscillatorUnison(); void updateChannelPowers(AudioSpan buffer); + const int voiceNumber {}; + Region* region { nullptr }; enum class State { diff --git a/tests/RegionActivationT.cpp b/tests/RegionActivationT.cpp index e65934b7c..41990934c 100644 --- a/tests/RegionActivationT.cpp +++ b/tests/RegionActivationT.cpp @@ -13,7 +13,7 @@ using namespace sfz::literals; TEST_CASE("Region activation", "Region tests") { sfz::MidiState midiState; - sfz::Region region { midiState }; + sfz::Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); SECTION("Basic state") diff --git a/tests/RegionT.cpp b/tests/RegionT.cpp index db2c9511a..c683cd96c 100644 --- a/tests/RegionT.cpp +++ b/tests/RegionT.cpp @@ -14,7 +14,7 @@ using namespace sfz::literals; TEST_CASE("[Region] Parsing opcodes") { sfz::MidiState midiState; - sfz::Region region { midiState }; + sfz::Region region { 0, midiState }; SECTION("sample") { @@ -1672,7 +1672,7 @@ TEST_CASE("[Region] Parsing opcodes") TEST_CASE("[Region] Non-conforming floating point values in integer opcodes") { sfz::MidiState midiState; - sfz::Region region { midiState }; + sfz::Region region { 0, midiState }; region.parseOpcode({ "offset", "2014.5" }); REQUIRE(region.offset == 2014); region.parseOpcode({ "pitch_keytrack", "-2.1" }); diff --git a/tests/RegionTriggersT.cpp b/tests/RegionTriggersT.cpp index 75f5d9b5d..4ee56cde9 100644 --- a/tests/RegionTriggersT.cpp +++ b/tests/RegionTriggersT.cpp @@ -13,7 +13,7 @@ using namespace sfz::literals; TEST_CASE("Basic triggers", "Region triggers") { sfz::MidiState midiState; - sfz::Region region { midiState }; + sfz::Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); SECTION("key") @@ -130,7 +130,7 @@ TEST_CASE("Basic triggers", "Region triggers") TEST_CASE("Legato triggers", "Region triggers") { sfz::MidiState midiState; - sfz::Region region { midiState }; + sfz::Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); SECTION("First note playing") { diff --git a/tests/RegionValueComputationsT.cpp b/tests/RegionValueComputationsT.cpp index e6547b01a..50deb2317 100644 --- a/tests/RegionValueComputationsT.cpp +++ b/tests/RegionValueComputationsT.cpp @@ -17,7 +17,7 @@ constexpr int numRandomTests { 64 }; TEST_CASE("[Region] Crossfade in on key") { sfz::MidiState midiState; - sfz::Region region { midiState }; + sfz::Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfin_lokey", "1" }); region.parseOpcode({ "xfin_hikey", "3" }); @@ -29,7 +29,7 @@ TEST_CASE("[Region] Crossfade in on key") TEST_CASE("[Region] Crossfade in on key - 2") { sfz::MidiState midiState; - sfz::Region region { midiState }; + sfz::Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfin_lokey", "1" }); region.parseOpcode({ "xfin_hikey", "5" }); @@ -44,7 +44,7 @@ TEST_CASE("[Region] Crossfade in on key - 2") TEST_CASE("[Region] Crossfade in on key - gain") { sfz::MidiState midiState; - sfz::Region region { midiState }; + sfz::Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfin_lokey", "1" }); region.parseOpcode({ "xfin_hikey", "5" }); @@ -59,7 +59,7 @@ TEST_CASE("[Region] Crossfade in on key - gain") TEST_CASE("[Region] Crossfade out on key") { sfz::MidiState midiState; - sfz::Region region { midiState }; + sfz::Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfout_lokey", "51" }); region.parseOpcode({ "xfout_hikey", "55" }); @@ -75,7 +75,7 @@ TEST_CASE("[Region] Crossfade out on key") TEST_CASE("[Region] Crossfade out on key - gain") { sfz::MidiState midiState; - sfz::Region region { midiState }; + sfz::Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfout_lokey", "51" }); region.parseOpcode({ "xfout_hikey", "55" }); @@ -92,7 +92,7 @@ TEST_CASE("[Region] Crossfade out on key - gain") TEST_CASE("[Region] Crossfade in on velocity") { sfz::MidiState midiState; - sfz::Region region { midiState }; + sfz::Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfin_lovel", "20" }); region.parseOpcode({ "xfin_hivel", "24" }); @@ -109,7 +109,7 @@ TEST_CASE("[Region] Crossfade in on velocity") TEST_CASE("[Region] Crossfade in on vel - gain") { sfz::MidiState midiState; - sfz::Region region { midiState }; + sfz::Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfin_lovel", "20" }); region.parseOpcode({ "xfin_hivel", "24" }); @@ -127,7 +127,7 @@ TEST_CASE("[Region] Crossfade in on vel - gain") TEST_CASE("[Region] Crossfade out on vel") { sfz::MidiState midiState; - sfz::Region region { midiState }; + sfz::Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfout_lovel", "51" }); region.parseOpcode({ "xfout_hivel", "55" }); @@ -144,7 +144,7 @@ TEST_CASE("[Region] Crossfade out on vel") TEST_CASE("[Region] Crossfade out on vel - gain") { sfz::MidiState midiState; - sfz::Region region { midiState }; + sfz::Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfout_lovel", "51" }); region.parseOpcode({ "xfout_hivel", "55" }); @@ -162,7 +162,7 @@ TEST_CASE("[Region] Crossfade out on vel - gain") TEST_CASE("[Region] Crossfade in on CC") { sfz::MidiState midiState; - sfz::Region region { midiState }; + sfz::Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfin_locc24", "20" }); region.parseOpcode({ "xfin_hicc24", "24" }); @@ -186,7 +186,7 @@ TEST_CASE("[Region] Crossfade in on CC") TEST_CASE("[Region] Crossfade in on CC - gain") { sfz::MidiState midiState; - sfz::Region region { midiState }; + sfz::Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfin_locc24", "20" }); region.parseOpcode({ "xfin_hicc24", "24" }); @@ -210,7 +210,7 @@ TEST_CASE("[Region] Crossfade in on CC - gain") TEST_CASE("[Region] Crossfade out on CC") { sfz::MidiState midiState; - sfz::Region region { midiState }; + sfz::Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfout_locc24", "20" }); region.parseOpcode({ "xfout_hicc24", "24" }); @@ -234,7 +234,7 @@ TEST_CASE("[Region] Crossfade out on CC") TEST_CASE("[Region] Crossfade out on CC - gain") { sfz::MidiState midiState; - sfz::Region region { midiState }; + sfz::Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfout_locc24", "20" }); region.parseOpcode({ "xfout_hicc24", "24" }); @@ -259,7 +259,7 @@ TEST_CASE("[Region] Crossfade out on CC - gain") TEST_CASE("[Region] Velocity bug for extreme values - veltrack at 0") { sfz::MidiState midiState; - sfz::Region region { midiState }; + sfz::Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "amp_veltrack", "0" }); REQUIRE(region.getNoteGain(64, 127_norm) == 1.0_a); @@ -270,7 +270,7 @@ TEST_CASE("[Region] Velocity bug for extreme values - veltrack at 0") TEST_CASE("[Region] Velocity bug for extreme values - positive veltrack") { sfz::MidiState midiState; - sfz::Region region { midiState }; + sfz::Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "amp_veltrack", "100" }); REQUIRE(region.getNoteGain(64, 127_norm) == 1.0_a); @@ -280,7 +280,7 @@ TEST_CASE("[Region] Velocity bug for extreme values - positive veltrack") TEST_CASE("[Region] Velocity bug for extreme values - negative veltrack") { sfz::MidiState midiState; - sfz::Region region { midiState }; + sfz::Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "amp_veltrack", "-100" }); REQUIRE(region.getNoteGain(64, 127_norm) == Approx(0.0).margin(0.0001)); @@ -291,7 +291,7 @@ TEST_CASE("[Region] rt_decay") { sfz::MidiState midiState; midiState.setSampleRate(1000); - sfz::Region region { midiState }; + sfz::Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "trigger", "release" }); region.parseOpcode({ "rt_decay", "10" }); @@ -311,7 +311,7 @@ TEST_CASE("[Region] rt_decay") TEST_CASE("[Region] Base delay") { sfz::MidiState midiState; - sfz::Region region{ midiState }; + sfz::Region region { 0, midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "delay", "10" }); REQUIRE( region.getDelay() == 10.0f ); From 2c3314d0adbc0e5acb0fad65fb0c5d5f5a569898 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Mon, 18 May 2020 11:59:31 +0200 Subject: [PATCH 05/19] Add a listener of voice state changes --- src/sfizz/Synth.cpp | 14 ++++++++++++-- src/sfizz/Synth.h | 8 +++++++- src/sfizz/Voice.cpp | 16 +++++++++++++--- src/sfizz/Voice.h | 26 ++++++++++++++++++++++---- 4 files changed, 54 insertions(+), 10 deletions(-) diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index b46a18b6f..2c889bf0c 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -44,6 +44,13 @@ sfz::Synth::~Synth() resources.filePool.emptyFileLoadingQueues(); } +void sfz::Synth::onVoiceStateChanged(int idNumber, Voice::State state) +{ + (void)idNumber; + (void)state; + DBG("Voice " << idNumber << ": state " << static_cast(state)); +} + void sfz::Synth::onParseFullBlock(const std::string& header, const std::vector& members) { switch (hash(header)) { @@ -1094,8 +1101,11 @@ void sfz::Synth::resetVoices(int numVoices) voices.clear(); voices.reserve(numVoices); - for (int i = 0; i < numVoices; ++i) - voices.push_back(absl::make_unique(i, resources)); + for (int i = 0; i < numVoices; ++i) { + auto voice = absl::make_unique(i, resources); + voice->setStateListener(this); + voices.emplace_back(std::move(voice)); + } voiceViewArray.clear(); voiceViewArray.reserve(numVoices); diff --git a/src/sfizz/Synth.h b/src/sfizz/Synth.h index 0819880e3..883fedf46 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -60,7 +60,7 @@ namespace sfz { * The jack_client.cpp file contains examples of the most classical usage of the * synth and can be used as a reference. */ -class Synth final : public Parser::Listener { +class Synth final : public Voice::StateListener, public Parser::Listener { public: /** * @brief Construct a new Synth object with no voices. If you want sound @@ -493,6 +493,12 @@ class Synth final : public Parser::Listener { */ const std::vector& getCCLabels() const noexcept { return ccLabels; } +protected: + /** + * @brief The voice callback which is called during a change of state. + */ + void onVoiceStateChanged(int idNumber, Voice::State state) override; + protected: /** * @brief The parser callback; this is called by the parent object each time diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 2f3ebe05f..fc65892c7 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -15,7 +15,7 @@ #include "absl/algorithm/container.h" sfz::Voice::Voice(int voiceNumber, sfz::Resources& resources) -: voiceNumber(voiceNumber), resources(resources) +: voiceNumber(voiceNumber), stateListener(nullptr), resources(resources) { filters.reserve(config::filtersPerVoice); equalizers.reserve(config::eqsPerVoice); @@ -36,7 +36,7 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, float value, triggerValue = value; this->region = region; - state = State::playing; + switchState(State::playing); ASSERT(delay >= 0); if (delay < 0) @@ -647,7 +647,7 @@ bool sfz::Voice::checkOffGroup(int delay, uint32_t group) noexcept void sfz::Voice::reset() noexcept { - state = State::idle; + switchState(State::idle); region = nullptr; currentPromise.reset(); sourcePosition = 0; @@ -766,3 +766,13 @@ void sfz::Voice::updateChannelPowers(AudioSpan buffer) channelEnvelopeFilters[i].tickLowpass(std::abs(input[s])); } } + + +void sfz::Voice::switchState(State s) +{ + if (s != state) { + state = s; + if (stateListener) + stateListener->onVoiceStateChanged(voiceNumber, s); + } +} diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index 0c7937c78..a9b347d27 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -48,6 +48,22 @@ class Voice { { return voiceNumber; } + + enum class State { + idle, + playing + }; + + class StateListener { + public: + virtual void onVoiceStateChanged(int /*idNumber*/, State /*state*/) {} + }; + + /** + * @brief Sets the listener which is called when the voice state changes. + */ + void setStateListener(StateListener *l) noexcept { stateListener = l; } + /** * @brief Change the sample rate of the voice. This is used to compute all * pitch related transformations so it needs to be propagated from the synth @@ -278,14 +294,16 @@ class Voice { void setupOscillatorUnison(); void updateChannelPowers(AudioSpan buffer); + /** + * @brief Modify the voice state and notify any listeners. + */ + void switchState(State s); + const int voiceNumber {}; + StateListener* stateListener = nullptr; Region* region { nullptr }; - enum class State { - idle, - playing - }; State state { State::idle }; bool noteIsOff { false }; From c239629be1079a2d74b9c6ac83ac3783ad581df7 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Sat, 30 May 2020 17:54:21 +0200 Subject: [PATCH 06/19] Initialize id number to -1, do not consider it as index --- src/sfizz/Region.h | 4 ++-- src/sfizz/Voice.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index 9b6e01b27..b18b7bd57 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -47,7 +47,7 @@ struct Region { ~Region() = default; /** - * @brief Get the number which identifies this region, also its index + * @brief Get the number which identifies this region */ int getIdNumber() const noexcept { @@ -246,7 +246,7 @@ struct Region { */ float getGainToEffectBus(unsigned number) const noexcept; - const int regionNumber {}; + const int regionNumber { -1 }; // Sound source: sample playback FileId sampleId {}; // Sample diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index a9b347d27..59058c593 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -42,7 +42,7 @@ class Voice { CC }; /** - * @brief Get the number which identifies this voice, its index + * @brief Get the number which identifies this voice */ int getIdNumber() const noexcept { @@ -299,7 +299,7 @@ class Voice { */ void switchState(State s); - const int voiceNumber {}; + const int voiceNumber { -1 }; StateListener* stateListener = nullptr; Region* region { nullptr }; From e07912f7ce5b01aa6c1df525a11994599e61b790 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Thu, 11 Jun 2020 23:50:41 +0200 Subject: [PATCH 07/19] Implement the identifier in a better and more safe way --- src/sfizz/NumericId.h | 31 +++++++++++++++++++++++++++++++ src/sfizz/Region.h | 9 +++++---- src/sfizz/Synth.cpp | 6 +++--- src/sfizz/Synth.h | 2 +- src/sfizz/Voice.cpp | 4 ++-- src/sfizz/Voice.h | 12 +++++++----- 6 files changed, 49 insertions(+), 15 deletions(-) create mode 100644 src/sfizz/NumericId.h diff --git a/src/sfizz/NumericId.h b/src/sfizz/NumericId.h new file mode 100644 index 000000000..1d975ec03 --- /dev/null +++ b/src/sfizz/NumericId.h @@ -0,0 +1,31 @@ +// 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 + +#pragma once + +/** + * @brief Numeric identifier + * + * It is a generic numeric identifier. The template wrapper serves to enforce a + * stronger compile-time check, such that one kind of identifier can't be + * mistaken for another kind, or for an unrelated integer such as an index. + */ +template +struct NumericId { + constexpr NumericId() = default; + + explicit constexpr NumericId(int number) + : number(number) + { + } + + constexpr bool valid() const noexcept + { + return number != -1; + } + + const int number = -1; +}; diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index b18b7bd57..ad81f97e3 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -16,6 +16,7 @@ #include "AudioBuffer.h" #include "MidiState.h" #include "FileId.h" +#include "NumericId.h" #include "absl/types/optional.h" #include #include @@ -36,7 +37,7 @@ namespace sfz { */ struct Region { Region(int regionNumber, const MidiState& midiState, absl::string_view defaultPath = "") - : regionNumber(regionNumber), midiState(midiState), defaultPath(std::move(defaultPath)) + : id{regionNumber}, midiState(midiState), defaultPath(std::move(defaultPath)) { ccSwitched.set(); @@ -49,9 +50,9 @@ struct Region { /** * @brief Get the number which identifies this region */ - int getIdNumber() const noexcept + NumericId getId() const noexcept { - return regionNumber; + return id; } /** @@ -246,7 +247,7 @@ struct Region { */ float getGainToEffectBus(unsigned number) const noexcept; - const int regionNumber { -1 }; + const NumericId id; // Sound source: sample playback FileId sampleId {}; // Sample diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 2c889bf0c..c609d3c50 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -44,11 +44,11 @@ sfz::Synth::~Synth() resources.filePool.emptyFileLoadingQueues(); } -void sfz::Synth::onVoiceStateChanged(int idNumber, Voice::State state) +void sfz::Synth::onVoiceStateChanged(NumericId id, Voice::State state) { - (void)idNumber; + (void)id; (void)state; - DBG("Voice " << idNumber << ": state " << static_cast(state)); + DBG("Voice " << id.number << ": state " << static_cast(state)); } void sfz::Synth::onParseFullBlock(const std::string& header, const std::vector& members) diff --git a/src/sfizz/Synth.h b/src/sfizz/Synth.h index 883fedf46..d04a5c963 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -497,7 +497,7 @@ class Synth final : public Voice::StateListener, public Parser::Listener { /** * @brief The voice callback which is called during a change of state. */ - void onVoiceStateChanged(int idNumber, Voice::State state) override; + void onVoiceStateChanged(NumericId idNumber, Voice::State state) override; protected: /** diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index fc65892c7..37dafdde6 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -15,7 +15,7 @@ #include "absl/algorithm/container.h" sfz::Voice::Voice(int voiceNumber, sfz::Resources& resources) -: voiceNumber(voiceNumber), stateListener(nullptr), resources(resources) +: id{voiceNumber}, stateListener(nullptr), resources(resources) { filters.reserve(config::filtersPerVoice); equalizers.reserve(config::eqsPerVoice); @@ -773,6 +773,6 @@ void sfz::Voice::switchState(State s) if (s != state) { state = s; if (stateListener) - stateListener->onVoiceStateChanged(voiceNumber, s); + stateListener->onVoiceStateChanged(id, s); } } diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index 59058c593..515feb128 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -14,6 +14,7 @@ #include "AudioSpan.h" #include "LeakDetector.h" #include "OnePoleFilter.h" +#include "NumericId.h" #include "absl/types/span.h" #include #include @@ -41,12 +42,13 @@ class Voice { NoteOff, CC }; + /** - * @brief Get the number which identifies this voice + * @brief Get the unique identifier of this voice in a synth */ - int getIdNumber() const noexcept + NumericId getId() const noexcept { - return voiceNumber; + return id; } enum class State { @@ -56,7 +58,7 @@ class Voice { class StateListener { public: - virtual void onVoiceStateChanged(int /*idNumber*/, State /*state*/) {} + virtual void onVoiceStateChanged(NumericId /*id*/, State /*state*/) {} }; /** @@ -299,7 +301,7 @@ class Voice { */ void switchState(State s); - const int voiceNumber { -1 }; + const NumericId id; StateListener* stateListener = nullptr; Region* region { nullptr }; From 37ef2b99429d6721d9e7f5872346066df1512d1d Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Thu, 11 Jun 2020 23:52:17 +0200 Subject: [PATCH 08/19] Update benchmarks --- benchmarks/BM_ADSR.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks/BM_ADSR.cpp b/benchmarks/BM_ADSR.cpp index e2d077c05..c17a1a313 100644 --- a/benchmarks/BM_ADSR.cpp +++ b/benchmarks/BM_ADSR.cpp @@ -35,7 +35,7 @@ class EnvelopeFixture : public benchmark::Fixture } sfz::MidiState midiState; - sfz::Region region{midiState}; + sfz::Region region{0, midiState}; sfz::ADSREnvelope envelope; std::vector output; }; From 2c10173356c42707612ebfa93fefd0a0fc8658fb Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 12 Jun 2020 00:26:02 +0200 Subject: [PATCH 09/19] Add the lookup functions by identifier --- src/sfizz/NumericId.h | 10 ++++++++++ src/sfizz/Synth.cpp | 34 ++++++++++++++++++++++++++++++++++ src/sfizz/Synth.h | 14 ++++++++++++++ 3 files changed, 58 insertions(+) diff --git a/src/sfizz/NumericId.h b/src/sfizz/NumericId.h index 1d975ec03..b2fed5017 100644 --- a/src/sfizz/NumericId.h +++ b/src/sfizz/NumericId.h @@ -27,5 +27,15 @@ struct NumericId { return number != -1; } + constexpr bool operator==(NumericId other) const noexcept + { + return number == other.number; + } + + constexpr bool operator!=(NumericId other) const noexcept + { + return number != other.number; + } + const int number = -1; }; diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index c609d3c50..001dae481 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -1056,6 +1056,40 @@ const sfz::EffectBus* sfz::Synth::getEffectBusView(int idx) const noexcept return (size_t)idx < effectBuses.size() ? effectBuses[idx].get() : nullptr; } +const sfz::Region* sfz::Synth::getRegionById(NumericId id) const noexcept +{ + const size_t size = regions.size(); + + if (size == 0 || !id.valid()) + return nullptr; + + // search a sequence of ordered identifiers with potential gaps + size_t index = static_cast(id.number); + index = std::min(index, size - 1); + + while (index > 0 && regions[index]->getId().number > id.number) + --index; + + return (regions[index]->getId() == id) ? regions[index].get() : nullptr; +} + +const sfz::Voice* sfz::Synth::getVoiceById(NumericId id) const noexcept +{ + const size_t size = voices.size(); + + if (size == 0 || !id.valid()) + return nullptr; + + // search a sequence of ordered identifiers with potential gaps + size_t index = static_cast(id.number); + index = std::min(index, size - 1); + + while (index > 0 && voices[index]->getId().number > id.number) + --index; + + return (voices[index]->getId() == id) ? voices[index].get() : nullptr; +} + const sfz::Voice* sfz::Synth::getVoiceView(int idx) const noexcept { return (size_t)idx < voices.size() ? voices[idx].get() : nullptr; diff --git a/src/sfizz/Synth.h b/src/sfizz/Synth.h index d04a5c963..9c5420e90 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -178,6 +178,20 @@ class Synth final : public Voice::StateListener, public Parser::Listener { * @brief Export a MIDI Name document describing the loaded instrument */ std::string exportMidnam(absl::string_view model = {}) const; + /** + * @brief Find the region which is associated with the given identifier. + * + * @param id + * @return const Region* + */ + const Region* getRegionById(NumericId id) const noexcept; + /** + * @brief Find the voice which is associated with the given identifier. + * + * @param id + * @return const Voice* + */ + const Voice* getVoiceById(NumericId id) const noexcept; /** * @brief Get a raw view into a specific region. This is mostly used * for testing. From 215077deaace5171b4723aa875631556759eaad1 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 12 Jun 2020 00:53:22 +0200 Subject: [PATCH 10/19] Make region removal order-preserving --- src/sfizz/Synth.cpp | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 001dae481..7c535aa6a 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -361,22 +361,20 @@ void sfz::Synth::finalizeSfzLoad() { resources.filePool.setRootDirectory(parser.originalDirectory()); - auto currentRegion = regions.begin(); - auto lastRegion = regions.rbegin(); - auto removeCurrentRegion = [¤tRegion, &lastRegion]() { - if (currentRegion->get() == nullptr) - return; - - DBG("Removing the region with sample " << currentRegion->get()->sampleId); - std::iter_swap(currentRegion, lastRegion); - ++lastRegion; + size_t currentRegionIndex = 0; + size_t currentRegionCount = regions.size(); + + auto removeCurrentRegion = [this, ¤tRegionIndex, ¤tRegionCount]() { + DBG("Removing the region with sample " << regions[currentRegionIndex]->sampleId); + regions.erase(regions.begin() + currentRegionIndex); + --currentRegionCount; }; size_t maxFilters { 0 }; size_t maxEQs { 0 }; - while (currentRegion < lastRegion.base()) { - auto region = currentRegion->get(); + while (currentRegionIndex < currentRegionCount) { + auto region = regions[currentRegionIndex].get(); if (!region->oscillator && !region->isGenerator()) { if (!resources.filePool.checkSampleId(region->sampleId)) { @@ -481,11 +479,10 @@ void sfz::Synth::finalizeSfzLoad() maxFilters = max(maxFilters, region->filters.size()); maxEQs = max(maxEQs, region->equalizers.size()); - ++currentRegion; + ++currentRegionIndex; } - const auto remainingRegions = std::distance(regions.begin(), lastRegion.base()); - DBG("Removing " << (regions.size() - remainingRegions) << " out of " << regions.size() << " regions"); - regions.resize(remainingRegions); + DBG("Removing " << (regions.size() - currentRegionCount) << " out of " << regions.size() << " regions"); + regions.resize(currentRegionCount); modificationTime = checkModificationTime(); for (auto& voice : voices) { From 0bbb1e930f1d0c889b032f673242297fb636339e Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 12 Jun 2020 00:53:36 +0200 Subject: [PATCH 11/19] Add test --- tests/SynthT.cpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/SynthT.cpp b/tests/SynthT.cpp index e59df54f0..112acb0dd 100644 --- a/tests/SynthT.cpp +++ b/tests/SynthT.cpp @@ -6,6 +6,7 @@ #include "sfizz/Synth.h" #include "sfizz/SfzHelpers.h" +#include "sfizz/NumericId.h" #include "catch2/catch.hpp" using namespace Catch::literals; using namespace sfz::literals; @@ -462,3 +463,26 @@ TEST_CASE("[Synth] velcurve") REQUIRE( synth.getRegionView(1)->velocityCurve(96_norm) == Approx(0.5f).margin(1e-2) ); REQUIRE( synth.getRegionView(1)->velocityCurve(127_norm) == 0.0_a ); } + +TEST_CASE("[Synth] Region by identifier") +{ + sfz::Synth synth; + synth.loadSfzString("regionByIdentifier.sfz", R"( +sample=*sine +sample=*sine +sample=doesNotExist.wav +sample=*sine +sample=doesNotExist.wav +sample=*sine +)"); + + REQUIRE(synth.getNumRegions() == 4); + REQUIRE(synth.getRegionView(0) == synth.getRegionById(NumericId{0})); + REQUIRE(synth.getRegionView(1) == synth.getRegionById(NumericId{1})); + REQUIRE(nullptr == synth.getRegionById(NumericId{2})); + REQUIRE(synth.getRegionView(2) == synth.getRegionById(NumericId{3})); + REQUIRE(nullptr == synth.getRegionById(NumericId{4})); + REQUIRE(synth.getRegionView(3) == synth.getRegionById(NumericId{5})); + REQUIRE(nullptr == synth.getRegionById(NumericId{6})); + REQUIRE(nullptr == synth.getRegionById(NumericId{})); +} From bdaf8f7f11d5461886280922a49655c569ec0426 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Fri, 12 Jun 2020 19:45:41 +0200 Subject: [PATCH 12/19] Debug message called an unknown variable (previous PR leftover) --- src/sfizz/Tuning.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sfizz/Tuning.cpp b/src/sfizz/Tuning.cpp index 2d4b3d99a..fa83ebca4 100644 --- a/src/sfizz/Tuning.cpp +++ b/src/sfizz/Tuning.cpp @@ -201,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; } From 5fa43a0efb44390a29ed5933cd156ae003f05e46 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Fri, 12 Jun 2020 20:04:21 +0200 Subject: [PATCH 13/19] Check the number of curves at compile time --- src/sfizz/Tuning.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/sfizz/Tuning.cpp b/src/sfizz/Tuning.cpp index fa83ebca4..36a8fbcd8 100644 --- a/src/sfizz/Tuning.cpp +++ b/src/sfizz/Tuning.cpp @@ -288,9 +288,9 @@ StretchTuning StretchTuning::createRailsbackFromRatio(float stretch) }; // known curves and their matching knob positions - const int num_curves = 3; - const float* curves[] = {railsback21, railsback41, railsback42}; - const float points[] = {0.25f, 0.5f, 1.0f}; + 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; @@ -319,7 +319,6 @@ StretchTuning StretchTuning::createRailsbackFromRatio(float stretch) // return createFromDetuneRatios(data); - } } // namespace sfz From 32d01ded03b896ee01caebb698dcee59805deb14 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Fri, 12 Jun 2020 20:20:01 +0200 Subject: [PATCH 14/19] Added some tests --- tests/TuningT.cpp | 106 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/tests/TuningT.cpp b/tests/TuningT.cpp index 76c94d675..430c031c2 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]); + } + } + } +} + + From ff34bb5ccb03df54032ae65a07d4c3bee64da75d Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 12 Jun 2020 17:53:15 +0200 Subject: [PATCH 15/19] Fix LV2 problems with the atom sequence - pop the frame when finished writing - add safety checks in case write space is short --- lv2/sfizz.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/lv2/sfizz.c b/lv2/sfizz.c index 52815295c..3aa123bd0 100644 --- a/lv2/sfizz.c +++ b/lv2/sfizz.c @@ -445,13 +445,17 @@ static void sfizz_lv2_send_file_path(sfizz_plugin_t *self, LV2_URID urid, const char *path) { LV2_Atom_Forge_Frame frame; - lv2_atom_forge_frame_time(&self->forge, 0); - lv2_atom_forge_object(&self->forge, &frame, 0, self->patch_set_uri); - lv2_atom_forge_key(&self->forge, self->patch_property_uri); - lv2_atom_forge_urid(&self->forge, urid); - lv2_atom_forge_key(&self->forge, self->patch_value_uri); - lv2_atom_forge_path(&self->forge, path, (uint32_t)strlen(path)); - lv2_atom_forge_pop(&self->forge, &frame); + + bool write_ok = + lv2_atom_forge_frame_time(&self->forge, 0) && + lv2_atom_forge_object(&self->forge, &frame, 0, self->patch_set_uri) && + lv2_atom_forge_key(&self->forge, self->patch_property_uri) && + lv2_atom_forge_urid(&self->forge, urid) && + lv2_atom_forge_key(&self->forge, self->patch_value_uri) && + lv2_atom_forge_path(&self->forge, path, (uint32_t)strlen(path)); + + if (write_ok) + lv2_atom_forge_pop(&self->forge, &frame); } @@ -775,6 +779,8 @@ run(LV2_Handle instance, uint32_t sample_count) { self->midnam->update(self->midnam->handle); } + + lv2_atom_forge_pop(&self->forge, &self->notify_frame); } static uint32_t From f15ed45035bf2e1b719da7b2bb3f2ddfeb7f9ca1 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 12 Jun 2020 21:40:57 +0200 Subject: [PATCH 16/19] Construct work atoms using a forge --- lv2/sfizz.c | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/lv2/sfizz.c b/lv2/sfizz.c index 3aa123bd0..e26dbd21d 100644 --- a/lv2/sfizz.c +++ b/lv2/sfizz.c @@ -114,6 +114,9 @@ typedef struct LV2_Atom_Forge forge; ///< Forge for writing atoms in run thread LV2_Atom_Forge_Frame notify_frame; ///< Cached for worker replies + // Atom forge + LV2_Atom_Forge forge_secondary; ///< Forge for writing into other buffers + // Logger LV2_Log_Logger logger; @@ -366,6 +369,7 @@ instantiate(const LV2_Descriptor *descriptor, // Initialize the forge lv2_atom_forge_init(&self->forge, self->map); + lv2_atom_forge_init(&self->forge_secondary, self->map); // Check the options for the block size and sample rate parameters if (options) @@ -491,28 +495,28 @@ sfizz_lv2_handle_atom_object(sfizz_plugin_t *self, const LV2_Atom_Object *obj) return; } + typedef struct + { + LV2_Atom atom; + char body[MAX_PATH_SIZE]; + } sfizz_path_atom_buffer_t; + if (key == self->sfizz_sfz_file_uri) { - const uint32_t original_atom_size = lv2_atom_total_size((const LV2_Atom *)atom); - const uint32_t null_terminated_atom_size = original_atom_size + 1; - char atom_buffer[MAX_PATH_SIZE]; - memcpy(&atom_buffer, atom, original_atom_size); - atom_buffer[original_atom_size] = 0; // Null terminate the string for safety - LV2_Atom *sfz_file_path = (LV2_Atom *)&atom_buffer; - sfz_file_path->type = self->sfizz_sfz_file_uri; - self->worker->schedule_work(self->worker->handle, null_terminated_atom_size, sfz_file_path); + LV2_Atom_Forge *forge = &self->forge_secondary; + sfizz_path_atom_buffer_t buffer; + lv2_atom_forge_set_buffer(forge, (uint8_t *)&buffer, sizeof(buffer)); + if (lv2_atom_forge_typed_string(forge, self->sfizz_sfz_file_uri, LV2_ATOM_BODY_CONST(atom), strnlen(LV2_ATOM_BODY_CONST(atom), atom->size))) + self->worker->schedule_work(self->worker->handle, lv2_atom_total_size(&buffer.atom), &buffer.atom); self->check_modification = false; } else if (key == self->sfizz_scala_file_uri) { - const uint32_t original_atom_size = lv2_atom_total_size((const LV2_Atom *)atom); - const uint32_t null_terminated_atom_size = original_atom_size + 1; - char atom_buffer[MAX_PATH_SIZE]; - memcpy(&atom_buffer, atom, original_atom_size); - atom_buffer[original_atom_size] = 0; // Null terminate the string for safety - LV2_Atom *scala_file_path = (LV2_Atom *)&atom_buffer; - scala_file_path->type = self->sfizz_scala_file_uri; - self->worker->schedule_work(self->worker->handle, null_terminated_atom_size, scala_file_path); + LV2_Atom_Forge *forge = &self->forge_secondary; + sfizz_path_atom_buffer_t buffer; + lv2_atom_forge_set_buffer(forge, (uint8_t *)&buffer, sizeof(buffer)); + if (lv2_atom_forge_typed_string(forge, self->sfizz_scala_file_uri, LV2_ATOM_BODY_CONST(atom), strnlen(LV2_ATOM_BODY_CONST(atom), atom->size))) + self->worker->schedule_work(self->worker->handle, lv2_atom_total_size(&buffer.atom), &buffer.atom); self->check_modification = false; } else From ba0716e7e190db4a6b1877a08a1fc3618e756e3b Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 12 Jun 2020 21:52:45 +0200 Subject: [PATCH 17/19] Make notify_frame local to run() --- lv2/sfizz.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lv2/sfizz.c b/lv2/sfizz.c index e26dbd21d..54b3f99b7 100644 --- a/lv2/sfizz.c +++ b/lv2/sfizz.c @@ -112,9 +112,6 @@ typedef struct // Atom forge LV2_Atom_Forge forge; ///< Forge for writing atoms in run thread - LV2_Atom_Forge_Frame notify_frame; ///< Cached for worker replies - - // Atom forge LV2_Atom_Forge forge_secondary; ///< Forge for writing into other buffers // Logger @@ -692,7 +689,9 @@ run(LV2_Handle instance, uint32_t sample_count) lv2_atom_forge_set_buffer(&self->forge, (uint8_t *)self->notify_port, notify_capacity); // Start a sequence in the notify output port. - lv2_atom_forge_sequence_head(&self->forge, &self->notify_frame, 0); + LV2_Atom_Forge_Frame notify_frame; + if (!lv2_atom_forge_sequence_head(&self->forge, ¬ify_frame, 0)) + assert(false); LV2_ATOM_SEQUENCE_FOREACH(self->control_port, ev) { @@ -784,7 +783,7 @@ run(LV2_Handle instance, uint32_t sample_count) self->midnam->update(self->midnam->handle); } - lv2_atom_forge_pop(&self->forge, &self->notify_frame); + lv2_atom_forge_pop(&self->forge, ¬ify_frame); } static uint32_t From 578dad574d8a7e175dc81bf7acd59354c8275e1d Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Sat, 13 Jun 2020 22:09:58 +0200 Subject: [PATCH 18/19] Build macOS in C++14, can fix runtime link issues --- .travis/script_osx.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis/script_osx.sh b/.travis/script_osx.sh index 0b271c84e..0385f8c3c 100755 --- a/.travis/script_osx.sh +++ b/.travis/script_osx.sh @@ -6,7 +6,7 @@ cmake -DCMAKE_BUILD_TYPE=Release \ -DSFIZZ_VST=ON \ -DSFIZZ_AU=ON \ -DSFIZZ_TESTS=OFF \ - -DCMAKE_CXX_STANDARD=17 \ + -DCMAKE_CXX_STANDARD=14 \ -DLV2PLUGIN_INSTALL_DIR=/Library/Audio/Plug-Ins/LV2 \ -DVSTPLUGIN_INSTALL_DIR=/Library/Audio/Plug-Ins/VST3 \ -DAUPLUGIN_INSTALL_DIR=/Library/Audio/Plug-Ins/Components \ From 399408923334cc1ef8deae22af8c2240ef2963d2 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Sat, 13 Jun 2020 16:55:25 -0700 Subject: [PATCH 19/19] Fix the bad macOS suffix in LV2 manifest template --- lv2/manifest.ttl.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lv2/manifest.ttl.in b/lv2/manifest.ttl.in index 1f954f015..3ea4e8838 100644 --- a/lv2/manifest.ttl.in +++ b/lv2/manifest.ttl.in @@ -3,5 +3,5 @@ <@LV2PLUGIN_URI@> a lv2:Plugin ; - lv2:binary <@PROJECT_NAME@@CMAKE_SHARED_LIBRARY_SUFFIX@> ; + lv2:binary <@PROJECT_NAME@@CMAKE_SHARED_MODULE_SUFFIX@> ; rdfs:seeAlso <@PROJECT_NAME@.ttl> .