1. Trước khi bắt đầu
Trò chơi là trải nghiệm nghe nhìn. Flutter là một công cụ tuyệt vời để tạo dựng hình ảnh đẹp mắt và giao diện người dùng ổn định, vì vậy, nó giúp bạn cải thiện khía cạnh trực quan của mọi thứ. Thành phần còn thiếu còn lại là âm thanh. Trong lớp học lập trình này, bạn sẽ tìm hiểu cách sử dụng trình bổ trợ flutter_soloud
để giới thiệu âm thanh và nhạc có độ trễ thấp cho dự án của mình. Bạn nên bắt đầu với một scaffold (giàn giáo) cơ bản để có thể chuyển thẳng đến các phần thú vị.
Tất nhiên, bạn có thể sử dụng những gì bạn học được ở đây để thêm âm thanh vào ứng dụng của bạn, chứ không chỉ trò chơi. Nhưng mặc dù hầu hết các trò chơi đều yêu cầu âm thanh và âm nhạc, nhưng hầu hết ứng dụng thì không, vì vậy, lớp học lập trình này tập trung vào trò chơi.
Điều kiện tiên quyết
- Hiểu biết cơ bản về Flutter.
- Có kiến thức về cách chạy và gỡ lỗi ứng dụng Flutter.
Kiến thức bạn sẽ học được
- Cách phát âm thanh một lần.
- Cách phát và tuỳ chỉnh vòng lặp âm nhạc không ngắt quãng.
- Cách nhỏ dần âm thanh vào và ra.
- Cách áp dụng hiệu ứng môi trường cho âm thanh.
- Cách xử lý các trường hợp ngoại lệ.
- Cách kết hợp tất cả tính năng này vào một bộ điều khiển âm thanh duy nhất.
Bạn cần có
- SDK Flutter
- Trình soạn thảo mã do bạn chọn
2. Thiết lập
- Tải các tệp sau xuống. Nếu bạn có kết nối chậm, đừng lo lắng. Bạn cần các tệp thực sự sau này để có thể cho phép tải xuống khi làm việc.
- Tạo một dự án Flutter bằng tên do bạn chọn.
- Tạo một tệp
lib/audio/audio_controller.dart
trong dự án. - Trong tệp đó, hãy nhập mã sau:
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
}
}
Như bạn có thể thấy, đây chỉ là bộ khung cho chức năng trong tương lai. Chúng ta sẽ triển khai tất cả trong lớp học lập trình này.
- Tiếp theo, hãy mở tệp
lib/main.dart
rồi thay thế nội dung của tệp bằng đoạn mã sau:
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();
}
},
),
],
),
],
),
),
);
}
}
- Sau khi tải các tệp âm thanh xuống, hãy tạo một thư mục trong gốc của dự án có tên là
assets
. - Trong thư mục
assets
, hãy tạo 2 thư mục con, một thư mục con tên làmusic
và thư mục còn lại tên làsounds
. - Di chuyển các tệp đã tải xuống vào dự án của bạn sao cho tệp bài hát nằm trong tệp
assets/music/looped-song.ogg
và âm thanh thốt lên ở các tệp sau:
assets/sounds/pew1.mp3
assets/sounds/pew2.mp3
assets/sounds/pew3.mp3
Bây giờ, cấu trúc dự án của bạn sẽ có dạng như sau:
Giờ đây, khi đã có các tệp này, bạn cần phải cho Flutter biết về chúng.
- Mở tệp
pubspec.yaml
rồi thay thế phầnflutter:
ở cuối tệp bằng đoạn mã sau:
pubspec.yaml
...
flutter:
uses-material-design: true
assets:
- assets/music/
- assets/sounds/
- Thêm phần phụ thuộc vào gói
flutter_soloud
và góilogging
.
pubspec.yaml
...
dependencies:
flutter:
sdk: flutter
flutter_soloud: ^2.0.0
logging: ^1.2.0
...
- Chạy dự án. Chưa có ứng dụng nào hoạt động vì bạn đã thêm chức năng này vào các phần sau.
/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];
Các mã này đến từ thư viện C++ SoLoud
cơ bản. Chúng không ảnh hưởng đến chức năng nên bạn có thể bỏ qua một cách an toàn.
3. Khởi động và tắt
Để phát âm thanh, bạn hãy sử dụng trình bổ trợ flutter_soloud
. Trình bổ trợ này dựa trên dự án SoLoud, một công cụ âm thanh C++ cho các trò chơi được Nintendo SNES Classic sử dụng cùng với các trò chơi khác.
Để khởi chạy công cụ âm thanh SoLoud, hãy làm theo các bước sau:
- Trong tệp
audio_controller.dart
, hãy nhập góiflutter_soloud
và thêm một trường_soloud
riêng tư vào lớp này.
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
}
...
Bộ điều khiển âm thanh quản lý công cụ SoLoud cơ bản thông qua trường này và sẽ chuyển tiếp tất cả lệnh gọi đến đó.
- Trong phương thức
initialize()
, hãy nhập mã sau:
lib/audio/audio_controller.dart
...
Future<void> initialize() async {
_soloud = SoLoud.instance;
await _soloud!.init();
}
...
Thao tác này sẽ điền trường _soloud
và chờ khởi chạy. Xin lưu ý những điều sau:
- SoLoud cung cấp trường
instance
singleton. Không có cách tạo thực thể cho nhiều thực thể SoLoud. Đây không phải là điều mà công cụ C++ cho phép, vì vậy, trình bổ trợ Dart cũng không cho phép điều này. - Quá trình khởi chạy trình bổ trợ không đồng bộ và kết thúc cho đến khi phương thức
init()
trả về. - Tóm lại, trong ví dụ này, bạn không phát hiện lỗi trong khối
try/catch
. Trong mã phát hành chính thức, bạn cần thực hiện việc này và báo cáo lỗi cho người dùng.
- Trong phương thức
dispose()
, hãy nhập mã sau:
lib/audio/audio_controller.dart
...
void dispose() {
_soloud?.deinit();
}
...
Bạn nên tắt SoLoud khi thoát khỏi ứng dụng, mặc dù mọi thứ sẽ hoạt động bình thường ngay cả khi bạn không làm vậy.
- Lưu ý rằng phương thức
AudioController.initialize()
đã được gọi từ hàmmain()
. Điều này có nghĩa là việc khởi động lại dự án sẽ khởi chạy SoLoud ở chế độ nền, nhưng sẽ không mang lại lợi ích gì cho bạn trước khi bạn thực sự phát một số âm thanh.
4. Phát âm thanh một lần
Tải và phát nội dung
Giờ đây, bạn đã biết SoLoud được khởi động khi khởi động, nên bạn có thể yêu cầu SoLoud phát âm thanh.
SoLoud phân biệt nguồn âm thanh, cụ thể là dữ liệu và siêu dữ liệu dùng để mô tả âm thanh và "thực thể âm thanh" (chính là những âm thanh thực sự được phát). Một ví dụ về nguồn âm thanh có thể là một tệp mp3 được tải vào bộ nhớ, sẵn sàng phát và được biểu thị bằng một thực thể của lớp AudioSource
. Mỗi khi bạn phát nguồn âm thanh này, SoLoud sẽ tạo một "phiên bản âm thanh" được biểu thị bằng loại SoundHandle
.
Bạn nhận được một thực thể AudioSource
bằng cách tải thực thể đó. Ví dụ: nếu có tệp mp3 trong thành phần, bạn có thể tải tệp đó để nhận AudioSource
. Sau đó, bạn yêu cầu SoLoud phát AudioSource
này. Bạn có thể phát đồng thời nhiều lần, thậm chí cùng lúc.
Khi dùng xong một nguồn âm thanh, bạn sẽ loại bỏ nguồn âm thanh đó bằng phương thức SoLoud.disposeSource()
.
Để tải và phát tài sản, hãy làm theo các bước sau:
- Trong phương thức
playSound()
của lớpAudioController
, hãy nhập đoạn mã sau:
lib/audio/audio_controller.dart
...
Future<void> playSound(String assetKey) async {
final source = await _soloud!.loadAsset(assetKey);
await _soloud!.play(source);
}
...
- Lưu tệp, tải lại rồi chọn Phát âm thanh. Bạn sẽ nghe thấy âm thanh ngớ ngẩn. Xin lưu ý những điều sau:
- Đối số
assetKey
được cung cấp có dạngassets/sounds/pew1.mp3
— cùng chuỗi mà bạn cung cấp cho bất kỳ API Flutter tải thành phần nào khác, chẳng hạn như tiện íchImage.asset()
. - Thực thể SoLoud cung cấp phương thức
loadAsset()
tải không đồng bộ tệp âm thanh từ các thành phần của dự án Flutter và trả về một thực thể của lớpAudioSource
. Có các phương thức tương đương để tải một tệp từ hệ thống tệp (phương thứcloadFile()
) và tải qua mạng từ một URL (phương thứcloadUrl()
). - Sau đó, thực thể
AudioSource
mới thu nạp được chuyển đến phương thứcplay()
của SoLoud. Phương thức này trả về một thực thể của loạiSoundHandle
đại diện cho âm thanh mới phát. Từ đó, tên người dùng này có thể được truyền đến các phương thức SoLoud khác để thực hiện những thao tác như tạm dừng, dừng hoặc chỉnh sửa âm lượng. - Mặc dù
play()
là phương thức không đồng bộ, nhưng về cơ bản, quá trình phát sẽ bắt đầu ngay lập tức. Góiflutter_soloud
sử dụng giao diện hàm đối ngoại (FFI) của Dart để gọi mã C trực tiếp và đồng bộ. Thông thường, không tìm thấy thông báo qua lại giữa mã Dart và mã nền tảng, vốn thường thấy ở hầu hết các trình bổ trợ Flutter. Lý do duy nhất khiến một số phương thức không đồng bộ là một số mã của trình bổ trợ chạy trong một vùng riêng và hoạt động giao tiếp giữa các vùng phân tách Dart không đồng bộ. - Bạn chỉ cần xác nhận rằng trường
_soloud
không rỗng bằng_soloud!
. Tôi xin nhắc lại một lần nữa để nói ngắn gọn. Mã sản xuất phải xử lý linh hoạt tình huống khi nhà phát triển cố gắng phát âm thanh trước khi bộ điều khiển âm thanh có cơ hội khởi chạy đầy đủ.
Xử lý các trường hợp ngoại lệ
Có thể bạn đã nhận thấy rằng một lần nữa, bạn đang bỏ qua các ngoại lệ có thể có. Hãy khắc phục vấn đề đó đối với phương pháp cụ thể này để học tập. (Tóm lại, lớp học lập trình sẽ quay lại phương thức bỏ qua các ngoại lệ sau phần này.)
- Để xử lý các ngoại lệ trong trường hợp này, hãy gói hai dòng của phương thức
playSound()
trong một khốitry/catch
và chỉ phát hiện các thực thể củaSoLoudException
.
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 gửi nhiều trường hợp ngoại lệ, chẳng hạn như ngoại lệ SoLoudNotInitializedException
hoặc SoLoudTemporaryFolderFailedException
. Tài liệu API của mỗi phương thức liệt kê các loại ngoại lệ có thể được gửi.
SoLoud cũng cung cấp một lớp mẹ cho tất cả các trường hợp ngoại lệ, đó là trường hợp ngoại lệ SoLoudException
. Nhờ đó, bạn có thể phát hiện mọi lỗi liên quan đến chức năng của công cụ phát âm thanh. Điều này đặc biệt hữu ích trong những trường hợp việc phát âm thanh không quan trọng. Ví dụ: khi bạn không muốn làm hỏng phiên chơi của người chơi chỉ vì hệ thống không tải được âm thanh kêu.
Như bạn thường thấy, phương thức loadAsset()
cũng có thể gửi ra lỗi FlutterError
nếu bạn cung cấp khoá tài sản không tồn tại. Nhìn chung, bạn nên giải quyết việc cố gắng tải các thành phần không đi kèm với trò chơi, do đó đó là lỗi.
Phát nhiều âm thanh
Bạn có thể nhận thấy chỉ phát tệp pew1.mp3
, nhưng có hai phiên bản âm thanh khác trong thư mục nội dung. Thường thì âm thanh sẽ tự nhiên hơn khi trò chơi có nhiều phiên bản âm thanh giống nhau và người dùng có thể phát các phiên bản khác nhau theo cách ngẫu nhiên hoặc luân phiên. Điều này giúp ngăn chặn (ví dụ: tiếng bước chân và tiếng súng) nghe có vẻ không đồng nhất và do đó là giả mạo.
- Đây là một bài tập không bắt buộc, hãy sửa đổi mã để phát một âm thanh khác mỗi khi người dùng nhấn vào nút này.
5. Phát vòng lặp âm nhạc
Quản lý các âm thanh chạy lâu hơn
Một số âm thanh sẽ phát trong thời gian dài. Ví dụ rõ ràng là âm nhạc, nhưng nhiều trò chơi cũng chơi không khí, chẳng hạn như tiếng gió hú qua hành lang, tiếng các nhà sư từ xa, tiếng kim loại hàng trăm năm tuổi kêu lách cách hay tiếng ho từ xa của bệnh nhân.
Đây là những nguồn âm thanh có thời gian phát có thể đo lường bằng phút. Bạn cần theo dõi các quảng cáo đó để có thể tạm dừng hoặc dừng quảng cáo khi cần. Các tệp này cũng thường được các tệp lớn hỗ trợ và có thể tốn nhiều bộ nhớ. Vì vậy, một lý do khác để theo dõi tệp là để bạn có thể loại bỏ thực thể AudioSource
khi không cần nữa.
Do đó, bạn sẽ giới thiệu một trường riêng tư mới cho AudioController
. Đây là tên người dùng của bài hát đang phát, nếu có. Hãy thêm dòng lệnh sau đây:
lib/audio/audio_controller.dart
...
class AudioController {
static final Logger _log = Logger('AudioController');
SoLoud? _soloud;
SoundHandle? _musicHandle; // ← Add this.
...
Bắt đầu phát nhạc
Về cơ bản, việc phát nhạc không khác với phát âm thanh một lần. Trước tiên, bạn vẫn cần tải tệp assets/music/looped-song.ogg
làm thực thể của lớp AudioSource
, sau đó sử dụng phương thức play()
của SoLoud để phát tệp đó.
Tuy nhiên, lần này bạn sẽ giữ thanh điều khiển âm thanh mà phương thức play()
quay lại để điều khiển âm thanh trong khi phát.
- Nếu muốn, hãy tự triển khai phương thức
AudioController.startMusic()
. Nếu bạn không nắm được một số thông tin chính xác thì cũng không sao. Điều quan trọng là nhạc sẽ bắt đầu khi bạn chọn Bắt đầu phát nhạc.
Dưới đây là cách triển khai tham chiếu:
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);
}
...
Lưu ý rằng bạn tải tệp nhạc ở chế độ đĩa (enum LoadMode.disk
). Điều này đơn giản có nghĩa là tệp chỉ được tải theo từng phần khi cần. Để có âm thanh chạy lâu hơn, thông thường, tốt nhất bạn nên tải ở chế độ đĩa. Đối với các hiệu ứng âm thanh ngắn, bạn nên tải và giải nén các hiệu ứng đó vào bộ nhớ (enum LoadMode.memory
mặc định).
Tuy nhiên, bạn có một vài vấn đề. Thứ nhất, nhạc quá lớn, tràn ngập âm thanh. Trong hầu hết các trò chơi, nhạc thường phát ở chế độ nền, qua đó giúp âm thanh giàu thông tin hơn như lời nói và hiệu ứng âm thanh. Bạn có thể dễ dàng khắc phục vấn đề này bằng cách sử dụng tham số âm lượng của phương thức phát. Ví dụ: bạn có thể thử _soloud!.play(musicSource, volume: 0.6)
để phát bài hát ở mức âm lượng 60%. Ngoài ra, bạn có thể đặt âm lượng vào bất cứ lúc nào sau này bằng cách dùng _soloud!.setVolume(_musicHandle, 0.6)
.
Vấn đề thứ hai là bài hát dừng đột ngột. Lý do là vì đây là một bài hát cần phát lặp lại và điểm bắt đầu của vòng lặp không phải là điểm bắt đầu của tệp âm thanh.
Đây là một lựa chọn phổ biến cho nhạc trò chơi vì bài hát bắt đầu bằng một đoạn mở đầu tự nhiên, sau đó có thể phát lâu dài khi cần mà không có một điểm lặp rõ ràng. Khi trò chơi cần chuyển sang trạng thái khác của bài hát đang phát, bài hát đó chỉ tắt dần.
Rất may là SoLoud cung cấp nhiều cách phát âm thanh lặp lại. Phương thức play()
lấy giá trị boolean cho tham số looping
, đồng thời nhận giá trị cho điểm bắt đầu của vòng lặp làm tham số loopingStartAt
. Mã kết quả có dạng như sau:
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),
);
...
Nếu bạn không đặt tham số loopingStartAt
, thì theo mặc định, tham số này sẽ là Duration.zero
(nói cách khác, là điểm bắt đầu của tệp âm thanh). Nếu bạn có một bản nhạc là một vòng lặp hoàn hảo mà không cần có phần giới thiệu, thì đây chính là điều bạn muốn làm.
- Để đảm bảo rằng nguồn âm thanh được xử lý đúng cách sau khi phát xong, hãy nghe luồng
allInstancesFinished
mà mỗi nguồn âm thanh cung cấp. Với các lệnh gọi nhật ký đã thêm, phương thứcstartMusic()
sẽ có dạng như sau:
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),
);
}
...
Âm thanh nhỏ dần
Vấn đề tiếp theo là âm nhạc không bao giờ kết thúc. Hãy triển khai hiệu ứng làm mờ.
Bạn có thể triển khai hiệu ứng làm mờ bằng cách dùng một loại hàm được gọi vài lần trong một giây, chẳng hạn như Ticker
hoặc Timer.periodic
, sau đó giảm âm lượng của bản nhạc xuống những lần giảm nhỏ. Như vậy có được, nhưng mất rất nhiều công sức.
May mắn thay, SoLoud cung cấp các phương pháp "quên quên" thuận tiện để giúp bạn làm việc này. Dưới đây là cách bạn có thể làm mờ nhạc trong khoảng thời gian năm giây và sau đó dừng thực thể âm thanh để nó không tiêu thụ tài nguyên CPU một cách không cần thiết. Thay thế phương thức fadeOutMusic()
bằng mã sau:
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. Áp dụng hiệu ứng
Một lợi thế lớn khi sử dụng một công cụ âm thanh phù hợp là bạn có thể xử lý âm thanh, chẳng hạn như định tuyến một số âm thanh thông qua âm vang, bộ cân bằng âm thanh hoặc bộ lọc thông thấp.
Trong trò chơi, vị trí có thể được dùng để phân biệt thính giác. Ví dụ: tiếng vỗ tay trong rừng khác với tiếng vỗ tay trong hầm bê tông. Trong khi rừng cây giúp phân tán và hấp thụ âm thanh, thì các bức tường trần của hầm trú ẩn phản chiếu sóng âm trở lại, dẫn đến âm vang. Tương tự như vậy, giọng nói của mọi người cũng khác khi nghe thấy qua tường. Tần số cao hơn của những âm thanh đó dễ bị suy giảm hơn khi chúng đi qua môi trường chất rắn, dẫn đến hiệu ứng bộ lọc thông thấp.
SoLoud cung cấp một số hiệu ứng âm thanh mà bạn có thể áp dụng cho âm thanh.
- Để người chơi nghe như đang ở trong một căn phòng lớn, chẳng hạn như nhà thờ hoặc hang động, hãy sử dụng enum
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);
}
...
Như bạn có thể thấy, bằng bộ lọc, bạn có thể tìm hiểu kỹ hơn về lãnh thổ cấp thấp hơn. Việc đặt tham số bộ lọc được thực hiện thông qua chỉ mục của tham số đó. Ví dụ: tham số Wet của động từ tự do có chỉ mục là 0
và tham số Room size có chỉ mục là 2
.
Với mã trước đó, bạn làm như sau:
- Bật bộ lọc động từ tự do trên toàn bộ hoặc cho toàn bộ danh sách kết hợp âm thanh chứ không chỉ một âm thanh duy nhất.
- Đặt thông số Wet thành
0.2
, tức là âm thanh thu được sẽ có 80% âm thanh gốc và 20% đầu ra của hiệu ứng âm vang. Nếu bạn đặt tham số này thành1.0
, thì việc này sẽ giống như chỉ nghe thấy sóng âm thanh quay lại bạn từ những bức tường xa của phòng và không có âm thanh gốc. - Đặt thông số Room size (Kích thước phòng) thành
0.9
. Bạn có thể điều chỉnh tham số này theo ý thích hoặc thậm chí thay đổi linh động.1.0
là một hang động lớn trong khi0.0
là một phòng tắm.
- Nếu bạn thích, hãy thay đổi mã và áp dụng một trong các bộ lọc sau hoặc kết hợp các bộ lọc sau:
FilterType.biquadResonantFilter
(có thể dùng làm bộ lọc thông báo thấp)FilterType.eqFilter
FilterType.echoFilter
FilterType.lofiFilter
FilterType.flangerFilter
FilterType.bassboostFilter
FilterType.waveShaperFilter
FilterType.robotizeFilter
FilterType.freeverbFilter
7. Xin chúc mừng
Bạn đã triển khai một bộ điều khiển âm thanh phát âm thanh, lặp lại nhạc và áp dụng hiệu ứng.
Tìm hiểu thêm
- Hãy thử dùng bộ điều khiển âm thanh hơn nữa bằng các tính năng như tải trước âm thanh khi khởi động, phát bài hát theo trình tự hoặc áp dụng bộ lọc dần theo thời gian.
- Đọc tài liệu về gói của
flutter_soloud
. - Đọc trang chủ của thư viện C++ cơ bản.
- Đọc thêm về Dart FFI, công nghệ dùng để giao tiếp với thư viện C++.
- Xem bài nói chuyện của Guy Somberg về chương trình âm thanh trò chơi để tìm cảm hứng. (Ngoài ra còn có một URL dài hơn.) Khi Guy nói về "phần mềm trung gian", anh ấy muốn nói đến các thư viện như SoLoud và FMOD. Phần còn lại của mã có xu hướng dành riêng cho từng trò chơi.
- Tạo bản dựng và phát hành trò chơi.