Dodaj dźwięk i muzykę do gry Flutter

1. Zanim zaczniesz

Gry to doświadczenia audiowizualne. Flutter to świetne narzędzie do tworzenia atrakcyjnych wizualizacji i przejrzystego interfejsu, które znacznie rozszerzają walory wizualne. Pozostały brakujący składnik to dźwięk. Z tego ćwiczenia w Codelabs dowiesz się, jak za pomocą wtyczki flutter_soloud wprowadzić do projektu dźwięk i muzykę o małym opóźnieniu. Zaczynasz od podstawowego rusztowania, aby od razu przejść do interesujących części.

Ręcznie rysowana ilustracja przedstawiająca słuchawki.

Korzystając z uzyskanych tu informacji, możesz oczywiście dodać dźwięk do aplikacji, a nie tylko gier. Choć niemal wszystkie gry wymagają dźwięku i muzyki, większość aplikacji tego nie wymaga, dlatego to ćwiczenia z programowania skupiają się na grach.

Wymagania wstępne

  • Podstawowa znajomość technologii Flutter.
  • Znajomość uruchamiania i debugowania aplikacji Flutter.

Czego się nauczysz

  • Jak odtwarzać dźwięki jednego uderzenia.
  • Dowiedz się, jak grać i dostosowywać pętle muzyczne bez przerw.
  • Jak wyciszyć lub wyciszyć dźwięki.
  • Jak zastosować efekty środowiskowe do dźwięków.
  • Jak radzić sobie z wyjątkami?
  • jak połączyć wszystkie te funkcje w jednym kontrolerze audio.

Wymagania

  • Pakiet SDK Flutter
  • Wybrany przez Ciebie edytor kodu.

2. Skonfiguruj

  1. Pobierz te pliki. Nie martw się, jeśli masz wolne połączenie. Rzeczywiste pliki będą potrzebne później, aby można je było pobrać w czasie pracy.
  1. Utwórz projekt Flutter o wybranej przez siebie nazwie.
  1. Utwórz plik lib/audio/audio_controller.dart w projekcie.
  2. W pliku wpisz ten kod:

lib/audio/audio_controller.dart

import 'dart:async';

import 'package:logging/logging.dart';

class AudioController {
  static final Logger _log = Logger('AudioController');

  Future<void> initialize() async {
    // TODO
  }

  void dispose() {
    // TODO
  }

  Future<void> playSound(String assetKey) async {
    _log.warning('Not implemented yet.');
  }

  Future<void> startMusic() async {
    _log.warning('Not implemented yet.');
  }

  void fadeOutMusic() {
    _log.warning('Not implemented yet.');
  }

  void applyFilter() {
    // TODO
  }

  void removeFilter() {
    // TODO
  }
}

Jak widać, jest to tylko zarys funkcji w przyszłości. Wdrożymy je wszystkie w trakcie tego ćwiczenia z programowania.

  1. Następnie otwórz plik lib/main.dart, a następnie zastąp jego zawartość tym kodem:

lib/main.dart

import 'dart:developer' as dev;

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';

import 'audio/audio_controller.dart';

