Log In  


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

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.

27


1

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


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.


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.


Also, I need to play it too.


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.


1

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


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.


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


1

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


1

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


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


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


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


how do i make a clip loop


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


How put QPA 1.1 bits in cartridge


@NazarFloppaLovesP8, see this cart from @luchak: https://www.lexaloffle.com/bbs/?tid=53933 for info on QPA.


Cart #defy_player-0 | 2024-09-29 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA


Is small cartridge that dropped a @bikibird defy file to pcm player



[Please log in to post a comment]