Sounds: synthesized sound set and a debug sound board
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0c38528520
commit
d8fd464e33
8 files changed
+648
-13
No files matched your search
@@ -0,0 +1,70 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import 'sfx.dart';
|
||||
import 'sound_bank.dart';
|
||||
import 'synth.dart';
|
||||
|
||||
/// Plays [SoundSpec]s: each is rendered once to a WAV in the temp dir and
|
||||
/// played on a small pool of audio-only mpv players (so two sounds can
|
||||
/// overlap). [volume] is 0..1, applied on a square curve (ears are log).
|
||||
class SynthSfx implements SfxService {
|
||||
double volume;
|
||||
SynthSfx({this.volume = 0.6});
|
||||
|
||||
static const _poolSize = 3;
|
||||
final List<Player> _pool = [];
|
||||
var _next = 0;
|
||||
final Map<String, Future<String>> _files = {};
|
||||
Directory? _dir;
|
||||
|
||||
@override
|
||||
Future<void> play(SfxCue cue) =>
|
||||
playSpec('${cue.kind.name}-${cue.readyIndex}', soundFor(cue.kind, readyIndex: cue.readyIndex));
|
||||
|
||||
/// Plays [spec], cached under [key] (same key = same sound).
|
||||
Future<void> playSpec(String key, SoundSpec spec) async {
|
||||
if (volume <= 0) return;
|
||||
try {
|
||||
final path = await (_files[key] ??= _write(key, spec));
|
||||
final player = await _player();
|
||||
await player.setVolume(100 * volume * volume);
|
||||
await player.open(Media(path));
|
||||
} catch (e) {
|
||||
debugPrint('sfx $key: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> _write(String key, SoundSpec spec) async {
|
||||
_dir ??= await Directory('${(await getTemporaryDirectory()).path}/watch_together_sfx').create(recursive: true);
|
||||
final f = File('${_dir!.path}/$key.wav');
|
||||
await f.writeAsBytes(wav(render(spec)));
|
||||
return f.path;
|
||||
}
|
||||
|
||||
Future<Player> _player() async {
|
||||
if (_pool.length < _poolSize) {
|
||||
final p = Player(
|
||||
configuration: const PlayerConfiguration(vo: 'null', osc: false, libass: false, title: 'sfx'),
|
||||
);
|
||||
await (p.platform as NativePlayer).setProperty('audio-display', 'no');
|
||||
_pool.add(p);
|
||||
return p;
|
||||
}
|
||||
return _pool[_next++ % _poolSize];
|
||||
}
|
||||
|
||||
/// Drops the cached renders (after editing a spec with hot reload).
|
||||
void forget() => _files.clear();
|
||||
|
||||
Future<void> dispose() async {
|
||||
for (final p in _pool) {
|
||||
await p.dispose();
|
||||
}
|
||||
_pool.clear();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user