Skip to content

Commit 8ccc543

Browse files
authored
[code_assets] Add mini_audio example (#2539)
Can play `meow.wav`. 🐈 Example similar in style to sqlite and #2537. But this interacts with audio, which is a bit harder to unit test. So instead have a `bin/` script.
1 parent d6132df commit 8ccc543

File tree

13 files changed

+94422
-0
lines changed

13 files changed

+94422
-0
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# mini_audio
2+
3+
This project is a minimal, cross-platform demonstration of how to use Dart's
4+
[build hooks][], with code assets and `dart:ffi` to call a simple function from
5+
a popular C library. This project uses the `miniaudio` library to play audio
6+
files.
7+
8+
[build hooks]: https://dart.dev/tools/hooks
9+
10+
## How it works
11+
12+
The native code is built by the [`hook/build.dart`](hook/build.dart), which is
13+
run by the Dart SDK CLI tools. This build hook compiles the C code from
14+
[`third_party/miniaudio.c`](third_party/miniaudio.c) into a dynamic library.
15+
16+
The Dart FFI bindings to the C code are generated by
17+
[`tool/ffigen.dart`](tool/ffigen.dart). FFIgen generates Dart FFI bindings from
18+
[`third_party/miniaudio.h`](third_party/miniaudio.h) into
19+
[`lib/src/third_party/miniaudio.g.dart`](lib/src/third_party/miniaudio.g.dart).
20+
21+
The Dart code in [`lib/src/mini_audio.dart`](lib/src/mini_audio.dart) uses the
22+
generated FFI bindings to provide a Dart API (not exposing any C types).
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:io';
6+
7+
import 'package:mini_audio/src/mini_audio.dart';
8+
9+
void main(List<String> args) async {
10+
final engine = MiniAudio();
11+
if (args.isEmpty) {
12+
print('Provide a path to a .wav file.');
13+
return;
14+
}
15+
16+
engine.playSound(args.first);
17+
18+
print('Press Enter to quit...');
19+
stdin.readLineSync();
20+
engine.uninit();
21+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:code_assets/code_assets.dart';
6+
import 'package:hooks/hooks.dart';
7+
import 'package:logging/logging.dart';
8+
import 'package:native_toolchain_c/native_toolchain_c.dart';
9+
10+
void main(List<String> args) async {
11+
await build(args, (input, output) async {
12+
if (input.config.buildCodeAssets) {
13+
final cbuilder = CBuilder.library(
14+
name: 'miniaudio',
15+
assetName: 'src/third_party/miniaudio.g.dart',
16+
sources: ['third_party/miniaudio.c'],
17+
defines: {
18+
if (input.config.code.targetOS == OS.windows)
19+
// Ensure symbols are exported in dll.
20+
'MA_API': '__declspec(dllexport)',
21+
},
22+
);
23+
await cbuilder.run(
24+
input: input,
25+
output: output,
26+
logger: Logger('')
27+
..level = Level.ALL
28+
..onRecord.listen((record) => print(record.message)),
29+
);
30+
}
31+
});
32+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
export 'src/mini_audio.dart';
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:ffi';
6+
7+
import 'package:ffi/ffi.dart';
8+
9+
import 'third_party/miniaudio.g.dart';
10+
11+
/// A wrapper around the miniaudio engine.
12+
final class MiniAudio {
13+
late final Pointer<ma_engine> _engine;
14+
15+
/// Initializes the miniaudio engine.
16+
MiniAudio() {
17+
_engine = malloc();
18+
final result = ma_engine_init(nullptr, _engine);
19+
if (result != ma_result.MA_SUCCESS) {
20+
throw MiniAudioException(
21+
'Failed to initialize miniaudio engine: ${result.name}.',
22+
);
23+
}
24+
}
25+
26+
/// Uninitializes the miniaudio engine and frees resources.
27+
void uninit() {
28+
ma_engine_uninit(_engine);
29+
malloc.free(_engine);
30+
}
31+
32+
/// Plays a sound from the given [filePath].
33+
void playSound(String filePath) => using((arena) {
34+
final filePath_ = filePath.toNativeUtf8(allocator: arena);
35+
final result = ma_engine_play_sound(_engine, filePath_.cast(), nullptr);
36+
if (result != ma_result.MA_SUCCESS) {
37+
throw MiniAudioException('Failed to play audio: ${result.name}}.');
38+
}
39+
});
40+
}
41+
42+
/// An exception that is thrown when an error occurs in the mini_audio library.
43+
final class MiniAudioException implements Exception {
44+
/// The error message.
45+
final String message;
46+
47+
/// Creates a new instance of the exception.
48+
MiniAudioException(this.message);
49+
}

0 commit comments

Comments
 (0)