Trying to make typewriter effect, but typewriter sound seems to play twice

I’m making a script that reveals a message one character at a time and plays a typewriter sound at a slightly randomized pitch with each character revealed.
This is the script:

local TextLabel = script.Parent
local Button = game.Players.LocalPlayer.PlayerGui:WaitForChild("ScreenGui").OptionYes
local message = "So, this is the place Joey told me about..." -- put your full message here
local message2 = "This old building is the perfect place to host something of value." -- put your full message here
local message3 = "Now it's time to open these rusted doors and hopefully find something worth my time." -- put your full message here
local message4 = "What is this place?..." -- put your full message here
local message5 = "..." -- put your full message here
local message6 = "The door is gone." -- put your full message here
local message7 = "I need to find some way out of here." -- put your full message here
local Audio = game.ReplicatedStorage.TypeWriter

local function StartNewGame()
	task.wait(5)
	for i = 1, #message, 1 do
		local CloneAudio = Audio:Clone()
		local PS = CloneAudio:FindFirstChildOfClass("PitchShiftSoundEffect")
		PS.Octave = math.random(95, 105)/100
		CloneAudio.Parent = game.SoundService
		TextLabel.Text = string.sub(message, 1, i)
		CloneAudio:Play()
		CloneAudio.Ended:Connect(function()
			CloneAudio:Destroy()
		end)
		task.wait(0.08)
	end
	task.wait(3)
	for i = 1, #message2, 1 do
		local CloneAudio = Audio:Clone()
		local PS = CloneAudio:FindFirstChildOfClass("PitchShiftSoundEffect")
		PS.Octave = math.random(95, 105)/100
		CloneAudio.Parent = game.SoundService
		TextLabel.Text = string.sub(message2, 1, i)
		CloneAudio:Play()
		CloneAudio.Ended:Connect(function()
			CloneAudio:Destroy()
		end)
		task.wait(0.08)
	end
	task.wait(3)
	for i = 1, #message3, 1 do
		local CloneAudio = Audio:Clone()
		local PS = CloneAudio:FindFirstChildOfClass("PitchShiftSoundEffect")
		PS.Octave = math.random(95, 105)/100
		CloneAudio.Parent = game.SoundService
		TextLabel.Text = string.sub(message3, 1, i)
		CloneAudio:Play()
		CloneAudio.Ended:Connect(function()
			CloneAudio:Destroy()
		end)
		task.wait(0.08)
	end
	task.wait(3)
	TextLabel.Visible = false
	task.wait(8)
	TextLabel.Visible = true
	for i = 1, #message4, 1 do
		local CloneAudio = Audio:Clone()
		local PS = CloneAudio:FindFirstChildOfClass("PitchShiftSoundEffect")
		PS.Octave = math.random(95, 105)/100
		CloneAudio.Parent = game.SoundService
		TextLabel.Text = string.sub(message4, 1, i)
		CloneAudio:Play()
		CloneAudio.Ended:Connect(function()
			CloneAudio:Destroy()
		end)
		task.wait(0.08)
	end
	task.wait(3)
	for i = 1, #message5, 1 do
		local CloneAudio = Audio:Clone()
		local PS = CloneAudio:FindFirstChildOfClass("PitchShiftSoundEffect")
		PS.Octave = math.random(95, 105)/100
		CloneAudio.Parent = game.SoundService
		TextLabel.Text = string.sub(message5, 1, i)
		CloneAudio:Play()
		CloneAudio.Ended:Connect(function()
			CloneAudio:Destroy()
		end)
		task.wait(1)
	end
	task.wait(3)
	for i = 1, #message6, 1 do
		local CloneAudio = Audio:Clone()
		local PS = CloneAudio:FindFirstChildOfClass("PitchShiftSoundEffect")
		PS.Octave = math.random(95, 105)/100
		CloneAudio.Parent = game.SoundService
		TextLabel.Text = string.sub(message6, 1, i)
		CloneAudio:Play()
		CloneAudio.Ended:Connect(function()
			CloneAudio:Destroy()
		end)
		task.wait(0.08)
	end
	task.wait(3)
	for i = 1, #message7, 1 do
		local CloneAudio = Audio:Clone()
		local PS = CloneAudio:FindFirstChildOfClass("PitchShiftSoundEffect")
		PS.Octave = math.random(95, 105)/100
		CloneAudio.Parent = game.SoundService
		TextLabel.Text = string.sub(message7, 1, i)
		CloneAudio:Play()
		CloneAudio.Ended:Connect(function()
			CloneAudio:Destroy()
		end)
		task.wait(0.08)
	end
	task.wait(3)
	TextLabel.Visible = false
