| 122 | } |
| 123 | |
| 124 | void Vocal::computeFormantRatio(const fft::Bins& formantFft) { |
| 125 | // Formant ratio from high-resolution narrow FFT (64 bins, 200-3500 Hz). |
| 126 | // All bins concentrated in the formant region — ~94% utilization. |
| 127 | const auto& bins = formantFft.raw(); |
| 128 | const int n = static_cast<int>(bins.size()); |
| 129 | if (n < 8) { |
| 130 | mFormantRatio = 0.0f; |
| 131 | return; |
| 132 | } |
| 133 | |
| 134 | // Ensure formant bin cache is current (freqToBin uses logf internally) |
| 135 | if (mFormantCachedBinCount != n) { |
| 136 | mFormantF1MinBin = fl::max(0, formantFft.freqToBin(250.0f)); |
| 137 | mFormantF1MaxBin = fl::min(n - 1, formantFft.freqToBin(900.0f)); |
| 138 | mFormantF2MinBin = fl::max(0, formantFft.freqToBin(1000.0f)); |
| 139 | mFormantF2MaxBin = fl::min(n - 1, formantFft.freqToBin(3000.0f)); |
| 140 | mFormantCachedBinCount = n; |
| 141 | } |
| 142 | |
| 143 | float f1Peak = 0.0f, f1Sum = 0.0f; |
| 144 | int f1Count = 0; |
| 145 | float f2Peak = 0.0f, f2Sum = 0.0f; |
| 146 | int f2Count = 0; |
| 147 | |
| 148 | for (int i = 0; i < n; ++i) { |
| 149 | const float mag = bins[i]; |
| 150 | if (i >= mFormantF1MinBin && i <= mFormantF1MaxBin) { |
| 151 | f1Peak = fl::max(f1Peak, mag); |
| 152 | f1Sum += mag; |
| 153 | ++f1Count; |
| 154 | } |
| 155 | if (i >= mFormantF2MinBin && i <= mFormantF2MaxBin) { |
| 156 | f2Peak = fl::max(f2Peak, mag); |
| 157 | f2Sum += mag; |
| 158 | ++f2Count; |
| 159 | } |
| 160 | } |
| 161 | |
| 162 | if (f1Count > 0 && f2Count > 0) { |
| 163 | float f1Avg = f1Sum / static_cast<float>(f1Count); |
| 164 | float f2Avg = f2Sum / static_cast<float>(f2Count); |
| 165 | if (f1Peak >= f1Avg * 1.5f && f2Peak >= f2Avg * 1.5f && f1Peak >= 1e-6f) { |
| 166 | mFormantRatio = f2Peak / f1Peak; |
| 167 | } else { |
| 168 | mFormantRatio = 0.0f; |
| 169 | } |
| 170 | } else { |
| 171 | mFormantRatio = 0.0f; |
| 172 | } |
| 173 | } |
| 174 | |
| 175 | void Vocal::computeBroadSpectralFeatures(const fft::Bins& broadFft) { |
| 176 | // Broad spectral features from low-res wide FFT (16 bins, 174.6-4698.3 Hz). |