ZOMG! I just realized it's October already! The PICO-8 advent calendar is just around the corner. Or, maybe not... We're still looking for tons more people to contribute a winter holiday themed PICO-8 project to the calendar. It doesn't have to be an epic masterpiece. Small games and demos are welcome. You can even reskin a game you previously released.
If you're interested, make sure you've joined the PICO-8 Discord server and then see https://discord.com/channels/215267007245975552/220602714982318081/1146905525158682665 for info on how to contribute, cuz we sure need you.
I've released the first eight lessons of my new music theory tutorial, available here: https://bikibird.itch.io/music-theory. It's designed for people who want to make their own music for PICO-8 games, but feel they don't have the skill or talent to do so.
The tutorial provides an introduction to the basics of pitch, melody, harmony, rhythm and composition within the context of PICO-8. It explores music in a decidedly mathy way through a series of interactive demos that use spatial relations to connect sound to vision.
Only the parts of music theory that are relevant to PICO-8 are covered. No prior knowledge of music is assumed. There's no reading sheet music or needing to play an instrument.
More lessons will be coming in October.

You were so excited to try your brand new skates on Christmas day that you skated out to the middle of lake without checking the ice conditions. Now you are on thin ice and just want to get back to your friend on the shore.
Game Play
Control your skates with the left and right buttons. Alternate left and right to speed up. Hold to turn. You haven't exactly learned to stop yet, but if you start turning circles, you will eventually slow down. You can walk on the snow, but it is very slow.
Credits:
Music composed by Vince Guaraldi
Christmas Time Is Here arranged by @Gruber
3d effects: @Mot's Instant 3d Plus Library
Special thanks to @thattomhall for advice on the ending
Everything else by @bikibird
Created for the 2022 Advent Calendar
This is the year you find out if Santa is really real. You and your siblings are staying up late to finally get proof. The light are out, but you're not scared!
Game Play
Requires mouse. Hold down left mouse button to turn on flashlight. Drag mouse across screen to catch Santa in the act. Press ❎ to snap a picture.
Credits
Up on the Housetop composed by Benjamin Hanby
Midilib flute by @ericb https://www.lexaloffle.com/bbs/?tid=49487
Flashlight effect informed by @Krystman https://www.lexaloffle.com/bbs/?tid=46286
Fizzle fader borrowed from @DrPete https://www.lexaloffle.com/bbs/?tid=29862
Everything else by @bikibird
Created for the 2022 Advent Calendar
WARNING: SPOILERS IN COMMENTS. SCROLL AT YOUR OWN RISK.
*
WARNING: SPOILERS IN COMMENTS. SERIOUSLY, GO BACK.
*
WARNING: SPOILERS IN COMMENTS. WELL, YOU CAN'T SAY I DIDN'T WARN YOU.
WARNING: SPOILERS IN COMMENTS. HERE THEY COME.
*
Add speech synthesis to your games with Speako8, a speech synthesis library for PICO-8 in under a thousand tokens! It's loosely based on a Klatt synthesizer and will remind some folks of Software Automatic Mouth (S.A.M.)
To add Speako8 to your games, copy and paste the library below:
Speako8's voice must be configured prior to first use:
function _init() spk8_pitch,spk8_rate,spk8_volume,spk8_quality,spk8_intonation,spk8_if0,spk8_shift,spk8_bandwidth,spk8_whisper= 140,1,1,.5,10,10,1,1,1 end |
Warning: This application may generate loud and harsh sounds. Protect your hearing! Do not test with headphones on and turn down the volume.
Variable | Range | Explanation |
---|---|---|
spk8_pitch | 60-230 | Fundamental pitch of voice (F0) in hertz |
spk8_rate | .1-2 | Standard rate of speech divisor— below 1 is slower; above 1 is faster. |
spk8_volume | .1-2 | Standard volume factor— below 1 is quieter; above 1 is louder. |
spk8_quality | .1-2 | Glottis open period— below .5 is creakier voice; .5 is modal voice; 1 is breathy voice; above 1 is weaker voice. |
spk8_intonation | 0-20 | Degree of pitch prosody in hertz— set to zero for robotic monotone. |
spk8_if0 | 0-20 | Degree to which the inherent pitch (F0) of vowels varies (in hertz)— set to zero for robotic monotone. |
spk8_shift | .8-1.2 | Factor by which to shift formant frequencies (F1, F2, F3)— above raises formant frequencies; below one lowers |
spk8_bandwidth | .5-5 | Factor by which to alter bandwidth of formant frequencies (F1, F2, F3)— below 1 narrows the bandwidth of the formants; above 1 widens. When formants are shifted upward, it is recommended to widen bandwidths as well. |
spk8_whisper | 1 or 2 | Speaking mode— Normal voice is 1; whispering is 2. |
To enable speech synthesis, you must call speako8
in the _update
function. Then call say
with a speech string:
function _update() speako8() -- must appear once unconditionally in _update. if btnp(5) then --❎ button -- use speaking if you want to check if anything is currently being said. -- if not speaking() then say("_/hh/-1.57/eh/-1.07/l/-1.33/3/ow/-1.03/-3/w/1.27/-3/er/1.65/-3/l/-1.64/-3/_/d") -- end elseif btnp(4) then -- 🅾️ button mute() --flushes the sound queue and immediately stops whatever is currently being said. end end |
Speech strings represent text phonetically and include prosody markup. The easiest way to create and test them is with the Declare web app. You can also try out different voice options with it.
If you are interested in learning more about speech synthesis, try googling these key words: Klatt synthesizer, formant, acoustic phonetics. It's a fascinating topic. An unminimized version of the library is included in the demo cart.
Special thanks to the gang on the Discord server for lending me their ears and keeping me motivated, especially @packbat, who did it for science. Thanks also to @IMLXH for finding the Klatt prosody rules.
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 usingsfx()
. Cue may also be called with additional parameters:defy_cue(clip_number,start,endpoint,looping).
start
andendpoint
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.
Lektrik Sportz Game
A Captain Neat-o Adventure
Let's get one thing straight. This is not Electric Football™. Doctor Lamento destroyed the football years ago because, you know, he's evil. But, I get ahead of myself. This is the story of Captain Neat-o who is far from home and falls into the clutches of Doctor Lamento.
The evil Doctor forces our hero to face off against his most formidable opponent, Neat-o himself, in a spectacle the Doctor calls Lektrik Sportz Game (because he can't spell and, you know, he's evil.)
Game Play
Run from one end of the field to the other while avoiding your opponents. You should make it home in under 5 minutes. Don't fall off.
Your teammates can help block the opponents and you can set their overall direction and confrontation style in the strategy session.
If you are using a keyboard to play, use the X key to advance to the next screen/teammate and the Z key to switch modes in the strategy session. Arrow keys select options, set direction, and move teammates.
The visiting team's strategy is randomly generated, but set for a single day. Learn their strategy over multiple plays and eventually defeat them in a single down! Then, come back tomorrow and play a new challenge.
Poetry Corner (Spoilers)
Credits
All assets used in the making of this game are courtesy of Toy Box Jam 3. I borrowed @Mot's Instant 3D Plus! library and hacked it a bit to get an additional camera angle. All other code, the poetry, and the idea for this adventure are my own.
This is the story of a hard working Christmas elf. I was never told their name. I call them Blinky. They had one last task to complete late one Christmas Eve: String some lights, plug them in, and connect them to the finial atop the tree. Easy for a smart elf like Blinky. Too bad they never finished that electrician's course...
Game Play
Connect the loose bulbs by touching them with the exposed wire from the light string. Loose bulbs are always safe, but if the string is connected to power and you touch the string to itself, some of the bulbs will explode. Electricity is powerful, but dangerous. Be careful with it.
There are three possible endings and eleven achievements to earn.
Controls
Press the Z key to disconnect the light string to/from the power outlet.
Press the X key to disconnect the light string to/from the finial.
Arrow keys set the direction of travel. If you wander off screen you will hear warning notes. Come back before you wander too far.
I made a web-based tool for extracting and converting MIDI file data to be played in PICO-8. It's nicely interactive and includes a PICO-8 console built into the page so that you can audition the results before you copy the SFX data. Give Denote a try and let me know what you think.
So, I've been working on a web based MIDI to PICO-8 converter for a while now. It was going pretty well, but today I introduced a bug, which totally corrupted the SFX data. The input was the traditional melody, Ghost Of John. The output was this:
Surprisingly, I like the way it sounds. I guess I composed some original music here, but it was completely accidental. I guess if you make enough mistakes, some of them are bound to turn out ok.
Now I just need to figure out what kind of game goes with this soundtrack. Any suggestions? Feel free to use this in your own projects if it fits.
In my last game, I included an iris effect to transition between two scenes. I used the midpoint circle algorithm to calculate the edges of the iris. This algo avoids trig and square root functions and so is super fast. Even at 60 frames a second, there's no lag. I decided to make a more generalized version, implemented as a coroutine. I hope you find it useful for your projects.
To use:
-
Copy the iris function from the cartridge above into your own cartridge.
-
When it's time for a transition, assign the coroutine to a variable in your update function:
effect = cocreate(iris)
- In the draw function, call the coroutine:
coresume(effect, 1, 128, 1, 15)
The parameters for the coroutine are starting radius, ending radius, step, and color. A positive step indicates by how much to open the iris on each frame. A negative step indicates a shrinking iris. See the code for examples.
Straight from the laboratory of Professor von Stroopwafel is a modest proposal to solve global climate change. Enjoy a lighthearted romp through a speculative future. It even has bells and whistles. Happy Earth Day, everyone.
Game Play
Catch falling scoops of ice cream and toppings to fulfill as many orders as possible before the timer runs out.
Controls
Press the X key to advance to the next screen.
Left and right keys move the cone.
Use the Z key at any time to see the ice cream order. Press Z again to dismiss it. The timer is always running so try not to look at the order too long or too often.
I'm working on an SFX that is supposed to sound like a bicycle bell, well actually an ice cream bell. Here's what I have so far:
Do you find it convincing? Do you have advice on making it better? If you have a better SFX of a bicycle or ice cream bell ding, please share.
I made an image demake tool for PICO-8. Load any image and create a dithered image that uses PICO-8 standard colors and/or hidden colors and is reduced to a maximum width and height of 128 x128. Depict supports Ordered, Atkinson, Floyd-Steinberg, and Sierra2 dithering as well as some artistic dithers: Rivers, Streets, Rain, Wind. Drag and drop the final result into the PICO-8 sprite sheet. Free on itch: Depict
All feedback welcomed.
Mix and match standard and hidden colors, but do do not exceed 16.
.png)
Rivers dither adds horizontal ripples.
.png)
Streets dither creates maze-like artifacts.
.png)
Rain dither makes vertical steaks.
.png)
Wind dither produces slashes.
.png)
Atkinson Dither
.png)
Ordered Dither
.png)
No Dither
.png)
My first Pico-8 game. Entered in Toy Box Jam 2020.
Tis the season to sing carols, drink nog, and decorate the night with Christmas sneks.
Press an arrow key to change the direction of the snek and connect bulbs to the live wire at the front of the snek to light them up. Build a long snek or a short snek. Ride the high speed rail to get around faster.
This is a mellow game with no set goals, but beware! If you touch the live wire to the snek you will create a short circuit!
When you are happy with your snek, press X to go to the forest and plant a tree decorated with your snek. Then go build another snek so you can plant another tree.
Merry Christmas and may 2021 bring you only good things.