Melody - the perfect music module for you

you can add as much songs as you want, with the script you wrote say you add 10 songs, ur gonna have to make the same loop 10 times

The same loop 10 times? Do you mean 10 while true do loops? Also what’s the problem with just looping through a table and music play and ending them?

1 Like

No, what I mean is you’re gonna have to copy it and paste it multiple times just to make a playlist of 10 songs.

Then just make a table and make a for i,v in pair loop. End of discussion. No need for module

1 Like

k told you from the beginning if you don’t think it’s useful then flag it

You was just saying a multiple times that “this method sucks! My module is better”

1 Like

Horrible model I don’t see why it can even be good horrible Only 4 lines of scirpt?

Changing the location where the audio plays.

Everyone, Calm down…

Okay, This Topic became off-topic quite quickly.
I am a tad bit confused on why everyone insists on posting how irrelevant and useless the module is, I just see this as a starting point for a piece of software which has potential, instead of calling his code dumb or useless try and promote something to make it awesome!

When it comes to something as generic as this module, I think it comes down to a users experience, i’ve been on roblox for a while now. Things like DataStore2, I find a waste of time (Just me, I know its good.) but thats because I have the mindset and skills to understand and create a unique interpretation of a DataStore handler.

These are generic modules which help people who don’t really understand studio to the fullest, one might understand how to require modules, but not manipulate sound objects or create a virtual queue with them.


Feedback

About OOP, I am 100% agreeing, but I would include a bit of ECS if you could, allowing you to essentially wrap these Sound objects into Components that the Module itself can manipulate to achieve awesome things.

And on top of that great suggestion from above, I suggest which could be an awesome addition to this module if you put the time into it. Sound Effects, these are not really used and honestly could go hand in hand with an Sound Wrapper, Imagine being able to control variables on a sound object that you normally don’t get! Exciting!


https://developer.roblox.com/en-us/api-reference/class/PitchShiftSoundEffect
https://developer.roblox.com/en-us/api-reference/class/CompressorSoundEffect
https://developer.roblox.com/en-us/api-reference/class/TremoloSoundEffect
https://developer.roblox.com/en-us/api-reference/class/FlangeSoundEffect
https://developer.roblox.com/en-us/api-reference/class/ReverbSoundEffect
https://developer.roblox.com/en-us/api-reference/class/EchoSoundEffect
https://developer.roblox.com/en-us/api-reference/class/DistortionSoundEffect
https://developer.roblox.com/en-us/api-reference/class/ChorusSoundEffect
https://developer.roblox.com/en-us/api-reference/class/EqualizerSoundEffect

8 Likes

This literally never happened in my first resource and it got down, so why?

Also I think there should just be a function to return the sound instance instead. More customizable.

Not going to lie, trying to throw someone elses work down just because yours didn’t turn out so good is really bad thinking. There is no debate for that.

As cluttered with useless arguments that this post has, it’s as simple as what @AsynchronousMatrix said. Please contribute to the resource if you think it’s bad.

It’s as simple as linking something useful, or just not bothering with the post at all if you don’t want to contribute. We’re in the resources category, so let’s all take the time to help and appreciate the module.

6 Likes

This is my personal rewrite because seeing the other complaints in the post

@wShadowRBLX You are free to use this and update it, no need to credit me, I’ve tried implementing what @AsynchronousMatrix said about OOP and ECS (keep in mind i dont know whats ecs nor i use it but based on my understanding its just wrapping on another object?)

local function InstanceTypeof(i: any) return if typeof(i) == "Instance" then i.ClassName else typeof(i) end
local function InvalidArgumentType(argnumber: number, funcname: string, expected: string, got: string)
	return string.format("invalid argument #%d to '%s' (%s expected, got %s)", argnumber, funcname, expected, got)
end

local Melody = {}
function Melody:__index(k)
	if Melody[k] then
		return Melody[k]
	end
	return self.__sound[k]
end
function Melody:__newindex(k, v)
	if self[k] then
		self[k] = v
		return
	end
	self.__sound[k] = v
	return
end
function Melody.__tostring()
	return "Melody"
end

function Melody:AddSoundIdToQueue(id: string | number)
	assert(InstanceTypeof(id) == "string" or InstanceTypeof(id) == "number", InvalidArgumentType(1, "AddSoundIdToQueue", "string or number", InstanceTypeof(id)))
	if typeof(id) == "string" then
		table.insert(self.__queue, id)
	else
		table.insert(self.__queue, "rbxassetid://"..id)
	end
	return
end

function Melody:RemoveSoundIdFromQueue(id: string | number): boolean
	assert(InstanceTypeof(id) == "string" or InstanceTypeof(id) == "number", InvalidArgumentType(1, "RemoveSoundIdFromQueue", "string or number", InstanceTypeof(id)))
	local searchquery = "rbxassetid://"
	if typeof(id) == "string" then
		searchquery = id
	else
		searchquery ..= id
	end
	local index = table.find(self.__queue, searchquery)
	if index then
		table.remove(self.__queue, index)
		return true
	end
	return false
