@quenty/soundplayer 7.25.0 → 7.25.1
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/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,18 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [7.25.1](https://github.com/Quenty/NevermoreEngine/compare/@quenty/soundplayer@7.25.0...@quenty/soundplayer@7.25.1) (2026-01-05)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* Add typing to SoundPlayer ([a7e538e](https://github.com/Quenty/NevermoreEngine/commit/a7e538eddcb002327c1fdff4e2a60c769f0a1ba5))
|
|
12
|
+
* Update the sound group package to include sound volume multipliers ([bf9591f](https://github.com/Quenty/NevermoreEngine/commit/bf9591fe2d08dbefae47f8c449b7bbf20c01fdfe))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
6
18
|
# [7.25.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/soundplayer@7.24.4...@quenty/soundplayer@7.25.0) (2026-01-04)
|
|
7
19
|
|
|
8
20
|
**Note:** Version bump only for package @quenty/soundplayer
|
package/LICENSE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2014-
|
|
3
|
+
Copyright (c) 2014-2026 James Onnen (Quenty)
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quenty/soundplayer",
|
|
3
|
-
"version": "7.25.
|
|
3
|
+
"version": "7.25.1",
|
|
4
4
|
"description": "Sound playback helper",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Roblox",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@quenty/adorneeutils": "^3.3.3",
|
|
29
29
|
"@quenty/baseobject": "^10.9.1",
|
|
30
|
-
"@quenty/blend": "^12.23.
|
|
30
|
+
"@quenty/blend": "^12.23.1",
|
|
31
31
|
"@quenty/brio": "^14.21.0",
|
|
32
32
|
"@quenty/instanceutils": "^13.21.0",
|
|
33
33
|
"@quenty/loader": "^10.9.1",
|
|
@@ -41,12 +41,12 @@
|
|
|
41
41
|
"@quenty/signal": "^7.11.3",
|
|
42
42
|
"@quenty/sounds": "^10.14.4",
|
|
43
43
|
"@quenty/table": "^3.9.0",
|
|
44
|
-
"@quenty/transitionmodel": "^7.24.
|
|
45
|
-
"@quenty/valueobject": "^13.22.
|
|
44
|
+
"@quenty/transitionmodel": "^7.24.1",
|
|
45
|
+
"@quenty/valueobject": "^13.22.1",
|
|
46
46
|
"@quentystudios/t": "^3.0.0"
|
|
47
47
|
},
|
|
48
48
|
"publishConfig": {
|
|
49
49
|
"access": "public"
|
|
50
50
|
},
|
|
51
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "5232cd2c58ca0dcdf591dd8ae78995211da2f3e2"
|
|
52
52
|
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
--!
|
|
1
|
+
--!strict
|
|
2
2
|
--[=[
|
|
3
|
+
This class provides layered synchronized sound playback with looping and scheduling, which is useful for
|
|
4
|
+
implementing complex ambient soundscapes or music tracks that require multiple layers to be played in sync, for example,
|
|
5
|
+
constructed music that adapts to game states.
|
|
6
|
+
|
|
3
7
|
@class LayeredLoopedSoundPlayer
|
|
4
8
|
]=]
|
|
5
9
|
|
|
@@ -12,14 +16,35 @@ local SoundLoopScheduleUtils = require("SoundLoopScheduleUtils")
|
|
|
12
16
|
local SoundUtils = require("SoundUtils")
|
|
13
17
|
local SpringTransitionModel = require("SpringTransitionModel")
|
|
14
18
|
local ValueObject = require("ValueObject")
|
|
15
|
-
local t = require("t")
|
|
19
|
+
local t: any = require("t")
|
|
16
20
|
|
|
17
21
|
local LayeredLoopedSoundPlayer = setmetatable({}, SpringTransitionModel)
|
|
18
22
|
LayeredLoopedSoundPlayer.ClassName = "LayeredLoopedSoundPlayer"
|
|
19
23
|
LayeredLoopedSoundPlayer.__index = LayeredLoopedSoundPlayer
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
|
|
25
|
+
export type LayeredLoopedSoundPlayer =
|
|
26
|
+
typeof(setmetatable(
|
|
27
|
+
{} :: {
|
|
28
|
+
_layerMaid: Maid.Maid,
|
|
29
|
+
_soundParent: ValueObject.ValueObject<Instance?>,
|
|
30
|
+
_soundGroup: ValueObject.ValueObject<SoundGroup?>,
|
|
31
|
+
_bpm: ValueObject.ValueObject<number?>,
|
|
32
|
+
_defaultCrossFadeTime: ValueObject.ValueObject<number>,
|
|
33
|
+
_volumeMultiplier: ValueObject.ValueObject<number>,
|
|
34
|
+
_layers: { [string]: LoopedSoundPlayer.LoopedSoundPlayer },
|
|
35
|
+
},
|
|
36
|
+
{} :: typeof({ __index = LayeredLoopedSoundPlayer })
|
|
37
|
+
))
|
|
38
|
+
& SpringTransitionModel.SpringTransitionModel<number>
|
|
39
|
+
|
|
40
|
+
--[=[
|
|
41
|
+
Constructs a new LayeredLoopedSoundPlayer.
|
|
42
|
+
|
|
43
|
+
@param soundParent Instance? -- Optional parent for sounds
|
|
44
|
+
@return LayeredLoopedSoundPlayer
|
|
45
|
+
]=]
|
|
46
|
+
function LayeredLoopedSoundPlayer.new(soundParent: Instance?): LayeredLoopedSoundPlayer
|
|
47
|
+
local self: LayeredLoopedSoundPlayer = setmetatable(SpringTransitionModel.new() :: any, LayeredLoopedSoundPlayer)
|
|
23
48
|
|
|
24
49
|
self._layerMaid = self._maid:Add(Maid.new())
|
|
25
50
|
|
|
@@ -38,29 +63,61 @@ function LayeredLoopedSoundPlayer.new(soundParent)
|
|
|
38
63
|
return self
|
|
39
64
|
end
|
|
40
65
|
|
|
41
|
-
|
|
66
|
+
--[=[
|
|
67
|
+
Sets the default cross fade time for the LayeredLoopedSoundPlayer.
|
|
68
|
+
]=]
|
|
69
|
+
function LayeredLoopedSoundPlayer.SetDefaultCrossFadeTime(
|
|
70
|
+
self: LayeredLoopedSoundPlayer,
|
|
71
|
+
crossFadeTime: ValueObject.Mountable<number>
|
|
72
|
+
): () -> ()
|
|
42
73
|
return self._defaultCrossFadeTime:Mount(crossFadeTime)
|
|
43
74
|
end
|
|
44
75
|
|
|
45
|
-
|
|
76
|
+
--[=[
|
|
77
|
+
Sets the volume multiplier for the LayeredLoopedSoundPlayer.
|
|
78
|
+
]=]
|
|
79
|
+
function LayeredLoopedSoundPlayer.SetVolumeMultiplier(
|
|
80
|
+
self: LayeredLoopedSoundPlayer,
|
|
81
|
+
volumeMultiplier: ValueObject.Mountable<number>
|
|
82
|
+
): () -> ()
|
|
46
83
|
return self._volumeMultiplier:Mount(volumeMultiplier)
|
|
47
84
|
end
|
|
48
85
|
|
|
49
|
-
|
|
50
|
-
|
|
86
|
+
--[=[
|
|
87
|
+
Sets the BPM for syncing sound playback.
|
|
88
|
+
]=]
|
|
89
|
+
function LayeredLoopedSoundPlayer.SetBPM(self: LayeredLoopedSoundPlayer, bpm: ValueObject.Mountable<number?>): () -> ()
|
|
90
|
+
return self._bpm:Mount(bpm)
|
|
51
91
|
end
|
|
52
92
|
|
|
53
|
-
|
|
93
|
+
--[=[
|
|
94
|
+
Sets the parent instance for the LayeredLoopedSoundPlayer.
|
|
95
|
+
]=]
|
|
96
|
+
function LayeredLoopedSoundPlayer.SetSoundParent(self: LayeredLoopedSoundPlayer, soundParent: Instance?): ()
|
|
54
97
|
assert(typeof(soundParent) == "Instance" or soundParent == nil, "Bad soundParent")
|
|
55
98
|
|
|
56
99
|
self._soundParent.Value = soundParent
|
|
57
100
|
end
|
|
58
101
|
|
|
59
|
-
|
|
102
|
+
--[=[
|
|
103
|
+
Sets the sound group for the LayeredLoopedSoundPlayer.
|
|
104
|
+
]=]
|
|
105
|
+
function LayeredLoopedSoundPlayer.SetSoundGroup(
|
|
106
|
+
self: LayeredLoopedSoundPlayer,
|
|
107
|
+
soundGroup: ValueObject.Mountable<SoundGroup?>
|
|
108
|
+
): () -> ()
|
|
60
109
|
return self._soundGroup:Mount(soundGroup)
|
|
61
110
|
end
|
|
62
111
|
|
|
63
|
-
|
|
112
|
+
--[=[
|
|
113
|
+
Swaps the layer to play the given sound on loop schedule.
|
|
114
|
+
]=]
|
|
115
|
+
function LayeredLoopedSoundPlayer.Swap(
|
|
116
|
+
self: LayeredLoopedSoundPlayer,
|
|
117
|
+
layerId: string,
|
|
118
|
+
soundId: SoundUtils.SoundId,
|
|
119
|
+
scheduleOptions: SoundLoopScheduleUtils.SoundLoopSchedule?
|
|
120
|
+
): ()
|
|
64
121
|
assert(type(layerId) == "string", "Bad layerId")
|
|
65
122
|
assert(SoundUtils.isConvertableToRbxAsset(soundId) or soundId == nil, "Bad soundId")
|
|
66
123
|
assert(SoundLoopScheduleUtils.isLoopedSchedule(scheduleOptions) or scheduleOptions == nil, "Bad scheduleOptions")
|
|
@@ -69,7 +126,15 @@ function LayeredLoopedSoundPlayer:Swap(layerId: string, soundId, scheduleOptions
|
|
|
69
126
|
layer:Swap(soundId, scheduleOptions)
|
|
70
127
|
end
|
|
71
128
|
|
|
72
|
-
|
|
129
|
+
--[=[
|
|
130
|
+
Swaps the layer to play on the next loop.
|
|
131
|
+
]=]
|
|
132
|
+
function LayeredLoopedSoundPlayer.SwapOnLoop(
|
|
133
|
+
self: LayeredLoopedSoundPlayer,
|
|
134
|
+
layerId: string,
|
|
135
|
+
soundId: SoundUtils.SoundId,
|
|
136
|
+
scheduleOptions: SoundLoopScheduleUtils.SoundLoopSchedule?
|
|
137
|
+
): ()
|
|
73
138
|
assert(type(layerId) == "string", "Bad layerId")
|
|
74
139
|
assert(SoundUtils.isConvertableToRbxAsset(soundId) or soundId == nil, "Bad soundId")
|
|
75
140
|
|
|
@@ -77,16 +142,32 @@ function LayeredLoopedSoundPlayer:SwapOnLoop(layerId, soundId, scheduleOptions)
|
|
|
77
142
|
layer:SwapOnLoop(soundId, scheduleOptions)
|
|
78
143
|
end
|
|
79
144
|
|
|
80
|
-
|
|
145
|
+
--[=[
|
|
146
|
+
Swaps the layer to play from a list of samples.
|
|
147
|
+
]=]
|
|
148
|
+
function LayeredLoopedSoundPlayer.SwapToSamples(
|
|
149
|
+
self: LayeredLoopedSoundPlayer,
|
|
150
|
+
layerId: string,
|
|
151
|
+
soundIdList: { SoundUtils.SoundId },
|
|
152
|
+
scheduleOptions: SoundLoopScheduleUtils.SoundLoopSchedule?
|
|
153
|
+
): ()
|
|
81
154
|
assert(type(layerId) == "string", "Bad layerId")
|
|
82
|
-
assert(
|
|
155
|
+
assert(type(soundIdList) == "table", "Bad soundIdList")
|
|
83
156
|
assert(SoundLoopScheduleUtils.isLoopedSchedule(scheduleOptions) or scheduleOptions == nil, "Bad scheduleOptions")
|
|
84
157
|
|
|
85
158
|
local layer = self:_getOrCreateLayer(layerId)
|
|
86
|
-
layer:SwapToSamples(
|
|
159
|
+
layer:SwapToSamples(soundIdList, scheduleOptions)
|
|
87
160
|
end
|
|
88
161
|
|
|
89
|
-
|
|
162
|
+
--[=[
|
|
163
|
+
Swaps the layer to play a random choice from a list of samples.
|
|
164
|
+
]=]
|
|
165
|
+
function LayeredLoopedSoundPlayer.SwapToChoice(
|
|
166
|
+
self: LayeredLoopedSoundPlayer,
|
|
167
|
+
layerId: string,
|
|
168
|
+
soundIdList: { SoundUtils.SoundId },
|
|
169
|
+
scheduleOptions: SoundLoopScheduleUtils.SoundLoopSchedule?
|
|
170
|
+
): ()
|
|
90
171
|
assert(type(layerId) == "string", "Bad layerId")
|
|
91
172
|
assert(type(soundIdList) == "table", "Bad soundIdList")
|
|
92
173
|
assert(SoundLoopScheduleUtils.isLoopedSchedule(scheduleOptions) or scheduleOptions == nil, "Bad scheduleOptions")
|
|
@@ -95,23 +176,41 @@ function LayeredLoopedSoundPlayer:SwapToChoice(layerId, soundIdList, scheduleOpt
|
|
|
95
176
|
layer:SwapToChoice(soundIdList, scheduleOptions)
|
|
96
177
|
end
|
|
97
178
|
|
|
98
|
-
|
|
179
|
+
--[=[
|
|
180
|
+
Plays the given sound once on the layer.
|
|
181
|
+
]=]
|
|
182
|
+
function LayeredLoopedSoundPlayer.PlayOnce(
|
|
183
|
+
self: LayeredLoopedSoundPlayer,
|
|
184
|
+
layerId: string,
|
|
185
|
+
soundId: SoundUtils.SoundId,
|
|
186
|
+
scheduleOptions: SoundLoopScheduleUtils.SoundLoopSchedule?
|
|
187
|
+
): ()
|
|
99
188
|
assert(type(layerId) == "string", "Bad layerId")
|
|
100
|
-
assert(type(soundIdList) == "table", "Bad soundIdList")
|
|
101
189
|
assert(SoundLoopScheduleUtils.isLoopedSchedule(scheduleOptions) or scheduleOptions == nil, "Bad scheduleOptions")
|
|
102
190
|
|
|
103
191
|
local layer = self:_getOrCreateLayer(layerId)
|
|
104
|
-
layer:PlayOnce(
|
|
192
|
+
layer:PlayOnce(soundId, scheduleOptions)
|
|
105
193
|
end
|
|
106
194
|
|
|
107
|
-
|
|
195
|
+
--[=[
|
|
196
|
+
Plays the given sound once on the next loop of the layer.
|
|
197
|
+
]=]
|
|
198
|
+
function LayeredLoopedSoundPlayer.PlayOnceOnLoop(
|
|
199
|
+
self: LayeredLoopedSoundPlayer,
|
|
200
|
+
layerId: string,
|
|
201
|
+
soundId: SoundUtils.SoundId,
|
|
202
|
+
scheduleOptions: SoundLoopScheduleUtils.SoundLoopSchedule?
|
|
203
|
+
): ()
|
|
108
204
|
assert(type(layerId) == "string", "Bad layerId")
|
|
109
205
|
|
|
110
206
|
local layer = self:_getOrCreateLayer(layerId)
|
|
111
207
|
layer:PlayOnceOnLoop(soundId, scheduleOptions)
|
|
112
208
|
end
|
|
113
209
|
|
|
114
|
-
function LayeredLoopedSoundPlayer
|
|
210
|
+
function LayeredLoopedSoundPlayer._getOrCreateLayer(
|
|
211
|
+
self: LayeredLoopedSoundPlayer,
|
|
212
|
+
layerId: string
|
|
213
|
+
): LoopedSoundPlayer.LoopedSoundPlayer
|
|
115
214
|
if self._layers[layerId] then
|
|
116
215
|
return self._layers[layerId]
|
|
117
216
|
end
|
|
@@ -142,7 +241,7 @@ function LayeredLoopedSoundPlayer:_getOrCreateLayer(layerId)
|
|
|
142
241
|
maid:GiveTask(Rx.combineLatest({
|
|
143
242
|
visible = self:ObserveRenderStepped(),
|
|
144
243
|
multiplier = self._volumeMultiplier:Observe(),
|
|
145
|
-
}):Subscribe(function(state)
|
|
244
|
+
}):Subscribe(function(state: any)
|
|
146
245
|
layer:SetVolumeMultiplier(state.multiplier * state.visible)
|
|
147
246
|
end))
|
|
148
247
|
|
|
@@ -158,13 +257,16 @@ function LayeredLoopedSoundPlayer:_getOrCreateLayer(layerId)
|
|
|
158
257
|
return layer
|
|
159
258
|
end
|
|
160
259
|
|
|
161
|
-
|
|
260
|
+
--[=[
|
|
261
|
+
Stops playback on the given layer.
|
|
262
|
+
]=]
|
|
263
|
+
function LayeredLoopedSoundPlayer.StopLayer(self: LayeredLoopedSoundPlayer, layerId: string): ()
|
|
162
264
|
assert(type(layerId) == "string", "Bad layerId")
|
|
163
265
|
|
|
164
266
|
self._layerMaid[layerId] = nil
|
|
165
267
|
end
|
|
166
268
|
|
|
167
|
-
function LayeredLoopedSoundPlayer
|
|
269
|
+
function LayeredLoopedSoundPlayer.StopAll(self: LayeredLoopedSoundPlayer): ()
|
|
168
270
|
self._layerMaid:DoCleaning()
|
|
169
271
|
end
|
|
170
272
|
|
|
@@ -8,6 +8,7 @@ local require = require(script.Parent.loader).load(script)
|
|
|
8
8
|
local RunService = game:GetService("RunService")
|
|
9
9
|
|
|
10
10
|
local Maid = require("Maid")
|
|
11
|
+
local Observable = require("Observable")
|
|
11
12
|
local Promise = require("Promise")
|
|
12
13
|
local PromiseMaidUtils = require("PromiseMaidUtils")
|
|
13
14
|
local RandomSampler = require("RandomSampler")
|
|
@@ -38,7 +39,7 @@ export type LoopedSoundPlayer =
|
|
|
38
39
|
_volumeMultiplier: ValueObject.ValueObject<number>,
|
|
39
40
|
_doSyncSoundPlayback: ValueObject.ValueObject<boolean>,
|
|
40
41
|
_currentActiveSound: ValueObject.ValueObject<Sound?>,
|
|
41
|
-
_currentSoundId: ValueObject.ValueObject<
|
|
42
|
+
_currentSoundId: ValueObject.ValueObject<SoundUtils.SoundId?>,
|
|
42
43
|
_defaultScheduleOptions: SoundLoopScheduleUtils.SoundLoopSchedule,
|
|
43
44
|
_currentLoopSchedule: ValueObject.ValueObject<SoundLoopScheduleUtils.SoundLoopSchedule>,
|
|
44
45
|
},
|
|
@@ -46,7 +47,7 @@ export type LoopedSoundPlayer =
|
|
|
46
47
|
))
|
|
47
48
|
& SpringTransitionModel.SpringTransitionModel<number>
|
|
48
49
|
|
|
49
|
-
function LoopedSoundPlayer.new(soundId: (string | number)?, soundParent: Instance?)
|
|
50
|
+
function LoopedSoundPlayer.new(soundId: (string | number)?, soundParent: Instance?): LoopedSoundPlayer
|
|
50
51
|
assert(soundId == nil or SoundUtils.isConvertableToRbxAsset(soundId), "Bad soundId")
|
|
51
52
|
|
|
52
53
|
local self: LoopedSoundPlayer = setmetatable(SpringTransitionModel.new() :: any, LoopedSoundPlayer)
|
|
@@ -81,31 +82,59 @@ function LoopedSoundPlayer.new(soundId: (string | number)?, soundParent: Instanc
|
|
|
81
82
|
return self
|
|
82
83
|
end
|
|
83
84
|
|
|
84
|
-
|
|
85
|
+
--[=[
|
|
86
|
+
Sets the cross fade time for the LoopedSoundPlayer.
|
|
87
|
+
]=]
|
|
88
|
+
function LoopedSoundPlayer.SetCrossFadeTime(
|
|
89
|
+
self: LoopedSoundPlayer,
|
|
90
|
+
crossFadeTime: ValueObject.Mountable<number>
|
|
91
|
+
): () -> ()
|
|
85
92
|
return self._crossFadeTime:Mount(crossFadeTime)
|
|
86
93
|
end
|
|
87
94
|
|
|
88
|
-
|
|
95
|
+
--[=[
|
|
96
|
+
Sets the volume multiplier for the LoopedSoundPlayer.
|
|
97
|
+
]=]
|
|
98
|
+
function LoopedSoundPlayer.SetVolumeMultiplier(self: LoopedSoundPlayer, volume: number): ()
|
|
99
|
+
assert(type(volume) == "number", "Bad volume")
|
|
100
|
+
|
|
89
101
|
self._volumeMultiplier.Value = volume
|
|
90
102
|
end
|
|
91
103
|
|
|
92
|
-
|
|
104
|
+
--[=[
|
|
105
|
+
Sets the sound group for the LoopedSoundPlayer.
|
|
106
|
+
]=]
|
|
107
|
+
function LoopedSoundPlayer.SetSoundGroup(
|
|
108
|
+
self: LoopedSoundPlayer,
|
|
109
|
+
soundGroup: ValueObject.Mountable<SoundGroup?>
|
|
110
|
+
): () -> ()
|
|
93
111
|
return self._soundGroup:Mount(soundGroup)
|
|
94
112
|
end
|
|
95
113
|
|
|
96
|
-
|
|
114
|
+
--[=[
|
|
115
|
+
Sets the BPM for syncing sound playback.
|
|
116
|
+
]=]
|
|
117
|
+
function LoopedSoundPlayer.SetBPM(self: LoopedSoundPlayer, bpm: number?): ()
|
|
97
118
|
assert(type(bpm) == "number" or bpm == nil, "Bad bpm")
|
|
98
119
|
|
|
99
120
|
self._bpm.Value = bpm
|
|
100
121
|
end
|
|
101
122
|
|
|
102
|
-
|
|
123
|
+
--[=[
|
|
124
|
+
Sets the parent instance for the sound.
|
|
125
|
+
]=]
|
|
126
|
+
function LoopedSoundPlayer.SetSoundParent(self: LoopedSoundPlayer, parent: Instance?): ()
|
|
103
127
|
self._soundParent.Value = parent
|
|
104
128
|
end
|
|
105
129
|
|
|
106
|
-
function LoopedSoundPlayer.Swap(
|
|
130
|
+
function LoopedSoundPlayer.Swap(
|
|
131
|
+
self: LoopedSoundPlayer,
|
|
132
|
+
soundId: SoundUtils.SoundId,
|
|
133
|
+
loopSchedule: SoundLoopScheduleUtils.SoundLoopSchedule?
|
|
134
|
+
): ()
|
|
107
135
|
assert(SoundUtils.isConvertableToRbxAsset(soundId) or soundId == nil, "Bad soundId")
|
|
108
136
|
loopSchedule = self:_convertToLoopedSchedule(loopSchedule)
|
|
137
|
+
assert(loopSchedule ~= nil, "Bad loopSchedule")
|
|
109
138
|
|
|
110
139
|
local maid = Maid.new()
|
|
111
140
|
|
|
@@ -117,11 +146,14 @@ function LoopedSoundPlayer.Swap(self: LoopedSoundPlayer, soundId, loopSchedule)
|
|
|
117
146
|
self._maid._swappingTo = maid
|
|
118
147
|
end
|
|
119
148
|
|
|
120
|
-
|
|
149
|
+
--[=[
|
|
150
|
+
Sets whether to sync sound playback to BPM.
|
|
151
|
+
]=]
|
|
152
|
+
function LoopedSoundPlayer.SetDoSyncSoundPlayback(self: LoopedSoundPlayer, doSyncSoundPlayback: boolean): ()
|
|
121
153
|
self._doSyncSoundPlayback.Value = doSyncSoundPlayback
|
|
122
154
|
end
|
|
123
155
|
|
|
124
|
-
function LoopedSoundPlayer._setupRender(self: LoopedSoundPlayer)
|
|
156
|
+
function LoopedSoundPlayer._setupRender(self: LoopedSoundPlayer): ()
|
|
125
157
|
self._maid:GiveTask(self._currentSoundId
|
|
126
158
|
:ObserveBrio(function(value)
|
|
127
159
|
return value ~= nil
|
|
@@ -132,17 +164,18 @@ function LoopedSoundPlayer._setupRender(self: LoopedSoundPlayer)
|
|
|
132
164
|
end
|
|
133
165
|
|
|
134
166
|
local maid = brio:ToMaid()
|
|
135
|
-
local soundId = brio:GetValue()
|
|
167
|
+
local soundId: SoundUtils.SoundId = brio:GetValue() :: any
|
|
136
168
|
|
|
137
169
|
maid:GiveTask(self:_renderSoundPlayer(soundId))
|
|
138
170
|
end))
|
|
139
171
|
end
|
|
140
172
|
|
|
141
|
-
function LoopedSoundPlayer._renderSoundPlayer(self: LoopedSoundPlayer, soundId)
|
|
173
|
+
function LoopedSoundPlayer._renderSoundPlayer(self: LoopedSoundPlayer, soundId: SoundUtils.SoundId): Maid.Maid
|
|
142
174
|
local maid = Maid.new()
|
|
143
175
|
|
|
144
176
|
local renderMaid = Maid.new()
|
|
145
|
-
local soundPlayer
|
|
177
|
+
local soundPlayer: SimpleLoopedSoundPlayer.SimpleLoopedSoundPlayer =
|
|
178
|
+
renderMaid:Add(SimpleLoopedSoundPlayer.new(soundId))
|
|
146
179
|
soundPlayer:SetTransitionTime(self._crossFadeTime)
|
|
147
180
|
|
|
148
181
|
renderMaid:GiveTask(self._soundGroup:Observe():Subscribe(function(soundGroup)
|
|
@@ -235,7 +268,11 @@ function LoopedSoundPlayer._renderSoundPlayer(self: LoopedSoundPlayer, soundId)
|
|
|
235
268
|
return maid
|
|
236
269
|
end
|
|
237
270
|
|
|
238
|
-
function LoopedSoundPlayer._setupLoopScheduling(
|
|
271
|
+
function LoopedSoundPlayer._setupLoopScheduling(
|
|
272
|
+
self: LoopedSoundPlayer,
|
|
273
|
+
soundPlayer: SimpleLoopedSoundPlayer.SimpleLoopedSoundPlayer,
|
|
274
|
+
loopSchedule: SoundLoopScheduleUtils.SoundLoopSchedule
|
|
275
|
+
): Maid.Maid
|
|
239
276
|
local maid = Maid.new()
|
|
240
277
|
|
|
241
278
|
if loopSchedule.maxLoops then
|
|
@@ -270,9 +307,14 @@ function LoopedSoundPlayer._setupLoopScheduling(self: LoopedSoundPlayer, soundPl
|
|
|
270
307
|
return maid
|
|
271
308
|
end
|
|
272
309
|
|
|
273
|
-
function LoopedSoundPlayer.SwapToSamples(
|
|
310
|
+
function LoopedSoundPlayer.SwapToSamples(
|
|
311
|
+
self: LoopedSoundPlayer,
|
|
312
|
+
soundIdList: { SoundUtils.SoundId },
|
|
313
|
+
loopSchedule: SoundLoopScheduleUtils.SoundLoopSchedule?
|
|
314
|
+
): ()
|
|
274
315
|
assert(type(soundIdList) == "table", "Bad soundIdList")
|
|
275
316
|
loopSchedule = self:_convertToLoopedSchedule(loopSchedule)
|
|
317
|
+
assert(loopSchedule ~= nil, "Bad loopSchedule")
|
|
276
318
|
|
|
277
319
|
local loopMaid = Maid.new()
|
|
278
320
|
|
|
@@ -289,9 +331,17 @@ function LoopedSoundPlayer.SwapToSamples(self: LoopedSoundPlayer, soundIdList, l
|
|
|
289
331
|
self._maid._swappingTo = loopMaid
|
|
290
332
|
end
|
|
291
333
|
|
|
292
|
-
|
|
334
|
+
--[=[
|
|
335
|
+
Swaps to a random choice from the provided list of sound IDs on loop.
|
|
336
|
+
]=]
|
|
337
|
+
function LoopedSoundPlayer.SwapToChoice(
|
|
338
|
+
self: LoopedSoundPlayer,
|
|
339
|
+
soundIdList: { SoundUtils.SoundId },
|
|
340
|
+
loopSchedule: SoundLoopScheduleUtils.SoundLoopSchedule?
|
|
341
|
+
): ()
|
|
293
342
|
assert(type(soundIdList) == "table", "Bad soundIdList")
|
|
294
343
|
loopSchedule = self:_convertToLoopedSchedule(loopSchedule)
|
|
344
|
+
assert(loopSchedule ~= nil, "Bad loopSchedule")
|
|
295
345
|
|
|
296
346
|
local loopMaid = Maid.new()
|
|
297
347
|
|
|
@@ -307,33 +357,64 @@ function LoopedSoundPlayer.SwapToChoice(self: LoopedSoundPlayer, soundIdList, lo
|
|
|
307
357
|
self._maid._swappingTo = loopMaid
|
|
308
358
|
end
|
|
309
359
|
|
|
310
|
-
|
|
360
|
+
--[=[
|
|
361
|
+
Plays the sound once
|
|
362
|
+
]=]
|
|
363
|
+
function LoopedSoundPlayer.PlayOnce(
|
|
364
|
+
self: LoopedSoundPlayer,
|
|
365
|
+
soundId: SoundUtils.SoundId,
|
|
366
|
+
loopSchedule: SoundLoopScheduleUtils.SoundLoopSchedule?
|
|
367
|
+
): ()
|
|
311
368
|
assert(SoundUtils.isConvertableToRbxAsset(soundId) or soundId == nil, "Bad soundId")
|
|
312
369
|
loopSchedule = self:_convertToLoopedSchedule(loopSchedule)
|
|
370
|
+
assert(loopSchedule ~= nil, "Bad loopSchedule")
|
|
313
371
|
|
|
314
372
|
self:Swap(soundId, SoundLoopScheduleUtils.maxLoops(1, loopSchedule))
|
|
315
373
|
end
|
|
316
374
|
|
|
317
|
-
|
|
375
|
+
--[=[
|
|
376
|
+
Swaps the sound on the next loop
|
|
377
|
+
]=]
|
|
378
|
+
function LoopedSoundPlayer.SwapOnLoop(
|
|
379
|
+
self: LoopedSoundPlayer,
|
|
380
|
+
soundId: SoundUtils.SoundId,
|
|
381
|
+
loopSchedule: SoundLoopScheduleUtils.SoundLoopSchedule?
|
|
382
|
+
): ()
|
|
318
383
|
assert(SoundUtils.isConvertableToRbxAsset(soundId) or soundId == nil, "Bad soundId")
|
|
319
384
|
loopSchedule = self:_convertToLoopedSchedule(loopSchedule)
|
|
385
|
+
assert(loopSchedule ~= nil, "Bad loopSchedule")
|
|
320
386
|
|
|
321
387
|
self:Swap(soundId, SoundLoopScheduleUtils.onNextLoop(loopSchedule))
|
|
322
388
|
end
|
|
323
389
|
|
|
324
|
-
|
|
390
|
+
--[=[
|
|
391
|
+
Plays the sound once on the next loop
|
|
392
|
+
]=]
|
|
393
|
+
function LoopedSoundPlayer.PlayOnceOnLoop(
|
|
394
|
+
self: LoopedSoundPlayer,
|
|
395
|
+
soundId: SoundUtils.SoundId,
|
|
396
|
+
loopSchedule: SoundLoopScheduleUtils.SoundLoopSchedule?
|
|
397
|
+
): ()
|
|
325
398
|
assert(SoundUtils.isConvertableToRbxAsset(soundId) or soundId == nil, "Bad soundId")
|
|
326
399
|
loopSchedule = self:_convertToLoopedSchedule(loopSchedule)
|
|
400
|
+
assert(loopSchedule ~= nil, "Bad loopSchedule")
|
|
327
401
|
|
|
328
402
|
self:PlayOnce(soundId, SoundLoopScheduleUtils.onNextLoop(loopSchedule))
|
|
329
403
|
end
|
|
330
404
|
|
|
331
|
-
function LoopedSoundPlayer._convertToLoopedSchedule(
|
|
405
|
+
function LoopedSoundPlayer._convertToLoopedSchedule(
|
|
406
|
+
self: LoopedSoundPlayer,
|
|
407
|
+
loopSchedule: SoundLoopScheduleUtils.SoundLoopSchedule?
|
|
408
|
+
): SoundLoopScheduleUtils.SoundLoopSchedule
|
|
332
409
|
assert(SoundLoopScheduleUtils.isLoopedSchedule(loopSchedule) or loopSchedule == nil, "Bad loopSchedule")
|
|
333
410
|
return loopSchedule or self._defaultScheduleOptions
|
|
334
411
|
end
|
|
335
412
|
|
|
336
|
-
function LoopedSoundPlayer._scheduleFirstPlay(
|
|
413
|
+
function LoopedSoundPlayer._scheduleFirstPlay(
|
|
414
|
+
self: LoopedSoundPlayer,
|
|
415
|
+
loopSchedule: SoundLoopScheduleUtils.SoundLoopSchedule,
|
|
416
|
+
callback
|
|
417
|
+
): Maid.Maid
|
|
337
418
|
assert(SoundLoopScheduleUtils.isLoopedSchedule(loopSchedule), "Bad loopSchedule")
|
|
338
419
|
assert(type(callback) == "function", "Bad callback")
|
|
339
420
|
|
|
@@ -374,7 +455,10 @@ function LoopedSoundPlayer._scheduleFirstPlay(self: LoopedSoundPlayer, loopSched
|
|
|
374
455
|
return maid
|
|
375
456
|
end
|
|
376
457
|
|
|
377
|
-
|
|
458
|
+
--[=[
|
|
459
|
+
Stops playback after the current loop finishes.
|
|
460
|
+
]=]
|
|
461
|
+
function LoopedSoundPlayer.StopAfterLoop(self: LoopedSoundPlayer): ()
|
|
378
462
|
local swapMaid = Maid.new()
|
|
379
463
|
|
|
380
464
|
swapMaid:GiveTask(self._currentSoundLooped:Connect(function()
|
|
@@ -386,7 +470,10 @@ function LoopedSoundPlayer.StopAfterLoop(self: LoopedSoundPlayer)
|
|
|
386
470
|
self._maid._swappingTo = swapMaid
|
|
387
471
|
end
|
|
388
472
|
|
|
389
|
-
function LoopedSoundPlayer._observeActiveSoundFinishLoop(
|
|
473
|
+
function LoopedSoundPlayer._observeActiveSoundFinishLoop(
|
|
474
|
+
self: LoopedSoundPlayer,
|
|
475
|
+
maxWaitTime: number
|
|
476
|
+
): Observable.Observable<()>
|
|
390
477
|
local startTime = os.clock()
|
|
391
478
|
|
|
392
479
|
return self._currentActiveSound:Observe():Pipe({
|
|
@@ -430,6 +517,9 @@ function LoopedSoundPlayer._observeActiveSoundFinishLoop(self: LoopedSoundPlayer
|
|
|
430
517
|
}) :: any
|
|
431
518
|
end
|
|
432
519
|
|
|
520
|
+
--[=[
|
|
521
|
+
Promises that resolve when the current loop is done.
|
|
522
|
+
]=]
|
|
433
523
|
function LoopedSoundPlayer.PromiseLoopDone(self: LoopedSoundPlayer): Promise.Promise<()>
|
|
434
524
|
local promise = self._maid:GivePromise(Promise.new())
|
|
435
525
|
|
|
@@ -442,11 +532,17 @@ function LoopedSoundPlayer.PromiseLoopDone(self: LoopedSoundPlayer): Promise.Pro
|
|
|
442
532
|
return promise
|
|
443
533
|
end
|
|
444
534
|
|
|
535
|
+
--[=[
|
|
536
|
+
Promises that never resolve, keeping the sound player alive.
|
|
537
|
+
]=]
|
|
445
538
|
function LoopedSoundPlayer.PromiseSustain(_self: LoopedSoundPlayer): Promise.Promise<()>
|
|
446
539
|
-- Never resolve (?)
|
|
447
540
|
return Promise.new()
|
|
448
541
|
end
|
|
449
542
|
|
|
543
|
+
--[=[
|
|
544
|
+
Gets the currently active sound instance.
|
|
545
|
+
]=]
|
|
450
546
|
function LoopedSoundPlayer.GetSound(self: LoopedSoundPlayer): Sound?
|
|
451
547
|
return self._currentActiveSound.Value
|
|
452
548
|
end
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
--!
|
|
1
|
+
--!strict
|
|
2
2
|
--[=[
|
|
3
3
|
@class SimpleLoopedSoundPlayer
|
|
4
4
|
]=]
|
|
@@ -16,8 +16,19 @@ local SimpleLoopedSoundPlayer = setmetatable({}, TimedTransitionModel)
|
|
|
16
16
|
SimpleLoopedSoundPlayer.ClassName = "SimpleLoopedSoundPlayer"
|
|
17
17
|
SimpleLoopedSoundPlayer.__index = SimpleLoopedSoundPlayer
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
export type SimpleLoopedSoundPlayer =
|
|
20
|
+
typeof(setmetatable(
|
|
21
|
+
{} :: {
|
|
22
|
+
Sound: Sound,
|
|
23
|
+
_volumeMultiplier: ValueObject.ValueObject<number>,
|
|
24
|
+
_maxVolume: number,
|
|
25
|
+
},
|
|
26
|
+
{} :: typeof({ __index = SimpleLoopedSoundPlayer })
|
|
27
|
+
))
|
|
28
|
+
& TimedTransitionModel.TimedTransitionModel
|
|
29
|
+
|
|
30
|
+
function SimpleLoopedSoundPlayer.new(soundId: SoundUtils.SoundId): SimpleLoopedSoundPlayer
|
|
31
|
+
local self: SimpleLoopedSoundPlayer = setmetatable(TimedTransitionModel.new() :: any, SimpleLoopedSoundPlayer)
|
|
21
32
|
|
|
22
33
|
self.Sound = self._maid:Add(SoundUtils.createSoundFromId(soundId))
|
|
23
34
|
self.Sound.Looped = true
|
|
@@ -32,7 +43,7 @@ function SimpleLoopedSoundPlayer.new(soundId)
|
|
|
32
43
|
self._maid:GiveTask(Rx.combineLatest({
|
|
33
44
|
visible = self:ObserveRenderStepped(),
|
|
34
45
|
multiplier = self._volumeMultiplier:Observe(),
|
|
35
|
-
}):Subscribe(function(state)
|
|
46
|
+
}):Subscribe(function(state: any)
|
|
36
47
|
self.Sound.Volume = state.visible * self._maxVolume * state.multiplier
|
|
37
48
|
end))
|
|
38
49
|
|
|
@@ -45,22 +56,35 @@ function SimpleLoopedSoundPlayer.new(soundId)
|
|
|
45
56
|
return self
|
|
46
57
|
end
|
|
47
58
|
|
|
48
|
-
|
|
59
|
+
--[=[
|
|
60
|
+
Sets the SoundGroup of the internal Sound.
|
|
61
|
+
@param soundGroup SoundGroup?
|
|
62
|
+
]=]
|
|
63
|
+
function SimpleLoopedSoundPlayer.SetSoundGroup(self: SimpleLoopedSoundPlayer, soundGroup: SoundGroup?): ()
|
|
49
64
|
assert(typeof(soundGroup) == "Instance" or soundGroup == nil, "Bad soundGroup")
|
|
50
65
|
|
|
51
66
|
self.Sound.SoundGroup = soundGroup
|
|
52
67
|
end
|
|
53
68
|
|
|
54
|
-
|
|
69
|
+
--[=[
|
|
70
|
+
Sets the volume multiplier for the sound player.
|
|
71
|
+
]=]
|
|
72
|
+
function SimpleLoopedSoundPlayer.SetVolumeMultiplier(self: SimpleLoopedSoundPlayer, volume: number): ()
|
|
55
73
|
self._volumeMultiplier.Value = volume
|
|
56
74
|
end
|
|
57
75
|
|
|
58
|
-
|
|
76
|
+
--[=[
|
|
77
|
+
Promises indefinitely until the sound player is destroyed.
|
|
78
|
+
]=]
|
|
79
|
+
function SimpleLoopedSoundPlayer.PromiseSustain(self: SimpleLoopedSoundPlayer): Promise.Promise<()>
|
|
59
80
|
-- Never resolve
|
|
60
|
-
return Promise.new()
|
|
81
|
+
return self._maid:GivePromise(Promise.new())
|
|
61
82
|
end
|
|
62
83
|
|
|
63
|
-
|
|
84
|
+
--[=[
|
|
85
|
+
Promises until the current loop is done.
|
|
86
|
+
]=]
|
|
87
|
+
function SimpleLoopedSoundPlayer.PromiseLoopDone(self: SimpleLoopedSoundPlayer): Promise.Promise<()>
|
|
64
88
|
return SoundPromiseUtils.promiseLooped(self.Sound)
|
|
65
89
|
end
|
|
66
90
|
|