├── .gitattributes ├── src ├── img │ ├── menu.png │ ├── paused.gif │ ├── playing.gif │ └── addSongsQR.png ├── SystemAssets │ ├── card.png │ └── card-pressed.png ├── pdxinfo ├── utils.lua ├── lists.lua ├── input.lua ├── fnt │ └── dos.fnt ├── funcs.lua └── main.lua ├── screenshots ├── main.png ├── settings.png └── nowplaying.png ├── .gitignore ├── LICENSE ├── MANUAL.md └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /src/img/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nanobot567/musik/HEAD/src/img/menu.png -------------------------------------------------------------------------------- /src/img/paused.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nanobot567/musik/HEAD/src/img/paused.gif -------------------------------------------------------------------------------- /screenshots/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nanobot567/musik/HEAD/screenshots/main.png -------------------------------------------------------------------------------- /src/img/playing.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nanobot567/musik/HEAD/src/img/playing.gif -------------------------------------------------------------------------------- /src/img/addSongsQR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nanobot567/musik/HEAD/src/img/addSongsQR.png -------------------------------------------------------------------------------- /screenshots/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nanobot567/musik/HEAD/screenshots/settings.png -------------------------------------------------------------------------------- /src/SystemAssets/card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nanobot567/musik/HEAD/src/SystemAssets/card.png -------------------------------------------------------------------------------- /screenshots/nowplaying.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nanobot567/musik/HEAD/screenshots/nowplaying.png -------------------------------------------------------------------------------- /src/SystemAssets/card-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nanobot567/musik/HEAD/src/SystemAssets/card-pressed.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build.bat 2 | build-run.bat 3 | build.sh 4 | build-run.sh 5 | pdxinfobuilder.py 6 | Games/ 7 | musik.pdx/ 8 | unused/ 9 | CHANGELOG.MD 10 | TODO.MD 11 | -------------------------------------------------------------------------------- /src/pdxinfo: -------------------------------------------------------------------------------- 1 | name=musik 2 | author=nanobot567 3 | description=simple music player for playdate 4 | bundleID=com.nano.musik 5 | version=4.3.2 6 | buildNumber=1265 7 | imagePath=SystemAssets 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Nanobot567 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/utils.lua: -------------------------------------------------------------------------------- 1 | -- utility functions 2 | 3 | function indexOf(tab, str) 4 | for i, s in ipairs(tab) do 5 | if s == str then 6 | return i 7 | end 8 | end 9 | return nil 10 | end 11 | 12 | function inTable(tab, str) 13 | for i, v in ipairs(tab) do 14 | if v == str then 15 | return true 16 | end 17 | end 18 | 19 | return false 20 | end 21 | 22 | function findSupportedTypes(str) 23 | if str ~= nil then 24 | if (string.find(str,"%.mp3",#str-3) ~= nil or string.find(str,"%.pda",#str-3) ~= nil) and string.find(str,"/",#str-1) == nil then 25 | return true 26 | end 27 | return false 28 | end 29 | end 30 | 31 | function fixFormatting(string) 32 | return string.gsub(string.gsub(string,"*","**"),"_","__") 33 | end 34 | 35 | function formatSeconds(seconds) 36 | local seconds = tonumber(seconds) 37 | 38 | if seconds <= 0 then 39 | return "0:00" 40 | else 41 | hours = string.format("%02.f", math.floor(seconds/3600)) 42 | mins = string.format("%02.f", math.floor(seconds/60 - (hours*60))) 43 | secs = string.format("%02.f", math.floor(seconds - hours*3600 - mins *60)) 44 | return mins..":"..secs 45 | end 46 | end 47 | 48 | function split(inputstr,sep) 49 | t = {} 50 | for str in string.gmatch(inputstr, "([^"..sep.."]+)") do 51 | -- "([^"..sep.."]+)" 52 | table.insert(t, str) 53 | end 54 | 55 | return t 56 | end 57 | -------------------------------------------------------------------------------- /src/lists.lua: -------------------------------------------------------------------------------- 1 | -- listviews 2 | local gfx = pd.graphics 3 | 4 | fileList = pd.ui.gridview.new(0, 10) 5 | fileList:setScrollDuration(250) 6 | fileList:setCellPadding(0, 0, 5, 10) 7 | fileList:setContentInset(24, 24, 13, 11) 8 | 9 | function fileList:drawCell(section, row, column, selected, x, y, width, height) 10 | local toWrite = fixFormatting(files[row]) 11 | if files[row] ~= nil then 12 | if selected then 13 | gfx.fillRoundRect(x, y, width, 20, 4) 14 | gfx.setImageDrawMode(dMColor2) 15 | else 16 | gfx.setImageDrawMode(dMColor1) 17 | end 18 | 19 | if (files[row] == currentFileName and dir == currentFileDir) then 20 | toWrite = "*" .. toWrite .. "*" 21 | elseif inTable(queueList, dir .. files[row]) then 22 | toWrite = "_" .. toWrite .. "_" 23 | end 24 | gfx.drawText(toWrite, x + 4, y + 2) 25 | end 26 | end 27 | 28 | settingsList = pd.ui.gridview.new(0, 10) 29 | settingsList:setScrollDuration(250) 30 | settingsList:setCellPadding(0, 0, 5, 10) 31 | settingsList:setContentInset(24, 24, 13, 11) 32 | 33 | function settingsList:drawCell(section, row, column, selected, x, y, width, height) 34 | local toWrite = settings[row] 35 | if settings[row] ~= nil then 36 | if selected then 37 | gfx.fillRoundRect(x, y, width, 20, 4) 38 | gfx.setImageDrawMode(dMColor2) 39 | else 40 | gfx.setImageDrawMode(dMColor1) 41 | end 42 | 43 | if settings[row] == currentFileName and dir == currentFileDir then 44 | toWrite = "*" .. toWrite .. "*" 45 | end 46 | gfx.drawText(toWrite, x + 4, y + 2) 47 | end 48 | end 49 | 50 | -------------------------------------------------------------------------------- /MANUAL.md: -------------------------------------------------------------------------------- 1 | # musik user manual 2 | 3 | ## Controls 4 | 5 | ### When in the "files" screen: 6 | 7 | - Up/Down - controls the file selection cursor 8 | - Right/Left - skip +4 or -4 in the file list 9 | - if left is pressed at the top of the file list, you will be sent up a folder. 10 | - A - play song / enter folder 11 | - B - up folder (or if at root of filesystem, you will go to "now playing") 12 | 13 | ### When in the "now playing" screen: 14 | - A - play / pause song 15 | - B - exit to files screen 16 | - (Up / Left) / (Down / Right) - skip to previous/next song 17 | 18 | > note: if you would like to change the volume without opening the Playdate menu, hold down `MENU` and press d-pad left or right! 19 | 20 | In the system menu, there is an options menu item titled "mode". This controls the current playing mode. The options are: 21 | - `none` - plays the song then stops 22 | - `shuffle` - plays a random song in the current folder 23 | - `loop folder` - plays all of the songs in the folder, then loops back to the top and continues 24 | - `loop one` - loops one song 25 | - `queue` - enters queue mode. more info on queue mode below. 26 | 27 | ### Queue mode 28 | 29 | In queue mode, you can queue up songs to be played. Shuffles through songs in the last folder once the queue list is empty. 30 | 31 | Controls: 32 | - A - add song to queue (can be done multiple times for one song) 33 | - B on italicised song - remove song from queue 34 | - B on regular/bolded song - play queue (when in `/music/` directory) / up folder (any other directory) 35 | 36 | Exiting queue mode (by going to 'now playing' screen) will switch the mode back to 'none' if no songs were selected. 37 | 38 | ### Settings 39 | 40 | Other settings can be accessed via the "settings" menu item. Here you will find various settings such as toggling dark mode and the 24 hour clock. 41 | 42 | - Up/Down - select setting 43 | - A - toggle setting 44 | - B - return to last screen 45 | 46 | ## Adding songs 47 | 48 | To add songs to musik, follow these steps: 49 | 1. Put your Playdate into data disk mode (you can do this by opening settings and navigating to `settings / system / reboot to data disk`) 50 | 2. Connect your Playdate to your computer and open the `PLAYDATE` drive 51 | 3. Navigate to `/Data/user.*****.musik/music/` or `/Data/user.*****.com.nano.musik/music/` 52 | 4. Drag and drop your MP3s / PDAs! 53 | 54 | NOTE: Musik also supports folders, so long file names shouldn't be a problem...? (Let me know if it is and I'll see what I can do) 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # musik 2 | a music player for playdate 3 | 4 | 5 | 6 |
7 | 8 | ## FAQ 9 | 10 | ### So how do I get this onto my Playdate? 11 | Visit [this page](https://help.play.date/games/sideloading/) on Playdate's website for information on how to sideload. 12 | 13 | ### Why though? 14 | When I first created musik, there were no public music players out for playdate (except for Audition, but it wasn't made to be an iPod type music player), so I decided to make one! 15 | 16 | ### How do I use this thing??? 17 | 18 | Check out the [user manual](https://codeberg.org/nanobot567/musik/src/branch/main/MANUAL.md)! 19 | 20 | ### I just found a bug, where do I report it? 21 | Head to the "issues" tab on Github and file a bug report. 22 | 23 | ### Why aren't my MP3s playing correctly? 24 | Try dragging them into [Audacity](https://audacityteam.org/) and re-exporting them as MP3s with no metadata. 25 | 26 | ### Sometimes an MP3 plays back slower/faster than the rest. 27 | If one of your MP3s has a different audio rate than the others, it can play back slower or faster than the others. 28 | 29 | To fix this, you can either (for all of your MP3s if you're unsure, or for the one MP3 that has a different play rate): 30 | 31 | - drag your MP3 into something like [Audacity](https://audacityteam.org/) 32 | - change the project rate (it should be in the bottom left corner) to 44100 Hz (or whichever you would like) 33 | - re-export the MP3 34 | 35 | or... 36 | 37 | - change the sample rate with ffmpeg: `ffmpeg -i input.mp3 -ar 44100 output.mp3` 38 | 39 | 57 | -------------------------------------------------------------------------------- /src/input.lua: -------------------------------------------------------------------------------- 1 | -- functions that have to do with input 2 | 3 | local pd = playdate 4 | local fs = pd.file 5 | 6 | function bAction() 7 | curRow = fileList:getSelectedRow() 8 | 9 | if dir == "/music/" then 10 | swapScreenMode() 11 | else 12 | local x = string.find(dir, "/") - 1 13 | local splitted = split(dir, "/") 14 | table.remove(splitted) 15 | for i = x, 0, -1 do 16 | lastdirs[i] = "/" .. table.concat(splitted, "/") .. "/" 17 | table.remove(splitted, #splitted) 18 | end 19 | 20 | dir = table.remove(lastdirs, #lastdirs) 21 | 22 | files = fs.listFiles(dir, false) 23 | 24 | if dir ~= "/music/" then 25 | table.insert(files, 1, "..") 26 | end 27 | 28 | local selRow = table.remove(lastDirPos) 29 | 30 | if selRow == nil then 31 | selRow = 1 32 | end 33 | 34 | fileList:setSelectedRow(selRow) 35 | fileList:scrollToRow(selRow) 36 | end 37 | 38 | audioFiles = {} 39 | 40 | for i = 1, #files do 41 | if files[curRow] ~= nil then 42 | if findSupportedTypes(files[curRow]) then 43 | table.insert(audioFiles, files[i]) 44 | end 45 | end 46 | end 47 | end 48 | 49 | function pd.downButtonDown() 50 | local function timerCallback() 51 | if fileList:getSelectedRow() ~= #files then 52 | fileList:selectNextRow(true) 53 | end 54 | end 55 | if screenMode == 0 or screenMode == 3 then 56 | downKeyTimer = pd.timer.keyRepeatTimerWithDelay(300, 50, timerCallback) 57 | end 58 | end 59 | 60 | function pd.downButtonUp() 61 | if screenMode == 0 or screenMode == 3 then 62 | downKeyTimer:remove() 63 | end 64 | end 65 | 66 | function pd.upButtonDown() 67 | local function timerCallback() 68 | if fileList:getSelectedRow() ~= 1 then 69 | fileList:selectPreviousRow(true) 70 | end 71 | end 72 | if screenMode == 0 or screenMode == 3 then 73 | upKeyTimer = pd.timer.keyRepeatTimerWithDelay(300, 50, timerCallback) 74 | end 75 | end 76 | 77 | function pd.upButtonUp() 78 | if screenMode == 0 or screenMode == 3 then 79 | upKeyTimer:remove() 80 | end 81 | end 82 | 83 | function updateCrank() 84 | if screenMode == 0 or screenMode == 3 then 85 | local crankTicks = pd.getCrankTicks(8) 86 | 87 | if crankTicks == 1 then 88 | if fileList:getSelectedRow() ~= #files then 89 | fileList:selectNextRow(true) 90 | end 91 | elseif crankTicks == -1 then 92 | if fileList:getSelectedRow() ~= 1 then 93 | fileList:selectPreviousRow(true) 94 | end 95 | end 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /src/fnt/dos.fnt: -------------------------------------------------------------------------------- 1 | --metrics={"baseline":7,"xHeight":0,"capHeight":0,"pairs":{},"left":[],"right":[]} 2 | datalen=2188 3 | data=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 4 | width=8 5 | height=8 6 | 7 | tracking=1 8 | 9 | 0 8 10 | 1 8 11 | 2 8 12 | 3 8 13 | 4 8 14 | 5 8 15 | 6 8 16 | 7 8 17 | 8 8 18 | 9 8 19 | space 8 20 | � 8 21 | A 8 22 | B 8 23 | C 8 24 | D 8 25 | E 8 26 | F 8 27 | G 8 28 | H 8 29 | I 8 30 | J 8 31 | K 8 32 | L 8 33 | M 8 34 | N 8 35 | O 8 36 | P 8 37 | Q 8 38 | R 8 39 | S 8 40 | T 8 41 | U 8 42 | V 8 43 | W 8 44 | X 8 45 | Y 8 46 | Z 8 47 | a 8 48 | b 8 49 | c 8 50 | d 8 51 | e 8 52 | f 8 53 | g 8 54 | h 8 55 | i 8 56 | j 8 57 | k 8 58 | l 8 59 | m 8 60 | n 8 61 | o 8 62 | p 8 63 | q 8 64 | r 8 65 | s 8 66 | t 8 67 | u 8 68 | v 8 69 | w 8 70 | x 8 71 | y 8 72 | z 8 73 | ! 5 74 | " 5 75 | # 6 76 | $ 7 77 | % 7 78 | & 7 79 | ' 2 80 | ( 5 81 | ) 7 82 | * 4 83 | + 4 84 | , 3 85 | - 4 86 | . 3 87 | / 5 88 | : 3 89 | ; 3 90 | < 4 91 | = 5 92 | > 5 93 | ? 6 94 | @ 7 95 | [ 5 96 | \ 5 97 | ] 6 98 | ^ 4 99 | _ 5 100 | ` 4 101 | { 5 102 | | 4 103 | } 7 104 | ~ 5 105 | 106 | -------------------------------------------------------------------------------- /src/funcs.lua: -------------------------------------------------------------------------------- 1 | local gfx = pd.graphics 2 | local disp = pd.display 3 | local timer = pd.timer 4 | local fs = pd.file 5 | 6 | local songEndErrorCounter = 0 7 | 8 | function lockScreenFunc() 9 | locked = true 10 | gfx.clear(bgColor) 11 | disp.setRefreshRate(5) 12 | end 13 | 14 | function swapScreenMode() 15 | if screenMode == 0 or screenMode == 3 then 16 | screenMode = 1 17 | 18 | if mode == 4 and currentAudio:isPlaying() == false then 19 | actualSongEnd() 20 | end 21 | elseif screenMode == 1 then 22 | if mode == 4 then 23 | screenMode = 3 24 | else 25 | screenMode = 0 26 | end 27 | 28 | if currentFileDir == "" then 29 | dir = "/music/" 30 | 31 | for i = 1, #files do 32 | if findSupportedTypes(files[curRow]) then 33 | table.insert(audioFiles, files[i]) 34 | end 35 | end 36 | else 37 | dir = currentFileDir 38 | files = fs.listFiles(dir, false) 39 | 40 | for i = 1, #files do 41 | if findSupportedTypes(files[curRow]) then 42 | table.insert(audioFiles, files[i]) 43 | end 44 | end 45 | -- set directory to currentFileDir 46 | 47 | for i, v in ipairs(files) do 48 | if v == currentFileName then 49 | fileList:setSelectedRow(i) 50 | fileList:scrollToRow(i, true) 51 | end 52 | end 53 | end 54 | if dir ~= "/music/" then 55 | table.insert(files, 1, "..") 56 | fileList:selectNextRow() 57 | end 58 | end 59 | end 60 | 61 | function swapColorMode(mode) 62 | if mode == false then 63 | bgColor = gfx.kColorWhite 64 | color = gfx.kColorBlack 65 | dMColor1 = gfx.kDrawModeCopy 66 | dMColor2 = gfx.kDrawModeFillWhite 67 | gfx.setColor(color) 68 | darkMode = false 69 | else 70 | bgColor = gfx.kColorBlack 71 | color = gfx.kColorWhite 72 | dMColor1 = gfx.kDrawModeFillWhite 73 | dMColor2 = gfx.kDrawModeCopy 74 | gfx.setColor(color) 75 | darkMode = true 76 | end 77 | end 78 | 79 | function saveSettings() 80 | local settingsFile = fs.open("/data/settings.json", fs.kFileWrite) 81 | settingsFile:write(json.encode({ darkMode, clockMode, showInfoEverywhere, screenRoundness, lockScreen, lockScreenTime, uiDesign })) 82 | settingsFile:close() 83 | end 84 | 85 | function loadSettings() 86 | local settingsFile, err = fs.open("/data/settings.json", fs.kFileRead) 87 | 88 | if err == nil then 89 | local settings = json.decode(settingsFile:read(100000)) 90 | 91 | darkMode = settings[1] 92 | clockMode = settings[2] 93 | showInfoEverywhere = settings[3] 94 | screenRoundness = tonumber(settings[4]) 95 | lockScreen = settings[5] 96 | lockScreenTime = tonumber(settings[6]) 97 | 98 | if lockScreen == true then 99 | lockTimer = timer.new((lockScreenTime * 60) * 1000, lockScreenFunc) 100 | end 101 | 102 | if settings[7] then 103 | uiDesign = settings[7] 104 | else 105 | uiDesign = "new" 106 | end 107 | 108 | if settings[8] then 109 | playPauseGraphicSwap = settings[8] 110 | else 111 | playPauseGraphicSwap = false 112 | end 113 | else 114 | darkMode = true 115 | clockMode = false 116 | showInfoEverywhere = false 117 | lockScreen = false 118 | screenRoundness = 4 119 | lockScreenTime = 2 120 | if lockScreen == true then 121 | lockTimer = timer.new((lockScreenTime * 60) * 1000, lockScreenFunc) 122 | end 123 | 124 | uiDesign = "new" 125 | playPauseGraphicSwap = false 126 | end 127 | end 128 | 129 | function handleMode(str) -- add queue mode 130 | print("mode is now " .. str) 131 | modeString = str 132 | 133 | if str == "shuffle" then 134 | mode = 1 135 | elseif str == "loop folder" then 136 | mode = 2 137 | elseif str == "loop one" then 138 | mode = 3 139 | elseif str == "queue" then 140 | getLengthVar = 0 141 | saveSongSpot = 0 142 | saveSongSpot2 = 0 143 | 144 | screenMode = 3 145 | mode = 4 146 | 147 | currentAudio:setFinishCallback(nil) 148 | currentAudio:stop() 149 | currentAudio = pd.sound.fileplayer.new() 150 | currentAudio:setFinishCallback(handleSongEnd) 151 | currentFileName = "" 152 | currentFileDir = "" 153 | currentFilePath = "" 154 | else 155 | mode = 0 156 | end 157 | 158 | if mode ~= 4 then 159 | queueList = {} 160 | queueListDirs = {} 161 | queueListNames = {} 162 | end 163 | end 164 | 165 | function newSettingsList() 166 | local setList = ({ "dark mode - " .. tostring(darkMode), 167 | "24 hour clock - " .. tostring(clockMode), 168 | "show extra info everywhere - " .. tostring(showInfoEverywhere), 169 | "show version - " .. tostring(showVersion), 170 | "screen roundness - " .. tostring(screenRoundness), 171 | "ui design - " .. tostring(uiDesign), 172 | "swap play graphic with pause graphic - " .. tostring(playPauseGraphicSwap), 173 | "lock screen - " .. tostring(lockScreen), 174 | }) 175 | 176 | if lockScreen == true then 177 | table.insert(setList, " lock after " .. tostring(lockScreenTime) .. " minute(s)") 178 | end 179 | 180 | return setList 181 | end 182 | 183 | function drawInfo() 184 | local extension 185 | local time = pd.getTime() 186 | if #tostring(time["hour"]) == 1 then 187 | time["hour"] = "0" .. time["hour"] 188 | end 189 | 190 | if clockMode == false then 191 | if tonumber(time["hour"]) > 12 then 192 | time["hour"] -= 12 193 | end 194 | end 195 | 196 | if #tostring(time["minute"]) == 1 then 197 | time["minute"] = "0" .. time["minute"] 198 | end 199 | 200 | local batteryPercent = pd.getBatteryPercentage() 201 | 202 | if string.find(batteryPercent, "100.") then 203 | batteryPercent = "100" 204 | else 205 | batteryPercent = string.sub(string.gsub(batteryPercent, "%.", ""), 1, 2) 206 | end 207 | 208 | local size = gfx.getTextSize(batteryPercent .. "%", dosFnt) 209 | 210 | dosFnt:drawTextAligned(time["hour"] .. ":" .. time["minute"], 1, 1, 400, 20, kTextAlignment.left) 211 | dosFnt:drawTextAligned(batteryPercent .. "%", 401 - size, 1, 400, 20, kTextAlignment.right) 212 | -- gfx.setImageDrawMode(dMColor2) 213 | end 214 | 215 | function actualSongEnd() 216 | updateGetLength = true 217 | 218 | local justInQueue = false 219 | 220 | audioFiles = {} 221 | for i = 1, #files do 222 | if findSupportedTypes(files[i]) then 223 | table.insert(audioFiles, files[i]) 224 | end 225 | end 226 | 227 | currentAudio:pause() 228 | 229 | if mode == 2 then 230 | if currentFileName == audioFiles[1] then 231 | currentPos = 1 232 | end 233 | if currentFileName == audioFiles[#audioFiles] then 234 | if not pd.buttonIsPressed("a") then 235 | if fs.isdir(dir .. audioFiles[1]) == false then 236 | currentFileName = audioFiles[1] 237 | currentAudio:load(dir .. audioFiles[1]) 238 | end 239 | end 240 | else 241 | local isdir = fs.isdir(dir .. audioFiles[currentPos + 1]) 242 | if isdir == true then 243 | currentPos += 2 244 | else 245 | currentPos += 1 246 | end 247 | 248 | currentFileName = audioFiles[currentPos] 249 | currentAudio:load(dir .. audioFiles[currentPos]) 250 | end 251 | elseif mode == 1 then 252 | local randthing = math.random(1, #audioFiles) 253 | if dir .. audioFiles[randthing] == currentFilePath and #audioFiles ~= 1 then 254 | while dir .. audioFiles[randthing] == currentFilePath do 255 | randthing = math.random(1, #audioFiles) 256 | end 257 | end 258 | currentFileName = audioFiles[randthing] 259 | currentAudio:load(dir .. audioFiles[randthing]) 260 | elseif mode == 0 then 261 | currentAudio = pd.sound.fileplayer.new() 262 | currentAudio:setFinishCallback(handleSongEnd) 263 | currentFileName = "" 264 | currentFileDir = "" 265 | currentFilePath = "" 266 | elseif mode == 4 then 267 | if #queueList == 1 then 268 | mode = 1 269 | modeMenuItem:setValue("shuffle") 270 | modeString = "shuffle" 271 | justInQueue = true 272 | end 273 | 274 | if #queueList ~= 0 then 275 | currentFileName = queueListNames[1] 276 | currentFileDir = queueListDirs[1] 277 | currentFilePath = queueList[1] 278 | table.remove(queueList, 1) 279 | table.remove(queueListDirs, 1) 280 | table.remove(queueListNames, 1) 281 | currentAudio:load(currentFilePath) 282 | else 283 | mode = 0 284 | modeMenuItem:setValue("none") 285 | modeString = "none" 286 | end 287 | end 288 | 289 | if mode ~= 4 then 290 | if justInQueue == false then 291 | currentFilePath = dir .. currentFileName 292 | currentFileDir = dir 293 | end 294 | end 295 | 296 | table.insert(lastSongNames, currentFileName) 297 | table.insert(lastSongDirs, currentFileDir) 298 | 299 | audioLen = getLengthVar 300 | 301 | currentAudio:setRate(1.0) 302 | 303 | if mode ~= 0 then 304 | pd.timer.new(10, function() 305 | currentAudio:pause() 306 | currentAudio:setOffset(0) 307 | currentAudio:play() 308 | end) 309 | currentAudio:play() 310 | else 311 | getLengthVar = 0 312 | currentFilePath = "" 313 | pd.setAutoLockDisabled(false) 314 | end 315 | end 316 | 317 | function handleSongEnd() -- fix literally everything :) have fun future aiden - i did it past me! aren't you proud of me? 318 | songEndErrorCounter = songEndErrorCounter + 1 319 | 320 | -- If the function has been called more than once in 100 frames, return immediately 321 | if songEndErrorCounter > 5 then 322 | actualSongEnd() 323 | return 324 | end 325 | 326 | -- Reset the counter after 100 frames 327 | pd.timer.new(300, function() 328 | songEndErrorCounter = 0 329 | end) 330 | 331 | if currentAudio:getLength() ~= nil and (math.abs(currentAudio:getOffset() - currentAudio:getLength()) <= 5) and (math.abs(getLengthVar - currentAudio:getLength()) <= 5) then 332 | actualSongEnd() 333 | else 334 | currentAudio:pause() 335 | currentAudio:setOffset(math.floor(saveSongSpot2 + 0.5)) 336 | currentAudio:play() 337 | 338 | safeToReset = false 339 | pd.timer.new(100, function() 340 | safeToReset = true 341 | end) 342 | end 343 | end 344 | -------------------------------------------------------------------------------- /src/main.lua: -------------------------------------------------------------------------------- 1 | -- musik by nanobot567. feel free to copy / share this code, just give credit please! :) 2 | 3 | -- small text font on card-pressed.png is consolas 9 4 | 5 | pd = playdate 6 | 7 | import "CoreLibs/crank" 8 | import "CoreLibs/graphics" 9 | import "CoreLibs/ui" 10 | import "CoreLibs/nineslice" 11 | import "CoreLibs/timer" 12 | 13 | import "utils" 14 | import "input" 15 | import "lists" 16 | import "funcs" 17 | -- import "crankFuncs" 18 | 19 | local gfx = pd.graphics 20 | local disp = pd.display 21 | local timer = pd.timer 22 | local fs = pd.file 23 | 24 | fs.mkdir("/music/") 25 | fs.mkdir("/data/") 26 | dir = "/music/" 27 | lastdirs = {} 28 | files = fs.listFiles(dir, false) 29 | 30 | local playingGraphic = gfx.image.new("img/playing") 31 | local pausedGraphic = gfx.image.new("img/paused") 32 | local menuGraphic = gfx.image.new("img/menu") 33 | dosFnt = gfx.font.new("fnt/dos") 34 | 35 | pd.setMenuImage(menuGraphic) 36 | 37 | currentAudio = pd.sound.fileplayer.new() 38 | currentFilePath, currentFileName, currentFileDir, modeString = "", "", "", "none" 39 | lastOffset, currentPos, songToHighlightRow, audioLen, lastScreenMode = 0, 1, 0, 0, 0 40 | darkMode, showInfoEverywhere = true, false 41 | audioFiles, lastSongDirs, lastSongNames = {}, {}, {} 42 | lastDirPos = { 1 } 43 | mode = 0 -- 0 is none, 1 is shuffle, 2 is loop folder, 3 is loop song, 4 is queue 44 | screenMode = 0 -- 0 is files, 1 is playing, 2 is settings 45 | clockMode = false -- true is 24 hr, false is 12 hr 46 | upKeyTimer = nil 47 | downKeyTimer = nil 48 | lockTimer = nil 49 | lockScreenTime = 2 -- minutes 50 | lockScreen = false 51 | locked = false 52 | showVersion = true 53 | screenRoundness = 4 54 | uiDesign = "new" 55 | playPauseGraphicSwap = false 56 | 57 | updateGetLength = true -- this resets the getLengthVar 58 | getLengthVar = 0 -- this is conected to getLength() 59 | saveSongSpot = 0 60 | saveSongSpot2 = 0 61 | safeToReset = true 62 | 63 | queueList = {} 64 | queueListDirs = {} 65 | queueListNames = {} 66 | 67 | bgColor = gfx.kColorBlack 68 | color = gfx.kColorWhite 69 | dMColor1 = gfx.kDrawModeFillWhite 70 | dMColor2 = gfx.kDrawModeCopy 71 | 72 | for i = 1, #files do 73 | if findSupportedTypes(files[curRow]) then 74 | table.insert(audioFiles, files[i]) 75 | end 76 | end 77 | 78 | fileList:setNumberOfRows(#files) 79 | 80 | print("-----------------------------------------------------------------------") 81 | print("Hey there, friend! Have fun debugging / hacking my app! :D - nanobot567") 82 | print("-----------------------------------------------------------------------") 83 | 84 | loadSettings() 85 | swapColorMode(darkMode) 86 | settings = newSettingsList() 87 | 88 | gfx.setColor(color) 89 | gfx.clear(bgColor) 90 | 91 | if files[1] == nil then 92 | gfx.setImageDrawMode(dMColor1) 93 | gfx.drawTextAligned("no files found!", 200, 10, kTextAlignment.center) -- updated the txt 94 | gfx.drawTextAligned("scan the code to view musik's documentation.", 200, 35, kTextAlignment.center) 95 | 96 | gfx.setImageDrawMode(gfx.kDrawModeNXOR) -- add a QR code to the add audio part of the GitHub repo info thing 97 | local addSongsQRImg = gfx.image.new("img/addSongsQR") 98 | assert(addSongsQRImg) 99 | 100 | addSongsQRImg:draw(120,65) 101 | 102 | gfx.setImageDrawMode(dMColor2) 103 | 104 | table.insert(files,"no files!") 105 | pd.stop() 106 | end 107 | 108 | local menu = pd.getSystemMenu() 109 | 110 | local playingMenuItem, error = menu:addMenuItem("now playing", swapScreenMode) 111 | modeMenuItem, error = menu:addOptionsMenuItem("mode", { "none", "shuffle", "loop folder", "loop one", "queue" }, "none", 112 | handleMode) 113 | local settingsModeMenuItem, error = menu:addMenuItem("settings", function() 114 | if screenMode ~= 2 then 115 | lastScreenMode = screenMode 116 | screenMode = 2 117 | settingsList:setSelectedRow(1) 118 | else 119 | screenMode = lastScreenMode 120 | end 121 | end) 122 | 123 | currentAudio:setRate(1.0) 124 | 125 | files = fs.listFiles(dir, false) 126 | 127 | function pd.update() 128 | timer.updateTimers() 129 | gfx.clear(bgColor) 130 | 131 | if currentAudio:getLength() ~= nil then -- if its not nil then go ahead 132 | if getLengthVar < currentAudio:getLength() then -- always take the higher getLength() and show that 133 | getLengthVar = currentAudio:getLength() 134 | end 135 | 136 | if currentAudio:getOffset() >= (getLengthVar * .9) then -- if the song is at or past 90% switch to actual length estimate after it has worked out its kinks 137 | getLengthVar = currentAudio:getLength() 138 | end 139 | 140 | if updateGetLength == true then -- reset var for next songs 141 | getLengthVar = 1 142 | updateGetLength = false 143 | end 144 | end 145 | 146 | if locked == true then 147 | gfx.drawTextInRect("locked! hold a and b to unlock...", 0, 110, 400, 240, nil, nil, kTextAlignment.center, nil) 148 | end 149 | 150 | if showVersion == true and screenMode ~= 1 then 151 | local musikTextX, musikTextY, musikTextAlignment = 200, 227, kTextAlignment.center 152 | 153 | if uiDesign == "classic" then 154 | musikTextX = 400 155 | musikTextY = 232 156 | musikTextAlignment = kTextAlignment.right 157 | end 158 | 159 | dosFnt:drawTextAligned("musik " .. pd.metadata.version .. " zeta", musikTextX, musikTextY, musikTextAlignment, nil) 160 | end 161 | 162 | local btnState = pd.getButtonState() 163 | 164 | if btnState ~= 0 and lockScreen == true and locked == false then 165 | lockTimer:reset() 166 | lockTimer.duration = (lockScreenTime * 60) * 1000 167 | lockTimer.timerEndedCallback = lockScreenFunc 168 | end 169 | 170 | if btnState == 48 and locked == true then 171 | locked = false 172 | disp.setRefreshRate(30) 173 | lockTimer = timer.new((lockScreenTime * 60) * 1000, lockScreenFunc) 174 | pd.wait(350) 175 | gfx.clear(bgColor) 176 | end 177 | 178 | if showInfoEverywhere == true then 179 | drawInfo() 180 | end 181 | 182 | if locked ~= true then 183 | if screenMode == 0 or screenMode == 3 then 184 | playingMenuItem:setTitle("now playing") 185 | settingsModeMenuItem:setTitle("settings") 186 | 187 | fileList:setNumberOfRows(#files) 188 | curRow = fileList:getSelectedRow() 189 | 190 | if fileList.needsDisplay == true then 191 | fileList:drawInRect(0, 0, 400, 230) 192 | end 193 | 194 | gfx.drawRoundRect(20, 13, 360, 209, screenRoundness) 195 | 196 | if pd.buttonJustPressed("right") then 197 | if curRow <= #files - 4 then 198 | fileList:setSelectedRow(curRow + 4) 199 | else 200 | fileList:setSelectedRow(#files) 201 | end 202 | fileList:scrollToRow(fileList:getSelectedRow()) 203 | elseif pd.buttonJustPressed("left") then 204 | if curRow ~= 1 then 205 | if curRow > 5 then 206 | fileList:setSelectedRow(curRow - 4) 207 | else 208 | fileList:setSelectedRow(1) 209 | end 210 | fileList:scrollToRow(fileList:getSelectedRow()) 211 | else 212 | bAction() 213 | end 214 | elseif pd.buttonJustPressed("a") then 215 | if files[curRow] == ".." and curRow == 1 then 216 | bAction() 217 | elseif fs.isdir(dir .. files[curRow]) == true then 218 | audioFiles = {} 219 | table.insert(lastDirPos, curRow) 220 | fileList:setSelectedRow(1) 221 | fileList:scrollToRow(1) 222 | 223 | table.insert(lastdirs, dir) 224 | dir = dir .. files[curRow] 225 | 226 | files = fs.listFiles(dir, false) 227 | 228 | for i = 1, #files do 229 | if findSupportedTypes(files[i]) then 230 | table.insert(audioFiles, files[i]) 231 | end 232 | end 233 | 234 | if dir ~= "/music/" then 235 | table.insert(files, 1, "..") 236 | end 237 | 238 | fileList:setNumberOfRows(#files) 239 | else 240 | if dir .. files[curRow] == currentFilePath then 241 | swapScreenMode() 242 | else 243 | if findSupportedTypes(files[curRow]) then 244 | audioFiles = {} 245 | for i = 1, #files do 246 | if findSupportedTypes(files[curRow]) then 247 | table.insert(audioFiles, files[i]) 248 | end 249 | end 250 | currentPos = curRow 251 | 252 | if screenMode ~= 3 then 253 | saveSongSpot = 0 254 | updateGetLength = true 255 | 256 | currentAudio:pause() 257 | 258 | table.insert(lastSongDirs, currentFileDir) 259 | table.insert(lastSongNames, currentFileName) 260 | 261 | currentAudio:load(dir .. files[curRow]) 262 | 263 | audioLen = getLengthVar 264 | pd.setAutoLockDisabled(true) 265 | 266 | currentFileName = files[curRow] 267 | currentFileDir = dir 268 | currentFilePath = dir .. files[curRow] 269 | 270 | currentAudio:setRate(1.0) 271 | currentAudio:setOffset(0) 272 | currentAudio:play() 273 | 274 | swapScreenMode() 275 | else 276 | table.insert(queueList, dir .. files[curRow]) 277 | table.insert(queueListDirs, dir) 278 | table.insert(queueListNames, files[curRow]) 279 | end 280 | end 281 | end 282 | end 283 | elseif pd.buttonJustPressed("b") then 284 | if screenMode ~= 3 then 285 | bAction() 286 | else 287 | if inTable(queueList, dir .. files[curRow]) then 288 | table.remove(queueList, indexOf(queueList, dir .. files[curRow])) 289 | table.remove(queueListDirs, indexOf(queueList, dir)) 290 | table.remove(queueListNames, indexOf(queueListNames, files[curRow])) 291 | else 292 | bAction() 293 | end 294 | end 295 | end 296 | 297 | gfx.setImageDrawMode(gfx.kDrawModeNXOR) 298 | 299 | if not playPauseGraphicSwap then 300 | if currentAudio:isPlaying() == true then 301 | playingGraphic:draw(0, 220) 302 | else 303 | pausedGraphic:draw(0, 220) 304 | end 305 | else 306 | if currentAudio:isPlaying() == true then 307 | pausedGraphic:draw(0, 220) 308 | else 309 | playingGraphic:draw(0, 220) 310 | end 311 | end 312 | 313 | gfx.setImageDrawMode(dMColor1) 314 | elseif screenMode == 1 then 315 | playingMenuItem:setTitle("files") 316 | settingsModeMenuItem:setTitle("settings") 317 | gfx.setImageDrawMode(dMColor1) 318 | 319 | local secondsX, secondsY, modeStringX, modeStringY, playingGraphicX, playingGraphicY = 200, 211, 388, 212, 10, 210 320 | 321 | if uiDesign == "classic" then 322 | secondsX = 200 323 | secondsY = 220 324 | modeStringX = 398 325 | modeStringY = 220 326 | playingGraphicX = 0 327 | playingGraphicY = 220 328 | end 329 | 330 | audioLen = getLengthVar 331 | if audioLen ~= nil and audioLen ~= 0 then 332 | gfx.drawTextInRect(fixFormatting(currentFileName), 20, 110, 360, 20, nil, "...", kTextAlignment.center) 333 | gfx.drawTextAligned((formatSeconds(currentAudio:getOffset()) .. " / " .. formatSeconds(audioLen)), secondsX, secondsY, kTextAlignment.center) 334 | else 335 | gfx.drawTextAligned("nothing playing", 200, 211, kTextAlignment.center) 336 | end 337 | 338 | if uiDesign == "new" then 339 | gfx.drawRoundRect(5, 205, 390, 30, screenRoundness) 340 | end 341 | 342 | gfx.drawLine(0, 10, 400, 10) 343 | gfx.drawTextAligned(modeString, modeStringX, modeStringY, kTextAlignment.right) 344 | 345 | if showInfoEverywhere == false then 346 | drawInfo() 347 | end 348 | 349 | if pd.buttonJustPressed("up") or pd.buttonJustPressed("left") then 350 | if currentAudio:getOffset() <= 1 then 351 | if lastSongDirs[#lastSongDirs] ~= "" and #lastSongDirs ~= 0 then 352 | currentAudio:pause() 353 | currentAudio:load(lastSongDirs[#lastSongDirs] .. lastSongNames[#lastSongNames]) 354 | currentAudio:setOffset(0) 355 | currentFilePath = lastSongDirs[#lastSongDirs] .. lastSongNames[#lastSongNames] 356 | currentFileDir = lastSongDirs[#lastSongDirs] 357 | currentFileName = lastSongNames[#lastSongNames] 358 | 359 | audioLen = getLengthVar 360 | currentAudio:play() 361 | 362 | table.remove(lastSongDirs, #lastSongDirs) 363 | table.remove(lastSongNames, #lastSongNames) 364 | end 365 | else 366 | currentAudio:setOffset(0) 367 | end 368 | elseif pd.buttonJustPressed("down") or pd.buttonJustPressed("right") then 369 | canGoNext = true 370 | actualSongEnd() -- pausing and starting handeled here | changed to actualSongEnd to make sure song ends 371 | end 372 | 373 | if pd.buttonJustPressed("a") then 374 | if audioLen ~= nil then 375 | if currentAudio:isPlaying() == true then 376 | lastOffset = currentAudio:getOffset() 377 | currentAudio:pause() 378 | pd.setAutoLockDisabled(false) 379 | else 380 | currentAudio:setOffset(lastOffset) 381 | currentAudio:play() 382 | pd.setAutoLockDisabled(true) 383 | end 384 | end 385 | elseif pd.buttonJustPressed("b") then 386 | swapScreenMode() 387 | end 388 | 389 | gfx.setImageDrawMode(gfx.kDrawModeNXOR) 390 | 391 | if not playPauseGraphicSwap then 392 | if currentAudio:isPlaying() == true then 393 | playingGraphic:draw(playingGraphicX, playingGraphicY) 394 | else 395 | pausedGraphic:draw(playingGraphicX, playingGraphicY) 396 | end 397 | else 398 | if currentAudio:isPlaying() == true then 399 | pausedGraphic:draw(playingGraphicX, playingGraphicY) 400 | else 401 | playingGraphic:draw(playingGraphicX, playingGraphicY) 402 | end 403 | end 404 | gfx.setImageDrawMode(dMColor1) 405 | elseif screenMode == 2 then 406 | playingMenuItem:setTitle("files") 407 | settingsModeMenuItem:setTitle("back") 408 | gfx.drawRoundRect(20, 13, 360, 209, screenRoundness) 409 | 410 | curRow = fileList:getSelectedRow() 411 | settingsList:setNumberOfRows(#settings) 412 | 413 | if settingsList.needsDisplay == true then 414 | settingsList:drawInRect(0, 0, 400, 230) 415 | end 416 | 417 | gfx.drawRoundRect(20, 13, 360, 209, screenRoundness) 418 | 419 | if pd.buttonJustPressed("up") then 420 | settingsList:selectPreviousRow() 421 | settingsList:scrollToRow(settingsList:getSelectedRow()) 422 | elseif pd.buttonJustPressed("down") then 423 | settingsList:selectNextRow() 424 | settingsList:scrollToRow(settingsList:getSelectedRow()) 425 | end 426 | 427 | if pd.buttonJustPressed("a") then 428 | local row = settingsList:getSelectedRow() 429 | if row == 1 then 430 | darkMode = not darkMode 431 | swapColorMode(darkMode) 432 | elseif row == 2 then 433 | clockMode = not clockMode 434 | elseif row == 3 then 435 | showInfoEverywhere = not showInfoEverywhere 436 | elseif row == 4 then 437 | showVersion = not showVersion 438 | elseif row == 5 then 439 | if screenRoundness >= 1 and screenRoundness < 8 then 440 | if screenRoundness == 1 or screenRoundness == 6 then 441 | screenRoundness += 2 442 | else 443 | screenRoundness += 1 444 | end 445 | elseif screenRoundness >= 8 then 446 | screenRoundness = 1 447 | end 448 | elseif row == 6 then 449 | if uiDesign == "new" then 450 | uiDesign = "classic" 451 | else 452 | uiDesign = "new" 453 | end 454 | elseif row == 7 then 455 | playPauseGraphicSwap = not playPauseGraphicSwap 456 | elseif row == 8 then 457 | lockScreen = not lockScreen 458 | if lockScreen == true then 459 | lockTimer = timer.new((lockScreenTime * 60) * 1000, lockScreenFunc) 460 | end 461 | elseif row == 9 then 462 | if lockScreenTime >= 1 and lockScreenTime ~= 5 then 463 | lockScreenTime += 1 464 | elseif lockScreenTime == 5 then 465 | lockScreenTime = 1 466 | end 467 | lockTimer = timer.new((lockScreenTime * 60) * 1000, lockScreenFunc) 468 | end 469 | 470 | settings = newSettingsList() 471 | elseif pd.buttonJustPressed("b") then 472 | screenMode = lastScreenMode 473 | end 474 | 475 | if pd.buttonJustPressed("left") or pd.buttonJustPressed("right") or pd.buttonJustPressed("up") or pd.buttonJustPressed("down") or pd.buttonJustPressed("a") or pd.buttonJustPressed("b") then 476 | saveSongSpot = currentAudio:getOffset() -- if you press a button save the curnet song play point 477 | pd.timer.new(40, function() 478 | if safeToReset == true then 479 | saveSongSpot2 = saveSongSpot 480 | end 481 | end) 482 | end 483 | end 484 | end 485 | 486 | updateCrank() 487 | end 488 | 489 | function pd.gameWillTerminate() 490 | saveSettings() 491 | end 492 | 493 | currentAudio:setFinishCallback(handleSongEnd) 494 | currentAudio:setStopOnUnderrun(false) 495 | --------------------------------------------------------------------------------