Implement flutter_soloud package in Flutter App. Add Effect (echo) to the audio file.

This article was originally published on Medium on Sep 28, 2024.

Hello, Futter developers! Cheers to those searching for a package in Flutter that will be able to add effect to their audio files. Here is a package called flutter_soloud, which gives you the chance to add effects such as echo, reverb, and so on to your audio file. It supports MP3, WAV, OGG, and FLAC formatted files. In this article, we will just focus on adding effects to an audio file. Let us come to the point and move forward step by step.

1 . First step: Initialize soloud

 import 'dart:async';
 import 'package:flutter/foundation.dart';
 import 'package:flutter_soloud/flutter_soloud.dart';
 import 'package:logging/logging.dart';

 final Logger _log = Logger('RecordProvider');
 AudioSource? voiceAudioSource;
 late Duration soundLength = Duration.zero;
 final ValueNotifier<bool> isPlaying = ValueNotifier(false);
 double echo = 0.0;

 Future<bool>initRecordedVoiceFile({required String recordedVoicePath})async{

    if(!SoLoud.instance.isInitialized){
      await SoLoud.instance.init().then((_) {
          Logger('main').info('player started');
          SoLoud.instance.setVisualizationEnabled(true);
          SoLoud.instance.setGlobalVolume(1);
          SoLoud.instance.setMaxActiveVoiceCount(32);
        },
        onError: (Object e) {
          Logger('main').severe('player starting error: $e');
        },
      );

    }
    try {
      voiceAudioSource = await SoLoud.instance.loadFile(recordedVoicePath);
      if (voiceAudioSource != null) {
        soundLength = SoLoud.instance.getLength(voiceAudioSource!);
        prepareAndPlay();
      }
    } on SoLoudException catch (e) {
      _log.severe("Cannot play sound . Ignoring.", e);
    }
    return true;
  }Code language: Dart (dart)

Explanation:

  • If the SoLoud instance is not initialized, it initializes using SoLoud.instance.init().
  • On successful initialization (then block), several configuration options are set:
  • Enabling visualizationSoLoud.instance.setVisualizationEnabled(true) allows the collection of audio visualization data, such as waveform or FFT.
  • Global volumeSoLoud.instance.setGlobalVolume(1) sets the global volume to 100% (maximum volume).
  • Max active voicesSoLoud.instance.setMaxActiveVoiceCount(32) limits the number of concurrently active audio voices to 32. This helps manage audio performance.
  • If there’s an error during initialization, it’s caught by the onError callback, and an error message is logged using Logger.
voiceAudioSource = await SoLoud.instance.loadFile(recordedVoicePath);Code language: Dart (dart)
  • The function attempts to load the recorded voice file from the path specified in recordedVoicePath using SoLoud.instance.loadFile().
  • The loaded file is stored in voiceAudioSource, which is presumably an instance variable of type AudioSource that holds the sound file in memory for playback.

Check if the Audio File is Loaded

if (voiceAudioSource != null) {
  soundLength = SoLoud.instance.getLength(voiceAudioSource!);
  prepareAndPlay();
}Code language: Dart (dart)

  • After loading the audio file, the code checks if the voiceAudioSource is not null, meaning the sound file was loaded successfully.
  • If successful, the following operations occur:
  • Sound lengthsoundLength is assigned the length of the loaded sound using SoLoud.instance.getLength(), which returns the duration of the sound file.
  • Prepare and Play: The function prepareAndPlay() is called to handle audio playback preparation and start playing the sound.

2. Second Step: handle audio playback

StreamSubscription<StreamSoundEvent>? subscription;  
SoundHandle? voiceHandle;

Future<void> prepareAndPlay() async {
    try {
      voiceHandle = await SoLoud.instance.play(voiceAudioSource!,paused: true); // Initially paused

      SoLoud.instance.setVolume(voiceHandle!, 1);
      final voiceSoundFilter = voiceAudioSource!.filters.echoFilter;
      if(!voiceSoundFilter.isActive){
        voiceSoundFilter.activate();
      }
      voiceSoundFilter.wet(soundHandle: voiceHandle).value = echo;

      if (kDebugMode) {
        print("activated echo filter $echo");
      }
      subscription = voiceAudioSource!.soundEvents.listen((eventResult) {
        if (eventResult.event == SoundEventType.handleIsNoMoreValid) {
          resetPlayer();
        }
      });
    } on SoLoudException catch (e) {
      _log.severe("Cannot play sound . Ignoring.", e);
    }
  }Code language: Dart (dart)

Explanation:

voiceHandle = await SoLoud.instance.play(voiceAudioSource!, paused: true); // Initially pausedCode language: Dart (dart)

  • This line plays the audio from the voiceAudioSource. The paused: true parameter means the audio will not start playing immediately but will be loaded into memory, ready to play.
  • The voiceHandle is an identifier for this particular playback instance, which you can later use to control the playback (like pausing, stopping, adjusting volume, etc.)

