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

1. Zanim zaczniesz

Gry to doświadczenia audiowizualne. Flutter to świetne narzędzie do tworzenia pięknych wizualizacji i solidnego interfejsu użytkownika, które znacznie ułatwia pracę nad stroną wizualną. Brakującym składnikiem jest dźwięk. Z tego ćwiczenia w Codelab dowiesz się, jak za pomocą wtyczki flutter_soloud dodać do projektu dźwięk i muzykę z niską latencją. Zaczynasz od podstawowego szablonu, dzięki czemu możesz od razu przejść do interesujących Cię części.

Ręcznie rysowana ilustracja słuchawek.

Oczywiście możesz wykorzystać to, czego się nauczysz, aby dodać dźwięk do aplikacji, a nie tylko gier. Jednak prawie wszystkie gry wymagają dźwięku i muzyki, a większość aplikacji nie. Dlatego ten samouczek skupia się na grach.

Wymagania wstępne

  • podstawowa znajomość Fluttera;
  • umiejętność uruchamiania i debugowania aplikacji Flutter;

Czego się nauczysz

  • Jak odtwarzać dźwięki jednosekundowe.
  • Jak odtwarzać i dostosowywać pętle muzyczne bez przerw.
  • Jak włączać i wyłączać dźwięk.
  • Jak zastosować efekty dźwiękowe do dźwięków.
  • Jak postępować w przypadku wyjątków.
  • Jak zamknąć wszystkie te funkcje w ramach jednego kontrolera audio.

Wymagania

  • Pakiet Flutter SDK
  • Edytor kodu do wyboru

2. Skonfiguruj

  1. Pobierz te pliki. Jeśli masz wolne połączenie, nie martw się. Potrzebujesz tych plików później, więc możesz je pobrać podczas pracy.
  1. Utwórz projekt Flutter o dowolnej nazwie.
  1. Utwórz w projekcie plik lib/audio/audio_controller.dart.
  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 widzisz, to tylko szkielet przyszłej funkcji. W ramach tego ćwiczenia zaimplementujemy to wszystko.

  1. Następnie otwórz plik lib/main.dart i 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),
      ),
      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: jeden o nazwie music, a drugi o nazwie sounds.
  3. Przenieś pobrane pliki do projektu, tak aby plik utworu znajdował się w pliku assets/music/looped-song.ogg, a dźwięki ławki w tych plikach:
  • assets/sounds/pew1.mp3
  • assets/sounds/pew2.mp3
  • assets/sounds/pew3.mp3

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

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

Teraz, gdy pliki są już w tym miejscu, musisz poinformować Fluttera o ich istnieniu.

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

pubspec.yaml

...

flutter:
  uses-material-design: true

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

Plik pubspec.yaml powinien teraz zawierać dodatkowe zależności od pakietów flutter_soloudlogging.

pubspec.yaml

...

dependencies:
  flutter:
    sdk: flutter

  flutter_soloud: ^3.1.10
  logging: ^1.3.0

...
  1. Uruchom projekt. Nic jeszcze nie działa, ponieważ funkcje są dodawane w następnych sekcjach.

10f0f751c9c47038.png

3. Inicjowanie i wyłączanie

Aby odtwarzać dźwięk, użyj wtyczki flutter_soloud. Ten wtyczka opiera się na projekcie SoLoud, czyli silniku audio w języku C++, który jest używany m.in. przez Nintendo SNES Classic.

7ce23849b6d0d09a.png

Aby zainicjować silnik audio SoLoud:

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

lib/audio/audio_controller.dart

import 'dart:async';

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
  }

  ...

Kontroler audio zarządza podstawowym mechanizmem SoLoud za pomocą tego pola 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();
  }

...

W ten sposób wypełnisz pole _soloud i poczekasz na inicjalizację. Pamiętaj:

  • SoLoud udostępnia pojedyncze pole instance. Nie ma możliwości utworzenia wielu instancji SoLoud. Silnik C++ nie zezwala na to, więc wtyczka Dart też nie może tego zrobić.
  • Inicjalizacja wtyczki jest asynchroniczna i nie kończy się, dopóki nie zwróci metody init().
  • W tym przykładzie, ze względów zwięzłości, nie przechwytujesz błędów w bloku try/catch. W kodzie produkcyjnym chcesz to zrobić i zgłosić użytkownikowi wszelkie błędy.
  1. W metodzie dispose() wpisz ten kod:

lib/audio/audio_controller.dart

...

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

...

Wyłączanie SoLoud po wyjściu z aplikacji jest dobrą praktyką, ale wszystko powinno działać prawidłowo, nawet jeśli zapomnisz to zrobić.

  1. Zwróć uwagę, że metoda AudioController.initialize() jest już wywoływana z funkcji main(). Oznacza to, że szybkie ponowne uruchomienie projektu inicjuje SoLoud w tle, ale nie przyniesie żadnych korzyści, dopóki nie odtworzysz żadnych dźwięków.

