Custom Sound Module script not working

Hello! This is my first forum post so apologies if anything here is done improperly. ^^

Basically, I’m making a game, and I’m attempting to make a Module to make playing sounds more convenient and easy!
As an example, If I wanna just play a sound with the module (at least the module how it’s MEANT to work) then I would need to just do SoundModule(SoundIdHere, SoundLocation, {PropertiesTable} ) and it’d play the sound super duper easy!

…However, I’m currently facing an issue where Sounds either Don’t make any Audio happen, they Stack, or they work perfectly fine for seemingly no reason?
In the clip below, there’s meant to be a Swing sound, and a Hit sound for my weapon.
However, as discussed above, sometimes the sounds don’t play, or sometimes they stack!
I have zero idea why this is happening.

I’ve tried several solutions I’ve seen on the forums (Using SoundService:PlayLocalSound(), Waiting till the sound is Loaded, SEVERAL other things, and NOTHING seems to work.)
I’m at my wits end, and would really like this module to work.
It’s why I’m making this post, cause I genuinely have no ideas left.
(It’s gotten bad enough I’ve asked the Roblox Assistant for help hoping for some miracle to happen and they’ll magically see some issue with my code…)
NOTE: This doesn’t happen with the dash for some reason! Also the sounds used in the video are PLACEHOLDERS!

I’ve left all of the code below, in just some hope that someone will point out some obvious flaw that I never could’ve seen.
For the record, I’m new to using RunService, and not entirely familiar with using Coroutines.
I’m trying, and failure is apart of the learning process, but god is it frustrating when NOTHING I’ve found works.

--[ This module is for sounds to be done on the Client. ]

--[ Containers ]--
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ReplicatedRemotes = ReplicatedStorage.Remotes
local Assets = ReplicatedStorage.Assets
local ReplicatedSounds = Assets.Audio

--[ Services ]--
local Debris = game:GetService("Debris")
local Players = game:GetService("Players")
local SoundService = game:GetService("SoundService")
local RunService = game:GetService("RunService")

--[ Instances ]--
local PlaySoundRemote = ReplicatedRemotes["VFX/SFX"].SFX.PlaySound

--[ Tables ]--
-- N/A

--[ Settings ]--
local MasterVolume = 3 -- Placeholder.

--[ Functions ]--

local function UpdateSoundId(SoundId)
	if typeof(SoundId) == "number" then
		SoundId = `rbxassetid://{SoundId}`
	elseif typeof(SoundId) == "string" then
		SoundId = SoundId
	end
	
	return SoundId
end

-- For the record, this is only here because i tried removing the  Sound.Ended:Wait() Sound:Destroy() stuff!
local function IsSoundRegisteredInDatabase(SoundId)
	for _, Sound in ReplicatedSounds:GetDescendants() do
		if Sound:IsA("Sound") then
			if Sound.SoundId == SoundId then
				return true, Sound
			end
		end
	end
	return false
end

