preload method
Reads an asset so that start does not have to wait.
Implementation
@override
Future<void> preload(String asset) async {
if (_sources.containsKey(asset)) return;
final AudioSource source;
try {
// The bytes first and the source from them, rather than `loadAsset`:
// the file's sample rate is in its header, and the filter below has to
// know it. See `cutoff.dart`.
final bytes = (await rootBundle.load(asset)).buffer.asUint8List();
_sampleRates[asset] = wavSampleRate(bytes) ?? _kAssumedSampleRate;
source = await _soloud.loadMem(asset, bytes);
} catch (error) {
// A missing sound leaves the game silent in one place rather than
// stopping it. Losing a footstep should not cost the play-test — but it
// is worth saying out loud, because silence is what a sound that works
// and a sound that failed to decode look like from the outside.
_failed.add(asset);
onIssue(Issue('could not load "$asset": $error'));
return;
}
_sources[asset] = source;
// A low-pass on every source, open all the way, so a voice heard through
// a wall can be dulled by moving its cutoff rather than by starting a
// filter mid-sound — which is a click. Per source rather than global,
// because a global filter has one cutoff for every voice at once, and a
// torch behind the door and a torch beside you are not the same torch.
try {
final filter = source.filters.biquadFilter;
if (!filter.isActive) filter.activate();
filter.type().value = _kLowPass;
// What a voice starts with before [start] sets its own: open for any
// speed down to half, so no voice ever begins past its Nyquist.
filter.frequency().value = openCutoff(
sampleRate: _sampleRates[asset]!,
rate: 0.5,
);
} catch (error) {
if (!_filterRefused) {
_filterRefused = true;
onIssue(
Issue(
'no low-pass filter on this platform, walls only quieten: $error',
),
);
}
}
}