Full-band music mix: acoustic guitar + kick + snare + hi-hat + optional voice. Produces feature distributions matching real 3-way MP3 recordings: backing: flat~0.5-0.7 form~0.4-0.8 pres~0.05-0.2 zcCV~0.8-1.5 vocal: flat~0.5-0.7 form~0.3-0.6 pres~0.01-0.05 zcCV~1.0-2.0 The guitar body resonances create realistic formant-band energy, and the drum pattern produces realistic time-domain features (j
| 534 | /// |
| 535 | /// @param vocalRatio 0.0 = backing only, 1.0 = voice at equal level |
| 536 | inline Sample makeFullBandMix(float vocalRatio, fl::u32 timestamp, |
| 537 | float amplitude = 16000.0f, |
| 538 | int count = 512, float sampleRate = 44100.0f) { |
| 539 | fl::vector<fl::i16> mixed(count, 0); |
| 540 | |
| 541 | // 1. Acoustic guitar (dominant — provides harmonic/formant structure) |
| 542 | auto guitar = makeAcousticGuitar(196.0f, timestamp, amplitude * 0.7f, |
| 543 | count, sampleRate); |
| 544 | const auto& guitarPcm = guitar.pcm(); |
| 545 | for (int i = 0; i < count; ++i) { |
| 546 | mixed[i] += guitarPcm[i]; |
| 547 | } |
| 548 | |
| 549 | // 2. Kick drum (low-frequency body) |
| 550 | auto kick = makeKickDrum(timestamp, amplitude * 0.25f, count, sampleRate); |
| 551 | const auto& kickPcm = kick.pcm(); |
| 552 | for (int i = 0; i < count; ++i) { |
| 553 | mixed[i] = static_cast<fl::i16>(fl::clamp( |
| 554 | static_cast<float>(mixed[i]) + static_cast<float>(kickPcm[i]), |
| 555 | -32768.0f, 32767.0f)); |
| 556 | } |
| 557 | |
| 558 | // 3. Snare (mid-frequency body + noise rattle) |
| 559 | auto snare = makeSnare(timestamp, amplitude * 0.15f, count, sampleRate); |
| 560 | const auto& snarePcm = snare.pcm(); |
| 561 | for (int i = 0; i < count; ++i) { |
| 562 | mixed[i] = static_cast<fl::i16>(fl::clamp( |
| 563 | static_cast<float>(mixed[i]) + static_cast<float>(snarePcm[i]), |
| 564 | -32768.0f, 32767.0f)); |
| 565 | } |
| 566 | |
| 567 | // 4. Hi-hat (high-frequency noise — reduced amplitude to avoid |
| 568 | // inflating zcCV beyond real-audio levels) |
| 569 | auto hat = makeHiHat(timestamp, false, amplitude * 0.08f, count, sampleRate); |
| 570 | const auto& hatPcm = hat.pcm(); |
| 571 | for (int i = 0; i < count; ++i) { |
| 572 | mixed[i] = static_cast<fl::i16>(fl::clamp( |
| 573 | static_cast<float>(mixed[i]) + static_cast<float>(hatPcm[i]), |
| 574 | -32768.0f, 32767.0f)); |
| 575 | } |
| 576 | |
| 577 | // 5. Voice (jittered vowel if ratio > 0) |
| 578 | if (vocalRatio > 0.001f) { |
| 579 | auto vocal = makeJitteredVowel(150.0f, 700.0f, 1200.0f, timestamp, |
| 580 | amplitude * vocalRatio, count, sampleRate); |
| 581 | const auto& vocalPcm = vocal.pcm(); |
| 582 | for (int i = 0; i < count; ++i) { |
| 583 | mixed[i] = static_cast<fl::i16>(fl::clamp( |
| 584 | static_cast<float>(mixed[i]) + static_cast<float>(vocalPcm[i]), |
| 585 | -32768.0f, 32767.0f)); |
| 586 | } |
| 587 | } |
| 588 | |
| 589 | return Sample(mixed, timestamp); |
| 590 | } |
| 591 | |
| 592 | // ============================================================================ |
| 593 | // Degenerate-Case Signal Generators (for false-positive testing) |
no test coverage detected