Page 1 of 1

play undetermined amount of mp3 files

Posted: Fri Jan 08, 2016 5:48 am
by Colours
I want to load one mp3 file at random out of an undetermined amount, and play it, but it's not working how I want it to. This works, but I want to be able to just dump an mp3 file in my songs folder and be able to play it without editing code or changing song name. I've looked through the file system tutorials to figure out how to read every file in my songs folder, and looked at the code for this one mp3 player made with love, but everything is so vague and hard to understand. Can I get assistance? :rofl:

Code: Select all

dir = "songs/"

function love.load()
    randomNumber = love.math.random(1, 2)
	sfx = love.audio.newSource(dir..randomNumber..".mp3")
end



function love.keypressed(key)
if key == "a" then
love.audio.play(sfx)
end
end

Re: play undetermined amount of mp3 files

Posted: Fri Jan 08, 2016 11:15 am
by davisdude

Code: Select all

songs = love.filesystem.getDirectoryItems( 'path/to/songs' )
-- Get random song
song = songs[love.math.random( #songs )]

Re: play undetermined amount of mp3 files

Posted: Fri Jan 08, 2016 11:36 am
by CorruptionEX
EDIT: I just noticed you got another reply, and that's the boiled down version of all this. But I'm gonna post anyways.

Hey, sorry it took a while, I maybe went a bit overboard on making an example...
I'm also just really slow... But anyways! Here ya go! :crazy: I'll explain more afterwards...

This may look like a lot, but it's very simple. I'm just horrible at explaining things simply. Sorry for the information dump, I can't help it ;_;
I also checked for possible errors, and so that adds a lot to the code. Probably 80% of this is unimportant to your question, but I wanted to give you a fuller explanation. If it's too much, just say, and I can boil it down.

Code: Select all

--Set our seed, so when we use math.random(), the number is actually random...
love.math.setRandomSeed(os.time())

--Where our loaded song will go, so leave it blank for now
local song
--The directory the songs are in
local dir = "songs/"
--What kind of file extensions we are looking for
local validExtensions = {".mp3", ".ogg"}
--The list of valid songs. We will generate this in love.load, so leave this blank too
local dirList

love.load = function(arg)
	--Get a list of things in the song folder
	dirList = love.filesystem.getDirectoryItems(dir)
	
	--Take out all the directories from the list
	for k,v in pairs(dirList) do
		if love.filesystem.isDirectory(dir .. v) then
			table.remove(dirList, k)
		end
	end
	
	--Do the same thing, but this time, check the file extensions!
	--Any file without an extension in validExtensions will be removed, even those with no extension
	for k,v in pairs(dirList) do
		for i,vv in ipairs(validExtensions) do
			--If this file has a valid extentsion, then we can break out of this inner loop; the file can stay
			if string.find(v, vv) then break end
			--If we get this far, and this was the last valid extension, then this must be a bad file...
			if i == #validExtensions then table.remove(dirList, k) end
		end
	end
	
	--[[
		So now dirList is a sequence of everything in dir
		...minus directories
		...minus any files without an extension in validExtensions
	]]--
	
	--Let's actually load the song now...
	pickNewSong()
end

pickNewSong = function()
	--The number of the song we will pick from the list (This is NOT the name of the song, just the place in the list)
	local songNumber = love.math.random(1, #dirList)
	
	--Load our song, finally...
	song = love.audio.newSource(dir .. dirList[songNumber], "stream")
end

love.keypressed = function(key, scancode, isrepeat)
	--Get those repeats outta here!
	if isrepeat then return end

	if key == "n" then
		--Can't just stop at one!
		--Just make sure we stop the old song before we start a new one
		song:stop()
		pickNewSong()
	end
	if key == "p" then
		--Is the song stopped? Then play it!
		if song:isStopped() then
			song:play()
		else
			--Ok, well maybe its paused? Resume it then!
			if song:isPaused() then
				song:resume()
			else
				--Ok well, the only thing it can be now is playing, so um, PAUSE!
				song:pause()
			end
		end
	end
	if key == "l" then
		--Want it to never stop?
		song:setLooping(not(song:isLooping()))
	end
	if key == "s" then
		--GAHH NEVER MIND MAKE IT STOPPPPP
		song:stop()
	end
end
So basically:
[*] Use love.filesystem.getDirectoryItems to get a sequence of all the items in the directory you give to the function. When I say sequence, I mean a table that only has integer keys, and in sequential order. So a table like:

Code: Select all

{
	[1] = "a",
	[2] = "b",
	[3] = "c"
}
is a sequence, but if it skipped a number, or used a key that isn't a whole number, it would not be a sequence.


[*] Take out all of the invalid entries in the sequence. When we use getDirectoryItems, it returns something like this
An example of what love.filesystem.getDirectoryItems returns.
An example of what love.filesystem.getDirectoryItems returns.
getDirectoryItems.png (26.84 KiB) Viewed 2372 times
Notice this is a sequence as well! See how it's unorganized, and contains EVERYTHING in the directory? We gotta get rid of that stuff. I used two different for loops to run through the items, and check what they are. The first one checks for directories, and the second checks if string.find finds any valid extension. If we find a match, break out of the loop. (Thats what the command break does, it just immediately exits the loop it is nested in) This part could be simplified, but I left it 'expanded' so that it's easier to show and explain.


[*] Finally, I have a separate function for actually loading the song, that way we can use it later if we want to load another! I also spiffed up the keypressed event to include more audio source functions. Most of the functions in love.audio have equivalents in the actual source, and I just prefer to use those. For example:

Code: Select all

love.audio.play(song)
song:play()

love.audio.stop(song)
song:stop()
Those pairs do the same thing, one just has less typing...
You can find all the other things a song object has in it here! Some special things for a song can only be accessed this way, such as getting the duration of the song, or skipping to a time in the song, so take a look if you want more control over the playback.


So uh, I think that's it maybe? I have a tendency to only make sense in my own head, so if that was confusing, feel free to ask me to re-explain any parts, or something else if you want to more than this.
Just remember this doesn't have any display at all. The screen is just black while the song plays, and you press keys for the playback commands.

Tell me whatcha think! :nyu:

Re: play undetermined amount of mp3 files

Posted: Fri Jan 08, 2016 11:20 pm
by Colours
Thanks for the help people. The more information the better. I think I understand most of it except for the for loops, specifically the pairs thing, and the hashtags. :neko:

Re: play undetermined amount of mp3 files

Posted: Sat Jan 09, 2016 3:54 am
by CorruptionEX
Ok so the hashtag is easy:
Putting a # in front of a variable that is a table or a string, returns its length.
Strings, the length is just how many characters there are in it.
Tables must be a sequence in order to return the proper length. They won't count the keys or anything after a number you skipped.

Code: Select all

t = {"a", "b", "c"}
print(#t) --will print 3

s = "Hello"
print(#s) --will print 5

t = {
    [1] = "a",
    [2] = "b",
    [3] = "c",
    pasta = "tasty",
    [5] = "e"
}
print(#t) --will print 3, because we skipped a number, and string-keys don't count!
Very simple, and useful, as long as you mind those 2 table limitations.

As for the for loops, I could go in depth, but I'll just explain what the for loops in my code are doing as a brief example.
Remember this part?

Code: Select all

for k,v in pairs(dirList) do
      if love.filesystem.isDirectory(dir .. v) then
         table.remove(dirList, k)
      end
end
[*] The first line starts the for loop. In this case, I define 'k' and 'v' as local variables. Any variable names here will exist only for the inside of the loop.
[*] We separate the variables we define, and the iterator with they keyword 'in'. Otherwise, that's not important.
[*] The iterator in this case is pairs(). Whatever table we give it, it will keep returning the key and the value for every pair in the table.

Code: Select all

t = {
    a = 9001
}
print( t[1] ) -- prints 9001
In this case, '1' is the key, and 9001 is the value.
So pairs() is going to give us every key-value pair in whatever table we give it, one for each run through the loop, until we have gone through all of them. This is why we define 'k' and 'v' at the beginning. pairs() will put the key in the first variable, and the value in the second.
[*] For the inside of the loop (which starts after we use the keyword 'do') All I do is check if the value we got is a path to a directory, and if so, we remove it from the list. (table.remove requires the table you are acting on, and which key you want to remove. Since the value we checked is stored with the key 'k' at the time, we use that. Hope that made sense...)

So for a better example:

Code: Select all

t = {
    a = 1,
    b = 2,
    c = 3,
    d = 4
}

for key,value in pairs(t) do
    print("KEY=" .. key)
    print("VALUE=" .. value)
    print("")
end

--[[ This will output:
KEY=d
VALUE=4

KEY=a
VALUE=1

KEY=c
VALUE=3

KEY=b
VALUE=2

]]--
There is one thing you must know about pairs(). It does not run through the table in order. It WILL go through all the entries, but you can't be sure how.
There is another form of pairs() you will see in my code, called ipairs()
It is exactly the same thing, except it requires a sequence, and will run through it in numerical order, starting at 1, up to the last sequential key it can find (so no skipping numbers). Kinda situational, but still just as helpful.

I hope this helps! It's better to understand what it all is rather than just blindly accept code from strangers online :P

Re: play undetermined amount of mp3 files

Posted: Sat Jan 09, 2016 11:11 am
by zorg
CorruptionEX wrote:Ok so the hashtag is easy:
Putting a # in front of a variable that is a table or a string, returns its length.
Strings, the length is just how many characters there are in it.
A minor detail, it doesn't return the number of characters, but the number of bytes in a string.
If you use utf-8 characters, those may have multiple bytes for a character, and the length will not match the character count.
If you're planning on supporting multiple languages, then you can require "utf-8" and use utf8.len instead (along with other functions that won't work correctly with utf-8 byte sequences).
There's an entry for the module on the wiki, linking to the lua reference manual.