4. Odtwarzanie dźwięków jednorazowych

Ładowanie komponentu i odtwarzanie

Teraz, gdy już wiesz, że SoLoud jest inicjowany podczas uruchamiania, możesz poprosić o odtworzenie dźwięku.

SoLoud rozróżnia źródło dźwięku, czyli dane i metadane używane do opisu dźwięku, oraz „wystąpienia dźwięku”, czyli dźwięki faktycznie odtwarzane. Przykładem źródła audio może być plik mp3 załadowany do pamięci, gotowy do odtworzenia i reprezentowany przez instancję klasy AudioSource. Za każdym razem, gdy odtwarzasz to źródło dźwięku, SoLoud tworzy „występ dźwięku”, który jest reprezentowany przez typ SoundHandle.

Wczytując instancję AudioSource, uzyskujesz instancję AudioSource. Jeśli np. masz w komponentach plik mp3, możesz go załadować, aby uzyskać AudioSource. Następnie mówisz SoLoud, aby odtworzyć AudioSource. Możesz go odtwarzać wiele razy, nawet jednocześnie.

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

Aby załadować komponent i odtworzyć go:

  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, przeładuj go i wybierz Odtwórz dźwięk. Powinieneś usłyszeć głupie „pyk”. Pamiętaj:
  • Podany argument assetKey ma postać assets/sounds/pew1.mp3 – to ten sam ciąg znaków, który podajesz do dowolnego innego interfejsu API do ładowania zasobów w Flutterze, np. do widżetu Image.asset().
  • Wystąpienie SoLoud udostępnia metodę loadAsset(), która asynchronicznie wczytuje plik audio z komponentów projektu Fluttera i zwraca wystąpienie klasy AudioSource. Istnieją równoważne metody wczytywania pliku z systemu plików (metoda loadFile()) i przez sieć z adresu URL (metoda loadUrl()).
  • Nowo uzyskana instancja AudioSource jest następnie przekazywana do metody play() w SoLoud. Ta metoda zwraca instancję typu SoundHandle, która reprezentuje odtwarzany obecnie dźwięk. Ten obiekt można następnie przekazać do innych metod SoLoud, aby wykonać takie czynności jak wstrzymanie, zatrzymanie lub zmiana głośności dźwięku.
  • Chociaż play() jest metodą asynchroniczną, odtwarzanie rozpoczyna się praktycznie natychmiast. Pakiet flutter_soloud używa interfejsu funkcji zewnętrznych (FFI) Darta do wywoływania kodu C bezpośrednio i synchronicznie. Nie ma zwykłej wymiany wiadomości między kodem Darta a kodem platformy, która jest charakterystyczna dla większości wtyczek Fluttera. Jedynym powodem, dla którego niektóre metody są asynchroniczne, jest to, że część kodu w pluginie jest wykonywana w osobnym izolowanym środowisku, a komunikacja między izolowanymi środowiskami Dart jest asynchroniczna.
  • Pole _soloud nie jest puste w przypadku _soloud!. Ponownie, ze względu na zwiększoną zwięzłość. Kod produkcyjny powinien odpowiednio reagować na sytuację, gdy deweloper próbuje odtworzyć dźwięk, zanim kontroler audio zostanie w pełni zainicjowany.

Wyjątki

Zauważysz pewnie, że znów ignorujesz możliwe wyjątki. Czas naprawić to w przypadku tej konkretnej metody na potrzeby nauki. (dla zwięzłości ćwiczenia w Codelabs po tej sekcji wracają do ignorowania wyjątków).

  • Aby w tym przypadku obsługiwać wyjątki, owiń 2 wiersze metody playSound() w blok try/catch i przechwytuj tylko wystąpienia instrukcji 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, np. SoLoudNotInitializedException lub SoLoudTemporaryFolderFailedException. Dokumentacja interfejsu API każdej metody zawiera listę wyjątków, które mogą wystąpić.

SoLoud udostępnia też klasę nadrzędną dla wszystkich wyjątków, czyli wyjątek SoLoudException, dzięki czemu możesz przechwytywać wszystkie błędy związane z funkcjonalnością silnika audio. Jest to szczególnie przydatne w przypadkach, gdy odtwarzanie dźwięku nie jest kluczowe. Na przykład, gdy nie chcesz, aby sesja gry gracza została przerwana tylko dlatego, że jeden z dźwięków „pyk-pyk” nie załadował się.

