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

이러한 API는 기본 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이 아님을 어설션하기만 하면 됩니다. 이 역시 간결함을 위해 설명하겠습니다. 프로덕션 코드는 오디오 컨트롤러가 완전히 초기화되기 전에 개발자가 사운드를 재생하려고 하는 상황을 적절하게 처리해야 합니다.

예외 처리

다시 한번 발생할 수 있는 예외를 무시하고 있다는 사실을 눈치채셨을 것입니다. 이 학습 방법에서 이 문제를 해결해 보겠습니다. 간결성을 위해 Codelab에서는 이 섹션 이후에는 예외를 무시하는 것으로 돌아갑니다.

  • 이 경우 예외를 처리하려면 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 파일만 재생하지만 assets 디렉터리에 다른 두 가지 버전의 사운드가 있다는 사실을 눈치채셨을 수도 있습니다. 게임에 같은 사운드의 버전이 여러 개 있을 때 더 자연스럽게 들릴 수 있으며, 서로 다른 버전을 랜덤으로 또는 돌아가면서 플레이하는 경우가 많습니다. 예를 들어 발걸음이나 총성이 너무 일정하게 들리지 않아 가짜인 것처럼 들리지 않게 할 수 있습니다.

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

일러스트레이션

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).

하지만 몇 가지 문제가 있습니다. 첫째, 음악이 너무 커서 소리를 압도합니다. 대부분의 게임에서 음악은 대부분의 경우 백그라운드에 있어 음성 및 음향 효과와 같이 보다 유익한 오디오에 중심을 둡니다. 재생 메서드의 볼륨 매개변수를 사용하면 쉽게 해결할 수 있습니다. 예를 들어 _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는 오디오에 적용할 수 있는 다양한 오디오 효과를 제공합니다.

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

...

보시다시피 필터를 사용하면 보다 낮은 수준의 영역을 자세히 분석할 수 있습니다. 필터 매개변수 설정은 매개변수의 색인을 사용하여 수행됩니다. 예를 들어 freeverb의 Wet 매개변수의 색인은 0이고 Room Size 매개변수의 색인은 2입니다.

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

  • 프리버브 필터를 전체적으로 사용 설정하거나 단일 사운드만이 아닌 전체 오디오 믹스에 사용 설정합니다.
  • Wet 매개변수를 0.2로 설정합니다. 그러면 오디오 출력은 원본의 80%, 반향 효과의 출력은 20% 가 됩니다. 이 매개변수를 1.0로 설정하면 방의 먼 벽에서 나오는 음파만 듣고 원본 오디오는 전혀 들리지 않습니다.
  • Room 크기 매개변수를 0.9로 설정합니다. 이 매개변수를 원하는 대로 변경하거나 동적으로 변경할 수도 있습니다. 1.0은(는) 거대한 동굴이고 0.0은(는) 화장실입니다.
  • 원하는 경우 코드를 변경하고 다음 필터 중 하나 또는 다음 필터 조합을 적용하세요.
  • FilterType.biquadResonantFilter (로우 패스 필터로 사용 가능)
  • FilterType.eqFilter
  • FilterType.echoFilter
  • FilterType.lofiFilter
  • FilterType.flangerFilter
  • FilterType.bassboostFilter
  • FilterType.waveShaperFilter
  • FilterType.robotizeFilter
  • FilterType.freeverbFilter

7. 축하합니다

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

자세히 알아보기

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

헤드폰 삽화