Rockbox Development > Feature Ideas
Random track selection
philden:
I periodically ponder the random play options in Rockbox. When I use an iPod in original firmware, I do like the option to easily shuffle all songs.
There are options in Rockbox to select 'Random' from the Database 'Artist', 'Album' menus, and others, but not from 'Track'. As far as I can see, the current random play options involve making a non-random playlist, and then shuffling it. That is why to 'shuffle all songs', you first need to make a playlist of all the songs, and then shuffle it. This becomes unwieldy with a large library, and is limited by the maximum playlist size.
I would be happy with the ability to make a random selection of <1,000 songs from the library. I doubt that I would ever get close to playing that number of songs before I next alter the library and would make a new list.
So, I'm wondering if it would be possible to add a random playlist generator of some sort?
Thanks!
philden:
Adding some more information, as I've been devising a workaround to make the random playlist externally, using a Mac.
I currently organize my music for Rockbox in a dedicated iTunes library, then use Carbon Copy Cloner to copy the music to the iPod (after removing embedded art and replacing with suitably sized cover.bmp files). So I found a way of making a random playlist in iTunes.
1. Make a SmartPlaylist to select a suitable number of songs, less than the Rockbox limit. This is a random selection, but listed in a sorted order. I could put this is Rockbox and then shuffle, but instead:
2. Make a second SmartPlaylist by random selection from the first, with the same number of songs.
3. Export as m3u
4. Put in a text editor and globally replace the file paths to match those on the iPod.
5. Copy to iPod and load into Rockbox.
philden:
Answering myself again, the best answer lies here - https://forums.rockbox.org/index.php/topic,53990.0.html
However, I would still like the option of a 'real' random play. By which I mean the next track is randomly selected from the whole library, which could even mean repeating the same song. This would go against the 'playlist' principle of Rockbox, unless it is a randomly created one-song playlist.
Bilgus:
its pretty easy to throw something together..
--- Code: -----[[ Lua RB Random Playlist
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2020 William Wilgus
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
]]
require ("actions")
--require("dbgettags")
local scrpath = rb.current_path()
local max_entries = 500;
local CANCEL_BUTTON = rb.actions.PLA_CANCEL
local sINVALIDDATABASE = "Invalid Database"
local sERROROPENING = "Error opening"
-- tag cache header
local sTCVERSION = string.char(0x10)
local sTCHEADER = string.reverse("TCH" .. sTCVERSION)
local DATASZ = 4 -- int32_t
local TCHSIZE = 3 * DATASZ -- 3 x int32_t
local function bytesLE_n(str)
str = str or ""
local tbyte={str:byte(1, -1)}
local bpos = 1
local num = 0
for k = 1,#tbyte do -- (k = #t, 1, -1 for BE)
num = num + tbyte[k] * bpos
bpos = bpos * 256
end
return num
end
-- uses database files to retrieve database tags
-- adds all unique tags into a lua table
-- ftable is optional
function get_tags(filename, hstr, ftable)
if not filename then return end
if not ftable then ftable = {} end
hstr = hstr or filename
local file = io.open('/' .. filename or "", "r") --read
if not file then rb.splash(100, sERROROPENING .. " " .. filename) return end
local fsz = file:seek("end")
local posln = 0
local tag_len = TCHSIZE
local idx
local function readchrs(count)
if posln >= fsz then return nil end
file:seek("set", posln)
posln = posln + count
return file:read(count)
end
-- check the header and get size + #entries
local tagcache_header = readchrs(DATASZ) or ""
local tagcache_sz = readchrs(DATASZ) or ""
local tagcache_entries = readchrs(DATASZ) or ""
if tagcache_header ~= sTCHEADER or
bytesLE_n(tagcache_sz) ~= (fsz - TCHSIZE) then
rb.splash(100, sINVALIDDATABASE .. " " .. filename)
return
end
-- local tag_entries = bytesLE_n(tagcache_entries)
for k, v in pairs(ftable) do ftable[k] = nil end -- clear table
ftable[1] = hstr
local tline = #ftable + 1
ftable[tline] = ""
local str = ""
while true do
tag_len = bytesLE_n(readchrs(DATASZ))
readchrs(DATASZ) -- idx = bytesLE_n(readchrs(DATASZ))
str = readchrs(tag_len) or ""
str = string.match(str, "(%Z+)%z") -- \0 terminated string
if str and math.random(10) > 5 then
if ftable[tline - 1] ~= str then -- Remove dupes
ftable[tline] = str
tline = tline + 1
if tline >= max_entries then break end
end
elseif posln >= fsz then
break
end
if rb.get_plugin_action(0) == CANCEL_BUTTON then
break
end
end
file:close()
return ftable
end -- get_tags
local function create_playlist()
math.randomseed(rb.current_tick()); -- some kind of randomness
local files = get_tags(rb.ROCKBOX_DIR .. "/database_4.tcd", "",nil)
local fcount = #files
if fcount > 1 then
rb.audio("stop")
rb.playlist("create", scrpath .. "/", "playback.m3u8")
end
for i = 2, fcount - 1 do
repeat
next_item = math.random(2, fcount - 1)
until (files[next_item] ~= "")
rb.playlist("insert_track", string.match(files[next_item], "[^;]+") or "?")
files[next_item] = ""
end
if fcount > 1 then
rb.playlist("start", 0, 0, 0)
end
files = nil -- empty table
end -- create_playlist
local function main()
if not rb.file_exists(rb.ROCKBOX_DIR .. "/database_4.tcd") then
rb.splash(rb.HZ, "Initialize Database")
os.exit(1);
end
rb.splash(10, "Searching for Files..")
collectgarbage("collect")
create_playlist();
collectgarbage("collect")
rb.splash(100, "Goodbye")
end
main() -- BILGUS
--- End code ---
problem being that for most there isn't enough ram to load all the tracks in the database
something incremental would have to be made
currently it just (pseudo) randomly grabs tracks and shuffles them randomly and spits out a playlist
as you play around with it..
math.random(10) > 5 make this something less selective for really large databases math.random(1000) > 750
you could probably make it happen in place by looping through the database multiple times with a really selective function
it could even be expanded to take the names into consideration or other database fields (see tagnav lua script)
Bilgus:
Well I went ahead and made probably exactly what you want
--- Code: -----[[ Lua RB Random Playlist
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2020 William Wilgus
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
]]
require ("actions")
--require("dbgettags")
local scrpath = rb.current_path()
local max_entries = 500;
local CANCEL_BUTTON = rb.actions.PLA_CANCEL
local sINVALIDDATABASE = "Invalid Database"
local sERROROPENING = "Error opening"
-- tag cache header
local sTCVERSION = string.char(0x10)
local sTCHEADER = string.reverse("TCH" .. sTCVERSION)
local DATASZ = 4 -- int32_t
local TCHSIZE = 3 * DATASZ -- 3 x int32_t
local function bytesLE_n(str)
str = str or ""
local tbyte={str:byte(1, -1)}
local bpos = 1
local num = 0
for k = 1,#tbyte do -- (k = #t, 1, -1 for BE)
num = num + tbyte[k] * bpos
bpos = bpos * 256
end
return num
end
-- uses database files to retrieve track file names
-- adds songs at random to new playlist
-- play plays the playlist when done
function create_random_playlist(filename, play)
if not filename then return end
if not play then play = false end
local file = io.open('/' .. filename or "", "r") --read
if not file then rb.splash(100, sERROROPENING .. " " .. filename) return end
local fsz = file:seek("end")
rb.audio("stop")
rb.playlist("create", scrpath .. "/", "random_playback.m3u8")
local posln = 0
local tag_len = TCHSIZE
local idx
local function readchrs(count)
if posln >= fsz then return nil end
file:seek("set", posln)
posln = posln + count
return file:read(count)
end
-- check the header and get size + #entries
local tagcache_header = readchrs(DATASZ) or ""
local tagcache_sz = readchrs(DATASZ) or ""
local tagcache_entries = readchrs(DATASZ) or ""
if tagcache_header ~= sTCHEADER or
bytesLE_n(tagcache_sz) ~= (fsz - TCHSIZE) then
rb.splash(100, sINVALIDDATABASE .. " " .. filename)
return
end
-- local tag_entries = bytesLE_n(tagcache_entries)
local fbegin = file:seek("cur") --Mark the beginning for later loop
local tracks = 0
local str = ""
local rand
local spread = 10;
while true do
tag_len = bytesLE_n(readchrs(DATASZ))
readchrs(DATASZ) -- idx = bytesLE_n(readchrs(DATASZ))
str = readchrs(tag_len) or ""
str = string.match(str, "(%Z+)%z") -- \0 terminated string
rand = math.random(1000)
if str and rand >= (500 - spread) and rand < (500 + spread) then
rb.playlist("insert_track", str)
tracks = tracks + 1
if tracks >= max_entries then break end
elseif posln >= fsz then
if tracks < max_entries then
spread = spread + 10
posln = fbegin -- go through again
else
break
end
end
if rb.get_plugin_action(0) == CANCEL_BUTTON then
break
end
end
file:close()
if tracks == max_entries and play == true then
rb.playlist("start", 0, 0, 0)
end
return ftable
end -- create_playlist
local function main()
if not rb.file_exists(rb.ROCKBOX_DIR .. "/database_4.tcd") then
rb.splash(rb.HZ, "Initialize Database")
os.exit(1);
end
math.randomseed(rb.current_tick()); -- some kind of randomness
rb.splash(10, "Searching for Files..")
collectgarbage("collect")
create_random_playlist(rb.ROCKBOX_DIR .. "/database_4.tcd", true);
collectgarbage("collect")
rb.splash(100, "Goodbye")
end
main() -- BILGUS
--- End code ---
Now:
it will go through the database file multiple times if need be and it lowers the selectivity the more times it loops through
you can make max entries as large as you like but 500 songs takes quite a while with my clip zip
Navigation
[0] Message Index
[#] Next page
Go to full version