Sound und Musik zum Flutter-Spiel hinzufügen

1. Hinweis

Spiele sind audiovisuelle Erlebnisse. Flutter ist ein hervorragendes Tool, um visuell ansprechende Bilder und eine solide Benutzeroberfläche zu erstellen. Die fehlende Zutat, die übrig ist, ist Audio. In diesem Codelab erfährst du, wie du mit dem flutter_soloud-Plug-in Sound und Musik mit niedriger Latenz in dein Projekt einführst. Sie beginnen mit einem einfachen Gerüst, damit Sie direkt zu den interessanten Stellen springen können.

Eine von Hand gezeichnete Illustration von Kopfhörern.

Natürlich können Sie das, was Sie hier gelernt haben, auch verwenden, um Ihren Apps und nicht nur in Ihren Spielen Audioinhalte hinzuzufügen. Während fast alle Spiele Ton und Musik erfordern, aber die meisten Apps nicht, liegt der Schwerpunkt dieses Codelab auf Spielen.

Vorbereitung

  • Grundkenntnisse in Flutter
  • Kenntnisse zum Ausführen und Debuggen von Flutter-Apps.

Lerninhalte

  • So werden One-Shot-Sounds abgespielt.
  • Unterbrechungsfreie Musikschleifen abspielen und anpassen
  • Ton ein- und ausblenden
  • So wendest du Umgebungseffekte auf Geräusche an.
  • So gehen Sie mit Ausnahmen um.
  • Hier erfährst du, wie du all diese Funktionen in einem einzigen Audio-Controller zusammenfassen kannst.

Voraussetzungen

  • Das Flutter SDK
  • Code-Editor Ihrer Wahl

2. Einrichten

  1. Laden Sie die folgenden Dateien herunter. Keine Sorge, wenn Ihre Verbindung langsam ist. Sie benötigen die Dateien später, sodass Sie sie herunterladen können, während Sie arbeiten.
  1. Erstellen Sie ein Flutter-Projekt mit einem Namen Ihrer Wahl.
  1. Erstellen Sie im Projekt eine lib/audio/audio_controller.dart-Datei.
  2. Geben Sie den folgenden Code in die Datei ein:

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
  }
}

Wie Sie sehen, ist dies nur ein Gerüst für zukünftige Funktionen. Das alles werden wir in diesem Codelab implementieren.

  1. Öffnen Sie als Nächstes die Datei lib/main.dart und ersetzen Sie ihren Inhalt durch den folgenden Code:

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. Nachdem die Audiodateien heruntergeladen wurden, erstellen Sie im Stammverzeichnis Ihres Projekts ein Verzeichnis namens assets.
  2. Erstellen Sie im Verzeichnis assets zwei Unterverzeichnisse, das eine music und das andere sounds.
  3. Verschiebe die heruntergeladenen Dateien in dein Projekt, sodass sich die Songdatei in der Datei assets/music/looped-song.ogg und die Pew-Töne in den folgenden Dateien befinden:
  • assets/sounds/pew1.mp3
  • assets/sounds/pew2.mp3
  • assets/sounds/pew3.mp3

Ihre Projektstruktur sollte jetzt in etwa so aussehen:

Eine Baumansicht des Projekts mit Ordnern wie „android“, „ios“ sowie Dateien wie „README.md“ und „analysis_options.yaml“. Darunter befinden sich das Verzeichnis „assets“ mit den Unterverzeichnissen „music“ und „sounds“, das Verzeichnis „lib“ mit „main.dart“ und das Unterverzeichnis „audio“ mit „audio_controller.dart“ sowie die Datei „pubspec.yaml“.  Die Pfeile zeigen auf die neuen Verzeichnisse und auf die Dateien, die Sie bisher bearbeitet haben.

Jetzt, da die Dateien vorhanden sind, müssen Sie Flutter sie mitteilen.

  1. Öffnen Sie die Datei pubspec.yaml und ersetzen Sie den Abschnitt flutter: am Ende der Datei durch Folgendes:

pubspec.yaml

...

flutter:
  uses-material-design: true

  assets:
    - assets/music/
    - assets/sounds/
  1. Fügen Sie eine Abhängigkeit vom Paket flutter_soloud und dem Paket logging hinzu.

pubspec.yaml

...

dependencies:
  flutter:
    sdk: flutter

  flutter_soloud: ^2.0.0
  logging: ^1.2.0

...
  1. Führen Sie das Projekt aus. Noch nichts funktioniert, da Sie die Funktionalität in den folgenden Abschnitten hinzugefügt haben.

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];

