1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
//! The real-time audio engine behind [`cranpose_services::audio`].
//!
//! `cranpose-services` defines the Compose-shaped API — [`AudioPlayer`], the
//! [`SoundId`] handle, `ProvideAudio`, `rememberSoundBank` — and ships a no-op
//! default. This crate is the implementation an app installs when it wants
//! sound: a software mixer on the platform's real-time thread, fed through a
//! lock-free queue from the UI thread.
//!
//! ```rust,ignore
//! // Once, at startup, before the first composition.
//! cranpose_audio::install();
//! ```
//!
//! # What runs where
//!
//! | Thread | Work |
//! | --- | --- |
//! | UI | decode, clip and voice handle bookkeeping, one queue push per call |
//! | Audio (real-time) | drain the queue, resample, mix, clamp |
//!
//! The audio callback allocates nothing, locks nothing and logs nothing. Clips
//! reach it as `Arc<[f32]>` inside a command; clips it drops travel back over a
//! second queue so the deallocation happens on the UI thread.
//!
//! # When the device is open
//!
//! Only while there is sound to make. The output device opens on the first
//! [`play`](cranpose_services::AudioPlayer::play), not on
//! [`install`](install) and not on
//! [`load_clip`](cranpose_services::AudioPlayer::load_clip) — a clip load is a
//! queue push, and the queue exists before any mixer does — and the mixer gives
//! the stream up again once nothing has sounded for a couple of seconds. A
//! silent screen therefore costs no audio thread and no output route, whether
//! it is the first screen or one reached after an hour of play.
//!
//! # Devices
//!
//! * Android and Wear OS: AAudio through the `ndk` crate (`aaudio` feature, on
//! by default). No Java glue and no C++ toolchain.
//! * Desktop: `cpal` (`cpal-backend` feature, off by default because it links
//! a system audio library).
//! * Anything else: [`AudioError::Unsupported`], and the service falls back to
//! the no-op player so the app still runs.
pub use AudioEngine;
pub use ;
use ;
use Rc;
/// Creates an engine without registering it, for an app that wants to hold the
/// handle itself.
/// Creates an engine and installs it as the platform audio player.
///
/// Call once at startup, on the thread that runs the composition. The output
/// device opens on the first sound, not here and not when clips are loaded, so
/// installing the engine in an app that never plays anything costs nothing.
/// Whether this build has a real output device compiled in. `false` means
/// [`install`] registers an engine that will report
/// [`AudioError::Unsupported`](cranpose_services::AudioError::Unsupported).