Flutter 게임에 사운드와 음악 추가

1. 시작하기 전에

게임은 시청각적 경험입니다. Flutter는 아름다운 시각적 요소와 견고한 UI를 빌드하는 데 유용한 도구이므로 시각적 측면에서 큰 도움이 됩니다. 남은 것은 오디오입니다. 이 Codelab에서는 flutter_soloud 플러그인을 사용하여 프로젝트에 지연 시간이 짧은 사운드와 음악을 도입하는 방법을 알아봅니다. 바로 흥미로운 부분으로 들어갈 수 있도록 먼저 기본 스캐폴드부터 시작합니다.

손으로 그린 헤드폰 삽화입니다.

물론 여기에서 배운 내용을 사용하여 게임뿐만 아니라 에 오디오를 추가할 수도 있습니다. 그러나 거의 모든 게임에 사운드와 음악이 필요하지만 대부분의 앱에는 필요하지 않으므로 이 Codelab에서는 게임에 중점을 둡니다.

기본 요건

  • Flutter에 관한 기본 지식
  • Flutter 앱을 실행하고 디버그하는 방법에 관한 지식

학습 내용

  • 원샷 사운드를 재생하는 방법
  • 끊김 없는 음악 루프를 재생하고 맞춤설정하는 방법
  • 사운드를 페이드 인 및 아웃하는 방법
  • 소리에 환경 효과를 적용하는 방법
  • 예외를 처리하는 방법
  • 이러한 모든 기능을 단일 오디오 컨트롤러로 캡슐화하는 방법

필요한 항목

  • Flutter SDK
  • 원하는 코드 편집기

2. 설정

  1. 다음 파일을 다운로드합니다. 연결 속도가 느리더라도 걱정하지 마세요. 나중에 실제 파일이 필요하므로 작업 중에 다운로드할 수 있습니다.
  1. 원하는 이름으로 Flutter 프로젝트를 만듭니다.
  1. 프로젝트에 lib/audio/audio_controller.dart 파일을 만듭니다.
  2. 파일에서 다음 코드를 입력합니다.

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

보시다시피 이는 향후 기능의 뼈대일 뿐입니다. 이 Codelab에서 모두 구현합니다.

  1. 그런 다음 lib/main.dart 파일을 열고 콘텐츠를 다음 코드로 바꿉니다.

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. 오디오 파일이 다운로드되면 프로젝트의 루트에 assets라는 디렉터리를 만듭니다.
  2. assets 디렉터리에서 musicsounds라는 두 하위 디렉터리를 만듭니다.
  3. 노래 파일이 assets/music/looped-song.ogg 파일에 있고 좌석 소리가 다음 파일에 있도록 다운로드한 파일을 프로젝트로 이동합니다.
  • assets/sounds/pew1.mp3
  • assets/sounds/pew2.mp3
  • assets/sounds/pew3.mp3

이제 프로젝트 구조가 다음과 같습니다.

`android`, `ios`와 같은 폴더와 `README.md`, `analysis_options.yaml`과 같은 파일이 있는 프로젝트의 트리 뷰입니다.   여기에서 `music` 및 `sounds` 하위 디렉터리가 있는 `assets` 디렉터리, `main.dart`가 있는 `lib` 디렉터리, `audio_controller.dart`가 있는 `audio` 하위 디렉터리, `pubspec.yaml` 파일을 볼 수 있습니다.  화살표는 새 디렉터리와 지금까지 터치한 파일을 가리킵니다.

이제 파일이 있으므로 Flutter에 파일을 알려야 합니다.

  1. pubspec.yaml 파일을 열고 파일 하단의 flutter: 섹션을 다음으로 바꿉니다.

pubspec.yaml

...

flutter:
  uses-material-design: true

  assets:
    - assets/music/
    - assets/sounds/
  1. flutter_soloud 패키지와 logging 패키지의 종속 항목을 추가합니다.

pubspec.yaml

...

dependencies:
  flutter:
    sdk: flutter

  flutter_soloud: ^2.0.0
  logging: ^1.2.0

...
  1. 프로젝트를 실행합니다. 다음 섹션에서 기능을 추가하므로 아직 아무것도 작동하지 않습니다.

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

이는 기본 SoLoud C++ 라이브러리에서 가져옵니다. 기능에 영향을 미치지 않으므로 안전하게 무시할 수 있습니다.