end

Button.MouseButton1Click:Connect(StartNewGame)

If I change local Button = game.Players.LocalPlayer.PlayerGui:WaitForChild("ScreenGui").OptionYes to local Button = game.Players.LocalPlayer.PlayerGui.ScreenGui.OptionYes it outputs an error, but works, at least in Roblox Studio. When I play the game on Roblox, the audio bugs and plays twice at once when it shouldn’t. Seriously the amount of differences between Roblox and Roblox Studio can make it so difficult to playtest a game.

Is there any way I can stop the audio from playing twice?

4 Likes

Is the Audio.PlayOnRemove property enabled? If so, when you are destroying the Sound it would play a second time.

image

2 Likes

It’s set to false. The audio plays twice at the same time, and if PlayOnRemove was set to true, this would be out of sync anyways. The only idea I have is that maybe the local script is being duplicated, sort of like how each player has their own GUI. Is it possible that the local script could be getting duplicated so it plays the audio twice?
The original audio is in ReplicatedStorage, and the clone audios are in SoundService, if that has any importance.

2 Likes

Have you tried the simple route of just adding a debounce? Doing so won’t solve the underlying issue but will still serve as a working solution at least temporarily.

Edit 1: Also I would recommend you make sure there is no accidental duplication on either end either with the button or the script itself as well as any possible other scripts interfering or calling the same trigger on the mouse click.

Edit 2: Also did you check if it’s looped?

Edit 3: Also try Button.MouseButton1Click:DisconnectAll() to make sure any other possible accidental connections are severed

(I haven’t read it fully yet so if I see the actual problem I will edit this)

3 Likes

I changed the first part of the script to this:

local TextLabel = script.Parent
local Button = game.Players.LocalPlayer.PlayerGui:WaitForChild("ScreenGui").OptionYes
local message = "So, this is the place Joey told me about..." -- put your full message here
local message2 = "This old building is the perfect place to host something of value." -- put your full message here
local message3 = "Now it's time to open these rusted doors and hopefully find something worth my time." -- put your full message here
local message4 = "What is this place?..." -- put your full message here
local message5 = "..." -- put your full message here
local message6 = "The door is gone." -- put your full message here
local message7 = "I need to find some way out of here." -- put your full message here
local Audio = game.ReplicatedStorage.TypeWriter

local function StartNewGame()
	task.wait(5)
	for i = 1, #message, 1 do
		local CloneAudio = Audio:Clone()
		local PS = CloneAudio:FindFirstChildOfClass("PitchShiftSoundEffect")
		PS.Octave = math.random(95, 105)/100
		CloneAudio.Parent = game.SoundService
		TextLabel.Text = string.sub(message, 1, i)
		CloneAudio:Play()
		print(i)
		CloneAudio.Ended:Connect(function()
			CloneAudio:Destroy()
		end)
		task.wait(0.08)
	end

It prints each number twice, which means that part of the script is being ran more than once. The script is being duplicated in the player’s PlayerGui.

I added this at the end:

if script.Parent.Parent.Parent.Name == "StarterGui" then
	script:Destroy()
end

I thought it might work, but each number was still being printed twice.

2 Likes

Have you tried adding a debounce as Mocha had suggested? If that part of the script is being run more than once then that should fix the issue. If it doesn’t then the script is probably being duplicated still.