Diese stammen aus der zugrunde liegenden SoLoud-C++-Bibliothek. Sie haben keine Auswirkungen auf die Funktionalität und können ignoriert werden.

3. Initialisieren und herunterfahren

Für die Audiowiedergabe verwenden Sie das Plug-in „flutter_soloud“. Dieses Plug-in basiert auf dem Projekt SoLoud, einer C++ Audio-Engine für Spiele, die unter anderem von Nintendo SNES Classic verwendet wird.

7ce23849b6d0d09a.png

So initialisieren Sie die SoLoud-Audio-Engine:

  1. Importieren Sie in der Datei audio_controller.dart das Paket flutter_soloud und fügen Sie der Klasse ein privates _soloud-Feld hinzu.

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
  }

  ...

Der Audio-Controller verwaltet die zugrunde liegende SoLoud-Engine über dieses Feld und leitet alle Aufrufe an diese weiter.

  1. Geben Sie in der Methode initialize() den folgenden Code ein:

lib/audio/audio_controller.dart

...

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

...

Dadurch wird das Feld _soloud ausgefüllt und auf die Initialisierung gewartet. Wichtige Hinweise:

  • SoLoud bietet ein Singleton-instance-Feld. Es gibt keine Möglichkeit, mehrere SoLoud-Instanzen zu instanziieren. Die C++-Engine lässt dies nicht zu und wird vom Dart-Plug-in ebenfalls nicht zugelassen.
  • Die Initialisierung des Plug-ins ist asynchron und erst abgeschlossen, wenn die Methode init() zurückgegeben wird.
  • Der Einfachheit halber werden in diesem Beispiel keine Fehler in einem try/catch-Block erfasst. Im Produktionscode sollten Sie dies tun und dem Nutzer eventuelle Fehler melden.
  1. Geben Sie in der Methode dispose() den folgenden Code ein:

lib/audio/audio_controller.dart

...

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

...

Es hat sich bewährt, SoLoud beim Beenden der App zu beenden, auch wenn du dies vergisst.

  1. Beachten Sie, dass die Methode AudioController.initialize() bereits über die Funktion main() aufgerufen wird. Das bedeutet, dass SoLoud bei einem Heiß-Neustart im Hintergrund initialisiert wird. Es nützt Ihnen jedoch nicht, bevor Sie tatsächlich einige Töne abspielen.

4. Töne aus einer Aufnahme abspielen

Asset laden und wiedergeben

Da Sie jetzt wissen, dass SoLoud beim Start initialisiert wird, können Sie es anfordern, Töne abzuspielen.

SoLoud unterscheidet zwischen einer Audioquelle, d. h. den Daten und Metadaten, die zur Beschreibung eines Tons verwendet werden, und ihren „Toninstanzen“, d. h. den tatsächlich abgespielten Tönen. Ein Beispiel für eine Audioquelle ist eine MP3-Datei, die in den Arbeitsspeicher geladen wird, die abgespielt werden kann und durch eine Instanz der AudioSource-Klasse dargestellt wird. Jedes Mal, wenn du diese Audioquelle abspielst, erstellt SoLoud eine „Toninstanz“ der durch den Typ SoundHandle dargestellt wird.

Sie erhalten eine AudioSource-Instanz, indem Sie sie laden. Wenn deine Assets beispielsweise eine MP3-Datei enthalten, kannst du sie laden, um eine AudioSource zu erhalten. Dann bittest du SoLoud, AudioSource zu spielen. Du kannst ihn viele Male spielen, sogar gleichzeitig.

Wenn Sie eine Audioquelle nicht mehr benötigen, können Sie sie mit der Methode SoLoud.disposeSource() entsorgen.

So laden Sie ein Asset und spielen es ab:

  1. Geben Sie in der Methode playSound() der Klasse AudioController den folgenden Code ein:

