1 #include <iostream> 2 #include <iomanip> 3 #include <sstream> 4 #include <algorithm> 5 6 #include "HeadlessUtils.h" 7 #include "Player.h" 8 9 #include "catch2/catch2.hpp" 10 11 #include "UnitTestUtilities.h" 12 #include "effect/ModControl.h" 13 14 using namespace Surge::Test; 15 16 TEST_CASE("ADSR Envelope Behaviour", "[mod]") 17 { 18 19 std::shared_ptr<SurgeSynthesizer> surge(Surge::Headless::createSurge(44100)); 20 REQUIRE(surge.get()); 21 22 /* 23 ** OK so lets set up a pretty simple setup 24 */ 25 26 auto runAdsr = [surge](float a, float d, float s, float r, int a_s, int d_s, int r_s, 27 bool isAnalog, float releaseAfter, float runUntil, float pushSusAt = -1, __anona43a01a10102(float a, float d, float s, float r, int a_s, int d_s, int r_s, bool isAnalog, float releaseAfter, float runUntil, float pushSusAt = -1, float pushSusTo = 0) 28 float pushSusTo = 0) { 29 auto *adsrstorage = &(surge->storage.getPatch().scene[0].adsr[0]); 30 std::shared_ptr<AdsrEnvelope> adsr(new AdsrEnvelope()); 31 adsr->init(&(surge->storage), adsrstorage, surge->storage.getPatch().scenedata[0], nullptr); 32 REQUIRE(adsr.get()); 33 34 int ids, ide; 35 setupStorageRanges(&(adsrstorage->a), &(adsrstorage->mode), ids, ide); 36 REQUIRE(ide > ids); 37 REQUIRE(ide >= 0); 38 REQUIRE(ids >= 0); 39 40 auto svn = [](Parameter *p, float vn) { 41 p->set_value_f01(p->value_to_normalized(limit_range(vn, p->val_min.f, p->val_max.f))); 42 }; 43 44 auto svni = [](Parameter *p, int vn) { p->val.i = vn; }; 45 46 auto inverseEnvtime = [](float desiredTime) { 47 // 2^x = desired time 48 auto x = log(desiredTime) / log(2.0); 49 return x; 50 }; 51 52 svn(&(adsrstorage->a), inverseEnvtime(a)); 53 svn(&(adsrstorage->d), inverseEnvtime(d)); 54 svn(&(adsrstorage->s), s); 55 svn(&(adsrstorage->r), inverseEnvtime(r)); 56 57 svni(&(adsrstorage->a_s), a_s); 58 svni(&(adsrstorage->d_s), d_s); 59 svni(&(adsrstorage->r_s), r_s); 60 61 adsrstorage->mode.val.b = isAnalog; 62 63 copyScenedataSubset(&(surge->storage), 0, ids, ide); 64 adsr->attack(); 65 66 bool released = false; 67 bool pushSus = false; 68 int i = 0; 69 std::vector<std::pair<float, float>> res; 70 res.push_back(std::make_pair(0.f, 0.f)); 71 72 while (true) 73 { 74 auto t = 1.0 * (i + 1) * BLOCK_SIZE * dsamplerate_inv; 75 i++; 76 if (t > runUntil || runUntil < 0) 77 break; 78 79 if (t > releaseAfter && !released) 80 { 81 adsr->release(); 82 released = true; 83 } 84 85 if (pushSusAt > 0 && !pushSus && t > pushSusAt) 86 { 87 pushSus = true; 88 svn(&(adsrstorage->s), pushSusTo); 89 copyScenedataSubset(&(surge->storage), 0, ids, ide); 90 } 91 92 adsr->process_block(); 93 res.push_back(std::make_pair((float)t, adsr->output)); 94 if (false && i > 270 && i < 290) 95 std::cout << i << " " << t << " " << adsr->output << " " << adsr->getEnvState() 96 << std::endl; 97 } 98 return res; 99 }; 100 __anona43a01a10502(std::vector<std::pair<float, float>> data) 101 auto detectTurnarounds = [](std::vector<std::pair<float, float>> data) { 102 auto pv = -1000.0; 103 int dir = 1; 104 std::vector<std::pair<float, int>> turns; 105 turns.push_back(std::make_pair(0, 1)); 106 for (auto &p : data) 107 { 108 auto t = p.first; 109 auto v = p.second; 110 if (pv >= 0) 111 { 112 int ldir = 0; 113 if (v > 0.999999f) 114 ldir = dir; // sometimes we get a double '1' 115 if (turns.size() > 1 && fabs(v - pv) < 5e-6 && fabs(v) < 1e-5) 116 ldir = 0; // bouncing off of 0 is annoying 117 else if (fabs(v - pv) < 1e-7) 118 ldir = 0; 119 else if (v > pv) 120 ldir = 1; 121 else 122 ldir = -1; 123 124 if (v != 1) 125 { 126 if (ldir != dir) 127 { 128 turns.push_back(std::make_pair(t, ldir)); 129 } 130 dir = ldir; 131 } 132 } 133 pv = v; 134 } 135 return turns; 136 }; 137 138 // With 0 sustain I should decay in decay time 139 auto runCompare = [&](float a, float d, float s, float r, int a_s, int d_s, int r_s, __anona43a01a10602(float a, float d, float s, float r, int a_s, int d_s, int r_s, bool isAnalog) 140 bool isAnalog) { 141 float sustime = 0.1; 142 float endtime = 0.1; 143 float totaltime = a + d + sustime + r + endtime; 144 145 auto simple = runAdsr(a, d, s, r, a_s, d_s, r_s, isAnalog, a + d + sustime, totaltime); 146 auto sturns = detectTurnarounds(simple); 147 INFO("ADSR: " << a << " " << d << " " << s << " " << r << " switches: " << a_s << " " << d_s 148 << " " << r_s); 149 if (s == 0) 150 { 151 if (sturns.size() != 3) 152 { 153 for (auto s : simple) 154 std::cout << s.first << " " << s.second << std::endl; 155 for (auto s : sturns) 156 std::cout << s.first << " " << s.second << std::endl; 157 } 158 REQUIRE(sturns.size() == 3); 159 REQUIRE(sturns[0].first == 0); 160 REQUIRE(sturns[0].second == 1); 161 REQUIRE(sturns[1].first == Approx(a).margin(0.01)); 162 REQUIRE(sturns[1].second == -1); 163 REQUIRE(sturns[2].first == Approx(a + d).margin(0.01)); 164 REQUIRE(sturns[2].second == 0); 165 } 166 else 167 { 168 if (sturns.size() != 5) 169 { 170 for (auto s : simple) 171 std::cout << s.first << " " << s.second << std::endl; 172 std::cout << "TURNS" << std::endl; 173 for (auto s : sturns) 174 std::cout << s.first << " " << s.second << std::endl; 175 } 176 REQUIRE(sturns.size() == 5); 177 REQUIRE(sturns[0].first == 0); 178 REQUIRE(sturns[0].second == 1); 179 REQUIRE(sturns[1].first == Approx(a).margin(0.01)); 180 REQUIRE(sturns[1].second == -1); 181 if (d_s == 0) 182 { 183 // this equality only holds in the linear case; in the polynomial case you get 184 // faster reach to non-zero sustain 185 REQUIRE(sturns[2].first == Approx(a + d * (1.0 - s)).margin(0.01)); 186 } 187 else if (a + d * (1.0 - s) > 0.1 && d > 0.05) 188 { 189 REQUIRE(sturns[2].first < a + d * (1.0 - s) + 0.01); 190 } 191 REQUIRE(sturns[2].second == 0); 192 REQUIRE(sturns[3].first == Approx(a + d + sustime).margin(0.01)); 193 REQUIRE(sturns[3].second == -1); 194 if (r_s == 0 || (s > 0.1 && r > 0.05)) // if we are in the non-linear releases at low 195 // sustain we get there early 196 { 197 REQUIRE(sturns[4].first == 198 Approx(a + d + sustime + r).margin((r_s == 0 ? 0.01 : (r * 0.1)))); 199 REQUIRE(sturns[4].second == 0); 200 } 201 } 202 }; 203 204 SECTION("Test the Digital Envelope") 205 { 206 for (int as = 0; as < 3; ++as) 207 for (int ds = 0; ds < 3; ++ds) 208 for (int rs = 0; rs < 3; ++rs) 209 { 210 runCompare(0.2, 0.3, 0.0, 0.1, as, ds, rs, false); 211 runCompare(0.2, 0.3, 0.5, 0.1, as, ds, rs, false); 212 213 for (int rc = 0; rc < 10; ++rc) 214 { 215 auto a = rand() * 1.0 / (float)RAND_MAX; 216 auto d = rand() * 1.0 / (float)RAND_MAX; 217 auto s = 0.8 * rand() * 1.0 / (float)RAND_MAX + 218 0.1; // we have tested the s=0 case above 219 auto r = rand() * 1.0 / (float)RAND_MAX; 220 runCompare(a, d, s, r, as, ds, rs, false); 221 } 222 } 223 } 224 225 SECTION("Quadrtic Digital hits Zero") 226 { 227 auto res = runAdsr(0.1, 0.1, 0.0, 0.1, 0, 1, 0, false, 0.4, 0.5); 228 for (auto p : res) 229 { 230 if (p.first > 0.22) 231 { 232 REQUIRE(p.second == 0.f); 233 } 234 } 235 } 236 237 SECTION("Test the Analog Envelope") 238 { 239 // OK so we can't check the same thing here since the turns aren't as tight in analog mode 240 // Also the analog ADSR sustains at half the given sustain. __anona43a01a10702(float a, float d, float s, float r) 241 auto testAnalog = [&](float a, float d, float s, float r) { 242 INFO("ANALOG " << a << " " << d << " " << s << " " << r); 243 auto holdFor = a + d + d + 0.5; 244 245 auto ae = runAdsr(a, d, s, r, 0, 0, 0, true, holdFor, holdFor + 4 * r); 246 auto aturns = detectTurnarounds(ae); 247 248 float maxt = 0, maxv = 0; 249 float zerot = 0; 250 float valAtRelEnd = -1; 251 std::vector<float> heldPeriod; 252 for (auto obs : ae) 253 { 254 // std::cout << obs.first << " " << obs.second << std::endl; 255 256 if (obs.first > a + d + d * 0.95 && obs.first < holdFor && 257 s > 0.05) // that 0.1 lets the delay ring off 258 { 259 REQUIRE(obs.second == Approx(s * s).margin(1e-3)); 260 heldPeriod.push_back(obs.second); 261 } 262 if (obs.first > a + d && obs.second < 5e-5 && zerot == 0) 263 zerot = obs.first; 264 if (obs.first > holdFor + r && valAtRelEnd < 0) 265 valAtRelEnd = obs.second; 266 267 if (obs.second > maxv) 268 { 269 maxv = obs.second; 270 maxt = obs.first; 271 } 272 } 273 274 // In the held period are we mostly constant 275 if (heldPeriod.size() > 10) 276 { 277 float sum = 0; 278 for (auto p : heldPeriod) 279 sum += p; 280 float mean = sum / heldPeriod.size(); 281 float var = 0; 282 for (auto p : heldPeriod) 283 var += (p - mean) * (p - mean); 284 var /= (heldPeriod.size() - 1); 285 float stddev = sqrt(var); 286 REQUIRE(stddev < d * 5e-3); 287 } 288 REQUIRE(maxt < a); 289 REQUIRE(maxv > 0.99); 290 if (s > 0.05) 291 { 292 REQUIRE(zerot > holdFor + r * 0.9); 293 REQUIRE(valAtRelEnd < s * 0.025); 294 } 295 }; 296 297 testAnalog(0.1, 0.2, 0.5, 0.1); 298 testAnalog(0.1, 0.2, 0.0, 0.1); 299 for (int rc = 0; rc < 50; ++rc) 300 { 301 auto a = rand() * 1.0 / (float)RAND_MAX + 0.03; 302 auto d = rand() * 1.0 / (float)RAND_MAX + 0.03; 303 auto s = 304 0.7 * rand() * 1.0 / (float)RAND_MAX + 0.2; // we have tested the s=0 case above 305 auto r = rand() * 1.0 / (float)RAND_MAX + 0.03; 306 testAnalog(a, d, s, r); 307 } 308 } 309 310 // This is just a rudiemntary little test of this in digital mode 311 SECTION("Test Digital Sus Push") 312 { __anona43a01a10802(float s1, float s2) 313 auto testSusPush = [&](float s1, float s2) { 314 auto digPush = runAdsr(0.05, 0.05, s1, 0.1, 0, 0, 0, false, 0.5, s2, 0.25, s2); 315 int obs = 0; 316 for (auto s : digPush) 317 { 318 if (s.first > 0.2 && obs == 0) 319 { 320 REQUIRE(s.second == Approx(s1).margin(1e-5)); 321 obs++; 322 } 323 if (s.first > 0.3 && obs == 1) 324 { 325 REQUIRE(s.second == Approx(s2).margin(1e-5)); 326 obs++; 327 } 328 } 329 }; 330 331 for (auto i = 0; i < 10; ++i) 332 { 333 auto s1 = 0.95f * rand() / (float)RAND_MAX + 0.02; 334 auto s2 = 0.95f * rand() / (float)RAND_MAX + 0.02; 335 testSusPush(s1, s2); 336 } 337 } 338 339 /* 340 ** This section recreates the somewhat painful SSE code in readable stuff 341 */ 342 auto analogClone = [](float a_sec, float d_sec, float s, float r_sec, float releaseAfter, __anona43a01a10902(float a_sec, float d_sec, float s, float r_sec, float releaseAfter, float runUntil, float pushSusAt = -1, float pushSusTo = 0) 343 float runUntil, float pushSusAt = -1, float pushSusTo = 0) { 344 float a = limit_range((float)(log(a_sec) / log(2.0)), -8.f, 5.f); 345 float d = limit_range((float)(log(d_sec) / log(2.0)), -8.f, 5.f); 346 float r = limit_range((float)(log(r_sec) / log(2.0)), -8.f, 5.f); 347 348 int i = 0; 349 bool released = false; 350 std::vector<std::pair<float, float>> res; 351 res.push_back(std::make_pair(0.f, 0.f)); 352 353 float v_c1 = 0.f; 354 float v_c1_delayed = 0.f; 355 bool discharge = false; 356 const float v_cc = 1.5f; 357 358 while (true) 359 { 360 float t = 1.0 * (i + 1) * BLOCK_SIZE * dsamplerate_inv; 361 i++; 362 if (t > runUntil || runUntil < 0) 363 break; 364 365 if (t > releaseAfter && !released) 366 { 367 released = true; 368 } 369 370 if (pushSusAt > 0 && t > pushSusAt) 371 s = pushSusTo; 372 373 auto gate = !released; 374 float v_gate = gate ? v_cc : 0.f; 375 376 // discharge = _mm_and_ps(_mm_or_ps(_mm_cmpgt_ss(v_c1_delayed, one), discharge), 377 // v_gate); 378 discharge = ((v_c1_delayed > 1) || discharge) && gate; 379 v_c1_delayed = v_c1; 380 381 float S = s * s; 382 383 float v_attack = discharge ? 0 : v_gate; 384 // OK so this line: 385 // __m128 v_decay = _mm_or_ps(_mm_andnot_ps(discharge, v_cc_vec), _mm_and_ps(discharge, 386 // S)); The semantic intent is discharge ? S : v_cc but in the ADSR discharge has a 387 // value of v_gate which is 1.5 (v_cc) not 1.0. That bitwise and with 1.5 acts as a 388 // binary filter (the mantissa) and a rounding operation (from the .5) so I need to 389 // duplicate it exactly here. 390 /* 391 __m128 sM = _mm_load_ss(&S); 392 __m128 dM = _mm_load_ss(&v_cc); 393 __m128 vdv = _mm_and_ps(dM, sM); 394 float v_decay = discharge ? vdv[0] : v_cc; 395 */ 396 // Alternately I can correct the SSE code for discharge 397 float v_decay = discharge ? S : v_cc; 398 float v_release = v_gate; 399 400 float diff_v_a = std::max(0.f, v_attack - v_c1); 401 float diff_v_d = (discharge && gate) ? v_decay - v_c1 : std::min(0.f, v_decay - v_c1); 402 // float diff_v_d = std::min( 0.f, v_decay - v_c1 ); 403 float diff_v_r = std::min(0.f, v_release - v_c1); 404 405 const float shortest = 6.f; 406 const float longest = -2.f; 407 const float coeff_offset = 2.f - log(samplerate / BLOCK_SIZE) / log(2.f); 408 409 float coef_A = pow(2.f, std::min(0.f, coeff_offset - a)); 410 float coef_D = pow(2.f, std::min(0.f, coeff_offset - d)); 411 float coef_R = pow(2.f, std::min(0.f, coeff_offset - r)); 412 413 v_c1 = v_c1 + diff_v_a * coef_A; 414 v_c1 = v_c1 + diff_v_d * coef_D; 415 v_c1 = v_c1 + diff_v_r * coef_R; 416 417 // adsr->process_block(); 418 float output = v_c1; 419 if (!gate && !discharge && v_c1 < 1e-6) 420 output = 0; 421 422 res.push_back(std::make_pair((float)t, output)); 423 } 424 return res; 425 }; 426 427 SECTION("Clone the Analog") 428 { __anona43a01a10a02(float a, float d, float s, float r) 429 auto compareSrgRepl = [&](float a, float d, float s, float r) { 430 auto t = a + d + 0.5 + r * 3; 431 auto surgeA = runAdsr(a, d, s, r, 0, 0, 0, true, a + d + 0.5, t); 432 auto replA = analogClone(a, d, s, r, a + d + 0.5, t); 433 434 REQUIRE(surgeA.size() == Approx(replA.size()).margin(3)); 435 auto sz = std::min(surgeA.size(), replA.size()); 436 for (auto i = 0; i < sz; ++i) 437 { 438 if (replA[i].second > 1e-5) // CI pipelines bounce around zero badly 439 REQUIRE(replA[i].second == Approx(surgeA[i].second).margin(1e-2)); 440 } 441 }; 442 443 compareSrgRepl(0.1, 0.2, 0.5, 0.1); 444 compareSrgRepl(0.1, 0.2, 0.0, 0.1); 445 446 for (int rc = 0; rc < 100; ++rc) 447 { 448 float a = rand() * 1.0 / (float)RAND_MAX + 0.03; 449 float d = rand() * 1.0 / (float)RAND_MAX + 0.01; 450 float s = 451 0.7 * rand() * 1.0 / (float)RAND_MAX + 0.1; // we have tested the s=0 case above 452 float r = rand() * 1.0 / (float)RAND_MAX + 453 0.1; // smaller versions can get one bad point in the pipeline 454 INFO("Testing " << rc << " with ADSR=" << a << " " << d << " " << s << " " << r); 455 compareSrgRepl(a, d, s, r); 456 } 457 } 458 459 SECTION("Test Analog Sus Push") 460 { __anona43a01a10b02(float s1, float s2) 461 auto testSusPush = [&](float s1, float s2) { 462 auto aSurgePush = runAdsr(0.05, 0.05, s1, 0.1, 0, 0, 0, true, 0.5, s2, 0.25, s2); 463 auto aDupPush = analogClone(0.05, 0.05, s1, 0.1, 0.5, 0.7, 0.25, s2); 464 465 int obs = 0; 466 INFO("Analog Dup passes sus push"); 467 for (auto s : aDupPush) 468 { 469 if (s.first > 0.2 && obs == 0) 470 { 471 REQUIRE(s.second == Approx(s1 * s1).margin(1e-2)); 472 obs++; 473 } 474 if (s.first > 0.4 && obs == 1) 475 { 476 REQUIRE(s.second == Approx(s2 * s2).margin(1e-2)); 477 obs++; 478 } 479 } 480 481 obs = 0; 482 INFO("Analog SSE passes sus push"); 483 for (auto s : aSurgePush) 484 { 485 if (s.first > 0.2 && obs == 0) 486 { 487 REQUIRE(s.second == Approx(s1 * s1).margin(1e-2)); 488 obs++; 489 } 490 if (s.first > 0.4 && obs == 1) 491 { 492 REQUIRE(s.second == Approx(s2 * s2).margin(1e-3)); 493 obs++; 494 } 495 } 496 }; 497 498 testSusPush(0.3, 0.7); 499 for (auto i = 0; i < 10; ++i) 500 { 501 auto s1 = 0.95f * rand() / (float)RAND_MAX + 0.02; 502 auto s2 = 0.95f * rand() / (float)RAND_MAX + 0.02; 503 testSusPush(s1, s2); 504 } 505 } 506 } 507 508 TEST_CASE("Non-MPE pitch bend", "[mod]") 509 { 510 SECTION("Simple Bend Distances") 511 { 512 auto surge = surgeOnSine(); 513 surge->mpeEnabled = false; 514 surge->storage.getPatch().scene[0].pbrange_up.val.i = 2; 515 surge->storage.getPatch().scene[0].pbrange_dn.val.i = 2; 516 517 auto f60 = frequencyForNote(surge, 60); 518 auto f62 = frequencyForNote(surge, 62); 519 auto f58 = frequencyForNote(surge, 58); 520 521 surge->pitchBend(0, 8192); 522 auto f60bendUp = frequencyForNote(surge, 60); 523 524 surge->pitchBend(0, -8192); 525 auto f60bendDn = frequencyForNote(surge, 60); 526 527 REQUIRE(f62 == Approx(f60bendUp).margin(0.3)); 528 REQUIRE(f58 == Approx(f60bendDn).margin(0.3)); 529 } 530 531 SECTION("Asymmetric Bend Distances") 532 { 533 auto surge = surgeOnSine(); 534 surge->mpeEnabled = false; 535 for (int tests = 0; tests < 20; ++tests) 536 { 537 int bUp = rand() % 24 + 1; 538 int bDn = rand() % 24 + 1; 539 540 surge->storage.getPatch().scene[0].pbrange_up.val.i = bUp; 541 surge->storage.getPatch().scene[0].pbrange_dn.val.i = bDn; 542 auto fUpD = frequencyForNote(surge, 60 + bUp); 543 auto fDnD = frequencyForNote(surge, 60 - bDn); 544 545 // Bend pitch and let it get there 546 surge->pitchBend(0, 8192); 547 for (int i = 0; i < 100; ++i) 548 surge->process(); 549 550 auto fUpB = frequencyForNote(surge, 60); 551 552 // Bend pitch and let it get there 553 surge->pitchBend(0, -8192); 554 for (int i = 0; i < 100; ++i) 555 surge->process(); 556 auto fDnB = frequencyForNote(surge, 60); 557 558 REQUIRE(fUpD == 559 Approx(fUpB).margin(3 * bUp)); // It can take a while for the midi lag to 560 // normalize and my pitch detector is so so 561 REQUIRE(fDnD == Approx(fDnB).margin(3 * bDn)); 562 563 surge->pitchBend(0, 0); 564 for (int i = 0; i < 100; ++i) 565 surge->process(); 566 } 567 } 568 } 569 570 TEST_CASE("Pitch Bend and Tuning", "[mod][tun]") 571 { 572 std::vector<std::string> testScales = {"test-data/scl/12-intune.scl", 573 "test-data/scl/zeus22.scl", "test-data/scl/6-exact.scl"}; 574 575 SECTION("Multi Scale Bend Distances") 576 { 577 auto surge = surgeOnSine(); 578 surge->mpeEnabled = false; 579 580 for (auto sclf : testScales) 581 { 582 INFO("Retuning pitch bend to " << sclf); 583 auto s = Tunings::readSCLFile(sclf); 584 surge->storage.retuneToScale(s); 585 for (int tests = 0; tests < 30; ++tests) 586 { 587 int bUp = rand() % 24 + 1; 588 int bDn = rand() % 24 + 1; 589 590 surge->storage.getPatch().scene[0].pbrange_up.val.i = bUp; 591 surge->storage.getPatch().scene[0].pbrange_dn.val.i = bDn; 592 auto fUpD = frequencyForNote(surge, 60 + bUp); 593 auto fDnD = frequencyForNote(surge, 60 - bDn); 594 595 // Bend pitch and let it get there 596 surge->pitchBend(0, 8192); 597 for (int i = 0; i < 500; ++i) 598 surge->process(); 599 600 auto fUpB = frequencyForNote(surge, 60); 601 602 // Bend pitch and let it get there 603 surge->pitchBend(0, -8192); 604 for (int i = 0; i < 500; ++i) 605 surge->process(); 606 auto fDnB = frequencyForNote(surge, 60); 607 auto dup = surge->storage.note_to_pitch(60 + bUp + 2) - 608 surge->storage.note_to_pitch(60 + bUp); 609 dup = dup * 8.17; 610 611 auto ddn = surge->storage.note_to_pitch(60 - bDn) - 612 surge->storage.note_to_pitch(60 - bDn - 2); 613 ddn = ddn * 8.17; 614 615 INFO("Pitch Bend " << bUp << " " << bDn << " " << fUpD << " " << fUpB << " " << dup 616 << " " << ddn); 617 REQUIRE(fUpD == 618 Approx(fUpB).margin(dup)); // It can take a while for the midi lag to 619 // normalize and my pitch detector is so so 620 REQUIRE(fDnD == Approx(fDnB).margin(ddn)); 621 622 surge->pitchBend(0, 0); 623 for (int i = 0; i < 100; ++i) 624 surge->process(); 625 } 626 } 627 } 628 } 629 630 TEST_CASE("MPE pitch bend", "[mod]") 631 { 632 SECTION("Channel 0 bends should be a correct global bend") 633 { // note that this test actually checks if channel 0 bends behave like non-MPE bends 634 auto surge = surgeOnSine(); 635 surge->mpeEnabled = true; 636 surge->storage.mpePitchBendRange = 48; 637 638 surge->storage.getPatch().scene[0].pbrange_up.val.i = 2; 639 surge->storage.getPatch().scene[0].pbrange_dn.val.i = 2; 640 641 auto f60 = frequencyForNote(surge, 60); 642 auto f62 = frequencyForNote(surge, 62); 643 auto f58 = frequencyForNote(surge, 58); 644 645 surge->pitchBend(0, 8192); 646 auto f60bendUp = frequencyForNote(surge, 60); 647 648 surge->pitchBend(0, -8192); 649 auto f60bendDn = frequencyForNote(surge, 60); 650 651 REQUIRE(f62 == Approx(f60bendUp).margin(0.3)); 652 REQUIRE(f58 == Approx(f60bendDn).margin(0.3)); 653 } 654 655 SECTION("Channel n bends should be a correct note bend") 656 { 657 auto surge = surgeOnSine(); 658 surge->mpeEnabled = true; 659 auto pbr = 48; 660 auto sbs = 8192 * 1.f / pbr; 661 662 surge->storage.mpePitchBendRange = pbr; 663 664 surge->storage.getPatch().scene[0].pbrange_up.val.i = 2; 665 surge->storage.getPatch().scene[0].pbrange_dn.val.i = 2; 666 667 // Play on channel 1 which is now an MPE bend channel and send bends on that chan 668 auto f60 = frequencyForNote(surge, 60, 2, 0, 1); 669 auto f62 = frequencyForNote(surge, 62, 2, 0, 1); 670 auto f58 = frequencyForNote(surge, 58, 2, 0, 1); 671 672 // for MPE bend after the note starts to be the most accurate simulation __anona43a01a10c02(int n, int b) 673 auto noteFreqWithBend = [surge](int n, int b) { 674 Surge::Headless::playerEvents_t events; 675 676 Surge::Headless::Event on, off, bend; 677 on.type = Surge::Headless::Event::NOTE_ON; 678 on.channel = 1; 679 on.data1 = n; 680 on.data2 = 100; 681 on.atSample = 100; 682 683 off.type = Surge::Headless::Event::NOTE_OFF; 684 off.channel = 1; 685 off.data1 = n; 686 off.data2 = 100; 687 off.atSample = 44100 * 2 + 100; 688 689 bend.type = Surge::Headless::Event::LAMBDA_EVENT; 690 bend.surgeLambda = [b](std::shared_ptr<SurgeSynthesizer> s) { s->pitchBend(1, b); }; 691 bend.atSample = 400; 692 693 events.push_back(on); 694 events.push_back(bend); 695 events.push_back(off); 696 697 return frequencyForEvents(surge, events, 0, 4000, 44100 * 2 - 8000); 698 }; 699 700 auto f60bendUp = noteFreqWithBend(60, 2 * sbs); 701 auto f60bendDn = noteFreqWithBend(60, -2 * sbs); 702 703 REQUIRE(f62 == Approx(f60bendUp).margin(0.3)); 704 REQUIRE(f58 == Approx(f60bendDn).margin(0.3)); 705 } 706 } 707 708 TEST_CASE("LfoTempoSync Latch Drift", "[mod]") 709 { 710 SECTION("Latch Drift") 711 { 712 auto surge = Surge::Headless::createSurge(44100); 713 714 int64_t bpm = 120; 715 surge->time_data.tempo = bpm; 716 717 REQUIRE(surge); 718 719 auto lfo = std::make_unique<LfoModulationSource>(); 720 auto ss = std::make_unique<StepSequencerStorage>(); 721 auto lfostorage = &(surge->storage.getPatch().scene[0].lfo[0]); 722 lfostorage->rate.temposync = true; 723 SurgeSynthesizer::ID rid; 724 surge->fromSynthSideId(lfostorage->rate.id, rid); 725 surge->setParameter01(rid, 0.455068, false, false); 726 lfostorage->shape.val.i = lt_square; 727 728 surge->storage.getPatch().copy_scenedata(surge->storage.getPatch().scenedata[0], 0); 729 730 lfo->assign(&(surge->storage), lfostorage, surge->storage.getPatch().scenedata[0], nullptr, 731 ss.get(), nullptr, nullptr); 732 lfo->attack(); 733 lfo->process_block(); 734 735 float p = -1000; 736 for (int64_t i = 0; i < 10000000; ++i) 737 { 738 if (lfo->output > p) 739 { 740 double time = i * dsamplerate_inv * BLOCK_SIZE; 741 double beats = time * bpm / 60; 742 int bt2 = round(beats * 2); 743 double drift = fabs(beats * 2 - bt2); 744 // std::cout << time / 60.0 << " " << drift << std::endl; 745 } 746 p = lfo->output; 747 lfo->process_block(); 748 } 749 } 750 } 751 752 TEST_CASE("CModulationSources", "[mod]") 753 { 754 SECTION("Legacy Mode") 755 { 756 auto surge = Surge::Headless::createSurge(44100); 757 REQUIRE(surge); 758 ControllerModulationSource a(ControllerModulationSource::SmoothingMode::LEGACY); 759 a.init(0.5f); 760 REQUIRE(a.output == 0.5f); 761 float t = 0.6; 762 a.set_target(t); 763 float priorO = a.output; 764 float dO = 100000; 765 for (int i = 0; i < 100; ++i) 766 { 767 a.process_block(); 768 REQUIRE(t - a.output < t - priorO); 769 REQUIRE(a.output - priorO < dO); 770 dO = a.output - priorO; 771 priorO = a.output; 772 } 773 } 774 775 SECTION("Fast Exp Mode gets there") 776 { 777 auto surge = Surge::Headless::createSurge(44100); 778 REQUIRE(surge); 779 780 ControllerModulationSource a(ControllerModulationSource::SmoothingMode::FAST_EXP); 781 a.init(0.5f); 782 REQUIRE(a.output == 0.5f); 783 float t = 0.6; 784 a.set_target(t); 785 float priorO = a.output; 786 float dO = 100000; 787 for (int i = 0; i < 200; ++i) 788 { 789 a.process_block(); 790 REQUIRE(t - a.output <= t - priorO); 791 REQUIRE(((a.output == t) || (a.output - priorO < dO))); 792 dO = a.output - priorO; 793 priorO = a.output; 794 } 795 REQUIRE(a.output == t); 796 } 797 798 SECTION("Slow Exp Mode gets there eventually") 799 { 800 auto surge = Surge::Headless::createSurge(44100); 801 REQUIRE(surge); 802 803 ControllerModulationSource a(ControllerModulationSource::SmoothingMode::SLOW_EXP); 804 a.init(0.5f); 805 REQUIRE(a.output == 0.5f); 806 float t = 0.6; 807 a.set_target(t); 808 int idx = 0; 809 while (a.output != t && idx < 10000) 810 { 811 a.process_block(); 812 idx++; 813 } 814 REQUIRE(a.output == t); 815 REQUIRE(idx < 1000); 816 } 817 818 SECTION("Go as a Line") 819 { 820 auto surge = Surge::Headless::createSurge(44100); 821 REQUIRE(surge); 822 823 ControllerModulationSource a(ControllerModulationSource::SmoothingMode::FAST_LINE); 824 a.init(0.5f); 825 for (int i = 0; i < 10; ++i) 826 { 827 float r = rand() / ((float)RAND_MAX); 828 a.set_target(r); 829 for (int j = 0; j < 60; ++j) 830 { 831 a.process_block(); 832 } 833 REQUIRE(a.output == r); 834 } 835 } 836 837 SECTION("Direct is Direct") 838 { 839 auto surge = Surge::Headless::createSurge(44100); 840 REQUIRE(surge); 841 842 ControllerModulationSource a(ControllerModulationSource::SmoothingMode::DIRECT); 843 a.init(0.5f); 844 for (int i = 0; i < 100; ++i) 845 { 846 float r = rand() / ((float)RAND_MAX); 847 a.set_target(r); 848 a.process_block(); 849 REQUIRE(a.output == r); 850 } 851 } 852 } 853 854 TEST_CASE("Keytrack Morph", "[mod]") 855 { 856 INFO("See issue 3046"); 857 SECTION("Run High Key") 858 { 859 auto surge = Surge::Headless::createSurge(44100); 860 REQUIRE(surge); 861 surge->loadPatchByPath("test-data/patches/Keytrack-Morph-3046.fxp", -1, "Test"); 862 for (int i = 0; i < 100; ++i) 863 surge->process(); 864 865 surge->playNote(0, 100, 127, 0); 866 for (int i = 0; i < 10; ++i) 867 { 868 surge->process(); 869 /* 870 * FIXME: Make this an assertive test. What we are really checking is is l_shape 3.33 871 * inside the oscillator but there's no easy way to assert that so just leave the test 872 * here as a debugging harness around issue 3046 873 */ 874 } 875 } 876 } 877 878 TEST_CASE("KeyTrack in Play Modes", "[mod]") 879 { 880 /* 881 * See issue 2892. In mono mode keytrack needs to follow held keys not just soloe playing voice 882 */ 883 auto playSequence = [](std::shared_ptr<SurgeSynthesizer> surge, std::vector<int> notes, __anona43a01a10e02(std::shared_ptr<SurgeSynthesizer> surge, std::vector<int> notes, bool mpe) 884 bool mpe) { 885 std::unordered_map<int, int> noteToChan; 886 int cmpe = 1; 887 for (auto n : notes) 888 { 889 int chan = 0; 890 if (mpe) 891 { 892 if (n < 0) 893 { 894 chan = noteToChan[-n]; 895 } 896 else 897 { 898 cmpe++; 899 if (cmpe > 15) 900 cmpe = 1; 901 noteToChan[n] = cmpe; 902 chan = cmpe; 903 } 904 } 905 if (n > 0) 906 { 907 surge->playNote(chan, n, 127, 0); 908 } 909 else 910 { 911 surge->releaseNote(chan, -n, 0); 912 } 913 for (int i = 0; i < 10; ++i) 914 surge->process(); 915 } 916 }; 917 918 auto modes = {pm_poly, pm_mono, pm_mono_st, pm_mono_fp, pm_mono_st_fp}; 919 auto mpe = {false, true}; 920 for (auto mp : mpe) 921 { 922 for (auto m : modes) 923 { __anona43a01a10f02() 924 auto cs = [m, mp]() { 925 auto surge = Surge::Headless::createSurge(44100); 926 surge->mpeEnabled = mp; 927 surge->storage.getPatch().scene[0].polymode.val.i = m; 928 surge->storage.getPatch().scene[0].keytrack_root.val.i = 60; 929 return surge; 930 }; 931 auto checkModes = [](std::shared_ptr<SurgeSynthesizer> surge, float low, float high, __anona43a01a11002(std::shared_ptr<SurgeSynthesizer> surge, float low, float high, float latest) 932 float latest) { 933 REQUIRE(surge->storage.getPatch().scene[0].modsources[ms_lowest_key]->output == 934 low); 935 REQUIRE(surge->storage.getPatch().scene[0].modsources[ms_highest_key]->output == 936 high); 937 REQUIRE(surge->storage.getPatch().scene[0].modsources[ms_latest_key]->output == 938 latest); 939 return true; 940 }; 941 DYNAMIC_SECTION("KeyTrack Test mode=" << m << " mpe=" << mp) 942 { 943 { 944 auto surge = cs(); 945 REQUIRE(checkModes(surge, 0, 0, 0)); 946 } 947 { 948 auto surge = cs(); 949 playSequence(surge, {48}, mp); 950 checkModes(surge, -1, -1, -1); 951 } 952 { 953 auto surge = cs(); 954 playSequence(surge, {48, -48}, mp); 955 checkModes(surge, -1, -1, -1); // This is the change in #3600 - keys are sticky 956 } 957 auto surge = cs(); 958 { 959 auto surge = cs(); 960 playSequence(surge, {48, 84, 36, 72, -36}, mp); 961 checkModes(surge, -1, 2, 1); 962 } 963 964 { 965 /* 966 * This is the one which fails in mono mode with the naive just-voices 967 * implementation 968 */ 969 auto surge = cs(); 970 playSequence(surge, {48, 84, 72}, mp); 971 checkModes(surge, -1, 2, 1); 972 } 973 } 974 } 975 } 976 } 977 978 TEST_CASE("High Low Latest Note Modulator in All Modes", "[mod]") 979 { 980 // See issue #3597 __anona43a01a11102(MonoVoicePriorityMode priomode, play_mode polymode) 981 auto setup = [](MonoVoicePriorityMode priomode, play_mode polymode) { 982 auto surge = Surge::Headless::createSurge(44100); 983 984 // Set synth to mono and low note priority 985 surge->storage.getPatch().scene[0].polymode.val.i = polymode; 986 surge->storage.getPatch().scene[0].monoVoicePriorityMode = priomode; 987 988 // Assign highest note keytrack to any parameter. Lets do this with highest latest and 989 // lowest 990 surge->setModulation(surge->storage.getPatch().scene[0].osc[0].pitch.id, ms_highest_key, 991 0.2); 992 surge->setModulation(surge->storage.getPatch().scene[0].osc[0].p[0].id, ms_latest_key, 0.2); 993 surge->setModulation(surge->storage.getPatch().scene[0].osc[0].p[1].id, ms_lowest_key, 0.2); 994 return surge; 995 }; 996 997 auto rcmp = [](std::shared_ptr<SurgeSynthesizer> surge, int ch, int key, int vel, int low, __anona43a01a11202(std::shared_ptr<SurgeSynthesizer> surge, int ch, int key, int vel, int low, int high, int latest) 998 int high, int latest) { 999 if (vel == 0) 1000 surge->releaseNote(ch, key, vel); 1001 else 1002 surge->playNote(ch, key, vel, 0); 1003 for (int i = 0; i < 50; ++i) 1004 surge->process(); 1005 for (auto v : surge->voices[0]) 1006 if (v->state.gate) 1007 { 1008 REQUIRE(v->modsources[ms_highest_key]->output * 12 + 60 == high); 1009 REQUIRE(v->modsources[ms_latest_key]->output * 12 + 60 == latest); 1010 REQUIRE(v->modsources[ms_lowest_key]->output * 12 + 60 == low); 1011 } 1012 }; 1013 1014 std::map<MonoVoicePriorityMode, std::string> lab; 1015 lab[NOTE_ON_LATEST_RETRIGGER_HIGHEST] = "legacy"; 1016 lab[ALWAYS_HIGHEST] = "always highest"; 1017 lab[ALWAYS_LATEST] = "always latest"; 1018 lab[ALWAYS_LOWEST] = "always lowest"; 1019 1020 for (auto mpemode : {true, false}) 1021 for (auto polymode : {pm_mono_st, pm_poly, pm_mono, pm_mono_fp, pm_mono_st, pm_mono_st_fp}) 1022 for (auto priomode : 1023 {NOTE_ON_LATEST_RETRIGGER_HIGHEST, ALWAYS_LOWEST, ALWAYS_HIGHEST, ALWAYS_LATEST}) 1024 { 1025 DYNAMIC_SECTION("Play Up Test " << lab[priomode] << " mpe=" << mpemode 1026 << " poly=" << polymode) 1027 { 1028 // From the issue: Steps to reproduce the behavior: 1029 auto surge = setup(priomode, polymode); 1030 surge->mpeEnabled = mpemode; 1031 1032 // press and hold three increasing notes 1033 int ch = 0; 1034 1035 if (mpemode) 1036 ch = 1; 1037 rcmp(surge, ch, 60, 127, 60, 60, 60); 1038 1039 if (mpemode) 1040 ch++; 1041 rcmp(surge, ch, 66, 127, 60, 66, 66); 1042 1043 if (mpemode) 1044 ch++; 1045 rcmp(surge, ch, 72, 127, 60, 72, 72); 1046 } 1047 DYNAMIC_SECTION("Play Down Test " << lab[priomode] << " mpe=" << mpemode 1048 << " polymode=" << polymode) 1049 { 1050 auto surge = setup(priomode, polymode); 1051 surge->mpeEnabled = mpemode; 1052 1053 // press and hold three decreasing notes 1054 int ch = 0; 1055 1056 if (mpemode) 1057 ch = 1; 1058 rcmp(surge, ch, 60, 127, 60, 60, 60); 1059 1060 if (mpemode) 1061 ch++; 1062 rcmp(surge, ch, 54, 127, 54, 60, 54); 1063 1064 if (mpemode) 1065 ch++; 1066 rcmp(surge, ch, 48, 127, 48, 60, 48); 1067 } 1068 1069 DYNAMIC_SECTION("Play V Test " << lab[priomode] << " mpe=" << mpemode 1070 << " polymode=" << polymode) 1071 { 1072 // From the issue: Steps to reproduce the behavior: 1073 auto surge = setup(priomode, polymode); 1074 surge->mpeEnabled = mpemode; 1075 1076 // press and hold three intersecting notes 1077 int ch = 0; 1078 1079 if (mpemode) 1080 ch = 1; 1081 rcmp(surge, ch, 60, 127, 60, 60, 60); 1082 1083 if (mpemode) 1084 ch++; 1085 rcmp(surge, ch, 72, 127, 60, 72, 72); 1086 1087 if (mpemode) 1088 ch++; 1089 rcmp(surge, ch, 66, 127, 60, 72, 66); 1090 } 1091 1092 DYNAMIC_SECTION("Releases one " << lab[priomode] << " mpe=" << mpemode 1093 << " polymode=" << polymode) 1094 { 1095 // From the issue: Steps to reproduce the behavior: 1096 auto surge = setup(priomode, polymode); 1097 surge->mpeEnabled = mpemode; 1098 1099 // press and hold three intersecting notes 1100 int ch = 0; 1101 1102 if (mpemode) 1103 ch = 1; 1104 rcmp(surge, ch, 60, 127, 60, 60, 60); 1105 1106 if (mpemode) 1107 ch++; 1108 rcmp(surge, ch, 72, 127, 60, 72, 72); 1109 1110 if (mpemode) 1111 ch++; 1112 rcmp(surge, ch, 66, 127, 60, 72, 66); 1113 1114 if (mpemode) 1115 ch--; 1116 rcmp(surge, ch, 72, 0, 60, 66, 66); 1117 } 1118 1119 DYNAMIC_SECTION("Releases one " << lab[priomode] << " mpe=" << mpemode 1120 << " polymode=" << polymode) 1121 { 1122 // From the issue: Steps to reproduce the behavior: 1123 auto surge = setup(priomode, polymode); 1124 surge->mpeEnabled = mpemode; 1125 1126 // press and hold three intersecting notes 1127 int ch = 0; 1128 1129 if (mpemode) 1130 ch = 1; 1131 rcmp(surge, ch, 60, 127, 60, 60, 60); 1132 1133 if (mpemode) 1134 ch++; 1135 rcmp(surge, ch, 72, 127, 60, 72, 72); 1136 1137 if (mpemode) 1138 ch++; 1139 rcmp(surge, ch, 66, 127, 60, 72, 66); 1140 1141 // and unwind them 1142 ch = 0; 1143 1144 if (mpemode) 1145 ch = 1; 1146 rcmp(surge, ch, 60, 0, 66, 72, 66); 1147 1148 if (mpemode) 1149 ch++; 1150 rcmp(surge, ch, 72, 0, 66, 66, 66); 1151 1152 if (mpemode) 1153 ch++; 1154 rcmp(surge, ch, 66, 0, 60, 60, 60); // 60 maps to 0 1155 } 1156 } 1157 } 1158 1159 TEST_CASE("High Low Latest with splits", "[mod]") 1160 { __anona43a01a11302(MonoVoicePriorityMode priomode, play_mode polymode) 1161 auto setup = [](MonoVoicePriorityMode priomode, play_mode polymode) { 1162 auto surge = Surge::Headless::createSurge(44100); 1163 1164 // Set synth to mono and low note priority 1165 surge->storage.getPatch().scene[0].polymode.val.i = polymode; 1166 surge->storage.getPatch().scene[0].monoVoicePriorityMode = priomode; 1167 1168 // Assign highest note keytrack to any parameter. Lets do this with highest latest and 1169 // lowest 1170 surge->setModulation(surge->storage.getPatch().scene[0].osc[0].pitch.id, ms_highest_key, 1171 0.2); 1172 surge->setModulation(surge->storage.getPatch().scene[0].osc[0].p[0].id, ms_latest_key, 0.2); 1173 surge->setModulation(surge->storage.getPatch().scene[0].osc[0].p[1].id, ms_lowest_key, 0.2); 1174 1175 surge->setModulation(surge->storage.getPatch().scene[1].osc[0].pitch.id, ms_highest_key, 1176 0.2); 1177 surge->setModulation(surge->storage.getPatch().scene[1].osc[0].p[0].id, ms_latest_key, 0.2); 1178 surge->setModulation(surge->storage.getPatch().scene[1].osc[0].p[1].id, ms_lowest_key, 0.2); 1179 1180 return surge; 1181 }; 1182 __anona43a01a11402(std::shared_ptr<SurgeSynthesizer> surge, int ch, int key, int vel) 1183 auto play = [](std::shared_ptr<SurgeSynthesizer> surge, int ch, int key, int vel) { 1184 if (vel == 0) 1185 surge->releaseNote(ch, key, vel); 1186 else 1187 surge->playNote(ch, key, vel, 0); 1188 for (int i = 0; i < 50; ++i) 1189 surge->process(); 1190 }; 1191 auto compval = [](std::shared_ptr<SurgeSynthesizer> surge, int sc, int low, int high, __anona43a01a11502(std::shared_ptr<SurgeSynthesizer> surge, int sc, int low, int high, int latest) 1192 int latest) { 1193 for (auto v : surge->voices[sc]) 1194 if (v->state.gate) 1195 { 1196 REQUIRE(v->modsources[ms_highest_key]->output * 12 + 60 == high); 1197 REQUIRE(v->modsources[ms_latest_key]->output * 12 + 60 == latest); 1198 REQUIRE(v->modsources[ms_lowest_key]->output * 12 + 60 == low); 1199 } 1200 }; 1201 for (auto mpemode : {true, false}) 1202 for (auto polymode : {pm_mono_st, pm_poly, pm_mono, pm_mono_fp, pm_mono_st, pm_mono_st_fp}) 1203 for (auto priomode : 1204 {NOTE_ON_LATEST_RETRIGGER_HIGHEST, ALWAYS_LOWEST, ALWAYS_HIGHEST, ALWAYS_LATEST}) 1205 { 1206 DYNAMIC_SECTION("DUAL " << priomode << " mpe=" << mpemode << " poly=" << polymode) 1207 1208 { 1209 auto surge = setup(priomode, polymode); 1210 surge->mpeEnabled = mpemode; 1211 1212 surge->storage.getPatch().scenemode.val.i = sm_dual; 1213 1214 int ch = 0; 1215 if (mpemode) 1216 ch = 1; 1217 1218 play(surge, ch, 60, 127); 1219 compval(surge, 0, 60, 60, 60); 1220 compval(surge, 1, 60, 60, 60); 1221 1222 if (mpemode) 1223 ch++; 1224 play(surge, ch, 70, 127); 1225 compval(surge, 0, 60, 70, 70); 1226 compval(surge, 1, 60, 70, 70); 1227 1228 if (mpemode) 1229 ch++; 1230 play(surge, ch, 65, 127); 1231 compval(surge, 0, 60, 70, 65); 1232 compval(surge, 1, 60, 70, 65); 1233 } 1234 1235 DYNAMIC_SECTION("KeySplit " << priomode << " mpe=" << mpemode 1236 << " poly=" << polymode) 1237 1238 { 1239 auto surge = setup(priomode, polymode); 1240 surge->mpeEnabled = mpemode; 1241 1242 surge->storage.getPatch().scenemode.val.i = sm_split; 1243 surge->storage.getPatch().splitpoint.val.i = 70; 1244 1245 int ch = 0; 1246 if (mpemode) 1247 ch = 1; 1248 1249 play(surge, ch, 50, 127); 1250 compval(surge, 0, 50, 50, 50); 1251 compval(surge, 1, 60, 60, 60); 1252 1253 if (mpemode) 1254 ch++; 1255 play(surge, ch, 90, 127); 1256 compval(surge, 0, 50, 50, 50); 1257 compval(surge, 1, 90, 90, 90); 1258 1259 if (mpemode) 1260 ch++; 1261 play(surge, ch, 69, 127); 1262 compval(surge, 0, 50, 69, 69); 1263 compval(surge, 1, 90, 90, 90); 1264 1265 if (mpemode) 1266 ch++; 1267 play(surge, ch, 70, 127); 1268 compval(surge, 0, 50, 69, 69); 1269 compval(surge, 1, 70, 90, 70); 1270 } 1271 1272 DYNAMIC_SECTION("ChSplit " << priomode << " mpe=" << mpemode 1273 << " poly=" << polymode) 1274 1275 { 1276 auto surge = setup(priomode, polymode); 1277 surge->mpeEnabled = mpemode; 1278 1279 surge->storage.getPatch().scenemode.val.i = sm_chsplit; 1280 surge->storage.getPatch().splitpoint.val.i = 64; 1281 1282 int cha = 0; 1283 int chb = 10; 1284 if (mpemode) 1285 cha = 1; 1286 1287 play(surge, cha, 50, 127); 1288 compval(surge, 0, 50, 50, 50); 1289 compval(surge, 1, 60, 60, 60); 1290 1291 play(surge, chb, 90, 127); 1292 compval(surge, 0, 50, 50, 50); 1293 compval(surge, 1, 90, 90, 90); 1294 1295 play(surge, cha + (mpemode ? 1 : 0), 69, 127); 1296 compval(surge, 0, 50, 69, 69); 1297 compval(surge, 1, 90, 90, 90); 1298 1299 play(surge, chb + (mpemode ? 1 : 0), 70, 127); 1300 compval(surge, 0, 50, 69, 69); 1301 compval(surge, 1, 70, 90, 70); 1302 } 1303 } 1304 } 1305 1306 TEST_CASE("ModLFO is well behaved", "[mod]") 1307 { 1308 for (int m = Surge::ModControl::mod_sine; m <= Surge::ModControl::mod_square; ++m) 1309 { 1310 DYNAMIC_SECTION("Mod Control Boundsd for type " << m) 1311 { 1312 for (int tries = 0; tries < 10; ++tries) 1313 { 1314 float rate = (float)rand() / (float)RAND_MAX * 10; 1315 float phase_offset = 0.1 * tries; 1316 float depth = 1.0; 1317 1318 INFO("200 Samples at " << rate << " with po " << phase_offset); 1319 Surge::ModControl mc; 1320 for (int s = 0; s < 200; ++s) 1321 { 1322 mc.pre_process(m, rate, depth, phase_offset); 1323 REQUIRE(mc.value() <= 1.0); 1324 REQUIRE(mc.value() >= -1.0); 1325 } 1326 } 1327 } 1328 } 1329 }