Understanding Playdate's asset files


Coding Diaires Playdate Audio

Understanding Playdate's asset files

Hello there, It's been a long time! How have you been ? I've been really busy with the Playdate lately and I have a project I'm working on (but I'll share that in a few months) but today I wanted to shine the light on a not that widely understood part of the Playdate SDK ; The way of handling asset files. Today I'll specifically focus on the PlayDate Audio files (PDA files) since it's been where I've done most research.

Chapter 1 : Inspiration

When I first got the Playdate, it reminded me of an era of technology I was born too late to know ; the 80-2000's era of technology and of course ; Walkmans, MP3 players etc.... My first project idea was a utility suite for the Playdate which I had to put in halt for technical issues of me not understanding how to build UI with the Playdate SDK. Anyways during my research for that project I found kicooya a playdate MP3 player and I was really impressed at the fact that it could convert MP3 files to PDA files on the fly.

Piece of Trivia :

If you didn't know, the playdate can only play video/audio files that have been encoded into the .PDA (for audio) or .PDV (for video) format. This is due to the hardware limitations primarily but also SDK limitations because Panic probably didn't thought this would be a requested feature as they intended the SDK to be used for gamedev and not app creation.

Chapter 2 : Investigation starts

So I was in a call with two friends from the playdate community ; NullPointer (Sam) and Remi. And this issue just got back to me and curiosity got the best of me. Here I am now writing this blogpost because I've started investigations to finally make my dream MP3 player.

I found this blogpost about someone who had the same problem and matt a moderator had answered with a piece of code to convert WAV audio files to PDA on runtime using the Playdate SDK :

function wav2pda(filename) 
    local fil = playdate.file 
    local wav = fil.open(filename, fil.kFileRead) 
    local datalen = fil.getSize(filename) - 44 local pda = fil.open(string.sub(filename,0,#filename-4)..".pda", fil.kFileWrite) wav:seek(44) pda:write("Playdate AUD\68\172\0\2") -- 44kHz 16 bit mono 
local offset = 0 while offset < datalen do 
    local n = math.min(datalen-offset, 1024) 
    pda:write(wav:read(n)) offset += n end wav:close() pda:close() 
end 
local snd = playdate.sound 
function playdate.AButtonDown() 
    local s = snd.sample.new(2, snd.kFormat16bitMono) -- 2 seconds 
    snd.micinput.recordToSample(s, function() s:save("test.wav") wav2pda("test.wav") end ) end 
function playdate.BButtonDown() 
    snd.sample.new("test"):play() end 
    
function playdate.update() end

With that we were off to a great start but I needed to understand what this code actually did. So I decided to add comments describing step by step what this algorithm was doing and I gained some insight about how the PDA audio files work.

function wav2pda(filename)

   local fil = playdate.file

   local wav = fil.open(filename, fil.kFileRead)

   local datalen = fil.getSize(filename) - 44 -- Only the data, not counting the WAV header

   local pda = fil.open(string.sub(filename,0,#filename-4)..".pda", fil.kFileWrite) -- the string.sub changes the extension for .wav to .pda
   wav:seek(44) -- Skip the 44 bytes (WAV header)
   pda:write("Playdate AUD\68\172\0\2") -- 44kHz 16 bit mono
   local offset = 0

   -- Reading the WAV file chunk by chunk and saving the raw values (padded from 0 to 1024) to the pda file

   while offset < datalen do
       local n = math.min(datalen-offset, 1024)
       pda:write(wav:read(n))
       offset += n
   end

   -- Close everything
   wav:close()
   pda:close()

end

local snd = playdate.sound

function playdate.AButtonDown()

   local s = snd.sample.new(2, snd.kFormat16bitMono) -- 2 seconds
   
   snd.micinput.recordToSample(s,
       function()
           s:save("test.wav")
           wav2pda("test.wav")
       end
   )

end

function playdate.BButtonDown()

   snd.sample.new("test"):play()

end

function playdate.update() end

Now we can see that pda is basically a stripped-down version of WAV without the header part (the first 44 bytes). Great! But what about that header that we add ; pda:write("Playdate AUD\68\172\0\2") -- 44kHz 16 bit mono ? Well the comment added by matt has graciously given us the key to understand how it works. We can see it starts by "Playdate AUD" which stands for Playdate Audio. But what about those last 4 bytes in the string ?

Chapter 3 : Those last 4 bytes in the string...

Well to know I saw only one option : take a look at .pda files generated from different audio files to make a list of what each byte sequence corresponded to. First we can take a look at how many Audio formats Playdate's audio files support. According to the Playdate SDK :

playdate.sound.sample:getFormat()

Returns the format of the sample, one of

Excellent.

Now my first idea is to jump into Audacity and record a sample sound and export it in multiple fashions to understand what the last 4 bytes mean. Here are my findings :

Conclusion : Actually doing something with this

Ok now what can we do with these things ? Well At the time of writing this I do not know but I'm sure you can find something! I believe in the vibrant Playdate dev community... they'll do their magic and create some awesome stuff. If you want to share your projects with this you can do so by sending me an email (hello@oxey405.com) or just joining my discord!

Have a great day and thank you for reading !