Defy Audio Player
The Defy Audio Player plays Defy formatted audio files via PICO-8's PCM channel. It's a PCM boombox! You can also use it to create binary strings of audio data for playback in your own cartridges.
Convert almost any audio file format to .defy here: https://bikibird.itch.io/defy.
This cart is intended to run in PICO-8, not play on the BBS, which is limited to a maximum files size of 250K. Instead, open PICO-8 and enter load #defy. Then enter run.
Controls
- Drag and drop your .defy file onto PICO-8 to play it. You do not have to wait for a file to finish before loading another.
- Press 🅾️ (z key) to pause playback. Press it again to resume.
- Press ⬅️ or ➡️ to switch visualizers.
- Press ⬆️ to eject and stop playback.
- Press ⬇️ to display title and format.
- Press ❎ to record binary string. If you press record prior to playing the file, Defy will start recording it as soon as the file is dropped. The binary string is copied to your computer's clipboard.
Audio String Player
The library below plays Defy formatted audio strings. For support for @luchak's QPA formatted audio strings see https://www.lexaloffle.com/bbs/?tid=53933 and https://github.com/luchak/qpa-format/.
Warning: Be careful testing new audio strings. Playback with a mismatched playback mode sounds terrible. Do not test with headphones. Protect your hearing.
When record is pressed, the first 32,000 bytes of audio data are copied to the clipboard as a binary string during playback. Use the code below in your own carts to play the binary string. Be aware that only a few seconds of audio will fit in a cart. If the captured string contains too much data, you may truncate it. One second of audio equates to approximately 5500, 2250, 1840, 1125, and 690 characters for 8, 4, 2.6, 2, and 1 bit formats respectively.
- Paste the library below into a new cart.
- To save tokens, delete defy_play functions for any unused bit formats
do --defy audio string library by bikibird
local buffer=0x8000 -- required all formats
local clips={} -- required all formats
local cued -- required all formats
-- locals required for 4, 2.6, 2, and 1 bit formats below
local step, new_sample, ad_index,c,direction
local index_table = {[0]=-1,-1,-1,-1,2,4,6,8,-1,-1,-1,-1,2,4,6,8}
local step_table ={7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, 50, 55, 60, 66, 73, 80, 88, 97, 107, 118,130, 143, 157, 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767}
local adpcm=function(sample,bits) --adapted from http://www.cs.columbia.edu/~hgs/audio/dvi/IMA_ADPCM.pdf
if bits >1 then
local delta=0
local temp_step =step
local sign = sample &(1<<(bits-1)) --hi bit of sample convert to 4 bit
local magnitude =(sample & (sign-1))<<(4-bits) -- convert sample to 4 bit
sign <<=(4-bits) -- convert sign to 4 bit 8==negative 0== positive
local mask = 4
for i=1,3 do
if (magnitude & mask >0) then
delta+=temp_step
end
mask >>>= 1
temp_step >>>= 1
end
if sign> 0 then -- negative magnitude
if new_sample < -32768+delta then
new_sample = -32768
else
new_sample-=delta
end
else -- positive magnitude
if new_sample >32767-delta then
new_sample =32767
else
new_sample +=delta
end
end
ad_index += index_table[sign+magnitude]
else --1-bit
if sample==1 then -- negative
if new_sample < -32768+step then
new_sample = -32768
else
new_sample-=step
end
else -- positive
if new_sample >32767-step then
new_sample =32767
else
new_sample +=step
end
end
if sample==direction then --if direction same, try larger step. if changed, try smaller step
ad_index+=1
else
ad_index-=1
direction =sample
end
end
if ad_index < 1 then
ad_index = 1
elseif (ad_index > #step_table) then
ad_index = #step_table
end
step = step_table[ad_index]
return new_sample\256+128
end
defy_load=function(clip) -- required all formats
add(clips,{clip=clip,start=1,endpoint=#clip, index=1, loop=false, done=false})
end
local cued=false -- required all formats
defy_cue=function(clip_number,start,endpoint,looping) --required all formats
clips[clip_number].start=start or clips[clip_number].start
clips[clip_number].index=clips[clip_number].start
clips[clip_number].endpoint=endpoint or #clips[clip_number].clip
clips[clip_number].loop=looping or false
clips[clip_number].done=false
step, new_sample, ad_index,delta,direction=7,0,0,0,0
cued=clip_number
end
defy_play=
{
[8]=function() -- 8 bit format
if cued and not clips[cued].done then
while stat(108)<1536 do
for i=0,511 do
poke (buffer+i,ord(clips[cued].clip,clips[cued].index))
clips[cued].index+=1
if (clips[cued].index>clips[cued].endpoint) then
if (clips[cued].loop) then
clips[cued].index=clips[cued].start
else
serial(0x808,buffer,i+1)
clips[cued].done=true
return
end
end
end
serial(0x808,buffer,512)
end
end
end,
[4]=function() -- 4 bit format
if cued and not clips[cued].done then
while stat(108)<1536 do
for i=0,255 do
c=ord(clips[cued].clip,clips[cued].index)
poke (buffer+i*2,adpcm((c&0xf0)>>>4,4),adpcm(c&0x0f,4))
clips[cued].index+=1
if (clips[cued].index>clips[cued].endpoint) then
if (clips[cued].loop) then
clips[cued].index=clips[cued].start
else
serial(0x808,buffer,i+1)
clips[cued].done=true
return
end
end
end
serial(0x808,buffer,512)
end
end
end,
[3]=function() -- 3 bit format
if cued and not clips[cued].done then
while stat(108)<1536 do
for i=0,170 do
c=ord(clips[cued].clip,clips[cued].index)
poke(buffer+i*3, adpcm((c>>>5)&0x07,3), adpcm((c>>>2)&0x07,3), adpcm(c&0x03,2,true))
clips[cued].index+=1
if (clips[cued].index>clips[cued].endpoint) then
if (clips[cued].loop) then
clips[cued].index=clips[cued].start
else
serial(0x808,buffer,i+1)
clips[cued].done=true
return
end
end
end
serial(0x808,buffer,510)
end
end
end,
[2]=function() -- 2 bit format
if cued and not clips[cued].done then
while stat(108)<1536 do
for i=0,128 do
c=ord(clips[cued].clip,clips[cued].index)
poke(buffer+i*4, adpcm((c>>>6)&0x03,2), adpcm((c>>>4)&0x03,2), adpcm((c>>>2)&0x03,2), adpcm(c&0x03,2))
clips[cued].index+=1
if (clips[cued].index>clips[cued].endpoint) then
if (clips[cued].loop) then
clips[cued].index=clips[cued].start
else
serial(0x808,buffer,i+1)
clips[cued].done=true
return
end
end
end
serial(0x808,buffer,512)
end
end
end,
[1]=function() -- 1 bit format
if cued and not clips[cued].done then
while stat(108)<1536 do
for i=0,64 do
c=ord(clips[cued].clip,clips[cued].index)
poke(buffer+i*8, adpcm((c>>>7)&1,1), adpcm((c>>>6)&1,1), adpcm((c>>>5)&1,1), adpcm((c>>>4)&1,1), adpcm((c>>>3)&1,1), adpcm((c>>>2)&1,1), adpcm((c>>>1)&1,1),adpcm(c&1,1))
clips[cued].index+=1
if (clips[cued].index>clips[cued].endpoint) then
if (clips[cued].loop) then
clips[cued].index=clips[cued].start
else
serial(0x808,buffer,i+1)
clips[cued].done=true
return
end
end
end
serial(0x808,buffer,512)
end
end
end
}
function eject() -- required all formats
clips[cued].done=true
end
end |
- Record some audio in the Defy Player. Once the binary string is created, playback will pause while the binary string is copied to the clipboard and then resume
- In
_init()add one or more load statements to add your audio clips to the clip table. Turn on puny font by pressing ctrl-p before pasting your string. Failing to do so will corrupt the string and distort the audio.
function _init() defy_load"my audio string" --Turn on puny font (ctrl-p) before pasting!!! end |
- The
cuefunction must be called before your audio string may be played. Cue your clip in the_updatefunction. Use whatever logic you would use when usingsfx(). Cue may also be called with additional parameters:defy_cue(clip_number,start,endpoint,looping).startandendpointare indexes into the string.loopingis a boolean. - Add a playback mode in
_update,defy_play[format](). Where format is 8, 4, 3, 2, or 1, corresponding to 8-bit, 4-bit, 2.6-bit, 2-bit, or 1-bit.
function _update() if (btnp(4)) then defy_cue(1) -- cues clip 1 for play from beginning to end, no looping. end defy_play[4]() -- play back of 4-bit audio string. called unconditionally in _update. end |
- Use
defy_eject(clip_number)to end a clip early or stop a looping clip.
Acknowledgements
Thank you, @luchak and @packbat, for all your sound advice. Thank you, @luchak for the antialiasing filter. Thank you, @Gabe_8_bit for feedback and your fancy oscilloscope code. It formed the basis of the simpler oscilloscope that was ultimately included. Thank you, @LazarevGaming, et al, for testing and feedback.
Thanks again for making this happen! It's a really cool project.
@packbat, you're welcome. Looking forward to seeing what people do with audio strings in their own carts. Still can't believe I got down to 1-bit compression as an option and the quality is actually usable.
I need help. How do you import the PCM? I've been trying this whole time to add the PCM I made but with no results, Please help.

Not sure where you are getting hung up. Quick summary is:
- Go to https://bikibird.itch.io/defy and create a data file using the .defy format.
- Open PICO- 8 and type
load #defy - Type run.
- Press the z key key to start recording.
- Drag and drop the .defy file on top of PICO-8 to copy your sample to the clipboard
- In your cartridge, press ctrl-p to enable puny font and add
defy_load"pasted your string here"in the _init() function. - Add cue and play in the _update function as described above.
The number one problem most folks have is forgetting to turn on puny font before pasting the string.
Here's a game that uses the defy library. https://www.lexaloffle.com/bbs/?tid=47097 You may find their code helpful.
Let me know how it goes. Happy to look at your cartridge if needed.
Oh wow, this is amazing! Super cool project thanks for making/posting it.
...I took a break for a few months from Pico-8 after I jerry-rigged a .wav to Pico-8 DAC converter in a Google Colab. I came back to it today and you implemented this SO much better than I did?? Thanks SO much for making this tool.
i opened a defy file in notepad and it was (probably binary data that is translated into) chinese and korean characters and other stuff

Are you curious why?
Defy consumes string representations of raw bytestreams where each byte, represented by a single character in your picture, holds 1, 2, 2.5, 4, or 8 audio samples (not to be confused with a long series of these samples, which is also referred to as a "sample" by musicians), depending on if you use 8-bit, 4-bit, 3-bit, 2-bit, or 1-bit encoding. Notepad is probably interpreting the bytestream as UTF-8, while Pico-8 will show it as its own variant of ASCII, named P8SCII. Also see chr() and ord().
Here's a small cart to transform Defy files into ready-to-play binary strings. To use it, drop a .defy file onto the cart. It will put the audio data from the .defy file into the clipboard into a form that you can paste directly into a string in your PICO-8 code.
You can also press X to preview the file.
Limitations:
- Only 8-bit Defy and the QPA formats are supported.
- Samples more than about 5.9 seconds long may be truncated.
(Note: you can also use the main Defy cart for 8-bit Defy files, and I'll add proper QPA-to-binary-string support to that cart as well in the near future.)
Suddenly remembered this exists. would this work with picotron by chance? 😲 Because I could maybe use the speako8 to record sounds then maybe import them with this, maybe
EDIT!!!!
No wait this is PCM too. shakes fist at sky
@Nbrother1607,
defy_cue(clip_number,start,endpoint,looping). start and endpoint are indexes into the string. looping is a boolean. Set it to true to loop.
@NazarFloppaLovesP8, see this cart from @luchak: https://www.lexaloffle.com/bbs/?tid=53933 for info on QPA.
[Please log in to post a comment]




