narraleaf-react 0.21.1 → 0.22.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.
|
@@ -44,7 +44,8 @@ export interface ISoundUserConfig {
|
|
|
44
44
|
*
|
|
45
45
|
* When {@link ISoundUserConfig.loop} is set together with {@link ISoundUserConfig.endTime},
|
|
46
46
|
* this is also where each repeat restarts from, so the two together describe a loop region
|
|
47
|
-
* rather than just a starting offset.
|
|
47
|
+
* rather than just a starting offset — unless {@link ISoundUserConfig.loopStart} moves the
|
|
48
|
+
* repeat's in point somewhere else.
|
|
48
49
|
* @default 0
|
|
49
50
|
*/
|
|
50
51
|
seek: number;
|
|
@@ -53,15 +54,35 @@ export interface ISoundUserConfig {
|
|
|
53
54
|
* through to the end of the file.
|
|
54
55
|
*
|
|
55
56
|
* Without `loop` the clip simply stops there. With `loop` it jumps back to
|
|
56
|
-
* {@link ISoundUserConfig.
|
|
57
|
-
*
|
|
58
|
-
* no gap and no
|
|
57
|
+
* {@link ISoundUserConfig.loopStart}, or to {@link ISoundUserConfig.seek} when no loop in point
|
|
58
|
+
* was given — which is how a piece of background music with an intro loops only its body. The
|
|
59
|
+
* jump is sample-accurate (it is the Web Audio node's own loop), so there is no gap and no
|
|
60
|
+
* drift over long sessions.
|
|
59
61
|
*
|
|
60
62
|
* Ignored for a clip that is streamed rather than decoded: an `<audio>` element has no loop
|
|
61
63
|
* region, only a plain repeat.
|
|
62
64
|
* @default undefined
|
|
63
65
|
*/
|
|
64
66
|
endTime?: number;
|
|
67
|
+
/**
|
|
68
|
+
* Position in seconds the clip returns to on every repeat — the **loop in point**.
|
|
69
|
+
*
|
|
70
|
+
* Only meaningful together with `loop` and {@link ISoundUserConfig.endTime}: it is the start of
|
|
71
|
+
* the region that repeats, while {@link ISoundUserConfig.seek} stays the position the *first*
|
|
72
|
+
* pass begins at. Leaving it out makes each repeat return to `seek`, which is the behaviour of
|
|
73
|
+
* a clip that has no separate intro.
|
|
74
|
+
*
|
|
75
|
+
* Separating the two is what expresses the standard "intro then loop" piece of background
|
|
76
|
+
* music — play from the top once, then repeat only the body forever:
|
|
77
|
+
*
|
|
78
|
+
* ```ts
|
|
79
|
+
* Sound.bgm({src: "theme.mp3", loop: true, seek: 0, loopStart: 12, endTime: 90});
|
|
80
|
+
* ```
|
|
81
|
+
*
|
|
82
|
+
* A value outside `[seek, endTime)` describes no playable region and falls back to `seek`.
|
|
83
|
+
* @default undefined
|
|
84
|
+
*/
|
|
85
|
+
loopStart?: number;
|
|
65
86
|
/**
|
|
66
87
|
* The type of the sound
|
|
67
88
|
* @default SoundType.Sound
|
|
@@ -97,6 +118,14 @@ export declare class Sound extends Actionable<SoundDataRaw, Sound> {
|
|
|
97
118
|
constructor(arg0: Partial<ISoundUserConfig> | string);
|
|
98
119
|
/**
|
|
99
120
|
* Start playing the sound and wait for it to finish.
|
|
121
|
+
*
|
|
122
|
+
* A clip of any {@link SoundType} may be played this way. `type` selects which volume slider
|
|
123
|
+
* governs the clip and nothing else, so putting an ambience track on `bgm` so the player's music
|
|
124
|
+
* slider controls it, and then playing it from an ordinary line, is a legitimate thing to want.
|
|
125
|
+
*
|
|
126
|
+
* It is *not* the same as {@link Scene.setBackgroundMusic}: a clip played here is not in the
|
|
127
|
+
* scene's background-music slot, so leaving the scene will not stop it and no cross-fade is
|
|
128
|
+
* arranged for it. That is true of every clip played this way, on any bus.
|
|
100
129
|
* @param duration - Optional fade duration in milliseconds.
|
|
101
130
|
* @chainable
|
|
102
131
|
* @example
|
|
@@ -128,6 +128,14 @@ export declare class LiveGame {
|
|
|
128
128
|
notify(message: string, duration?: number | null): NotificationToken;
|
|
129
129
|
/**
|
|
130
130
|
* Play a sound immediately and return the SoundToken.
|
|
131
|
+
*
|
|
132
|
+
* The clip starts at the volume its {@link Sound} was configured with — `Sound.voice({src, volume:
|
|
133
|
+
* 0.4})` starts at 0.4, not at full volume. There is no fade: the token's volume is already
|
|
134
|
+
* settled when this resolves and no ramp is left running, so a `setVolume` or a fade driven on
|
|
135
|
+
* the returned token afterwards wins outright.
|
|
136
|
+
*
|
|
137
|
+
* A source given as a string or `URL` becomes a default `Sound`, which is full volume — pass a
|
|
138
|
+
* `Sound` to say otherwise.
|
|
131
139
|
*/
|
|
132
140
|
playSound(sound: Sound | string | URL): Promise<SoundToken>;
|
|
133
141
|
/**
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -28,7 +28,28 @@ export declare class AudioManager {
|
|
|
28
28
|
* Doing it here avoids "AudioContext is not defined" on the server.
|
|
29
29
|
*/
|
|
30
30
|
initialize(): void;
|
|
31
|
+
/**
|
|
32
|
+
* The volume a clip started with no explicit target should reach.
|
|
33
|
+
*
|
|
34
|
+
* Full volume was never a sensible default here: a `Sound` carries the volume it was configured
|
|
35
|
+
* with, so a caller that says nothing about volume is asking for *that*, not for 1. Reading
|
|
36
|
+
* `state` rather than the user config is deliberate - it is the same value {@link SoundElement.play}
|
|
37
|
+
* and {@link SoundElement.resume} put in their `FadeOptions`, so a clip replayed after
|
|
38
|
+
* {@link AudioManager.setVolume} comes back at the volume it was last set to instead of jumping
|
|
39
|
+
* back to whatever the author first wrote down.
|
|
40
|
+
*/
|
|
41
|
+
private static defaultFade;
|
|
31
42
|
play(sound: SoundElement, options?: FadeOptions): Awaitable<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Start a clip and hand the token back once playback is under way.
|
|
45
|
+
*
|
|
46
|
+
* With no `duration` the target volume is written to the token *synchronously* before this
|
|
47
|
+
* returns, and nothing is left running: no gain automation is in flight when the caller gets the
|
|
48
|
+
* token. That is what makes an explicit `token.setVolume` or a fade the caller drives itself
|
|
49
|
+
* afterwards the last writer, which several hosts rely on. Defaulting `duration` to anything
|
|
50
|
+
* above 0 would break that - `token.fade` below is deliberately not awaited, so a non-zero
|
|
51
|
+
* default would leave a ramp running past the return and over whatever the caller did next.
|
|
52
|
+
*/
|
|
32
53
|
playSoundToken(sound: SoundElement, options?: FadeOptions): Promise<SoundToken>;
|
|
33
54
|
stop(sound: SoundElement, duration?: number): Awaitable<void>;
|
|
34
55
|
setVolume(sound: SoundElement, volume: number, duration?: number): Awaitable<void>;
|
|
@@ -42,13 +63,51 @@ export declare class AudioManager {
|
|
|
42
63
|
*/
|
|
43
64
|
seek(sound: SoundElement, time: number): Awaitable<void>;
|
|
44
65
|
/**
|
|
45
|
-
* The in/out points of a clip
|
|
66
|
+
* The in/out points of a clip.
|
|
46
67
|
*
|
|
47
68
|
* `endTime` is left off entirely when the author set none: passing `undefined` explicitly is the
|
|
48
69
|
* same thing to the backend, but omitting it keeps `{...region}` spreads from writing a key that
|
|
49
70
|
* reads as "there is a region here" to anything inspecting the object.
|
|
50
71
|
*/
|
|
51
72
|
private static clipRegionOf;
|
|
73
|
+
/**
|
|
74
|
+
* The region as the sound backend's play options.
|
|
75
|
+
*
|
|
76
|
+
* A looping clip deliberately hands over **no** `endTime`. The backend turns `endTime` into a
|
|
77
|
+
* timer that stops the token after one pass, whether or not the clip loops, so passing it here
|
|
78
|
+
* is what kept the loop region from ever repeating. The region reaches a looping clip through
|
|
79
|
+
* {@link AudioManager.applyLoopRegion} instead; for a one-shot `endTime` *is* the out point and
|
|
80
|
+
* the backend's timer is exactly the right mechanism.
|
|
81
|
+
*/
|
|
82
|
+
private static playRegionOf;
|
|
83
|
+
/**
|
|
84
|
+
* Write a looping clip's region onto the Web Audio node the backend is playing it through.
|
|
85
|
+
*
|
|
86
|
+
* **This is a shim against `@narraleaf/sound@0.1.0`'s internals, and the only place in this
|
|
87
|
+
* repo that reaches into them.** Two things in that version make the region unusable from the
|
|
88
|
+
* outside:
|
|
89
|
+
*
|
|
90
|
+
* - `SoundToken`'s constructor arms `setTimeout(stop, duration * 1000)` whenever a duration was
|
|
91
|
+
* given, without consulting `loop` — so a looping clip with an out point hard-stops after its
|
|
92
|
+
* first pass through the region.
|
|
93
|
+
* - `Sound.createToken` pins `loopStart` to the playback start offset, so "play the intro from
|
|
94
|
+
* 0s, then repeat 12s→90s forever" cannot be expressed at all.
|
|
95
|
+
*
|
|
96
|
+
* The fix belongs upstream and is small: honour `loop` before arming the duration timer, and
|
|
97
|
+
* accept an independent `loopStart` in `PlayOptions`. Until that ships, this manager withholds
|
|
98
|
+
* `endTime` from a looping clip's play options (which is what disarms the timer) and sets the
|
|
99
|
+
* loop region here.
|
|
100
|
+
*
|
|
101
|
+
* `SoundToken.sourceController` is TypeScript-`private` while `AudioSourceController.getSource`
|
|
102
|
+
* is public, so the node is reachable at runtime but not through the types — hence the cast and
|
|
103
|
+
* the shape check. If a later backend changes that shape this returns silently and the clip
|
|
104
|
+
* degrades to the behaviour it has today: the region plays once and the clip stops.
|
|
105
|
+
*
|
|
106
|
+
* Seeking survives this. `SoundToken.seek` rebuilds the buffer source and copies `loop`,
|
|
107
|
+
* `loopStart` and `loopEnd` off the old node onto the new one, so a jump inside a looping track
|
|
108
|
+
* keeps the region.
|
|
109
|
+
*/
|
|
110
|
+
private static applyLoopRegion;
|
|
52
111
|
private static clampToRegion;
|
|
53
112
|
setRate(sound: SoundElement, rate: number): Awaitable<void>;
|
|
54
113
|
getPosition(sound: SoundElement): number;
|