lib/audio/audio_controller.dart

  ...

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

  ...
  1. Speichern Sie die Datei, aktualisieren Sie sie bei Bedarf und wählen Sie dann Ton abspielen aus. Sie sollten ein albernes Pew-Geräusch hören. Wichtige Hinweise:
  • Das angegebene Argument assetKey sieht in etwa so aus: assets/sounds/pew1.mp3. Das ist derselbe String, den Sie für jede andere Flutter API zum Laden von Assets verwenden würden, z. B. das Image.asset()-Widget.
  • Die SoLoud-Instanz bietet eine loadAsset()-Methode, die asynchron eine Audiodatei aus den Assets des Flutter-Projekts lädt und eine Instanz der AudioSource-Klasse zurückgibt. Es gibt gleichwertige Methoden zum Laden einer Datei aus dem Dateisystem (Methode loadFile()) und zum Laden über das Netzwerk von einer URL (Methode loadUrl()).
  • Die neu erworbene Instanz AudioSource wird dann an die Methode play() von SoLoud übergeben. Diese Methode gibt eine Instanz des Typs SoundHandle zurück, die den gerade abgespielten Ton darstellt. Dieser Ziehpunkt kann wiederum an andere SoLoud-Methoden übergeben werden, um beispielsweise die Wiedergabe anzuhalten, zu stoppen oder die Lautstärke zu ändern.
  • Obwohl play() eine asynchrone Methode ist, beginnt die Wiedergabe im Grunde sofort. Das flutter_soloud-Paket verwendet die fremde Funktionsschnittstelle von Dart (FFI), um C-Code direkt und synchron aufzurufen. Die übliche Kommunikation zwischen Dart-Code und Plattformcode, die für die meisten Flutter-Plug-ins charakteristisch ist, ist nirgendwo zu finden. Einige Methoden sind nur asynchron, da ein Teil des Codes des Plug-ins in einem eigenen Isola ausgeführt wird und die Kommunikation zwischen Dart-Isolierungen asynchron ist.
  • Sie bestätigen einfach mit _soloud!, dass das Feld _soloud nicht null ist. Auch das ist der Kürze halber. Der Produktionscode sollte mit Situationen umgehen können, in denen der Entwickler versucht, einen Ton abzuspielen, bevor der Audio-Controller vollständig initialisiert werden konnte.

Ausnahmen für den Deal

Vielleicht haben Sie bemerkt, dass Sie wieder mögliche Ausnahmen ignorieren. Lassen Sie uns das für diese Methode zu Lernzwecken ändern. Der Einfachheit halber werden im Codelab Ausnahmen nach diesem Abschnitt ignoriert.

  • Um in diesem Fall mit Ausnahmen zu umgehen, umschließen Sie die beiden Zeilen der playSound()-Methode in einem try/catch-Block und erfassen nur Instanzen von 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 löst verschiedene Ausnahmen aus, z. B. die Ausnahmen SoLoudNotInitializedException oder SoLoudTemporaryFolderFailedException. In der API-Dokumentation der einzelnen Methoden sind die Arten von Ausnahmen aufgeführt, die möglicherweise ausgelöst werden.

SoLoud stellt für alle Ausnahmen auch eine übergeordnete Klasse bereit, die Ausnahme SoLoudException, damit Sie alle Fehler im Zusammenhang mit der Funktionalität der Audio-Engine abfangen können. Dies ist besonders dann hilfreich, wenn die Audiowiedergabe keine Rolle spielt. Das ist beispielsweise der Fall, wenn Sie die Spielsitzung des Spielers nur dann zum Absturz bringen möchten, weil eines der Töne von Pendel-Pew-Effekt nicht geladen werden konnte.

Wie zu erwarten wäre, kann die Methode loadAsset() auch den Fehler FlutterError ausgeben, wenn Sie einen nicht vorhandenen Assetschlüssel angeben. Wenn Sie versuchen, Assets zu laden, die nicht mit dem Spiel enthalten sind, sollten Sie sich in der Regel gezielt umsehen. Daher liegt ein Fehler vor.

Verschiedene Töne abspielen

Vielleicht hast du bemerkt, dass du nur die Datei pew1.mp3 wiedergibst, aber im Asset-Verzeichnis befinden sich zwei weitere Versionen des Tons. Es klingt oft natürlicher, wenn Spiele mehrere Versionen desselben Tons haben und die verschiedenen Versionen zufällig oder abwechselnd gespielt werden. So wird beispielsweise verhindert, dass Fußschritte und Schusswaffen zu uneinheitlich und daher unecht klingen.

  • Als optionale Übung können Sie den Code so ändern, dass bei jedem Antippen der Schaltfläche ein anderer Pew-Ton abgespielt wird.

Illustration von

5. Musikschleifen abspielen

Lang andauernde Geräusche verwalten

Einige Audioinhalte sind für einen längeren Zeitraum gedacht. Musik ist das offensichtliche Beispiel, aber viele Spiele spielen auch eine Atmosphäre, beispielsweise der Wind, der durch die Gänge heult, das Gesang der Mönche aus der Ferne, das Knarren jahrhundertealter Metalle oder das Husten von Patienten in der Ferne.

Dies sind Audioquellen mit einer Wiedergabezeit, die in Minuten gemessen werden kann. Sie müssen sie im Auge behalten, damit Sie sie bei Bedarf pausieren oder stoppen können. Sie werden außerdem oft durch große Dateien gesichert und können viel Arbeitsspeicher verbrauchen. Ein weiterer Grund für deren Verfolgung besteht darin, dass Sie die AudioSource-Instanz entsorgen können, wenn sie nicht mehr benötigt wird.

