Visualization: Logarithmic Waveform (prevents saturation)
| 266 | |
| 267 | // Visualization: Logarithmic Waveform (prevents saturation) |
| 268 | void drawWaveform(const fl::span<const int16_t>& pcm, float /* peak */) { |
| 269 | clearDisplay(); |
| 270 | fl::CRGBPalette16 palette = getCurrentPalette(); |
| 271 | |
| 272 | int samplesPerPixel = pcm.size() / WIDTH; |
| 273 | int centerY = HEIGHT / 2; |
| 274 | |
| 275 | for (size_t x = 0; x < WIDTH; x++) { |
| 276 | size_t sampleIndex = x * samplesPerPixel; |
| 277 | if (sampleIndex >= pcm.size()) break; |
| 278 | |
| 279 | // Get the raw sample value |
| 280 | float sample = float(pcm[sampleIndex]) / 32768.0f; // Normalize to -1.0 to 1.0 |
| 281 | |
| 282 | // Apply logarithmic scaling to prevent saturation |
| 283 | float absSample = fl::fabsf(sample); |
| 284 | float logAmplitude = 0.0f; |
| 285 | |
| 286 | if (absSample > 0.001f) { // Avoid log(0) |
| 287 | // Logarithmic compression: log10(1 + gain * sample) |
| 288 | float scaledSample = absSample * audioGain.value() * autoGainValue; |
| 289 | logAmplitude = fl::log10f(1.0f + scaledSample * 9.0f) / fl::log10f(10.0f); // Normalize to 0-1 |
| 290 | } |
| 291 | |
| 292 | // Apply smooth sensitivity curve |
| 293 | logAmplitude = fl::powf(logAmplitude, 0.7f); // Gamma correction for better visual response |
| 294 | |
| 295 | // Calculate amplitude in pixels |
| 296 | int amplitude = int(logAmplitude * (HEIGHT / 2)); |
| 297 | amplitude = fl::clamp(amplitude, 0, HEIGHT / 2); |
| 298 | |
| 299 | // Preserve the sign for proper waveform display |
| 300 | if (sample < 0) amplitude = -amplitude; |
| 301 | |
| 302 | // Color mapping based on amplitude intensity |
| 303 | uint8_t colorIndex = fl::map_range<int, uint8_t>(abs(amplitude), 0, HEIGHT/2, 40, 255); |
| 304 | fl::CRGB color = ColorFromPalette(palette, colorIndex + hue); |
| 305 | |
| 306 | // Apply brightness scaling for low amplitudes |
| 307 | if (abs(amplitude) < HEIGHT / 4) { |
| 308 | color.fadeToBlackBy(128 - (abs(amplitude) * 512 / HEIGHT)); |
| 309 | } |
| 310 | |
| 311 | // Draw vertical line from center |
| 312 | if (amplitude == 0) { |
| 313 | // Draw center point for zero amplitude |
| 314 | int ledIndex = xyMap(x, centerY); |
| 315 | if (ledIndex >= 0 && ledIndex < NUM_LEDS) { |
| 316 | leds[ledIndex] = color.fadeToBlackBy(200); |
| 317 | } |
| 318 | } else { |
| 319 | // Draw line from center to amplitude |
| 320 | int startY = (amplitude > 0) ? centerY : centerY + amplitude; |
| 321 | int endY = (amplitude > 0) ? centerY + amplitude : centerY; |
| 322 | |
| 323 | for (int y = startY; y <= endY; y++) { |
| 324 | if (y >= 0 && y < HEIGHT) { |
| 325 | int ledIndex = xyMap(x, y); |
no test coverage detected