Mix a vocal signal with guitar + drums/bass at a given vocal-to-backing ratio. ratio = 1.0 means equal levels, 0.5 means vocal is half as loud as backing. Matches real 3-way audio structure: guitar (broadband harmonics) + drums (kick+hihat) + optional voice. The guitar component is key — it adds formant-band energy that inflates backing formant ratio in 3-way mixes.
| 579 | /// (kick+hihat) + optional voice. The guitar component is key — it adds |
| 580 | /// formant-band energy that inflates backing formant ratio in 3-way mixes. |
| 581 | inline audio::Sample makeVocalInFullMix(float vocalRatio, fl::u32 timestamp, |
| 582 | float amplitude = 16000.0f, |
| 583 | int count = 512, float sampleRate = 44100.0f) { |
| 584 | // 1. Vocal: jittered vowel /a/ (F0=150, F1=700, F2=1200) |
| 585 | auto vocal = makeJitteredVowel(150.0f, 700.0f, 1200.0f, timestamp, |
| 586 | amplitude * vocalRatio, count, sampleRate); |
| 587 | |
| 588 | // 2. Guitar: harmonic series at 220Hz with 1/h^1.5 rolloff |
| 589 | // Adds energy in formant bands (F1=200-900Hz, F2=1000-3000Hz) that |
| 590 | // inflates backing formant ratio — matching real 3-way audio behavior. |
| 591 | fl::vector<fl::i16> guitarData(count, 0); |
| 592 | float guitarF0 = 220.0f; |
| 593 | float maxFreq = fl::min(5000.0f, sampleRate / 2.0f); |
| 594 | for (int h = 1; h * guitarF0 < maxFreq; ++h) { |
| 595 | float freq = guitarF0 * static_cast<float>(h); |
| 596 | float hAmp = amplitude * 0.25f / fl::powf(static_cast<float>(h), 1.5f); |
| 597 | for (int i = 0; i < count; ++i) { |
| 598 | float phase = 2.0f * FL_M_PI * freq * static_cast<float>(i) / sampleRate; |
| 599 | guitarData[i] += static_cast<fl::i16>(hAmp * fl::sinf(phase)); |
| 600 | } |
| 601 | } |
| 602 | |
| 603 | // 3. Bass guitar: 80 Hz fundamental with harmonics |
| 604 | fl::vector<fl::i16> bassData(count, 0); |
| 605 | float bassF0 = 80.0f; |
| 606 | for (int h = 1; h <= 5; ++h) { |
| 607 | float freq = bassF0 * static_cast<float>(h); |
| 608 | float hAmp = amplitude * 0.3f / static_cast<float>(h * h); |
| 609 | for (int i = 0; i < count; ++i) { |
| 610 | float phase = 2.0f * FL_M_PI * freq * static_cast<float>(i) / sampleRate; |
| 611 | bassData[i] += static_cast<fl::i16>(hAmp * fl::sinf(phase)); |
| 612 | } |
| 613 | } |
| 614 | |
| 615 | // 4. Kick drum pattern (amplitude-decayed) |
| 616 | fl::vector<fl::i16> kickData(count, 0); |
| 617 | for (int i = 0; i < count; ++i) { |
| 618 | float t = static_cast<float>(i) / sampleRate; |
| 619 | float decay = fl::expf(-t / 0.030f); |
| 620 | kickData[i] = static_cast<fl::i16>(amplitude * 0.3f * decay * |
| 621 | fl::sinf(2.0f * FL_M_PI * 55.0f * t)); |
| 622 | } |
| 623 | |
| 624 | // 5. Hi-hat noise (high-frequency percussion) |
| 625 | fl::fl_random rng(timestamp + 77); |
| 626 | fl::vector<fl::i16> hatData(count, 0); |
| 627 | float prevNoise = 0.0f; |
| 628 | for (int i = 0; i < count; ++i) { |
| 629 | float t = static_cast<float>(i) / sampleRate; |
| 630 | float decay = fl::expf(-t / 0.010f); |
| 631 | float noiseVal = (static_cast<float>(rng.random16()) / 32767.5f) - 1.0f; |
| 632 | float hp = noiseVal - 0.85f * prevNoise; |
| 633 | prevNoise = noiseVal; |
| 634 | hatData[i] = static_cast<fl::i16>(amplitude * 0.12f * decay * hp); |
| 635 | } |
| 636 | |
| 637 | // Sum all components |
| 638 | const auto& vocalPcm = vocal.pcm(); |