3. 초기화 및 종료

오디오를 재생하려면 flutter_soloud 플러그인을 사용합니다. 이 플러그인은 Nintendo SNES Classic에서 사용하는 게임용 C++ 오디오 엔진인 SoLoud 프로젝트를 기반으로 합니다.

7ce23849b6d0d09a.png

SoLoud 오디오 엔진을 초기화하려면 다음 단계를 따르세요.

  1. audio_controller.dart 파일에서 flutter_soloud 패키지를 가져오고 비공개 _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
  }

  ...

오디오 컨트롤러는 이 필드를 통해 기본 SoLoud 엔진을 관리하고 모든 호출을 전달합니다.

  1. initialize() 메서드에 다음 코드를 입력합니다.

lib/audio/audio_controller.dart

...

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

...

이렇게 하면 _soloud 필드가 채워지고 초기화가 대기됩니다. 다음에 유의하세요.

  • SoLoud는 싱글톤 instance 필드를 제공합니다. 여러 SoLoud 인스턴스를 인스턴스화하는 방법은 없습니다. 이는 C++ 엔진에서 허용하지 않으므로 Dart 플러그인에서도 허용되지 않습니다.
  • 플러그인 초기화는 비동기식이며 init() 메서드가 반환될 때까지 완료되지 않습니다.
  • 이 예에서는 간결성을 위해 try/catch 블록에서 오류를 포착하지 않습니다. 프로덕션 코드에서는 이를 실행하고 사용자에게 오류를 보고해야 합니다.
  1. dispose() 메서드에 다음 코드를 입력합니다.

lib/audio/audio_controller.dart

...

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

...

앱 종료 시 SoLoud를 종료하는 것이 좋지만, 종료하지 않아도 모든 것이 제대로 작동합니다.

  1. AudioController.initialize() 메서드는 이미 main() 함수에서 호출되었습니다. 즉, 프로젝트를 핫 리스타트하면 백그라운드에서 SoLoud가 초기화되지만 실제로 사운드를 재생하기 전에는 아무런 도움이 되지 않습니다.

4. 원샷 소리 재생

애셋 로드 및 재생

이제 SoLoud가 시작 시 초기화된다는 것을 알았으므로 소리를 재생하도록 요청할 수 있습니다.

SoLoud는 사운드를 설명하는 데 사용되는 데이터 및 메타데이터인 오디오 소스와 실제로 재생되는 사운드인 '사운드 인스턴스'를 구분합니다. 오디오 소스의 예로는 메모리에 로드되어 재생 준비가 되고 AudioSource 클래스의 인스턴스로 표시되는 mp3 파일을 들 수 있습니다. 이 오디오 소스를 재생할 때마다 SoLoud는 SoundHandle 유형으로 표시되는 '사운드 인스턴스'를 만듭니다.

AudioSource 인스턴스는 로드하여 가져옵니다. 예를 들어 애셋에 mp3 파일이 있는 경우 이 파일을 로드하여 AudioSource를 가져올 수 있습니다. 그런 다음 SoLoud에 이 AudioSource를 재생하도록 지시합니다. 여러 번 재생하거나 동시에 재생할 수도 있습니다.

오디오 소스 사용이 끝나면 SoLoud.disposeSource() 메서드로 소스를 폐기합니다.

애셋을 로드하고 재생하려면 다음 단계를 따르세요.

  1. AudioController 클래스의 playSound() 메서드에서 다음 코드를 입력합니다.