end

function Melody:RemoveCurrentSoundIdFromQueue(skipsong: boolean?)
	skipsong = if typeof(skipsong) == "boolean" then skipsong else true
	self.__sound:Stop()
	self:RemoveSoundIdFromQueue(self.__sound.SoundId)
	print(skipsong)
	if skipsong and #self.__queue > 0 then
		self.__sound.SoundId = self.__queue[1]
		self.__sound:Play()
	end
	return
end

function Melody:GetQueue()
	return self.__queue
end

function Melody:SkipCurrentSong()
	return self:RemoveCurrentSoundIdFromQueue(true)
end

function Melody:Play()
	if #self.__queue == 0 then
		return error("queue is empty", 2)
	end
	self.__sound.SoundId = self.__queue[1]
	return self.__sound:Play()
end

function Melody:Destroy()
	self.__sound:Destroy()
	table.remove(self.__queue)
	self.__sound = nil
	self.__queue = nil
	return
end

local function new(ids: {string|number}?, parent: Instance?)
	local queue = {}
	
	if typeof(ids) == "table" then
		for i, v in ipairs(ids) do
			if typeof(v) == "string" then
				table.insert(queue, v)
				continue
			elseif typeof(v) == "number" then
				table.insert(queue, "rbxassetid://"..v)
				continue
			end
		end
	end
	
	local sound = Instance.new("Sound")
	local newmelody = setmetatable({
		__sound = sound;
		__queue = queue;
	}, Melody)
	sound.Ended:Connect(function(id: string)
		if sound.Looped then return end
		newmelody:RemoveSoundIdFromQueue(id)
		sound.SoundId = newmelody.__queue[1]
		sound:Play()
	end)
	if parent then sound.Parent = parent end
	return newmelody
end
return setmetatable({new = new}, {__call = new})

I did not extensively test this! You should make a test suite yourself, below is that I used for testing it (in Studio)

  11:45:24.209  > _G.Melody = require(game.ServerStorage.Melody).new({8172854902; 7063614050}, workspace)  -  Studio
  11:45:27.375  > _G.Melody:Play()  -  Studio
  11:45:30.406  > print(_G.Melody:GetQueue())  -  Studio
  11:45:30.408   ▼  {
                    [1] = "rbxassetid://8172854902",
                    [2] = "rbxassetid://7063614050"
                 }  -  Server
  11:45:38.114  > _G.Melody:SkipCurrentSong()  -  Studio
  11:45:38.116  true  -  Server - Melody:55
  11:45:47.277  > print(_G.Melody:GetQueue())  -  Studio
  11:45:47.279   ▼  {
                    [1] = "rbxassetid://7063614050"
                 }  -  Server
  11:45:55.346  > _G.Melody:SkipCurrentSong()  -  Studio
  11:45:55.348  true  -  Server - Melody:55
  11:45:59.993  > _G.Melody:Play()  -  Studio
  11:45:59.995  _G.Melody:Play():1: queue is empty  -  Server - Melody:73
  11:45:59.996  Stack Begin  -  Studio
  11:45:59.997  Script 'ServerStorage.Melody', Line 73 - function Play  -  Studio - Melody:73
  11:45:59.999  Script '_G.Melody:Play()', Line 1  -  Studio
  11:46:00.000  Stack End  -  Studio

The way this works is that it wraps on the Sound instance type and adds in the functionality of what Melody provides, this means it’s essentially the same as the Sound object with Melody’s functionalities (so you can reference whatever on the original Sound object and it will return as expected unless the module overwrote it with it’s own which it overwrote the Play() and Destroy() functions)

Lastly, I made this because of boredom, so if you ask why would I go far as rewriting someone’s stuff, that’s the awnser

Thanks, I’ve modified it, added more functions, and added the functionality of looping the playlist so you don’t play a couple of songs and then stop.

I’ve read the updated version and it seems like you do not understand the concept of Object Oriented Programming (shortened, OOP) which is used in the rewrite, I have fixed the changes you made but you need to learn how to use OOP and understand the concept of it, search around the DevForum, I’ll link a topic I can find below

--made by @xshadowsgamer2018
--rewritten by @ItzEthanPlayz_YT

local function InstanceTypeof(i: any) return if typeof(i) == "Instance" then i.ClassName else typeof(i) end
local function InvalidArgumentType(argnumber: number, funcname: string, expected: string, got: string)
	return string.format("invalid argument #%d to '%s' (%s expected, got %s)", argnumber, funcname, expected, got)
end

local Melody = {}

function Melody:__index(k)
	if Melody[k] then
		return Melody[k]
	end
	return self.__sound[k]
end
function Melody:__newindex(k, v)
	if self[k] then
		self[k] = v
		return
	end
	self.__sound[k] = v
	return
end
function Melody.__tostring()
	return "Melody"
end