Jak można się spodziewać, metoda loadAsset() może też zwrócić błąd FlutterError, jeśli podasz klucz zasobu, który nie istnieje. Próba załadowania zasobów, które nie są częścią gry, to coś, co należy naprawić. To błąd.

odtwarzać różne dźwięki;

Zauważysz, że odtwarzasz tylko plik pew1.mp3, ale w katalogu zasobów są 2 inne wersje dźwięku. Często brzmi to bardziej naturalnie, gdy gry mają kilka wersji tego samego dźwięku i odtwarzają je losowo lub naprzemiennie. Zapobiega to temu, aby dźwięki kroków czy strzałów nie brzmiały zbyt jednolicie i nienaturalnie.

  • Opcjonalnie możesz zmodyfikować kod, aby za każdym naciśnięciem przycisku odtwarzać inny dźwięk.

Ilustracja

5. odtwarzanie pętli muzycznych;

Zarządzanie dźwiękami o dłuższym czasie trwania

Niektóre dźwięki są przeznaczone do odtwarzania przez dłuższy czas. Muzyka jest oczywistym przykładem, ale wiele gier odtwarza też dźwięki otoczenia, takie jak szum wiatru w korytarzach, odległe śpiewy mnichów, skrzypienie wiekowego metalu czy odległy kaszel pacjentów.

Są to źródła dźwięku, których czas odtwarzania można mierzyć w minutach. Musisz je śledzić, aby w razie potrzeby móc je wstrzymać lub zatrzymać. Często są one też tworzone na podstawie dużych plików i mogą zużywać dużo pamięci. Kolejnym powodem, dla którego warto je śledzić, jest możliwość usunięcia instancji AudioSource, gdy nie są już potrzebne.

W związku z tym wprowadzisz nowe pole prywatne do AudioController. Jest to identyfikator 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łączanie muzyki

W zasadzie odtwarzanie muzyki nie różni się od odtwarzania dźwięku jednorazowego. Najpierw musisz załadować plik assets/music/looped-song.ogg jako instancję klasy AudioSource, a potem odtworzyć go za pomocą metody play() w SoLoud.

Tym razem jednak przechwytujesz uchwyt dźwięku zwracany przez metodę play(), aby manipulować dźwiękiem podczas jego odtwarzania.

  • Jeśli chcesz, samodzielnie zaimplementuj metodę AudioController.startMusic(). Nie musisz podawać wszystkich szczegółów. Ważne jest to, że muzyka zaczyna się od razu po wybraniu opcji Rozpocznij odtwarzanie muzyki.

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,
    );
  }

...

Pamiętaj, że plik muzyczny jest wczytywany w trybie dyskowym (enumeracja LoadMode.disk). Oznacza to, że plik jest wczytywany tylko w porcjach, w miarę potrzeby. W przypadku dłuższych plików audio najlepiej jest je wczytywać w trybie dyskowym. W przypadku krótkich efektów dźwiękowych lepiej jest je wczytać i rozpakowywać do pamięci (domyślna wartość enum LoadMode.memory).

Występuje jednak kilka problemów. Po pierwsze, muzyka jest zbyt głośna i przytłumia inne dźwięki. W większości gier muzyka jest odtwarzana w tle, aby stworzyć przestrzeń dla bardziej informacyjnych dźwięków, takich jak dialogi i efekty dźwiękowe. Ma to na celu naprawienie użycia parametru głośności w metodzie odtwarzania. Możesz na przykład użyć _soloud!.play(musicSource, volume: 0.6), aby odtworzyć utwór z poziomem głośności 60%. Możesz też ustawić głośność w dowolnym momencie później, na przykład za pomocą polecenia _soloud!.setVolume(_musicHandle, 0.6).

Drugim problemem jest to, że utwór nagle się zatrzymuje. Dzieje się tak, ponieważ jest to utwór, który ma być odtwarzany w pętli, a punkt początkowy pętli nie znajduje się na początku pliku audio.

88d2c57fffdfe996.png

Jest to popularny wybór w przypadku muzyki do gier, ponieważ oznacza to, że utwór zaczyna się od naturalnego wstępu, a potem odtwarzany jest tak długo, jak to konieczne, bez oczywistego punktu pętli. Gdy gra musi przejść do innego utworu, dźwięk ścieżki cichnie.

Na szczęście SoLoud umożliwia odtwarzanie dźwięku w pętli. Metoda play() przyjmuje wartość logiczną dla parametru looping, a także 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, jego domyślna wartość to Duration.zero (czyli początek pliku audio). Jeśli masz utwór, który jest idealną pętlą bez wstępu, to jest to właściwa opcja.

  • Aby mieć pewność, że źródło dźwięku jest prawidłowo usuwane po zakończeniu odtwarzania, odsłuchaj strumienia allInstancesFinished, który zapewnia każde źródło dźwięku. Po dodaniu wywołań logowania 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. Czas na wdrożenie efektu ściemniania.