Aus diesem Grund führen Sie ein neues privates Feld für AudioController ein. Es ist ein Handle für den aktuell wiedergegebenen Titel, falls vorhanden. Fügen Sie folgende Zeile hinzu:

lib/audio/audio_controller.dart

...

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

  SoLoud? _soloud;

  SoundHandle? _musicHandle;    // ← Add this.

  ...

Musik starten

Im Wesentlichen unterscheidet sich das Abspielen von Musik nicht vom Abspielen eines Sounds mit einer Aufnahme. Du musst trotzdem zuerst die Datei assets/music/looped-song.ogg als Instanz der AudioSource-Klasse laden und dann die play()-Methode von SoLoud verwenden, um sie abzuspielen.

Dieses Mal verwendest du jedoch den Tonziehpunkt, den die Methode play() zurückgibt, um das Audio während der Wiedergabe zu bearbeiten.

  • Wenn Sie möchten, können Sie die Methode AudioController.startMusic() selbst implementieren. Es ist in Ordnung, wenn Sie einige der Details nicht richtig machen. Wichtig ist, dass die Musik gestartet wird, wenn Sie Musik starten auswählen.

Hier ist eine Referenzimplementierung:

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

...

Beachten Sie, dass die Musikdatei im Datenträgermodus (die LoadMode.disk-Enum) geladen wird. Das bedeutet einfach, dass die Datei nur bei Bedarf in Teilen geladen wird. Für Audioinhalte mit einer längeren Laufzeit empfiehlt es sich im Allgemeinen, im Datenträgermodus zu laden. Bei kurzen Soundeffekten ist es sinnvoller, sie in den Arbeitsspeicher zu laden und zu dekomprimieren (die Standard-Aufzählung LoadMode.memory).

Es gibt jedoch ein paar Probleme. Erstens: Die Musik ist zu laut, sodass die Töne übertrieben werden. In den meisten Spielen befindet sich die Musik die meiste Zeit im Hintergrund. So werden die informativeren Audioinhalte wie Sprache und Soundeffekte in den Mittelpunkt gestellt. Dieses Problem lässt sich leicht beheben, indem Sie den Lautstärkeparameter der Wiedergabemethode verwenden. Sie können beispielsweise versuchen, _soloud!.play(musicSource, volume: 0.6), um den Titel mit 60% Lautstärke abzuspielen. Alternativ kannst du die Lautstärke später mit einem Wert wie _soloud!.setVolume(_musicHandle, 0.6) einstellen.

Das zweite Problem ist, dass der Song plötzlich anhält. Das liegt daran, dass der Titel in einer Schleife abgespielt werden sollte und der Startpunkt der Schleife nicht der Anfang der Audiodatei ist.

88d2c57fffdfe996.png

Dies ist eine beliebte Wahl für Spielemusik, da der Song mit einem natürlichen Intro beginnt und dann so lange wie nötig abgespielt wird – ohne einen offensichtlichen Schleifenpunkt. Wenn das Spiel den gerade abgespielten Titel verlassen muss, wird er einfach ausgeblendet.

Zum Glück bietet SoLoud die Möglichkeit, Audioschleifen abzuspielen. Die Methode play() verwendet einen booleschen Wert für den Parameter looping sowie den Wert für den Startpunkt der Schleife als Parameter loopingStartAt. Der Code sieht dann so aus:

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

...

Wenn Sie den Parameter loopingStartAt nicht festlegen, wird standardmäßig Duration.zero verwendet (also der Anfang der Audiodatei). Wenn du einen Musiktitel hast, der eine perfekte Schleife ohne Einleitung bietet, dann ist das genau das Richtige für dich.

  • Damit die Audioquelle nach der Wiedergabe ordnungsgemäß entsorgt wird, solltest du den allInstancesFinished-Stream der einzelnen Audioquellen anhören. Mit hinzugefügten Logaufrufen sieht die Methode startMusic() dann so aus:

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

...

Ton ausblenden

Das nächste Problem ist, dass die Musik nie zu Ende ist. Lassen Sie uns eine Blende implementieren.

Eine Möglichkeit, die Ausblendung zu implementieren, wäre eine Funktion, die mehrmals pro Sekunde aufgerufen wird, z. B. Ticker oder Timer.periodic, und die Lautstärke der Musik dabei geringfügig zu verringern. Das würde funktionieren, ist aber aufwendig.