void main() async {
  // The `flutter_soloud` package logs everything
  // (from severe warnings to fine debug messages)
  // using the standard `package:logging`.
  // You can listen to the logs as shown below.
  Logger.root.level = kDebugMode ? Level.FINE : Level.INFO;
  Logger.root.onRecord.listen((record) {
    dev.log(
      record.message,
      time: record.time,
      level: record.level.value,
      name: record.loggerName,
      zone: record.zone,
      error: record.error,
      stackTrace: record.stackTrace,
    );
  });

  WidgetsFlutterBinding.ensureInitialized();

  final audioController = AudioController();
  await audioController.initialize();

  runApp(
    MyApp(audioController: audioController),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({required this.audioController, super.key});

  final AudioController audioController;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter SoLoud Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.brown),
        useMaterial3: true,
      ),
      home: MyHomePage(audioController: audioController),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.audioController});

  final AudioController audioController;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  static const _gap = SizedBox(height: 16);

  bool filterApplied = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Flutter SoLoud Demo')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            OutlinedButton(
              onPressed: () {
                widget.audioController.playSound('assets/sounds/pew1.mp3');
              },
              child: const Text('Play Sound'),
            ),
            _gap,
            OutlinedButton(
              onPressed: () {
                widget.audioController.startMusic();
              },
              child: const Text('Start Music'),
            ),
            _gap,
            OutlinedButton(
              onPressed: () {
                widget.audioController.fadeOutMusic();
              },
              child: const Text('Fade Out Music'),
            ),
            _gap,
            Row(
              mainAxisSize: MainAxisSize.min,
              children: [
                const Text('Apply Filter'),
                Checkbox(
                  value: filterApplied,
                  onChanged: (value) {
                    setState(() {
                      filterApplied = value!;
                    });
                    if (filterApplied) {
                      widget.audioController.applyFilter();
                    } else {
                      widget.audioController.removeFilter();
                    }
                  },
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}
  1. Po pobraniu plików audio utwórz w katalogu głównym projektu katalog o nazwie assets.
  2. W katalogu assets utwórz 2 podkatalogi: music i sounds.
  3. Przenieś pobrane pliki do projektu, aby plik utworu znajdował się w pliku assets/music/looped-song.ogg, a dźwięki z ławki znajdują się w tych plikach:
  • assets/sounds/pew1.mp3
  • assets/sounds/pew2.mp3
  • assets/sounds/pew3.mp3

Struktura Twojego projektu powinna teraz wyglądać mniej więcej tak:

Widok drzewa projektu z folderami, takimi jak „android”, „ios”, plikami takimi jak „README.md” i „analysis_options.yaml”. Jest wśród nich katalog „assets” z podkatalogami „music” i „sounds”, katalog „lib” z „main.dart” i podkatalogiem „audio” z plikiem „audio_controller.dart” oraz plik „pubspec.yaml”.  Strzałki wskazują nowe katalogi i wywołane do tej pory pliki.

Skoro pliki są już dostępne, musisz poinformować o nich Flutter.

  1. Otwórz plik pubspec.yaml, a następnie zastąp sekcję flutter: na dole pliku tym:

pubspec.yaml

...

flutter:
  uses-material-design: true

  assets:
    - assets/music/
    - assets/sounds/
  1. Dodaj zależność od pakietu flutter_soloud i pakietu logging.

pubspec.yaml

...

dependencies:
  flutter:
    sdk: flutter

  flutter_soloud: ^2.0.0
  logging: ^1.2.0

...
  1. Uruchom projekt. Nic jeszcze nie działa, ponieważ te funkcje zostały dodane w sekcjach poniżej.

10f0f751c9c47038.png

/flutter_soloud/src/filters/filters.cpp:21:24: warning: implicit conversion loses integer precision: 'decltype(__x.base() - __y.base())' (aka 'long') to 'int' [-Wshorten-64-to-32];

Pochodzą one z biblioteki C++ SoLoud. Nie mają one wpływu na funkcjonalność i można je bezpiecznie zignorować.

3. Inicjowanie i wyłączanie

Aby odtworzyć dźwięk, musisz użyć wtyczki flutter_soloud. Ta wtyczka jest oparta na projekcie SoLoud, czyli silniku dźwięku w języku C++ używanym między innymi w grach firmy Nintendo SNES Classic.

7ce23849b6d0d09a.png

Aby zainicjować silnik dźwięku SoLoud, wykonaj te czynności:

  1. W pliku audio_controller.dart zaimportuj pakiet flutter_soloud i dodaj do klasy prywatne pole _soloud.

lib/audio/audio_controller.dart

import 'dart:ui';

import 'package:flutter_soloud/flutter_soloud.dart';  // ← Add this...
import 'package:logging/logging.dart';

class AudioController {
  static final Logger _log = Logger('AudioController');

  SoLoud? _soloud;                                    // ← ... and this.

  Future<void> initialize() async {
    // TODO
  }

  ...

Za pomocą tego pola kontroler audio zarządza bazowym mechanizmem SoLoud i przekierowuje do niego wszystkie wywołania.

  1. W metodzie initialize() wpisz ten kod:

lib/audio/audio_controller.dart

...

  Future<void> initialize() async {
    _soloud = SoLoud.instance;
    await _soloud!.init();
  }

...

Zapełnia pole _soloud i oczekuje na zainicjowanie. Pamiętaj:

  • SoLoud udostępnia pojedyncze pole instance. Nie można utworzyć kilku instancji SoLoud. Mechanizm C++ nie pozwala na to, więc wtyczka Dart również na to nie zezwala.
  • Inicjowanie wtyczki jest asynchroniczne i nie kończy się, dopóki nie zostanie zwrócona metoda init().
  • Dla zachowania zwięzłości w tym przykładzie nie wykrywasz błędów w bloku try/catch. Warto to zrobić w kodzie produkcyjnym i zgłosić ewentualne błędy użytkownikowi.
  1. W metodzie dispose() wpisz ten kod:

lib/audio/audio_controller.dart

...

  void dispose() {
    _soloud?.deinit();
  }

...

Wyłączanie funkcji SoLoud przy wychodzeniu z aplikacji to dobra praktyka, chociaż wszystko powinno działać jak należy, nawet jeśli tego nie zrobisz.

  1. Zwróć uwagę, że metoda AudioController.initialize() jest już wywołana z funkcji main(). Oznacza to, że ponowne uruchomienie projektu z pamięcią inicjuje funkcję SoLoud w tle, ale nie przyniesie to pożytku, dopóki nie odtworzy się niektórych dźwięków.

4. Odtwórz dźwięki jednego uderzenia

Wczytywanie zasobu i odtwarzanie go

Gdy już wiesz, że funkcja SoLoud jest inicjowana podczas uruchamiania, możesz poprosić o włączenie dźwięków.

SoLoud odróżnia źródło dźwięku, którym jest dane i metadane używane do opisania dźwięku, od jego „wystąpień dźwięku”, czyli rzeczywiście odtwarzanych dźwięków. Przykładem źródła dźwięku może być plik mp3 wczytany do pamięci, gotowy do odtwarzania i reprezentowany przez instancję klasy AudioSource. Za każdym razem, gdy odtwarzasz to źródło dźwięku, SoLoud tworzy „wystąpienie dźwięku” reprezentowany przez typ SoundHandle.

Po jej wczytaniu otrzymasz instancję AudioSource. Jeśli np. masz w zasobach plik MP3, możesz go załadować, aby otrzymać AudioSource. Następnie kazasz SoLoud odtworzyć ten utwór AudioSource. Możesz w nie grać wiele razy, a nawet jednocześnie.

Gdy nie używasz już danego źródła dźwięku, możesz je usunąć za pomocą metody SoLoud.disposeSource().

Aby wczytać zasób i go odtworzyć, wykonaj te czynności:

  1. W metodzie playSound() klasy AudioController wpisz ten kod:

lib/audio/audio_controller.dart

  ...

  Future<void> playSound(String assetKey) async {
    final source = await _soloud!.loadAsset(assetKey);
    await _soloud!.play(source);
  }

  ...
  1. Zapisz plik, załaduj go ponownie, a potem kliknij Odtwórz dźwięk. Usłyszysz głupią minę. Pamiętaj:
  • Podany argument assetKey ma postać assets/sounds/pew1.mp3 – ten sam ciąg znaków, który trzeba podać w przypadku dowolnego innego interfejsu Flutter API, który wczytuje zasoby, np. widżetu Image.asset().
  • Instancja SoLoud udostępnia metodę loadAsset(), która asynchronicznie wczytuje plik audio z zasobów projektu Flutter i zwraca instancję klasy AudioSource. Istnieją równoważne metody wczytywania pliku z systemu plików (metoda loadFile()) oraz wczytywania przez sieć z adresu URL (metoda loadUrl()).
  • Nowo pozyskana instancja AudioSource jest następnie przekazywana do metody play() SoLoud. Ta metoda zwraca wystąpienie typu SoundHandle, które reprezentuje nowo odtwarzany dźwięk. Ten nick może z kolei być przekazywany do innych metod SoLoud, np. w celu wstrzymywania, zatrzymywania czy zmiany głośności.
  • Chociaż play() jest metodą asynchroniczną, odtwarzanie zaczyna się zasadniczo od razu. Pakiet flutter_soloud wykorzystuje interfejs funkcji obcych (FFI) Dart do bezpośredniego i synchronicznego wywoływania kodu C. Normalny komunikat pomiędzy kodem Dart a kodem platformy, charakterystyczny dla większości wtyczek Flutter, nie jest nigdzie dostępny. Jedynym powodem, dla którego niektóre metody są asynchroniczne, jest to, że część kodu wtyczki jest uruchamiana samodzielnie, a komunikacja między izolowanymi elementami Dart jest asynchroniczna.
  • Musisz tylko zapewnić, że pole _soloud nie jest puste w parametrze _soloud!. Powtarzam to dla zwięzłości. Kod produkcyjny powinien bezproblemowo obsługiwać sytuację, w której deweloper próbuje odtworzyć dźwięk, zanim kontroler audio zdąży się w pełni zainicjować.

Zgoda z wyjątkami

Być może zauważyliście, że ponownie ignorujecie potencjalne wyjątki. Poprawmy to w przypadku tej konkretnej metody w celach edukacyjnych. (dla zwięzłości ćwiczenia w Codelabs po tej sekcji wracają do ignorowania wyjątków).

  • Aby rozwiązać w tym przypadku wyjątki, spakuj 2 wiersze metody playSound() w blok try/catch i przechwytuj tylko wystąpienia SoLoudException.

lib/audio/audio_controller.dart

  ...

  Future<void> playSound(String assetKey) async {
    try {
      final source = await _soloud!.loadAsset(assetKey);
      await _soloud!.play(source);
    } on SoLoudException catch (e) {
      _log.severe("Cannot play sound '$assetKey'. Ignoring.", e);
    }
  }

  ...

SoLoud zgłasza różne wyjątki, takie jak SoLoudNotInitializedException lub SoLoudTemporaryFolderFailedException. Dokumentacja interfejsu API każdej metody zawiera listę rodzajów wyjątków, które mogą zostać zgłoszone.

SoLoud udostępnia też klasę nadrzędną dla wszystkich wyjątków (wyjątek SoLoudException), dzięki czemu możesz przechwytywać wszystkie błędy związane z działaniem silnika audio. Jest to szczególnie przydatne w przypadkach, gdy odtwarzanie dźwięku nie ma kluczowego znaczenia. Jest tak na przykład wtedy, gdy nie chcesz przerywać sesji gry gracza tylko dlatego, że nie udało się wczytać jednego z dźwięków.

Zgodnie z Twoimi oczekiwaniami metoda loadAsset() może też zgłosić błąd FlutterError, jeśli podasz klucz zasobu, który nie istnieje. Podczas wczytywania zasobów, które nie są dołączone do gry, należy zwykle rozwiązać ten problem, więc pojawia się błąd.

Odtwarzanie różnych dźwięków

Pewnie zauważysz, że odtwarzasz tylko plik pew1.mp3, ale w katalogu zasobów są też 2 inne wersje dźwięku. Często brzmi to bardziej naturalnie, gdy gry mają kilka wersji tego samego dźwięku i grają różne wersje w losowy lub naprzemienny sposób. Zapobiega to na przykład temu, że odgłosy kroków lub strzałów wydają się zbyt jednolite, a tym samym fałszywe.

  • Opcjonalnie zmodyfikuj kod tak, aby przy każdym dotknięciu przycisku odtwarzał inny dźwięk.

Ilustracja:

5. Odtwarzanie pętli muzycznych

Zarządzaj dźwiękami, które trwają dłużej

Niektóre dźwięki są przeznaczone do odtwarzania przez dłuższy czas. Oczywistym przykładem jest muzyka, ale w wielu grach panuje klimat, taki jak szum wiatru biegnący przez korytarze, śpiew mnichów w oddali, skrzypienie wiekowego metalu czy kaszle w oddali pacjentów.

Są to źródła dźwięku, których czas odtwarzania może być mierzony w minutach. Musisz je monitorować, aby w razie potrzeby je wstrzymać lub zatrzymać. Często mają też duże pliki i mogą zajmować dużo pamięci, dlatego warto je śledzić, aby pozbyć się instancji AudioSource, gdy nie jest już potrzebna.

Z tego powodu wprowadzisz w usłudze AudioController nowe pole prywatne. Jest to nick aktualnie odtwarzanego utworu (jeśli taki istnieje). Dodaj ten wiersz:

lib/audio/audio_controller.dart

...

class AudioController {
  static final Logger _log = Logger('AudioController');

  SoLoud? _soloud;

  SoundHandle? _musicHandle;    // ← Add this.

  ...

Włącz muzykę

W skrócie: odtwarzanie muzyki niczym się nie różni od odtwarzania dźwięku pojedynczego uderzenia. Nadal musisz jednak najpierw wczytać plik assets/music/looped-song.ogg jako instancję klasy AudioSource, a następnie użyć metody play() SoLoud, aby go odtworzyć.

Tym razem chcesz jednak chwycić uchwyt dźwięku, który zwraca metodę play(), aby manipulować dźwiękiem podczas odtwarzania.

  • Jeśli chcesz, możesz samodzielnie zaimplementować metodę AudioController.startMusic(). Nie martw się, jeśli nie podasz wszystkich szczegółów. Ważne jest, aby odtwarzanie muzyki rozpoczynało się, gdy klikniesz Włącz muzykę.

Oto przykładowa implementacja:

lib/audio/audio_controller.dart

...

  Future<void> startMusic() async {
    if (_musicHandle != null) {
      if (_soloud!.getIsValidVoiceHandle(_musicHandle!)) {
        _log.info('Music is already playing. Stopping first.');
        await _soloud!.stop(_musicHandle!);
      }
    }
    final musicSource = await _soloud!
        .loadAsset('assets/music/looped-song.ogg', mode: LoadMode.disk);
    _musicHandle = await _soloud!.play(musicSource);
  }

...

Zwróć uwagę, że plik muzyczny wczytuje się w trybie dysku (wyliczenie LoadMode.disk). Oznacza to, że plik jest wczytywany we fragmentach, które są potrzebne. Jeśli dźwięk działa dłużej, lepiej ładować go w trybie dysku. W przypadku krótkich efektów lepiej ładować je i skompresować do pamięci (domyślna lista LoadMode.memory).

Jest jednak kilka problemów. Po pierwsze, muzyka jest zbyt głośna, co przytłacza dźwięki. W większości gier muzyka jest przez większość czasu odtwarzana w tle, nadając centralną scenę dźwiękom zawierającym więcej informacji, na przykład mową lub efektami dźwiękowymi. Ten problem można łatwo rozwiązać, korzystając z parametru głośności metody odtwarzania. Możesz na przykład użyć polecenia _soloud!.play(musicSource, volume: 0.6), by odtworzyć utwór z głośnością 60%. Głośność możesz też ustawić w dowolnym momencie za pomocą polecenia w rodzaju _soloud!.setVolume(_musicHandle, 0.6).

Drugi problem polega na tym, że utwór nagle się zatrzymuje. Dzieje się tak, ponieważ jest to utwór, który powinien być odtwarzany w pętli, a jego punktem początkowym nie jest początek pliku audio.

88d2c57fffdfe996.png

Jest to popularny wybór w przypadku muzyki z gier, ponieważ oznacza, że utwór zaczyna się od naturalnego wprowadzenia, a potem odtwarza się tak długo, jak to konieczne, bez wyraźnej pętli pętli. Gdy gra musi przejść z aktualnie odtwarzanego utworu, po prostu dźwięk go zanika.

Na szczęście SoLoud zapewnia sposoby odtwarzania dźwięku w pętli. Metoda play() przyjmuje wartość logiczną parametru looping oraz wartość punktu początkowego pętli jako parametr loopingStartAt. Wynikowy kod wygląda tak:

lib/audio/audio_controller.dart

...

_musicHandle = await _soloud!.play(
  musicSource,
  volume: 0.6,
  looping: true,
  // ↓ The exact timestamp of the start of the loop.
  loopingStartAt: const Duration(seconds: 25, milliseconds: 43),
);

...

Jeśli nie ustawisz parametru loopingStartAt, domyślnie zostanie przyjęta wartość Duration.zero (czyli początek pliku audio). Jeśli masz utwór muzyczny, który idealnie nadaje się do pętli, bez wprowadzenia, to właśnie jest dla Ciebie.

  • Aby mieć pewność, że po zakończeniu odtwarzania źródło dźwięku zostało prawidłowo usunięte, posłuchaj strumienia allInstancesFinished z każdego z nich. Po dodaniu wywołań dziennika metoda startMusic() wygląda tak:

lib/audio/audio_controller.dart

...

  Future<void> startMusic() async {
    if (_musicHandle != null) {
      if (_soloud!.getIsValidVoiceHandle(_musicHandle!)) {
        _log.info('Music is already playing. Stopping first.');
        await _soloud!.stop(_musicHandle!);
      }
    }
    _log.info('Loading music');
    final musicSource = await _soloud!
        .loadAsset('assets/music/looped-song.ogg', mode: LoadMode.disk);
    musicSource.allInstancesFinished.first.then((_) {
      _soloud!.disposeSource(musicSource);
      _log.info('Music source disposed');
      _musicHandle = null;
    });

    _log.info('Playing music');
    _musicHandle = await _soloud!.play(
      musicSource,
      volume: 0.6,
      looping: true,
      loopingStartAt: const Duration(seconds: 25, milliseconds: 43),
    );
  }

...

Zanikanie dźwięku

Kolejny problem polega na tym, że muzyka nigdy się nie kończy. Zastosujmy zanikanie.

Jednym ze sposobów wdrożenia zanikania jest użycie funkcji, która jest wywoływana kilka razy na sekundę, np. Ticker lub Timer.periodic, i zmniejsza głośność muzyki. To zadziała, ale jest pracochłonne.

Na szczęście SoLoud udostępnia wygodne metody, które pozwalają to zrobić. Oto jak możesz wyciszyć muzykę na przestrzeni 5 sekund, a następnie zatrzymać wystąpienie dźwięku, tak aby nie zużywało niepotrzebnie zasobów procesora. Zastąp metodę fadeOutMusic() tym kodem:

lib/audio/audio_controller.dart

...

  void fadeOutMusic() {
    if (_musicHandle == null) {
      _log.info('Nothing to fade out');
      return;
    }
    const length = Duration(seconds: 5);
    _soloud!.fadeVolume(_musicHandle!, 0, length);
    _soloud!.scheduleStop(_musicHandle!, length);
  }

...

6. Zastosuj efekty

Jedną z ogromnych zalet korzystania z odpowiedniego silnika dźwięku jest możliwość przetwarzania dźwięku, na przykład przekazywania niektórych dźwięków przez pogłos, korektor lub filtr dolnoprzepustowy.

W grach to ustawienie umożliwia słuchowe różnicowanie lokalizacji. Na przykład klaskanie brzmi inaczej w lesie niż w betonowym schronie. Las pomaga się rozpraszać i pochłaniać dźwięki, a odkryte ściany w schronie odbijają fale dźwiękowe, tworząc pogłos. Również głosy ludzi słyszane przez ścianę mogą brzmieć inaczej. Wyższe częstotliwości tych dźwięków są łatwiej stłumione, gdy przechodzą przez światło stałe, co skutkuje efektem filtra dolnoprzepustowego.

Ilustracja przedstawiająca 2 osoby rozmawiające w pomieszczeniu. Fale dźwiękowe docierają nie tylko między ludźmi, ale też odbijają się od ścian i sufitu.

SoLoud udostępnia kilka różnych efektów dźwiękowych, które możesz zastosować do dźwięku.

  • Aby brzmieć, jakby odtwarzacz był w dużym pomieszczeniu, np. katedrze lub jaskini, użyj wyliczenia FilterType.freeverbFilter:

lib/audio/audio_controller.dart

...

  void applyFilter() {
    _soloud!.addGlobalFilter(FilterType.freeverbFilter);
    _soloud!.setFilterParameter(FilterType.freeverbFilter, 0, 0.2);
    _soloud!.setFilterParameter(FilterType.freeverbFilter, 2, 0.9);
  }

  void removeFilter() {
    _soloud!.removeGlobalFilter(FilterType.freeverbFilter);
  }

...

Jak widać, za pomocą filtrów możesz zagłębić się w mniej problemów. Ustawienie parametru filtra odbywa się za pomocą indeksu parametru. Na przykład parametr Wet w funkcji Freeverb ma indeks 0, a indeks rozmiaru pomieszczenia 2.

Za pomocą poprzedniego kodu:

  • Włącz filtr swobodny na całym świecie lub dla całego miksu audio, a nie tylko dla pojedynczego dźwięku.
  • Ustaw parametr Wet na 0.2, co oznacza, że wygenerowany dźwięk będzie w 80% oryginalny, a 20% generacji z efektu pogłosu. Ustawienie tego parametru na 1.0 jest jakby słychać tylko fale dźwiękowe, które do Ciebie dochodzi z odległych ścian pomieszczenia, bez żadnego oryginalnego dźwięku.
  • Ustaw wartość parametru Rozmiar sali na 0.9. Możesz dostosować ten parametr do swoich potrzeb lub nawet zmieniać go dynamicznie. 1.0 to ogromna jaskinia, a 0.0 to łazienka.
  • W razie potrzeby zmień kod i zastosuj jeden z tych filtrów lub kombinację tych filtrów:
  • FilterType.biquadResonantFilter (może służyć jako filtr dolnoprzepustowy)
  • FilterType.eqFilter
  • FilterType.echoFilter
  • FilterType.lofiFilter
  • FilterType.flangerFilter
  • FilterType.bassboostFilter
  • FilterType.waveShaperFilter
  • FilterType.robotizeFilter
  • FilterType.freeverbFilter

7. Gratulacje

Wdrożono kontroler dźwięku, który odtwarza dźwięki, zapętla muzykę i stosuje efekty.

Więcej informacji

  • Wypróbuj funkcje takie jak wstępne wczytywanie dźwięków przy uruchamianiu, odtwarzanie utworów w sekwencji czy stopniowe stosowanie filtrów.
  • Przeczytaj dokumentację pakietu flutter_soloud.
  • Przeczytaj stronę główną biblioteki C++.
  • Dowiedz się więcej o Dart FFI, technologii używanej do łączenia się z biblioteką C++.
  • Aby znaleźć inspirację, obejrzyj film Guya Somberga o programowaniu audio z gier. Istnieje też dłuższy utwór. Gdy Guy mówi o oprogramowaniu pośredniczącym, ma na myśli takie biblioteki jak SoLoud i FMOD. Pozostała część kodu jest zazwyczaj specyficzna dla każdej gry.
  • Utwórz grę i opublikuj ją.

Ilustracja przedstawiająca słuchawki