local a=require((game:GetService("\082\101\112\108\105\099\097\116\101\100\083\116\111\114\097\103\101")):WaitForChild("\078\101\118\101\114\109\111\114\101"))local C=game:GetService("\083\111\117\110\100\083\101\114\118\105\099\101")local P=game:GetService("\068\101\098\114\105\115")local B=a("\077\097\105\100")local Z=a("\113\071\085\073")local L=a("\084\097\098\108\101")local y={}y.ClassName="\083\111\117\110\100\080\108\097\121\101\114"function y.new(a,P)local Z=setmetatable({},y)Z._children={}Z._maid=B.new()Z._folder=a or error("\078\111 \102\111\108\100\101\114")Z.ParentSoundPlayer=P Z._soundMap={}Z.Sounds=setmetatable({},{__newindex=function(a,C,P)assert(typeof(P)=="\073\110\115\116\097\110\099\101"and P:IsA("\083\111\117\110\100"))rawset(a,C,P)Z._soundMap[P.Name:lower()]=P end})if Z.ParentSoundPlayer then if Z.ParentSoundPlayer:GetSoundGroup()then Z.SoundGroup=Z.ParentSoundPlayer:GetSoundGroup()else Z.SoundGroup=Instance.new("\083\111\117\110\100\071\114\111\117\112")Z.SoundGroup.Name=a.Name Z.SoundGroup.Volume=1 Z.SoundGroup.Parent=C end end if Z.SoundGroup then for a,C in pairs(Z._folder:GetDescendants())do if C:IsA("\083\111\117\110\100")then Z.Sounds[#Z.Sounds+1]=C end end Z._maid.DescendantAdded=Z._folder.DescendantAdded:Connect(function(a)if a:IsA("\083\111\117\110\100")then Z.Sounds[#Z.Sounds+1]=a end end)end return Z end function y.GetName(a)return a._folder.Name end function y.__index(C,a)if y[a]then return y[a]elseif a=="\080\097\114\101\110\116\083\111\117\110\100\080\108\097\121\101\114"or a=="\083\111\117\110\100\071\114\111\117\112"or a=="\067\117\114\114\101\110\116\077\117\115\105\099\078\097\109\101"then return nil elseif a=="\067\104\105\108\100\114\101\110"then error("\091\083\111\117\110\100\080\108\097\121\101\114\093 - \083\104\111\117\108\100 \110\101\118\101\114 \103\101\116 \116\111 \116\104\105\115 \112\111\105\110\116\046 \084\114\105\101\100 \116\111 \105\110\100\101\120 \099\104\105\108\100\114\101\110\033")elseif C._children[a]then return C._children[a]elseif type(a)=="\115\116\114\105\110\103"then local P=C._folder:FindFirstChild(a)if P and P:IsA("\070\111\108\100\101\114")then local B=y.new(P,C)C._children[a]=B return B else error(("\091\083\111\117\110\100\080\108\097\121\101\114\093 - \066\097\100 \105\110\100\101\120 \'\037\115\046\037\115\' \100\111\101\115 \110\111\116 \101\120\105\115\116"):format(C._folder:GetFullName(),tostring(a)))end else error(("\091\083\111\117\110\100\080\108\097\121\101\114\093 - \066\097\100 \105\110\100\101\120 \037\113 \111\110 \115\111\117\110\100 \112\108\097\121\101\114"):format(tostring(a)))end return C end function y.GetSoundGroup(a)return a.SoundGroup end function y.GetNewSound(P,a,C)assert(P.SoundGroup,"\083\111\117\110\100 \112\108\097\121\101\114 \110\101\101\100\115 \115\111\117\110\100 \103\114\111\117\112 \116\111 \102\117\110\099\116\105\111\110")C=C or P.SoundGroup assert(typeof(C)=="\073\110\115\116\097\110\099\101")local B if type(a)=="\115\116\114\105\110\103"then local C=P._soundMap[a:lower()]if C then B=C:Clone()end elseif typeof(a)=="\073\110\115\116\097\110\099\101"and a:IsA("\083\111\117\110\100")then B=a:Clone()end if not B then warn(("\091\083\111\117\110\100\080\108\097\121\101\114\093 - \085\110\097\098\108\101 \116\111 \103\101\116 \110\101\119 \115\111\117\110\100 \102\114\111\109 \097\114\103\117\109\101\110\116 \037\113"):format(tostring(a)))return nil end B.SoundGroup=C:IsA("\083\111\117\110\100\071\114\111\117\112")and C or P.SoundGroup B.Parent=C return B end function y.PlaySound(B,a,C)local Z=B:GetNewSound(a,C)if not Z then warn(("\091\083\111\117\110\100\080\108\097\121\101\114\093 - \085\110\097\098\108\101 \116\111 \102\105\110\100 \115\111\117\110\100 \037\113"):format(tostring(a)))return false end Z.Looped=false Z:Play()P:AddItem(Z,Z.TimeLength+.1)return Z end function y.PlayRandom(B,a,C,P)assert(type(C)=="\110\105\108"or typeof(C)=="\073\110\115\116\097\110\099\101")a=a or"\080\108\097\121\083\111\117\110\100"local Z=B.Sounds if a=="\080\108\097\121\077\117\115\105\099"then P=P or{}P.LoopOptions=P.LoopOptions or B.Sounds end if#Z==1 then return B[a](B,Z[1],C,P)elseif#Z<=1 then warn(("\091\083\111\117\110\100\080\108\097\121\101\114\093 - \078\111 \111\112\116\105\111\110\115 \116\111 \112\108\097\121 \114\097\110\100\111\109 \115\111\117\110\100 \102\111\114 \037\113"):format(tostring(B._folder)))return false end return B[a](B,Z[math.random(#Z)],C,P)end function y.PlayRandomMusic(P,a,C)P:PlayRandom("\080\108\097\121\077\117\115\105\099",a,C)end function y.PlayMusic(y,a,C,P)P=P or{}if y.CurrentMusicName==tostring(a)then return false,"\065\108\114\101\097\100\121 \112\108\097\121\105\110\103"end y:StopMusic()local G=y:GetNewSound(a,C)if not G then warn(("\091\083\111\117\110\100\080\108\097\121\101\114\093 - \085\110\097\098\108\101 \116\111 \102\105\110\100 \109\117\115\105\099 \037\113"):format(tostring(a)))return false end G.Looped=true G:Play()if P.FadeInTime and P.FadeInTime>0 then local a=G.Volume G.Volume=0 Z.TweenTransparency(G,{Volume=a},P.FadeInTime)end local R=B.new()y.CurrentMusicName=G.Name R.Cleanup=function()y.CurrentMusicName=nil if P.FadeOutTime==0 then G:Stop()G:Destroy()else Z.TweenTransparency(G,{Volume=0},P.FadeOutTime or.5)delay(1,function()G:Stop()G:Destroy()end)end end if P.LoopOptions then R.DidLoop=G.DidLoop:Connect(function(B,Z)if Z>0 then local B=L.DeepCopy(P)B.UsedOptions=B.UsedOptions or{}B.UsedOptions[tostring(a)]=true local Z={}for a,C in pairs(B.LoopOptions)do if not B.UsedOptions[tostring(C)]then table.insert(Z,C)end end if#Z<=0 then B.UsedOptions={}Z=L.DeepCopy(B.LoopOptions)end if#Z<=0 then warn("\091\083\111\117\110\100\080\108\097\121\101\114\093 - \083\111\109\101\104\111\119 \116\104\101\114\101 \097\114\101 \110\111 \111\112\116\105\111\110\115 \105\110 \076\111\111\112\079\112\116\105\111\110\115")R.DidLoop=nil return nil end G:Stop()B.FadeInTime=0 local n=Z[math.random(#Z)]y:PlayMusic(n,C,B)end end)end y._maid.Music=R return G end function y.StopMusic(a)a._maid.Music=nil for a,C in pairs(a._children)do C:StopMusic()end end return y
--- Sound playback
-- @classmod SoundPlayer

local require = require(game:GetService("ReplicatedStorage"):WaitForChild("Nevermore"))

local SoundService = game:GetService("SoundService")
local Debris = game:GetService("Debris")

local Maid = require("Maid")
local qGUI = require("qGUI")
local Table = require("Table")

local SoundPlayer = {}
SoundPlayer.ClassName = "SoundPlayer"

function SoundPlayer.new(folder, parentSoundPlayer)
	local self = setmetatable({}, SoundPlayer)

	self._children = {}
	self._maid = Maid.new()
	self._folder = folder or error("No folder")
	self.ParentSoundPlayer = parentSoundPlayer

	self._soundMap = {}
	self.Sounds = setmetatable({}, {__newindex = function(this, index, value)
		assert(typeof(value) == "Instance" and value:IsA("Sound"))
		rawset(this, index, value)
		self._soundMap[value.Name:lower()] = value
	end})

	if self.ParentSoundPlayer then
		-- Make sure only the second layer has a sound group? Hacky.
		if self.ParentSoundPlayer:GetSoundGroup() then
			self.SoundGroup = self.ParentSoundPlayer:GetSoundGroup()
		else
			self.SoundGroup = Instance.new("SoundGroup")
			self.SoundGroup.Name = folder.Name
			self.SoundGroup.Volume = 1
			self.SoundGroup.Parent = SoundService
		end
	end


	if self.SoundGroup then
		for _, item in pairs(self._folder:GetDescendants()) do
			if item:IsA("Sound") then
				self.Sounds[#self.Sounds+1] = item
			end
		end

		self._maid.DescendantAdded = self._folder.DescendantAdded:Connect(function(item)
			if item:IsA("Sound") then
				self.Sounds[#self.Sounds+1] = item
			end
		end)
	end

	return self
end

function SoundPlayer:GetName()
	return self._folder.Name
end

function SoundPlayer:__index(index)
	if SoundPlayer[index] then
		return SoundPlayer[index]
	elseif index == "ParentSoundPlayer" or index == "SoundGroup" or index == "CurrentMusicName" then
		return nil
	elseif index == "Children" then
		error("[SoundPlayer] - Should never get to this point. Tried to index children!")
	elseif self._children[index] then
		return self._children[index]
	elseif type(index) == "string" then
		local folder = self._folder:FindFirstChild(index)
		if folder and folder:IsA("Folder") then
			local newPlayer = SoundPlayer.new(folder, self)
			self._children[index] = newPlayer

			return newPlayer
		else
			error(("[SoundPlayer] - Bad index '%s.%s' does not exist"):format(self._folder:GetFullName(), tostring(index)))
		end
	else
		error(("[SoundPlayer] - Bad index %q on sound player"):format(tostring(index)))
	end

	return self
end

function SoundPlayer:GetSoundGroup()
	return self.SoundGroup
end

function SoundPlayer:GetNewSound(soundName, parent)
	assert(self.SoundGroup, "Sound player needs sound group to function")

	parent = parent or self.SoundGroup

	assert(typeof(parent) == "Instance")

	local sound
	if type(soundName) == "string" then
		local soundObject = self._soundMap[soundName:lower()]
		if soundObject then
			sound = soundObject:Clone()
		end
	elseif typeof(soundName) == "Instance" and soundName:IsA("Sound") then
		sound = soundName:Clone()
	end

	if not sound then
		warn(("[SoundPlayer] - Unable to get new sound from argument %q"):format(tostring(soundName)))
		return nil
	end

	sound.SoundGroup = parent:IsA("SoundGroup") and parent or self.SoundGroup
	sound.Parent = parent

	return sound
end

function SoundPlayer:PlaySound(soundName, parent)
	local sound = self:GetNewSound(soundName, parent)
	if not sound then
		warn(("[SoundPlayer] - Unable to find sound %q"):format(tostring(soundName)))
		return false
	end

	sound.Looped = false
	sound:Play()

	Debris:AddItem(sound, sound.TimeLength+0.1)

	return sound
end

function SoundPlayer:PlayRandom(methodName, parent, PlayOptions)
	assert(type(parent) == "nil" or typeof(parent) == "Instance")

	methodName = methodName or "PlaySound"

	local options = self.Sounds
	if methodName == "PlayMusic" then
		-- Inject loop options into the music options
		PlayOptions = PlayOptions or {}
		PlayOptions.LoopOptions = PlayOptions.LoopOptions or self.Sounds -- Show options if we need looping
	end

	if #options == 1 then
		return self[methodName](self, options[1], parent, PlayOptions)
	elseif #options <= 1 then
		warn(("[SoundPlayer] - No options to play random sound for %q"):format(tostring(self._folder)))
		return false
	end

	return self[methodName](self, options[math.random(#options)], parent, PlayOptions)
end

function SoundPlayer:PlayRandomMusic(parent, options)
	self:PlayRandom("PlayMusic", parent, options)
end

function SoundPlayer:PlayMusic(soundName, parent, options)
	options = options or {}

	if self.CurrentMusicName == tostring(soundName) then
		return false, "Already playing"
	end

	self:StopMusic()

	local sound = self:GetNewSound(soundName, parent)
	if not sound then
		warn(("[SoundPlayer] - Unable to find music %q"):format(tostring(soundName)))
		return false
	end

	sound.Looped = true
	sound:Play()

	if options.FadeInTime and options.FadeInTime > 0 then
		local Original = sound.Volume
		sound.Volume = 0
		qGUI.TweenTransparency(sound, {Volume = Original}, options.FadeInTime)
	end



	local maid = Maid.new()
	self.CurrentMusicName = sound.Name
	maid.Cleanup = function()
		self.CurrentMusicName = nil
		if options.FadeOutTime == 0 then
			sound:Stop()
			sound:Destroy()
		else
			qGUI.TweenTransparency(sound, {Volume = 0}, options.FadeOutTime or 0.5)
			delay(1, function()
				sound:Stop()
				sound:Destroy()
			end)
		end
	end

	-- If we have LoopOptions then we need to stop the current music after 1 play and pick a new piece of unplayed music
	if options.LoopOptions then
		maid.DidLoop = sound.DidLoop:Connect(function(soundId, loopCount)
			if loopCount > 0 then

				local newOptions = Table.DeepCopy(options)
				newOptions.UsedOptions = newOptions.UsedOptions or {}

				-- Note the current opion as used
				newOptions.UsedOptions[tostring(soundName)] = true

				-- Identify available
				local available = {}
				for _, item in pairs(newOptions.LoopOptions) do
					if not newOptions.UsedOptions[tostring(item)] then
						table.insert(available, item)
					end
				end

				-- If we have no more options left, reset the queue
				if #available <= 0 then
					newOptions.UsedOptions = {}
					available = Table.DeepCopy(newOptions.LoopOptions)
				end

				if #available <= 0 then
					warn("[SoundPlayer] - Somehow there are no options in LoopOptions")
					maid.DidLoop = nil
					return nil
				end

				-- Make sure to stop current music immediately (no point in letting it loop)
				sound:Stop()

				-- Start new music immediately
				newOptions.FadeInTime = 0

				-- Pick next random sound
				local option = available[math.random(#available)]
				self:PlayMusic(option, parent, newOptions)
			end
		end)
	end

	self._maid.Music = maid

	return sound
end

function SoundPlayer:StopMusic()
	self._maid.Music = nil
	for _, child in pairs(self._children) do
		child:StopMusic()
	end
end

return SoundPlayer