function Melody:AddSoundIdToQueue(id: string | number)
	assert(InstanceTypeof(id) == "string" or InstanceTypeof(id) == "number", InvalidArgumentType(1, "AddSoundIdToQueue", "string or number", InstanceTypeof(id)))
	if typeof(id) == "string" then
		table.insert(self.__queue, id)
	else
		table.insert(self.__queue, "rbxassetid://"..id)
	end
	return
end

function Melody:RemoveSoundIdFromQueue(id: string | number): boolean
	assert(InstanceTypeof(id) == "string" or InstanceTypeof(id) == "number", InvalidArgumentType(1, "RemoveSoundIdFromQueue", "string or number", InstanceTypeof(id)))
	local searchquery = "rbxassetid://"
	if typeof(id) == "string" then
		searchquery = id
	else
		searchquery ..= id
	end
	local index = table.find(self.__queue, searchquery)
	if index then
		table.remove(self.__queue, index)
		return true
	end
	return false
end

function Melody:RemoveCurrentSoundIdFromQueue(skipsong: boolean?)
	skipsong = if typeof(skipsong) == "boolean" then skipsong else true
	self.__sound:Stop()
	self:RemoveSoundIdFromQueue(self.__sound.SoundId)
	print(skipsong)
	if skipsong and #self.__queue > 0 then
		self.__sound.SoundId = self.__queue[1]
		self.__sound:Play()
	end
	return
end

function Melody:GetQueue()
	return self.__queue
end

function Melody:SkipCurrentSong()
	return self:RemoveCurrentSoundIdFromQueue(true)
end

function Melody:Play()
	if #self.__queue == 0 then
		return error("queue is empty", 2)
	end
	self.__sound.SoundId = self.__queue[1]
	return self.__sound:Play()
end

function Melody:Pause()
	return self.__sound:Pause()
end

function Melody:Stop()
	return self.__sound:Stop()
end

function Melody:Destroy()
	local sound = self.__sound
	table.remove(self.__queue)
	self.__sound = nil
	self.__queue = nil
	self.__endedHandlerEvent:Disconnect()
	self.__queueindex = nil
	return sound:Destroy()
end

function Melody:GetCurrentSoundId()
	return self.__sound.SoundId
end

function Melody:GetCurrentQueueIndex()
	return self.__queueindex
end

function Melody:GetOriginalSound()
	return self.__sound
end

local function new(ids: {string|number}?, parent: Instance?)
	local queue = {}

	if typeof(ids) == "table" then
		for i, v in ipairs(ids) do
			if typeof(v) == "string" then
				table.insert(queue, v)
				continue
			elseif typeof(v) == "number" then
				table.insert(queue, "rbxassetid://"..v)
				continue
			end
		end
	end

	local sound = Instance.new("Sound")
	local newmelody = setmetatable({
		__sound = sound;
		__queue = queue;
		__queueindex = 1;
	}, Melody)
	
	--// had to use rawset here to prevent something like " __endedHandlerEvent is not a valid member of Sound "Sound" "
	rawset(newmelody, "__endedHandlerEvent", sound.Ended:Connect(function(id: string)
		if sound.Looped then return end
		if newmelody.__queueindex == #queue then
			newmelody.__queueindex = 1
			sound.SoundId = newmelody.__queue[newmelody.__queueindex]
			sound:Play()
		else
			newmelody.__queueindex += 1
			sound.SoundId = newmelody.__queue[newmelody.__queueindex]
			sound:Play()
		end
	end))
	
	if parent then sound.Parent = parent end
	return newmelody
end
return setmetatable({new = new}, {__call = new})

Also again, this is wrapped around the original sound object, having a function that gets the original object wouldn’t really make sense but I decided to rename the GetSound function to GetOriginalSound to clear out the fact that when you make a Melody object, it already has every function and properties a normal Sound instance provides so you can set it things from the Sound instance directly instead of using GetOriginalSound and setting properties from the returned original sound instance

EDIT: There was a error when you create a new sound, I fixed that

Just to add- _G is not good and shouldn’t be used in scripts.

There’s nothing wrong with _G. People make it out to be a memory eating variable but it does the same as a module.

You might get crazy from _G variables, here’s what I mean

Let’s say you want to wait for multiple variables of _G, along the lines of

while not _G.amongusnotfunny do
task.wait()
end
while not _G.THISISNOTFUNNYANYMORESTOP do
task.wait()
end
while not _G.STOPTHISMADNESS do
task.wait()
end

And etc. You will go along with these lines right? While modules is just require and boom your variables are here.

I’ve only used _G only in the command bar for easier testing, have you seen any usage of _G in my scripts or production games?

When I tested Melody, I did everything from the command bar, not via a LuaSourceContainer, it’s obvious that I would need to store the created object somewhere to test it because what’s the point of testing if you can’t test the functionality, right?

2 Likes

Well yeah, that’s the only useful use right now

I know this topic is dead but I can see its useful for someone that is lazy like me most of the times because I can pop in it and do 4 lines of code and there you go and also for those that are saying “This module is useless” there are free models that are music players like this one (kind of) : rickroll music player - Roblox so don’t say this module is “useless” and that stuff, also @AsynchronousMatrix is right about this arguing.