Helper: Create audio::Context with synthetic audio designed to trigger beat detection The audio::detector::Beat uses spectral flux (change in audio::fft::FFT magnitudes between frames), so we need to create sequences with temporal energy variation. accentMultiplier: Scale factor for accent strength (1.0 = normal, 0.5 = weak, 2.0 = strong)
| 71 | // |
| 72 | // accentMultiplier: Scale factor for accent strength (1.0 = normal, 0.5 = weak, 2.0 = strong) |
| 73 | shared_ptr<audio::Context> createMockAudioContext(u32 timestamp, bool isDownbeat, |
| 74 | bool isQuiet, float accentMultiplier = 1.0f) { |
| 75 | vector<i16> pcmData; |
| 76 | |
| 77 | if (isQuiet) { |
| 78 | // Low energy "silence" between beats - allows spectral flux to detect |
| 79 | // onset |
| 80 | for (int i = 0; i < 512; i++) { |
| 81 | pcmData.push_back(500); // Very low amplitude background |
| 82 | } |
| 83 | } else { |
| 84 | // Generate audio with sudden energy (beat onset) |
| 85 | // Downbeats have more bass energy for accent detection |
| 86 | i16 baseAmplitude = isDownbeat ? 25000 : 15000; |
| 87 | // Clamp to i16 range to prevent UBSan overflow (max accentMultiplier is 2.0) |
| 88 | float amplitudeFloat = baseAmplitude * accentMultiplier; |
| 89 | i16 amplitude = static_cast<i16>(fl::min(amplitudeFloat, 32767.0f)); |
| 90 | |
| 91 | // Use multiple frequencies to create richer spectral content. |
| 92 | // Frequencies chosen to fall clearly within CQ bass bins (fmin=30 Hz). |
| 93 | for (int i = 0; i < 512; i++) { |
| 94 | float t = static_cast<float>(i) / 44100.0f; |
| 95 | |
| 96 | // Bass component at 100 Hz (stronger for downbeats) |
| 97 | float bass = fl::sin(2.0f * 3.14159f * 100.0f * t); |
| 98 | |
| 99 | // Mid-range component at 400 Hz |
| 100 | float mid = fl::sin(2.0f * 3.14159f * 400.0f * t); |
| 101 | |
| 102 | // High component at 800 Hz (less for downbeats to emphasize bass) |
| 103 | float high = fl::sin(2.0f * 3.14159f * 800.0f * t); |
| 104 | |
| 105 | // Weight frequencies differently for downbeats vs regular beats |
| 106 | float mix; |
| 107 | if (isDownbeat) { |
| 108 | mix = bass * 0.6f + mid * 0.3f + high * 0.1f; // Bass-heavy |
| 109 | } else { |
| 110 | mix = bass * 0.4f + mid * 0.4f + high * 0.2f; // More balanced |
| 111 | } |
| 112 | |
| 113 | // Apply amplitude envelope (impulse-like: sudden onset, decay) |
| 114 | float envelopeT = static_cast<float>(i) / 512.0f; |
| 115 | float envelope = fl::exp(-envelopeT * 5.0f); // Exponential decay |
| 116 | |
| 117 | i16 value = static_cast<i16>(amplitude * mix * envelope); |
| 118 | pcmData.push_back(value); |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | audio::Sample sample(pcmData, timestamp); |
| 123 | auto context = make_shared<audio::Context>(sample); |
| 124 | |
| 125 | // Pre-compute audio::fft::FFT so it's available during update |
| 126 | context->getFFT16(); |
| 127 | |
| 128 | return context; |
| 129 | } |
| 130 |
no test coverage detected