lib/audio/audio_controller.dart

  ...

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

  ...
  1. 파일을 저장하고 핫 리로드한 다음 소리 재생을 선택합니다. 우스꽝스러운 삐 소리가 들립니다. 다음에 유의하세요.
  • 제공된 assetKey 인수는 assets/sounds/pew1.mp3와 같습니다. Image.asset() 위젯과 같이 애셋을 로드하는 다른 Flutter API에 제공하는 문자열과 동일합니다.
  • SoLoud 인스턴스는 Flutter 프로젝트의 애셋에서 오디오 파일을 비동기식으로 로드하고 AudioSource 클래스의 인스턴스를 반환하는 loadAsset() 메서드를 제공합니다. 파일 시스템에서 파일을 로드하고 (loadFile() 메서드) URL에서 네트워크를 통해 로드하는 (loadUrl() 메서드) 동일한 메서드가 있습니다.
  • 그러면 새로 획득한 AudioSource 인스턴스가 SoLoud의 play() 메서드로 전달됩니다. 이 메서드는 새로 재생되는 사운드를 나타내는 SoundHandle 유형의 인스턴스를 반환합니다. 이 핸들은 다른 SoLoud 메서드에 전달되어 소리를 일시중지하거나, 중지하거나, 볼륨을 수정하는 등의 작업을 실행할 수 있습니다.
  • play()는 비동기 메서드이지만 재생은 기본적으로 즉시 시작됩니다. flutter_soloud 패키지는 Dart의 외래 함수 인터페이스 (FFI)를 사용하여 C 코드를 직접 동기식으로 호출합니다. 대부분의 Flutter 플러그인에 특징적인 Dart 코드와 플랫폼 코드 간의 일반적인 메시지 주고받기가 어디에도 없습니다. 일부 메서드가 비동기인 유일한 이유는 플러그인의 코드 중 일부가 자체적으로 격리되어 실행되고 Dart 격리 간의 통신이 비동기이기 때문입니다.
  • _soloud!_soloud 필드가 null이 아님을 어설션하기만 하면 됩니다. 다시 말하지만, 간략히 보여주기 위해 그렇게 했습니다. 프로덕션 코드는 오디오 컨트롤러가 완전히 초기화되기 전에 개발자가 사운드를 재생하려고 하는 상황을 적절하게 처리해야 합니다.

예외 처리

가능한 예외를 다시 무시하고 있는 것을 눈치챘을 것입니다. 이 학습 방법에서 이 문제를 해결해 보겠습니다. 간결성을 위해 이 섹션 이후에는 예외 무시로 돌아갑니다.

  • 이 경우 예외를 처리하려면 playSound() 메서드의 두 줄을 try/catch 블록에 래핑하고 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는 SoLoudNotInitializedException 또는 SoLoudTemporaryFolderFailedException 예외와 같은 다양한 예외를 발생시킵니다. 각 메서드의 API 문서에는 발생할 수 있는 예외 유형이 나열되어 있습니다.

또한 SoLoud는 모든 예외의 상위 클래스인 SoLoudException 예외를 제공하므로 오디오 엔진의 기능과 관련된 모든 오류를 포착할 수 있습니다. 이는 오디오 재생이 중요하지 않은 경우에 특히 유용합니다. 예를 들어 삐삐 소리 중 하나가 로드되지 않았다고 해서 플레이어의 게임 세션을 비정상 종료하고 싶지 않은 경우

예상할 수 있듯이 존재하지 않는 애셋 키를 제공하면 loadAsset() 메서드에서 FlutterError 오류가 발생할 수도 있습니다. 게임과 번들로 묶이지 않은 애셋을 로드하려고 시도하는 것은 일반적으로 해결해야 할 문제이므로 오류입니다.

다른 소리 재생

pew1.mp3 파일만 재생되지만 애셋 디렉터리에는 다른 두 가지 버전의 사운드가 있습니다. 게임에 동일한 사운드의 여러 버전이 있고 다양한 버전을 무작위로 또는 순환하여 재생하면 더 자연스럽게 들릴 수 있습니다. 예를 들어 발자국 소리와 총소리가 너무 균일하여 가짜처럼 들리는 것을 방지합니다.

  • 선택적 연습으로, 버튼을 탭할 때마다 다른 퓨 사운드가 재생되도록 코드를 수정합니다.

일러스트레이션

5. 음악 루프 재생

더 오래 재생되는 사운드 관리하기

일부 오디오는 장시간 재생되도록 설계되었습니다. 음악이 가장 대표적인 예이지만 많은 게임에서는 복도를 지나가는 바람 소리, 멀리서 들리는 수도승의 독경 소리, 수백 년 된 금속의 삐걱거리는 소리, 환자의 기침 소리 등 분위기를 조성하는 사운드도 재생합니다.

분 단위로 측정할 수 있는 재생 시간이 포함된 오디오 소스입니다. 필요할 때 일시중지하거나 중지할 수 있도록 추적해야 합니다. 또한 대용량 파일로 지원되는 경우가 많고 메모리를 많이 사용할 수 있으므로 더 이상 필요하지 않을 때 AudioSource 인스턴스를 삭제할 수 있도록 추적하는 것이 좋습니다.