Setting the Volume

SoLoud.instance.setVolume(voiceHandle!, 1);Code language: Dart (dart)

This sets the volume for the current audio being played, identified by voiceHandle. Here, the volume is set to 1, which is 100% (full volume).

Applying the Echo Filte

final voiceSoundFilter = voiceAudioSource!.filters.echoFilter;Code language: Dart (dart)

This line retrieves the echoFilter from the voiceAudioSource. Each sound in SoLoud can have multiple filters applied, and in this case, the code is specifically selecting the echo filter for the loaded sound.

if (!voiceSoundFilter.isActive) {
  voiceSoundFilter.activate();
}Code language: Dart (dart)

It checks if the echo filter is not yet active, and if it’s not, it activates the filter. This allows the audio to be played with an echo effect.

voiceSoundFilter.wet(soundHandle: voiceHandle).value = echo;Code language: Dart (dart)
  • This sets the wet mix value of the echo filter. The wet value controls how much of the echo effect is mixed with the original sound. The echo variable is a value (likely a user-defined variable) that determines how strong or subtle the echo effect should be.
  • The soundHandle parameter specifies that this filter is applied to the sound currently associated with voiceHandle (the one being played).

Listening for Sound Events

subscription = voiceAudioSource!.soundEvents.listen((eventResult) {
  if (eventResult.event == SoundEventType.handleIsNoMoreValid) {
    resetPlayer();
  }
});Code language: Dart (dart)
  • This sets up an event listener on the voiceAudioSource. SoLoud can emit various events during sound playback, and this code listens for those events.
  • Specifically, it checks if the event is of type handleIsNoMoreValid, which typically indicates that the sound handle (the currently playing sound) is no longer valid, either because the sound has finished playing or was stopped.
  • When this event occurs, the function resetPlayer() is called, which likely resets the state of the player (e.g., stopping playback, resetting UI controls, etc.).

Resetting player after completing 1 cycle

void resetPlayer() {
    if(kDebugMode){
      print("\n\n=====================  resetPlayer() called ========================\n");
    }
    SoLoud.instance.stop(voiceHandle!);
    isPlaying.value = false;
    prepareAndPlay(); // Reset the audio to its initial state
  }Code language: Dart (dart)
  • resetPlayer() is used to stop the current audio playbackreset the playback state, and reinitialize the player.
  • It halts the audio using SoLoud.instance.stop(), sets the playback position to the beginning, and updates the UI to reflect that the sound is no longer playing.
  • The function then calls prepareAndPlay() to load the audio again and prepare it for a fresh playback cycle.
  • This method is useful when you want to reset and start the audio from the beginning, whether because the sound has finished playing or the user manually triggered the reset.

3 . Step Three: PlayPause Audio file or Toggle the Player

 void toggleVoicePlayPause() async {

    if (voiceHandle != null ) {
      try{
        SoLoud.instance.pauseSwitch(voiceHandle!);
        isPlaying.value = !SoLoud.instance.getPause(voiceHandle!);
        if (kDebugMode) {
          print("${isPlaying.value}");
        }
      }on SoLoudException catch (e) {
        _log.severe("Cannot play sound . Ignoring.", e);
      }
    }
  }Code language: Dart (dart)
  • The toggleVoicePlayPause function toggles the play/pause state of an audio file loaded with voiceHandle.
  • It uses pauseSwitch() to switch between playing and pausing the sound and updates the isPlaying variable to reflect the current state.
  • The function checks whether the sound is paused using getPause(), and the isPlaying variable is updated accordingly, which controls the UI’s play/pause state.
  • If there’s an issue during playback (such as an invalid sound handle), it handles the error gracefully by logging it.

4. Step Four: Add a function to change echo value dynamically

  void updateEchoValue(double updatedValue) {
    if (kDebugMode) {
      print("Updated ECHO value : $updatedValue");
    }
    if(voiceAudioSource != null){
      final voiceSoundFilter = voiceAudioSource!.filters.echoFilter;
      voiceSoundFilter.wet(soundHandle: voiceHandle).value = updatedValue;
      echo = updatedValue;
    }
  }Code language: Dart (dart)
  • updateEchoValue(double updatedValue) dynamically updates the echo effect applied to an audio file.
  • The function first checks if an audio file (voiceAudioSource) is loaded. If it is, the echo filter for the current audio playback is retrieved.
  • It then updates the wet parameter of the echo filter, which controls the intensity of the echo effect in real -time.
  • The function also updates the local variable echo to keep track of the current echo setting.

If you’re building a SaaS product and need help with audio, we’d love to talk. Click here to book a free SaaS plan & quote.

Scroll to Top