return function(SoundId, Location, Properties, Connections)
	if not SoundId then warn("No SoundId provided.") return end
	
	if RunService:IsClient() then
		local NewSoundId = UpdateSoundId(SoundId) 
		local Success, SoundToPlay = IsSoundRegisteredInDatabase(NewSoundId)
		
		if not Success then
			SoundToPlay = Instance.new("Sound", ReplicatedSounds)
			SoundToPlay.SoundId = NewSoundId
		end
		
		if Properties then
			for Property, Value in Properties do
				local Success, ErrorCode = pcall(function()
					if SoundToPlay[Property] then
						SoundToPlay[Property] = Value
					end
				end)
				
				if not Success then
					warn(`{Property} is not a valid Property of Instance {SoundToPlay}.`)
				end
			end
		end
		
		if not Location then
			SoundToPlay.Parent = workspace:WaitForChild("Sounds")
		elseif typeof(Location) == "Vector3" then
			local Part = Instance.new("Part")
			Part.Parent = workspace:WaitForChild("Sounds")
			Part.Size = Vector3.one
			Part.Position = Location
			Part.Transparency = 1
			Part.Anchored = true
			Part.CanCollide = false
			Part.CanTouch = false
			Part.CanQuery = false
			
			SoundToPlay.Parent = Part
			
		elseif Location:IsA("BasePart") then
			SoundToPlay.Parent = Location
		end
		
		SoundToPlay.Volume *= MasterVolume
		
		-- Coroutine so .Loaded:Wait() doesn't prevent code from running.
		local Coroutine = coroutine.create(function()
			if not SoundToPlay.IsLoaded then
				SoundToPlay.Loaded:Wait()
			end
			
			SoundToPlay:Play()
			
			SoundToPlay.Ended:Wait()
			SoundToPlay.Parent = ReplicatedSounds
		end)
	
		coroutine.resume(Coroutine)
		
		return SoundToPlay
		
	elseif RunService:IsServer() then
		if not Properties["Player"] then
			PlaySoundRemote:FireAllClients(SoundId, Location, Properties)
		elseif Properties["Player"] then
			local Player = Players:FindFirstChild(Properties["Player"])
			
			table.remove(Properties, table.find(Properties, Properties["Player"] ) )
			
			PlaySoundRemote:FireClient(Player, SoundId, Location, Properties )
		end
	end
end

Maybe I’m missing something obvious, but I’m really just at a loss at this point.
If you have any ideas I haven’t tried, please let me know, but for now I’m gonna keep hammering away at it and seeing if I can fix it. Thank you DevForum. :heart:

I can’t really read your script right now but, maybe there’s something checking whether or not the sound’s already playing, and if it is, it makes it so the sound doesn’t play

The sound simply does Sound:Play() right now. There’s no additional checks in place for anything like that.
And even if that were the case, it wouldn’t explain why the sounds sometimes stack, play fine, or don’t play at all!

for my sword i didnt use .IsLoaded. i had that problem as well at the start but i scrapped .IsLoaded first because i vaguely remember it preventing my sound from playing when i clicked to use my sword (otherwise its just me). i just put a debounce to make sure my slashing sound + animation finished before playing it again.

if theyre stacking u could have multiple sound instances in where u have the sounds stored when u create them. did u try printing to see how many sound instances u created?

u can do like:

