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
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.
[Please log in to post a comment]