따라서 AudioController에 새 비공개 필드를 도입합니다. 현재 재생 중인 노래의 핸들입니다(있는 경우). 다음 줄을 추가합니다.

lib/audio/audio_controller.dart

...

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

  SoLoud? _soloud;

  SoundHandle? _musicHandle;    // ← Add this.

  ...

음악 시작

본질적으로 음악을 재생하는 것은 일회성 사운드를 재생하는 것과 다르지 않습니다. 먼저 assets/music/looped-song.ogg 파일을 AudioSource 클래스의 인스턴스로 로드한 후 SoLoud의 play() 메서드를 사용하여 재생해야 합니다.

하지만 이번에는 play() 메서드가 반환하는 사운드 핸들을 사용하여 재생 중인 오디오를 조작합니다.

  • 원하는 경우 AudioController.startMusic() 메서드를 직접 구현합니다. 일부 세부정보를 잘못 이해해도 괜찮습니다. 중요한 점은 음악 시작을 선택하면 음악이 시작된다는 것입니다.

다음은 참조 구현입니다.

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

...

디스크 모드 (LoadMode.disk enum)에서 음악 파일을 로드합니다. 즉, 파일이 필요에 따라 청크 단위로만 로드됩니다. 실행 시간이 더 긴 오디오의 경우에는 일반적으로 디스크 모드로 로드하는 것이 가장 좋습니다. 짧은 음향 효과의 경우 메모리에 로드하고 압축 해제하는 것이 좋습니다 (기본 LoadMode.memory enum).

하지만 몇 가지 문제가 있습니다. 첫째, 음악 소리가 너무 커서 다른 소리를 덮어버립니다. 대부분의 게임에서는 음악이 대부분 배경에 재생되므로 음성 및 음향 효과와 같이 더 많은 정보를 제공하는 오디오가 중앙에 표시됩니다. 이는 play 메서드의 볼륨 매개변수를 사용하여 쉽게 해결할 수 있습니다. 예를 들어 _soloud!.play(musicSource, volume: 0.6)를 사용해 볼 수 있습니다. 그러면 노래가 60% 볼륨으로 재생됩니다. 또는 나중에 _soloud!.setVolume(_musicHandle, 0.6)와 같은 방법으로 볼륨을 설정할 수 있습니다.

두 번째 문제는 노래가 갑자기 중지된다는 점입니다. 이는 반복 재생되어야 하는 노래이고 반복의 시작점이 오디오 파일의 시작 지점이 아니기 때문입니다.

88d2c57fffdfe996.png

곡이 자연스러운 인트로로 시작한 다음 명확한 루프 포인트 없이 필요한 만큼 재생된다는 의미이므로 게임 음악에서 인기 있는 옵션입니다. 게임에서 현재 재생 중인 노래를 전환해야 하는 경우 노래를 페이드 아웃합니다.

다행히 SoLoud는 오디오를 반복 재생하는 방법을 제공합니다. play() 메서드는 looping 매개변수의 불리언 값과 루프의 시작점 값을 loopingStartAt 매개변수로 사용합니다. 결과 코드는 다음과 같습니다.

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

...

loopingStartAt 매개변수를 설정하지 않으면 기본값은 Duration.zero(즉, 오디오 파일의 시작 부분)입니다. 아무런 소개 없이 연속 재생되는 음악 트랙이 있다면 다음과 같은 방법을 사용하는 것이 좋습니다.

  • 재생이 끝난 후 오디오 소스가 올바르게 삭제되도록 하려면 각 오디오 소스에서 제공하는 allInstancesFinished 스트림을 수신합니다. 로그 호출을 추가하면 startMusic() 메서드는 다음과 같이 표시됩니다.

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

...

사운드 페이드

다음 문제는 음악이 끝나지 않는다는 점입니다. 페이드 효과를 구현해 보겠습니다.

페이드를 구현할 수 있는 한 가지 방법은 Ticker 또는 Timer.periodic와 같이 초당 여러 번 호출되는 일종의 함수를 사용하여 음악 볼륨을 조금씩 낮추는 것입니다. 이렇게 하면 되지만 많은 작업이 필요합니다.

