Generate a jittered vowel: synthetic vowel with per-period amplitude and timing jitter Simulates real vocal cord irregularity — higher envelope jitter and lower autocorrelation than clean synthetic vowels
| 254 | /// Simulates real vocal cord irregularity — higher envelope jitter and lower autocorrelation |
| 255 | /// than clean synthetic vowels |
| 256 | inline Sample makeJitteredVowel(float f0, float f1Freq, float f2Freq, |
| 257 | u32 timestamp, float amplitude = 16000.0f, |
| 258 | int count = 512, float sampleRate = 44100.0f) { |
| 259 | fl::vector<fl::i16> data(count, 0); |
| 260 | fl::fl_random rng(123); |
| 261 | const float f1Bw = 150.0f; |
| 262 | const float f2Bw = 200.0f; |
| 263 | const float maxFreq = fl::min(4000.0f, sampleRate / 2.0f); |
| 264 | |
| 265 | // Generate clean vowel base |
| 266 | for (int h = 1; h * f0 < maxFreq; ++h) { |
| 267 | float freq = f0 * h; |
| 268 | float naturalAmp = 1.0f / static_cast<float>(h); |
| 269 | float f1Gain = fl::expf(-0.5f * (freq - f1Freq) * (freq - f1Freq) / (f1Bw * f1Bw)); |
| 270 | float f2Gain = fl::expf(-0.5f * (freq - f2Freq) * (freq - f2Freq) / (f2Bw * f2Bw)); |
| 271 | float formantGain = fl::max(f1Gain, f2Gain); |
| 272 | float harmonicAmp = amplitude * naturalAmp * fl::max(0.05f, formantGain); |
| 273 | |
| 274 | for (int i = 0; i < count; ++i) { |
| 275 | float phase = 2.0f * FL_M_PI * freq * i / sampleRate; |
| 276 | data[i] += static_cast<fl::i16>(harmonicAmp * fl::sinf(phase)); |
| 277 | } |
| 278 | } |
| 279 | |
| 280 | // Apply per-period amplitude jitter (simulates vocal cord irregularity) |
| 281 | float periodSamples = sampleRate / f0; |
| 282 | float pos = 0.0f; |
| 283 | float currentJitter = 1.0f; |
| 284 | for (int i = 0; i < count; ++i) { |
| 285 | pos += 1.0f; |
| 286 | if (pos >= periodSamples) { |
| 287 | pos -= periodSamples; |
| 288 | // Random amplitude jitter: +/- 20% |
| 289 | currentJitter = 0.80f + 0.40f * (static_cast<float>(rng.random16()) / 65535.0f); |
| 290 | } |
| 291 | float sample = static_cast<float>(data[i]) * currentJitter; |
| 292 | data[i] = static_cast<fl::i16>(fl::clamp(sample, -32768.0f, 32767.0f)); |
| 293 | } |
| 294 | return Sample(data, timestamp); |
| 295 | } |
| 296 | |
| 297 | /// Generate a guitar string decay: harmonic series with 1/h^2 rolloff and exponential decay |
| 298 | /// Very periodic waveform — low envelope jitter, high autocorrelation, low ZC CV |