Jednym ze sposobów na wdrożenie efektu ściemniania jest użycie funkcji wywoływanej kilka razy na sekundę, takiej jak Ticker lub Timer.periodic, i zmniejszanie głośności muzyki o niewielkie wartości. To może zadziałać, ale wymaga dużo pracy.

Na szczęście SoLoud udostępnia wygodne metody, które wykonują te czynności za Ciebie. Oto jak możesz płynnie wyciszyć muzykę w ciągu 5 sekund, a potem zatrzymać odtwarzanie, 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. Stosowanie efektów

Jedną z wielkich zalet posiadania odpowiedniego silnika audio jest możliwość przetwarzania dźwięku, np. kierowania niektórych dźwięków przez pogłos, korektor lub filtr dolnoprzepustowy.

W grach może służyć do różnicowania dźwięków w zależności od lokalizacji. Na przykład oklaski brzmią inaczej w lesie niż w betonowym bunkrze. Las pomaga rozpraszać i absorbować dźwięk, a gołe ściany bunkra odbijają fale dźwiękowe, co powoduje pogłos. Podobnie głosy ludzi brzmią inaczej, gdy słyszy się je przez ścianę. Wyższe częstotliwości tych dźwięków są bardziej tłumione podczas przepływu przez stały materiał, co powoduje efekt filtra dolnoprzepustowego.

Ilustracja przedstawiająca 2 osoby rozmawiające w pokoju. Fale dźwiękowe nie tylko przenoszą się bezpośrednio od jednej osoby do drugiej, 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 uzyskać efekt dużej sali, na przykład katedry lub jaskini, użyj pola SoLoud.filters:

lib/audio/audio_controller.dart

...

  void applyFilter() {
    _soloud!.filters.freeverbFilter.activate();
    _soloud!.filters.freeverbFilter.wet.value = 0.2;
    _soloud!.filters.freeverbFilter.roomSize.value = 0.9;
  }

  void removeFilter() {
    _soloud!.filters.freeverbFilter.deactivate();
  }

...

Pole SoLoud.filters zapewnia dostęp do wszystkich typów filtrów i ich parametrów. Każdy parametr ma też wbudowane funkcje, takie jak stopniowe zanikanie i oscylacja.

Uwaga: _soloud!.filters udostępnia filtry globalne. Jeśli chcesz zastosować filtry do jednego źródła, użyj odpowiednika AudioSource.filters, który działa tak samo.

W poprzednim kodzie:

  • Włącz filtr freeverb globalnie.
  • Ustaw parametr Wet na 0.2, co oznacza, że uzyskany dźwięk będzie w 80% oryginalny, a w 20% efekt pogłosu. Jeśli ustawisz ten parametr na 1.0, usłyszysz tylko fale dźwiękowe, które wracają do Ciebie od odległych ścian pokoju, a nie oryginalny dźwięk.
  • Ustaw parametr Powierzchnia pokoju na 0.9. Możesz dostosować ten parametr do swoich potrzeb lub nawet zmienić go dynamicznie. 1.0 to ogromna jaskinia, a 0.0 to łazienka.
  • Jeśli chcesz, zmień kod i zastosuj jeden z tych filtrów lub ich kombinację:
  • biquadFilter (może być używany jako filtr dolnoprzepustowy)
  • pitchShiftFilter
  • equalizerFilter
  • echoFilter
  • lofiFilter
  • flangerFilter
  • bassboostFilter
  • waveShaperFilter
  • robotizeFilter

7. Gratulacje

Wdrożyłeś kontroler dźwięku, który odtwarza dźwięki, odtwarza muzykę w pętli i zachowuje efekty.

Więcej informacji

  • Możesz też skorzystać z dodatkowych funkcji sterownika dźwięku, takich jak wstępne wczytywanie dźwięków przy uruchamianiu, odtwarzanie utworów w kolejności lub stopniowe stosowanie filtra.
  • Zapoznaj się z dokumentacją pakietu flutter_soloud.
  • Przeczytaj stronę główną biblioteki C++.
  • Dowiedz się więcej o Dart FFI, czyli technologii używanej do obsługi interfejsu z biblioteką C++.
  • Aby zaczerpnąć inspiracji, obejrzyj wykład Guya Somberga na temat programowania dźwięku w grach. (dostępny jest też dłuższy film). Gdy Guy mówi o „middleware”, ma na myśli biblioteki takie jak SoLoud i FMOD. Pozostała część kodu jest zwykle specyficzna dla każdej gry.
  • Utwórz grę i opublikuj ją.

Ilustracja przedstawiająca słuchawki