narraleaf-react 0.22.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/game/nlcore/common/game.d.ts +4 -2
- package/dist/game/nlcore/common/types.d.ts +2 -1
- package/dist/game/nlcore/elements/sound.d.ts +28 -2
- package/dist/game/nlcore/game/audioBus.d.ts +268 -0
- package/dist/game/nlcore/game/preference.d.ts +10 -1
- package/dist/game/nlcore/game.d.ts +32 -0
- package/dist/game/nlcore/gameTypes.d.ts +41 -0
- package/dist/game/player/elements/player/PreferenceUpdateAnnouncer.d.ts +8 -0
- package/dist/game/player/lib/AudioManager.d.ts +147 -5
- package/dist/main.js +42 -42
- package/package.json +1 -1
|
@@ -1,33 +1,119 @@
|
|
|
1
|
-
import { Sound as SoundElement,
|
|
1
|
+
import { Sound as SoundElement, SoundBusId } from "../../nlcore/elements/sound";
|
|
2
2
|
import { SoundToken } from "@narraleaf/sound";
|
|
3
3
|
import { FadeOptions } from "../../nlcore/elements/type";
|
|
4
4
|
import { Awaitable } from "../../../util/data";
|
|
5
5
|
import { GameState } from "../gameState";
|
|
6
6
|
import { LogicAction } from "../../nlcore/action/logicAction";
|
|
7
|
+
import { AudioBusState } from "../../nlcore/game/audioBus";
|
|
7
8
|
export type AudioDataRaw = {
|
|
8
9
|
isPlaying: boolean;
|
|
9
10
|
position: number;
|
|
10
11
|
};
|
|
11
12
|
export type AudioManagerDataRaw = {
|
|
12
13
|
sounds: [string, AudioDataRaw][];
|
|
13
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Bus volumes, keyed by bus id.
|
|
16
|
+
*
|
|
17
|
+
* The name is historical - these used to be the three fixed "groups". A save written before
|
|
18
|
+
* buses existed carries exactly `bgm`/`sound`/`voice`, which are still buses, so it restores
|
|
19
|
+
* unchanged.
|
|
20
|
+
*/
|
|
21
|
+
groups: [string, number][];
|
|
14
22
|
};
|
|
15
23
|
export declare class AudioManager {
|
|
16
24
|
private gameState;
|
|
25
|
+
/**
|
|
26
|
+
* How much of the backend's channel budget to ask for.
|
|
27
|
+
*
|
|
28
|
+
* `@narraleaf/sound` defaults to 128 *including* the master, and throws outright when a
|
|
29
|
+
* `createChannel` would cross it. Three buses never came close; a game that gives every member
|
|
30
|
+
* of a large voiced cast its own bus can, and the failure mode is a hard throw at boot. This is
|
|
31
|
+
* a per-channel `GainNode` and nothing else, so a generous ceiling costs effectively nothing.
|
|
32
|
+
*/
|
|
33
|
+
private static readonly MaxChannels;
|
|
34
|
+
/**
|
|
35
|
+
* Time constant of the bus-volume ramp, in seconds. ~20ms: long enough to turn a step into a
|
|
36
|
+
* slew nobody hears, short enough that a slider still feels attached to the sound.
|
|
37
|
+
*/
|
|
38
|
+
private static readonly BusRampTimeConstant;
|
|
39
|
+
/**
|
|
40
|
+
* When to pin the exact target after the ramp. `setTargetAtTime` approaches asymptotically and
|
|
41
|
+
* never lands, so five time constants in (within 0.7% - inaudible) the value is written
|
|
42
|
+
* outright. Without this a long drag would accumulate a drift the mixer's own bookkeeping does
|
|
43
|
+
* not have.
|
|
44
|
+
*/
|
|
45
|
+
private static readonly BusRampSettle;
|
|
17
46
|
private state;
|
|
18
47
|
private channels;
|
|
19
|
-
private
|
|
48
|
+
private busTree;
|
|
49
|
+
private busSubscription;
|
|
50
|
+
private unknownBuses;
|
|
20
51
|
private globalVolume;
|
|
21
52
|
private sound;
|
|
22
53
|
private ready;
|
|
23
54
|
private isReady;
|
|
24
55
|
private isInitializing;
|
|
25
56
|
constructor(gameState: GameState);
|
|
57
|
+
/**
|
|
58
|
+
* The volume of every bus, and the tree they are wired into.
|
|
59
|
+
*
|
|
60
|
+
* It lives on `Game`, not here: a bus volume is a player setting that exists before the audio
|
|
61
|
+
* context unlocks and outlives any one player mount. This manager is the thing that makes it
|
|
62
|
+
* audible, not the thing that remembers it.
|
|
63
|
+
*/
|
|
64
|
+
private get mixer();
|
|
26
65
|
/**
|
|
27
66
|
* Must be called ONCE on the client side to prepare audio subsystem.
|
|
28
67
|
* Doing it here avoids "AudioContext is not defined" on the server.
|
|
29
68
|
*/
|
|
30
69
|
initialize(): void;
|
|
70
|
+
/**
|
|
71
|
+
* Build the host's declared bus tree into real channels, once.
|
|
72
|
+
*
|
|
73
|
+
* Walked front to back the tree hands every bus out after its parent, so a child always has a
|
|
74
|
+
* live parent channel to be created from - that is what nests the gain nodes, and cascading
|
|
75
|
+
* gain then falls out of the audio graph rather than out of any arithmetic here.
|
|
76
|
+
*
|
|
77
|
+
* Done once, at boot, and never re-shaped: `Channel.remove()` stops every token in its subtree,
|
|
78
|
+
* so re-parenting a bus while the game runs would cut the music off. Volumes stay live.
|
|
79
|
+
*/
|
|
80
|
+
private realizeBusTree;
|
|
81
|
+
/**
|
|
82
|
+
* Write a bus's volume onto its gain node, ramping rather than stepping.
|
|
83
|
+
*
|
|
84
|
+
* `Channel.setVolume` assigns `gain.value` bare with no `cancelScheduledValues`, so a slider
|
|
85
|
+
* drag arrives as a staircase of discontinuities - the zipper. The backend exposes no ramping
|
|
86
|
+
* setter, only `getGainNode()`, so the ramp is driven from here.
|
|
87
|
+
*
|
|
88
|
+
* **Who owns the value.** `Channel.volume` remains authoritative bookkeeping and the
|
|
89
|
+
* `AudioParam` is authoritative for what is heard, and this method is the only writer of
|
|
90
|
+
* either, so the two can only disagree for the ~20ms a ramp is in flight. `channel.setVolume`
|
|
91
|
+
* is still called first and deliberately: it is what clamps to 0..1, what keeps
|
|
92
|
+
* `channel.getVolume()` truthful, and what `Channel.mute()`/`unmute()` re-read when they
|
|
93
|
+
* rewrite the gain themselves. Its bare write is then superseded in the same synchronous turn -
|
|
94
|
+
* `cancelScheduledValues` drops the implicit `setValueAtTime` that the assignment inserted at
|
|
95
|
+
* `currentTime`, and the ramp is scheduled from the value the parameter actually had. Nothing
|
|
96
|
+
* else in the engine touches a bus gain node, so there is no other automation to fight.
|
|
97
|
+
*
|
|
98
|
+
* Falls back to the plain assignment whenever the graph is not reachable - a backend without
|
|
99
|
+
* `getGainNode`, or a parameter without `setTargetAtTime`. Stepping is the behaviour that
|
|
100
|
+
* shipped; degrading to it is strictly no worse.
|
|
101
|
+
*/
|
|
102
|
+
private applyBusVolume;
|
|
103
|
+
/**
|
|
104
|
+
* The value a bus's gain parameter has right now, or `null` when the graph cannot be reached.
|
|
105
|
+
*/
|
|
106
|
+
private static readGain;
|
|
107
|
+
/**
|
|
108
|
+
* The channel a clip plays through.
|
|
109
|
+
*
|
|
110
|
+
* A bus id nothing declared is not fatal. Refusing to play would turn one typo in one clip's
|
|
111
|
+
* `type` into silence or a thrown action mid-scene; routing it to the always-seeded sfx bus
|
|
112
|
+
* keeps the game audible and says so once, per id, in the log. This is also where a bus name
|
|
113
|
+
* that {@link import("../../nlcore/game/audioBus").acceptsAudioBus} let through at story-build time is
|
|
114
|
+
* finally caught.
|
|
115
|
+
*/
|
|
116
|
+
private channelFor;
|
|
31
117
|
/**
|
|
32
118
|
* The volume a clip started with no explicit target should reach.
|
|
33
119
|
*
|
|
@@ -129,11 +215,67 @@ export declare class AudioManager {
|
|
|
129
215
|
* as it did before.
|
|
130
216
|
*/
|
|
131
217
|
preload(sound: SoundElement): Promise<void>;
|
|
218
|
+
/**
|
|
219
|
+
* Start a new game: stop everything and put the mixer back on the wire.
|
|
220
|
+
*
|
|
221
|
+
* The tree itself is **not** rebuilt - the channels are the same channels, because a bus is
|
|
222
|
+
* part of the game's declared shape, not part of its state. What is re-applied is every bus's
|
|
223
|
+
* current volume, read back off the mixer rather than off `SoundType`, which is what lets a
|
|
224
|
+
* host's own buses exist here at all.
|
|
225
|
+
*
|
|
226
|
+
* Note the seeded three are then immediately overwritten from the preferences, exactly as
|
|
227
|
+
* before; a bus the host declared keeps whatever volume the player left it at, because that is
|
|
228
|
+
* a setting and not something a new game should undo.
|
|
229
|
+
*/
|
|
132
230
|
reset(): void;
|
|
133
|
-
|
|
231
|
+
/**
|
|
232
|
+
* Set **the player's** volume for a bus, 0..1, live. 1 means "leave the author's mix alone".
|
|
233
|
+
*
|
|
234
|
+
* Reaches sounds that are **already playing**: a bus is a gain node every clip beneath it is
|
|
235
|
+
* routed through, so nothing has to be found, stopped or restarted for the change to be heard.
|
|
236
|
+
* Setting a bus that has not been realized yet is fine - the value is kept and applied when the
|
|
237
|
+
* audio context unlocks.
|
|
238
|
+
*
|
|
239
|
+
* Equivalent to `game.audioBuses.setVolume(...)`, which is the surface a host should prefer:
|
|
240
|
+
* it exists before the player mounts.
|
|
241
|
+
*/
|
|
242
|
+
setBusVolume(id: SoundBusId, volume: number): void;
|
|
243
|
+
/**
|
|
244
|
+
* The player's volume for a bus - what was last set, else 1. Not the author's declared mix
|
|
245
|
+
* (`game.audioBuses.getDeclaredVolume`) and not what is on the gain node
|
|
246
|
+
* (`getEffectiveVolume`).
|
|
247
|
+
*/
|
|
248
|
+
getBusVolume(id: SoundBusId): number;
|
|
249
|
+
/**
|
|
250
|
+
* Every bus with its parent and both of its volumes, parents first. `volume` is the half a
|
|
251
|
+
* host persists.
|
|
252
|
+
*/
|
|
253
|
+
getBuses(): AudioBusState[];
|
|
254
|
+
/**
|
|
255
|
+
* @deprecated Use {@link AudioManager.setBusVolume}. Kept because the three sound types are
|
|
256
|
+
* still bus ids and hosts call this with them.
|
|
257
|
+
*/
|
|
258
|
+
setGroupVolume(type: SoundBusId, volume: number): void;
|
|
259
|
+
/**
|
|
260
|
+
* @deprecated Use {@link AudioManager.getBusVolume}.
|
|
261
|
+
*/
|
|
262
|
+
getGroupVolume(type: SoundBusId): number;
|
|
134
263
|
setGlobalVolume(volume: number): void;
|
|
135
264
|
getGlobalVolume(): number;
|
|
136
|
-
getGroupVolume(type: SoundType): number;
|
|
137
265
|
destroy(): void;
|
|
266
|
+
/**
|
|
267
|
+
* The three volume preferences are aliases onto the three seeded buses, and this is the alias.
|
|
268
|
+
*
|
|
269
|
+
* They stay the way a player's music/sfx/voice sliders are driven - widening the preference key
|
|
270
|
+
* union to cover arbitrary bus ids is not possible without reshaping `GamePreference`, and is
|
|
271
|
+
* not needed: a host bus is driven through {@link AudioManager.setBusVolume} instead.
|
|
272
|
+
*
|
|
273
|
+
* They write the **player's** half only. This used to write the bus's whole gain, and because
|
|
274
|
+
* these preferences default to 1 it meant that a host declaring `{id: "sound", volume: 0.6}`
|
|
275
|
+
* had its mix silently overwritten with 1 the moment the audio subsystem started - the author
|
|
276
|
+
* set SFX to 60% and every player heard 100%. Custom buses were unaffected because nothing
|
|
277
|
+
* aliases them, so the three buses every existing project uses were the only ones that ignored
|
|
278
|
+
* the declaration. Now `soundVolume: 1` means "do not attenuate further" and 0.6 survives.
|
|
279
|
+
*/
|
|
138
280
|
private setupGroupVolume;
|
|
139
281
|
}
|