4 Likes

If your local script is under StarterGui it will be duplicated into PlayerGui, meaning you will have two scripts running at the same time, a simple fix is moving the local script somewhere else, like StarterPlayerScripts under StarterPlayer where it won’t be duplicated.

3 Likes

Based on the descriptions given in previous discussions, I would argue that there is something in your code that is duplicating the sound.

Looking through your code, I would recommend a couple of changes for simplicity and possible fixing:

  1. Update the messages from individual variables to a table of strings. This will make it much easier to add more strings or delete strings from your typer.

  2. After fixing your message variables, you can just loop through every message in the table instead of having to copy and paste new loops for every message.

Revised code is available below:

local TextLabel = script.Parent
local Button = game.Players.LocalPlayer.PlayerGui:WaitForChild("ScreenGui").OptionYes
local Audio = game.ReplicatedStorage.TypeWriter

local messages = {
	[1] = "So, this is the place Joey told me about...", -- put your full message here
	[2] = "This old building is the perfect place to host something of value.", -- put your full message here
	[3] = "Now it's time to open these rusted doors and hopefully find something worth my time.", -- put your full message here
	[4] = "What is this place?...", -- put your full message here
	[5] = "...", -- put your full message here
	[6] = "The door is gone.", -- put your full message here
	[7] = "I need to find some way out of here.", -- put your full message here
}

local function StartNewGame()
	task.wait(5)
	
	-- Create a loop that loops through all of your messages
	for _, message in ipairs(messages) do
		-- Normal character / typewriter loop
		for i = 1, #message, 1 do
			local CloneAudio = Audio:Clone()
			local PS = CloneAudio:FindFirstChildOfClass("PitchShiftSoundEffect")
			PS.Octave = math.random(95, 105)/100
			print(i)
			CloneAudio.Parent = game.SoundService
			TextLabel.Text = string.sub(message, 1, i)
			CloneAudio:Play()
			CloneAudio.Ended:Connect(function()
				CloneAudio:Destroy()
			end)
			task.wait(0.08) -- Wait between characters
		end
		task.wait(3) -- Wait 3 seconds after each message has finished typing
	end
	
	task.wait(3)
	TextLabel.Visible = false
end

Button.MouseButton1Click:Connect(StartNewGame)

When using this version of the script I only get each number printed once:

When in game I only hear each sound once: (I increased the wait time between each character so you can hear each sound individually better)

2 Likes

That script still prints every number twice.


I appreciate you compressing the message script into one for loop, however I had each message have a separate for loop because certain messages had longer pauses between each character. One for loop means I edit each message as one. I could possibly change this by adding if statements to check the current message iteration, but would be more ideal to leave uncompressed.

If I move the script under StarterPlayerScripts, it still prints every number twice. The typewriter sound is being duplicated twice as well. If I isolate the button, the text, the sound effect, and the message script in a new game with nothing else, each number is still printed twice.

1 Like

To account for the different timings, you can add a check at the top of the loop to check for the specific index that you are looking for to activate a special function. (It would be the 4th in this case)

local TextLabel = script.Parent
local Button = game.Players.LocalPlayer.PlayerGui:WaitForChild("ScreenGui").OptionYes
local Audio = game.ReplicatedStorage.TypeWriter

local messages = {
	[1] = "So, this is the place Joey told me about...", -- put your full message here
	[2] = "This old building is the perfect place to host something of value.", -- put your full message here
	[3] = "Now it's time to open these rusted doors and hopefully find something worth my time.", -- put your full message here
	[4] = "What is this place?...", -- put your full message here
	[5] = "...", -- put your full message here
	[6] = "The door is gone.", -- put your full message here
	[7] = "I need to find some way out of here.", -- put your full message here
}