print(#SoundFolder)

u can always do a check as well to make sure that the sound ur creating doesnt already exist like:

if not soundFolder:FindFirstChild("nameOfSound") then
-- create sound here
end

this is also my code for a sword i made a month ago based off of the sword in jailbreak. hopefully it helps u!

local sword = script.Parent
local swordRemote = sword:WaitForChild("swordRemote")
local Animtrack = nil

local anims = {
	Idle = 110871183700731,
	Slash = 107277601477337,
	equipped = false,
	DB = false,
	Sounds = {
		whoosh = 102827148482507,
		slash = 113464450582997
	}
}

swordRemote.OnServerEvent:Connect(function(p, equipState)
	local c = p.Character
	local h:Humanoid = c:WaitForChild("Humanoid")
	local trail:Trail = sword.blade:WaitForChild("Trail")
	local slash = script.Parent.Handle:WaitForChild("slash")
	local whoosh = script.Parent.Handle:WaitForChild("whoosh")
	local animator:Animator = h:WaitForChild("Animator")
	local anim = sword:WaitForChild("Animation")

	if equipState == "equip" then
		anims.equipped = true
		anim.AnimationId = "rbxassetid://"..anims.Idle
		Animtrack = animator:LoadAnimation(anim)
		Animtrack:Play()
	elseif equipState == "action" then
		if anims.equipped == true then
			if not anims.DB then
				anims.DB = true
				if Animtrack then
					Animtrack:Stop()
					Animtrack = nil
				end
				anim.AnimationId = "rbxassetid://"..anims.Slash
				Animtrack = animator:LoadAnimation(anim)
				Animtrack:Play()
				
				local randomNo = math.random(1,5)
				local currentTime = os.clock()
				local animLength = Animtrack.Length
				if randomNo == 1 then
					slash.SoundId = "rbxassetid://"..anims.Sounds.slash
					slash:Play()
					h.WalkSpeed = 35
					
					trail.Transparency = NumberSequence.new(0.8)
				else
					whoosh.SoundId = "rbxassetid://"..anims.Sounds.whoosh
					whoosh:Play()
					
					trail.Transparency = NumberSequence.new(0.8)
				end
				
				Animtrack:GetMarkerReachedSignal("TrailEnd"):Connect(function()
						trail.Transparency = NumberSequence.new(1)
				end)
				
				Animtrack:GetMarkerReachedSignal("Ended"):Connect(function()
					--0.2 second cooldown before the sword can be used again
					task.delay(0.2, function()
						anims.DB = false
						anim.AnimationId = "rbxassetid://"..anims.Idle
						Animtrack = animator:LoadAnimation(anim)
						Animtrack:Play()
						h.WalkSpeed = 16
					end)
				end)
			end
		end
	elseif equipState == "unequip" then
			anims.equipped = false
		if Animtrack then
			Animtrack:Stop()
			Animtrack = nil
		end
	end
end)

Thanks, but my module creates new instances of sounds based on the parameters given to the module. And your system works on a pre-existing sound asset. I believe that would fix my issue, however… I’m REALLY trying to avoid that.

Also, removing .IsLoaded has NOT fixed my issue, i’ve tried it before making this post, and it didn’t fix the issue :slightly_frowning_face:

also tip!
when you do

 "rbxassetid://"..anims.Slash

you could just do

 `rbxassetid://{anims.Slash}`

^^

1 Like

yes! im aware theres no problem at all. just trying to help a little! :slight_smile:

i haven’t used coroutine.resume, coroutine.create, runservice:IsClient/IsServer so i’m a bit limited on what i can do unfortunately mb. :pensive:

what i would suggest is maybe having a look into contentprovider as u can preload assets in ur game and after u have the soundID set, try putting the timePosition to 0 just in case. it may not even work but just putting it out there!


also thanks a million for the tip! im 1000% gonna be using that to save myself for future work!! :smiley_cat:

1 Like

I’ve tried using contentprovider, unfortunately it also did not work with my current method, I’m beginning to think what I want to do is unachievable with Roblox’s APIs. which is very unfortunate!

1 Like

i do believe its possible with what ur trying to do, just keep narrowing down the problem and fill gaps in your knowledge, if you’ve been at it too long; have a break from that area and come back to it.

it’s frustrating to be in the same place and never get anywhere so i relate to that heavy! :slight_smile:

and perhaps

hopefully that steers you in some direction. best of luck mud!! (-:

1 Like

Hi target! Unfortunately, I’m gonna have to take a break from this as it’s still not working :[.

Unfortunately, I tried this, and all of the systems I’ve found either work off of pre-existing assets, or work in similar ways as mine. (I’ve tried isolating the difference between the scripts, to no avail.)

No. I’ve already tried removing the coroutines and just using Sound:Play(), however that doesn’t fix the issue. the coroutines are NOT the issue.

This is helpful, but I’ve already read through all of the Sound and SoundService documentation, and the Player documentation doesn’t appear to contain anything useful to my current situation.
I’ll try reading through SoundGroups and seeing if that changes anything. Thank you target.

But as of right now, none of these fix my issue :slightly_frowning_face:.
Thanks again target, it’s been very nice having someone to talk about my issue with. :]

1 Like

Figured it out!
I had a variable [“Pitch”] which was being set to 0 every time i played the swing sfx, which caused it to just not play at all :slightly_frowning_face:
fixed now! note to self, look at the properties you’re changing!!! :broken_heart: