Log In  

Cart #defy-9 | 2023-09-07 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
22

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 cue function must be called before your audio string may be played. Cue your clip in the _update function. Use whatever logic you would use when using sfx(). Cue may also be called with additional parameters: defy_cue(clip_number,start,endpoint,looping). start and endpoint are indexes into the string. looping is 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.

P#109080 2022-03-23 21:56 ( Edited 2023-09-07 14:57)

1

Thanks again for making this happen! It's a really cool project.

P#109110 2022-03-24 18:00
1

@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.

P#109214 2022-03-26 20:10

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.

P#111478 2022-05-07 20:26

Also, I need to play it too.

P#111482 2022-05-07 21:37
1

Not sure where you are getting hung up. Quick summary is:

  1. Go to https://bikibird.itch.io/defy and create a data file using the .defy format.
  2. Open PICO- 8 and type load #defy
  3. Type run.
  4. Press the z key key to start recording.
  5. Drag and drop the .defy file on top of PICO-8 to copy your sample to the clipboard
  6. In your cartridge, press ctrl-p to enable puny font and add defy_load"pasted your string here" in the _init() function.
  7. 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.

P#111487 2022-05-07 22:30 ( Edited 2022-05-08 19:28)
1

Oh wow, this is amazing! Super cool project thanks for making/posting it.

P#111519 2022-05-08 18:58
2

...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.

P#115250 2022-08-04 21:20 ( Edited 2022-08-04 21:20)

So glad you like it. Stay tuned for my speech synth library...

P#115252 2022-08-04 21:31
1

Thank you so much for this, it's amazing!

P#115261 2022-08-05 03:39
1
P#115352 2022-08-07 03:10

i opened a defy file in notepad and it was (probably binary data that is translated into) chinese and korean characters and other stuff

P#128325 2023-04-09 04:36 ( Edited 2023-05-06 07:11)
2

@Nbrother1607

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().

P#128330 2023-04-09 05:50 ( Edited 2023-04-09 12:16)
2

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.

Cart #defy_to_string-1 | 2023-10-30 | Code ▽ | Embed ▽ | No License
2

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.)

P#136606 2023-10-30 06:15 ( Edited 2023-10-30 17:24)
1

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

P#145589 2024-04-03 18:19 ( Edited 2024-04-03 18:21)

[Please log in to post a comment]

Follow Lexaloffle:          
Generated 2024-04-20 07:46:28 | 0.150s | Q:50