Audio Visualization

Learn how to use audio data

flutter_soloud provides real-time audio visualization capabilities through the AudioData class. This allows you to access both FFT (Fast Fourier Transform) and wave data from the currently playing audio.

Setup

Before using audio visualization, you need to enable it. For optimal visualization performance, it's recommended to set an appropriate buffer size during initialization:

// Initialize with a smaller buffer size for better visualization performance
await SoLoud.instance.init(
  // Default is 2048, smaller values = lower latency, it could be set also to 512.
  // The smaller the buffer, the more likely the
  // system hits buffer underruns (ie, the play head marches on but there's no
  // data ready to be played) and the sound breaks down horribly. 1024 is a good
  // compromise.
  bufferSize: 1024,
);
SoLoud.instance.setVisualizationEnabled(true);

AudioData Class

The AudioData class provides three different ways to acquire audio samples through the GetSamplesKind enum:

  1. GetSamplesKind.wave - Get 256 floats of wave audio data
  2. GetSamplesKind.linear - Get data linearly: first 256 floats are FFT values, next 256 are wave samples
  3. GetSamplesKind.texture - Get data in a 2D matrix of 256 linear rows

Basic Example

Here's a minimal example of how to get audio data for visualization:

class AudioVisualizer extends StatefulWidget {
  @override
  State<AudioVisualizer> createState() => _AudioVisualizerState();
}

class _AudioVisualizerState extends State<AudioVisualizer> with SingleTickerProviderStateMixin {
  late final Ticker ticker;
  late final AudioData audioData;
  
  @override
  void initState() {
    super.initState();
    audioData = AudioData(GetSamplesKind.linear);
    ticker = createTicker(_tick);
    ticker.start();
  }

  @override
  void dispose() {
    ticker.stop();
    audioData.dispose();
    super.dispose();
  }

  void _tick(Duration elapsed) {
    if (context.mounted) {
      try {
        audioData.updateSamples();
        setState(() {});
      } on Exception catch (e) {
        debugPrint('$e');
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: double.infinity,
      height: 100,
      child: CustomPaint(
        painter: AudioVisualizerPainter(
            samples: audioData.getAudioData(),
        ),
        child: Container(),
      ),
    );
  }
}

Basic FFT Visualization Example

Here's a minimal implementation of an AudioVisualizerPainter that displays FFT data as vertical bars:

class AudioVisualizerPainter extends CustomPainter {
  final Float32List samples;
  final Paint barPaint;

  AudioVisualizerPainter({
    required this.samples,
  }) : barPaint = Paint()..color = Colors.blue;

  @override
  void paint(Canvas canvas, Size size) {
    if (samples.isEmpty) return;

    // We're only interested in FFT data (first 256 values)
    final fftData = samples.sublist(0, 256);
    final barWidth = size.width / fftData.length;
    
    for (var i = 0; i < fftData.length; i++) {
      // FFT data typically contains values between 0.0 and 1.0
      final barHeight = fftData[i] * size.height;
      
      canvas.drawRect(
        Rect.fromLTWH(
          i * barWidth,           // Left position
          size.height - barHeight, // Top position (from bottom)
          barWidth,               // Width of each bar
          barHeight,              // Height of each bar
        ),
        barPaint,
      );
    }
  }

  @override
  bool shouldRepaint(covariant AudioVisualizerPainter oldDelegate) {
    return true; // Always repaint when new data arrives
  }
}

This creates a simple frequency spectrum analyzer where:

  • Each bar represents one frequency band from the FFT data
  • Bar height represents the amplitude of that frequency
  • Bars are drawn from left to right, with lower frequencies on the left

Different Sample Kinds

Wave Data Only

final audioData = AudioData(GetSamplesKind.wave);
// Later in your code:
audioData.updateSamples();
final waveData = audioData.getAudioData(); // Returns 256 wave samples

Linear Data (FFT + Wave)

final audioData = AudioData(GetSamplesKind.linear);
// Later in your code:
audioData.updateSamples();
final samples = audioData.getAudioData();
final fftData = samples.sublist(0, 256);    // FFT data
final waveData = samples.sublist(256, 512); // Wave data

2D Texture Data

final audioData = AudioData(GetSamplesKind.texture);
// Later in your code:
audioData.updateSamples();
final textureData = audioData.getAudioData(); // Returns 512x256 matrix

// Each row in the matrix contains 512 values:
// - First 256 values are FFT data
// - Last 256 values are wave data
// The matrix is updated on each call to updateSamples(),
// where the newest data is added at the top and older rows
// are shifted down

When using texture mode, every call to updateSamples() will:

  1. Shift all existing rows down one position
  2. Add the newest FFT and wave data as a new row at the top
  3. The oldest row at the bottom will be discarded

This creates a history of audio data that can be used for waterfall-style visualizations or other time-based effects.

FFT Smoothing

To achieve smoother FFT visualization, you can use the setFftSmoothing method:

// Set smoothing factor (0.0 to 1.0)
// 0.0 = no smoothing
// 1.0 = maximum smoothing
SoLoud.instance.setFftSmoothing(0.7);

Enhanced Visualization with audio_flux

For more advanced audio visualization features, consider using the audio_flux package, which provides additional visualization capabilities using also shaders.

Important Notes

  1. Always call audioData.dispose() when you no longer need the visualization to prevent memory leaks.
  2. The visualization feature must be enabled using setVisualizationEnabled(true).
  3. Each call to updateSamples() updates the internal buffer with new audio data.
  4. Use alwaysReturnData: false with getAudioData() to avoid processing the same data multiple times:
final samples = audioData.getAudioData(alwaysReturnData: false);