local function StartNewGame()
	task.wait(5)
	
	-- Create a loop that loops through all of your messages
	for index, message in ipairs(messages) do
		-- Test for the message that activates the visibility
		if index == 4 then
			TextLabel.Visible = false
			task.wait(8)
			TextLabel.Visible = true
		end
		
		-- Normal character / typewriter loop
		for i = 1, #message, 1 do
			local CloneAudio = Audio:Clone()
			local PS = CloneAudio:FindFirstChildOfClass("PitchShiftSoundEffect")
			PS.Octave = math.random(95, 105)/100
			print(i)
			CloneAudio.Parent = game.SoundService
			TextLabel.Text = string.sub(message, 1, i)
			CloneAudio:Play()
			CloneAudio.Ended:Connect(function()
				CloneAudio:Destroy()
			end)
			task.wait(.08) -- Wait between characters
		end
		task.wait(3) -- Wait 3 seconds after each message has finished typing
	end
	
	task.wait(3)
	TextLabel.Visible = false
end

Button.MouseButton1Click:Connect(StartNewGame)

As for your duplicate sound problem, I think it may be an issue of how your game is set up. Are there any other scripts that access the sound?

1 Like

im pretty sure theres a SoundService property for this, but i cant remember what its called

1 Like

I changed the script to this:

local TextLabel = script.Parent
local Button = game.Players.LocalPlayer.PlayerGui:WaitForChild("ScreenGui").OptionYes
local Audio = game.ReplicatedStorage.TypeWriter
local WaitTime = 0.08

local messages = {
	[1] = "So, this is the place Joey told me about...", -- put your full message here
	[2] = "This old building is the perfect place to host something of value.", -- put your full message here
	[3] = "Now it's time to open these rusted doors and hopefully find something worth my time.", -- put your full message here
	[4] = "What is this place?...", -- put your full message here
	[5] = "...", -- put your full message here
	[6] = "The door is gone.", -- put your full message here
	[7] = "I need to find some way out of here.", -- put your full message here
}

local function StartNewGame()
	task.wait(5)

	-- Create a loop that loops through all of your messages
	for index, message in ipairs(messages) do
		-- Test for the message that activates the visibility
		if index == 4 then
			TextLabel.Visible = false
			task.wait(8)
			TextLabel.Visible = true
		end
		if index == 5 then
		WaitTime = 1
		end
		if index == 6 then
			WaitTime = 0.08
		end

		-- Normal character / typewriter loop
		for i = 1, #message, 1 do
			local CloneAudio = Audio:Clone()
			local PS = CloneAudio:FindFirstChildOfClass("PitchShiftSoundEffect")
			PS.Octave = math.random(95, 105)/100
			print(i)
			CloneAudio.Parent = game.SoundService
			TextLabel.Text = string.sub(message, 1, i)
			CloneAudio:Play()
			CloneAudio.Ended:Connect(function()
				CloneAudio:Destroy()
			end)
			task.wait(WaitTime) -- Wait between characters
		end
		task.wait(3) -- Wait 3 seconds after each message has finished typing
	end

	task.wait(3)
	TextLabel.Visible = false
end

Button.MouseButton1Click:Connect(StartNewGame)

Now I just have to try and fix the duplicated audio. The duplicated printing and audio are less in sync now compared to before. (0.001 seconds after → ~0.025+ seconds after)

I don’t have a solution for you, however for the sake of readability why don’t you wrap the loops into a function, then call them like that?

Itd help with organization and overall readability, and as for your special wait times you can have a parameter called “wait” that waits the allotted time.

2 Likes

If I delete the duplicated script, this happens:


Is there any way I can stop that from happening?

What do you mean by “duplicated script”? How is there a duplicated script?

1 Like

It’s duplicated in the player’s PlayerGui

Do you have two instances of the script in your game…? I’m not sure if the duplicated prints are supposed to be intentional.

1 Like

I’m trying to remove the duplicated prints. If I delete the duplicated script, it still prints twice, which I’m trying to fix.

You still have a seperate script printing. Try clicking the duplicated prints and see where it takes you.

1 Like

It says source not available for the Studio one. For the client one, it takes me to the local script. This is what the print looks like.