Zum Glück bietet SoLoud praktische Fire-and-Forget-Methoden, die dies für dich erledigen. Hier erfahren Sie, wie Sie die Musik über fünf Sekunden ausblenden und dann die Toninstanz beenden können, damit keine CPU-Ressourcen mehr beansprucht werden. Ersetzen Sie die Methode fadeOutMusic() durch diesen Code:

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. Effekte anwenden

Ein großer Vorteil einer geeigneten Audio-Engine besteht darin, dass Sie die Audioverarbeitung übernehmen können, zum Beispiel durch die Weiterleitung einiger Töne durch einen Nachhall, einen Equalizer oder einen Tiefpassfilter.

In Spielen kann dies verwendet werden, um Orte akustisch zu unterscheiden. Zum Beispiel klingt ein Klatschen im Wald anders als in einem Betonbunker. Während ein Wald dazu beiträgt, den Schall aufzulösen und zu absorbieren, reflektieren die kahlen Wände eines Bunkers die Schallwellen zurück und sorgen so für Nachhall. Außerdem klingen Menschenstimme anders, wenn sie durch eine Wand zu hören sind. Die höheren Frequenzen dieser Töne werden auf ihrem Weg durch das feste Medium leichter abgeschwächt, was zu einem Tiefpass-Filtereffekt führt.

Illustration von zwei Personen, die sich in einem Raum unterhalten. Die Schallwellen dringen nicht nur direkt von einer Person zur anderen aus, sondern prallen auch von Wänden und Decke ab.

SoLoud bietet verschiedene Audioeffekte, die du auf Audioinhalte anwenden kannst.

  • Wenn es sich so anfühlt, als befände sich der Player in einem großen Raum, z. B. einer Kathedrale oder einer Höhle, verwenden Sie die FilterType.freeverbFilter-Enum:

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

...

Wie du siehst, tauchst du mit Filtern in ein untergeordnetes Territorium ein. Das Festlegen eines Filterparameters erfolgt über den Index des Parameters. Der Wet-Parameter des Freeverbs hat beispielsweise den Index 0 und der Raumgröße-Parameter den Index 2.

Mit dem vorherigen Code gehen Sie so vor:

  • Aktiviere den Freeverb-Filter global oder für den gesamten Audiomix, nicht nur für einen einzelnen Ton.
  • Setze den Parameter Wet auf 0.2, damit das Audio zu 80% aus dem Originalton und zu 20% durch den Halleffekt erzeugt wird. Wenn du diesen Parameter auf 1.0 setzt, ist das so, als ob nur die Schallwellen zu hören sind, die von den weiter entfernten Wänden des Raums zu dir kommen, und nicht die Originalaudioinhalte.
  • Legen Sie den Parameter Raumgröße auf 0.9 fest. Sie können diesen Parameter nach Belieben anpassen oder sogar dynamisch ändern. 1.0 ist eine riesige Höhle und 0.0 ein Badezimmer.
  • Wenn Sie möchten, ändern Sie den Code und wenden Sie einen der folgenden Filter oder eine Kombination der folgenden Filter an:
  • FilterType.biquadResonantFilter (kann als Tiefpassfilter verwendet werden)
  • FilterType.eqFilter
  • FilterType.echoFilter
  • FilterType.lofiFilter
  • FilterType.flangerFilter
  • FilterType.bassboostFilter
  • FilterType.waveShaperFilter
  • FilterType.robotizeFilter
  • FilterType.freeverbFilter

7. Glückwunsch

Sie haben einen Audio-Controller implementiert, der Klänge abspielt, Musik als Schleife wiedergeben und Effekte anwendet.

Weitere Informationen

  • Du kannst den Audio-Controller mit Funktionen wie dem Vorabladen von Tönen beim Starten, dem Abspielen von Songs in einer bestimmten Reihenfolge oder einer schrittweisen Anwendung eines Filters nach und nach ausbauen.
  • Lesen Sie die Paketdokumentation für flutter_soloud.
  • Lesen Sie die Startseite der zugrunde liegenden C++-Bibliothek.
  • Weitere Informationen zu Dart FFI, der Technologie für die Schnittstelle zur C++-Bibliothek
  • Sehen Sie sich Guy Sombergs Vortrag zum Thema Audioprogrammierung für Spiele an, um sich inspirieren zu lassen. (Es gibt auch eine längere.) Wenn Guy von „Middleware“ spricht, meint er Bibliotheken wie SoLoud und FMOD. Der Rest des Codes ist tendenziell für jedes Spiel spezifisch.
  • Entwickeln Sie Ihr Spiel und veröffentlichen Sie es.

Illustration von Kopfhörern