고맙게도 SoLoud는 이 작업을 편리하게 해주는 파이어 앤 포겟(fire-and-forget) 방법을 제공합니다. 다음은 5초 동안 음악을 페이드 아웃한 다음 사운드 인스턴스를 중지하여 CPU 리소스를 불필요하게 소비하지 않도록 하는 방법입니다. fadeOutMusic() 메서드를 다음 코드로 바꿉니다.

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. 효과 적용

적절한 오디오 엔진을 마음대로 사용할 경우 한 가지 큰 장점은 에코, 이퀄라이저 또는 로우 패스 필터를 통해 일부 사운드를 라우팅하는 등의 오디오 처리를 수행할 수 있다는 것입니다.

게임에서는 위치를 청각적으로 구분하는 데 사용할 수 있습니다. 예를 들어 숲과 콘크리트 벙커에서는 박수 소리가 다르게 울립니다. 숲은 소리를 분산시키고 흡수하는 데 도움이 되지만 벙커의 맨 벽은 음파를 다시 반사하여 리버브를 일으킵니다. 마찬가지로 벽을 통해 들을 때는 사람들의 목소리가 다르게 들립니다. 이러한 사운드의 주파수가 높을수록 단단한 매체를 통과할 때 더 쉽게 감쇠되어 저역 통과 필터 효과가 발생합니다.

방에서 대화하는 두 사람의 삽화 음파는 한 사람에서 다른 사람에게 직접 전달될 뿐만 아니라 벽과 천장에서 반사됩니다.

SoLoud는 오디오에 적용할 수 있는 다양한 오디오 효과를 제공합니다.

  • 플레이어가 대성당이나 동굴 같은 큰 방에 있는 것처럼 들리게 하려면 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();
  }

...

SoLoud.filters 필드를 사용하면 모든 필터 유형과 해당 매개변수에 액세스할 수 있습니다. 모든 매개변수에는 점진적 페이드 및 진동과 같은 내장 기능도 있습니다.

참고: _soloud!.filters는 전역 필터를 노출합니다. 단일 소스에 필터를 적용하려면 동일하게 작동하는 대응 항목 AudioSource.filters를 사용하세요.

이전 코드를 사용하여 다음을 수행합니다.

  • 전역적으로 freeverb 필터를 사용 설정합니다.
  • Wet 매개변수를 0.2로 설정합니다. 그러면 오디오 출력은 원본의 80%, 반향 효과의 출력은 20% 가 됩니다. 이 매개변수를 1.0로 설정하면 방의 먼 벽에서 반사되어 돌아오는 음파만 듣고 원래 오디오는 듣지 못하는 것과 같습니다.
  • 방 크기 매개변수를 0.9로 설정합니다. 이 매개변수를 원하는 대로 변경하거나 동적으로 변경할 수도 있습니다. 1.0는 거대한 동굴이고 0.0는 욕실입니다.
  • 원하는 경우 코드를 변경하고 다음 필터 중 하나 또는 다음 필터 조합을 적용합니다.
  • biquadFilter (로우 패스 필터로 사용 가능)
  • pitchShiftFilter
  • equalizerFilter
  • echoFilter
  • lofiFilter
  • flangerFilter
  • bassboostFilter
  • waveShaperFilter
  • robotizeFilter

7. 축하합니다

사운드를 재생하고, 음악을 루프하고, 효과를 적용하는 오디오 컨트롤러를 구현했습니다.

자세히 알아보기

  • 시작 시 사운드 미리 로드, 노래 순차 재생, 시간 경과에 따라 필터 점진적 적용과 같은 기능으로 오디오 컨트롤러를 한층 더 발전시켜 보세요.
  • flutter_soloud패키지 문서를 읽어보세요.
  • 기본 C++ 라이브러리의 홈페이지를 읽습니다.
  • C++ 라이브러리와의 상호작용에 사용되는 기술인 Dart FFI에 관해 자세히 알아보세요.
  • 게임 오디오 프로그래밍에 관한 가이 솜버그의 강연을 시청하여 아이디어를 얻어 보세요. 더 긴 버전도 있습니다. 가이가 '미들웨어'라고 할 때 그는 SoLoud 및 FMOD와 같은 라이브러리를 의미합니다. 나머지 코드는 게임마다 다릅니다.
  • 게임을 빌드하고 출시합니다.

헤드폰 삽화