Log In  
[back to top]

[ :: Read More :: ]


In versions of PICO-8 prior to 0.2.2, and according to the manual:

SFX instruments are only retriggered when the pitch changes, or the previous note
has zero volume. This is useful for instruments that change more slowly over time.
For example: a bell that gradually fades out. To invert this behaviour, effect 3
(normally 'drop') can be used when triggering the note. All other effect values have
their usual meaning when triggering SFX instruments.

However, in version 0.2.2, these SFX also retrigger when they have exceeded their total length.

The SFX up top demonstrates this: in 0.2.1b,

  • SFX 03 plays a single 16-tick note three times, 128 ticks apart, and
  • SFX 04 plays eight 16-tick notes at equal 32-tick intervals;

but in 0.2.2c,

  • SFX 03 plays a 64-tick note, two 16-tick notes, and then two 16-tick notes, and
  • SFX 04 plays eight 16-tick notes at a syncopated rhythm, alternating 48 and 16 ticks.

This will break some pre-0.2.2 compositions.

p.s. Slight edit correction: the manual is also incorrect in the opposite direction, because custom SFX instruments do not retrigger if the previous note has zero volume but uses the same pitch and instrument.

P#93989 2021-06-24 19:58 ( Edited 2021-06-24 20:12)

[ :: Read More :: ]

This is the most minor thing ever, but the manual currently says under stat x

20..23  Note number (0..31) on channel 0..3

...but if the LEN of the SFX or LOOP endpoint of the SFX is greater than 32 and the SFX plays that long, then stat(20..23) will display numbers greater than 32 just fine.

I don't know if this is worth fixing, but something like "(normally 0..31)" would be technically accurate and still probably convey the desired information.

P#92483 2021-05-24 02:07

[ :: Read More :: ]


For reasons, I loaded up the IMSLP page for Bach's first cello suite yesterday and downloaded one of the scores - Shin-Itchiro Yokoyama's, I think it was - to transcribe into PICO-8.

I don't know if anyone on here has a dire need for more PICO-8 J.S. Bach beyond Gruber's arrangements of Bach's 2-Part Inventions from a couple years ago, but it was pretty rewarding to do and I like the results.

P#87805 2021-02-17 17:01

[ :: Read More :: ]

So, I wanted to do a quick experiment to see what happened if you played a note on a custom SFX instrument with reverb - would the reverb continue past the line where the note is played? would it cut off? Either would definitely be cool.

So I made a test cart:

Cart #dezapemiyi-0 | 2021-02-15 | Code ▽ | Embed ▽ | No License

...and did an export command from the command line to save a .wav of SFX 1 to open in Audacity, and PICO-8 immediately crashed.

It looks like it's an issue with SFX that contain custom SFX instruments, from my very quick testing.

Edit: I haven't noticed this error at any point recently; I'm going to chalk it up to gremlins and resolve the report.

P#87706 2021-02-15 23:06 ( Edited 2022-02-15 16:03)

[ :: Read More :: ]

As we've been messing around with doing various things in PICO-8, we've realized that there are a fair few operations involving moving blocks of bytes around in base RAM - animating sprites by switching sprites with each other on the spritesheet, storing the current draw state or random number seed during an operation so it can be restored afterwards, etc. and so on - where hardcoding memory addresses for temporary storage could lead to one operation clobbering data for another.

We don't know a lot about computer science, but it feels like the proper way to handle this is either using a peek to grab the data as a variable in Lua RAM and poke()ing it back afterwards or with a call stack somewhere in the user data RAM that functions can push data onto and pull data from. If a call stack actually is a good tool to have for this, it seems like a good function to implement as a library routine.

When we were thinking about it a while ago, we came up with:

-- stack handling in 55 tokens
-- incl. 25 tokens of overflow/
-- underflow handling

    local pointer=0x5e00
    -- i.e. just after user data

    function push(addr,len)
        pointer -= len
        if pointer < 0x4300 then
            stop("call stack overflow from "..addr)
    function pop(addr,len)
        if pointer+len > 0x5e00 then
            stop("call stack underflow to "..tostr(addr,true))
        pointer += len

...but I wanna kick it out onto the forums for people better at optimizing PICO-8 code to review and comment on.

P#84842 2020-11-29 18:49

[ :: Read More :: ]

*edit 2: 0.2.3 has a built-in way to do this with tostr

edit: see downthread for a better function

I was thinking about high scores in PICO-8 a while ago and it occurred to me that they'd make more sense as unsigned 32 bit integers than 16b.16b fixed point decimals. The easy part in that case is adding points - simply increment in units of 0x0.0001 instead of units of 1 - but if it's a high score, I'd also like to be able to display it.


function tostr_u32(n)
    -- return n as a 32-bit uint
    -- 92 tokens, ~1/780 of a 30 FPS CPU per call
    -- " " as thousands divider

    -- calculate ones
    -- (0x.03e8 = 1000 * 0x0.0001)
    local s=tostr(shl(n%0x.03e8,16))
    if n>0 then
        -- if not-actually-a-sign-bit is set
        -- have to be a little tricksy

        -- splitting in half
        --  n&0x0.ffff lower
        --  lshr(n,16) upper
        -- upper half unit = 65 536
        -- so within thousands:
        local m=536*lshr(n,16)
        -- originally used
--      local m=536*lshr(n,16)+n&0x0.ffff
        -- but that returned wrong results
        -- and doing the division by
        -- 1000 in two steps:

    while n~=0 do
        while #s%4~=3 do
            -- pad with zeros
            .." "..s

    return s

I'm sure this could be minimized further and/or optimized further and/or made more general, but I'd be willing to use it as is so I figured I'd share.

P#82745 2020-10-09 19:53 ( Edited 2021-09-10 12:40)

[ :: Read More :: ]

Cart #packbat_taptempo-1 | 2020-08-05 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

This one is super basic and was originally just going to be a utility within a bigger project, but it turned out so useful by itself that I wanted to share it. I'm sure not everyone has this problem, but I've found that changing the tempo after the SFX is written usually doesn't feel right - the piece ends up being built to make sense at the tempo it was created at.

To use the tap tempo tool, you decide what kind of rhythm you want to use, pick beats to tap on, figure out how many SFX lines between each beat you're tapping on, set that number appropriately, and tap it out.

(A couple examples: if I'm tapping quarter notes that I'll later subdivide into sixteenth notes, I leave it on 4 and tap every quarter note; if I'm planning to use a tresillo rhythm, 3+3+2, I set it to eight lines, tap the first beat of each group on the [x] button, and tap the other beats on the table.)

Then, when you're done tapping, you can hit [o] (z or c on keyboard, usually) to stop, reboot PICO-8, and punch in the calculated SPD to start composing with it.

If the tempo is jumping around weirdly while you're tapping, there might be some taps counting as multiple (I think the electrical engineering term is "bouncing"); increasing the "ignore gaps under" threshold will probably help stabilize it. (2 worked fine for me with PICO-8 standalone, but I found myself turning it up to 5 when I was testing it on the web just now.)

Hope it works for folks!

Edit: Figured out that I introduced a bug right before uploading - should work better now.

P#79921 2020-07-25 15:06 ( Edited 2020-08-05 15:43)

[ :: Read More :: ]

I was testing out various PICO-8 cartridges in the new version, including my own Rain Gif cart, and I discovered that when I chose the built-in "Save Gif" option, it would report that a GIF was saved to the desktop:

...but no file would appear.

On further testing, I discovered that on manually saving an image, it throws an error:

I'm not sure why this would happen - I've already checked whitelisting PICO-8 in my virus checker and that changes nothing. (And besides, I can save files and use the audio exporter just fine.) I'm running Windows 7 (yes, I know...) and installed PICO-8 yesterday using the installer download.

P#78963 2020-07-06 01:31

[ :: Read More :: ]


Something I noticed today while experimenting with PICO-8 sound effects: in 0.2.0i, if a silent (volume=0) row immediately before a non-silent row with an SFX instrument has the same SFX instrument as its instrument (e.g. if the user shift-clicked the SFX instrument to set the entire SFX to that instrument), even though the UI shows no difference, the program sometimes plays the SFX differently. (It looks like the effect lasts as long as the nominal length of the SFX instrument - 32 times its SPD.)

I assume this is a bug in how SFX instruments are implemented; if it's not, it strikes me as a bug in how the information is displayed.

P#77796 2020-06-08 16:28

[ :: Read More :: ]

In previous versions of PICO-8, pressing the Backspace key would consistently delete the note on the line immediately above the cursor.

In 0.2.0i, it doesn't seem to consistently do that:

On at least one occasion not captured here, Backspace deleted the effect that had just been added, rather than any notes.

This unpredictability makes it frustrating to work, because I don't know what my inputs will do.

P#76394 2020-05-11 20:57

[ :: Read More :: ]


For fairly whimsical reasons, I decided to write a four-part crab canon in PICO-8. Tenor and soprano channels play the melody forwards, bass and alto channels play the melody backwards.

If anyone wants to use it for anything, feel free to ask - but given that it uses all 64 SFX slots and all four sound channels for eighty seconds of music, I don't expect it to be useful to anyone in particular. It was fun to break out some of the partwriting skills from community college music theory classes again, though.

P#76249 2020-05-09 18:07

[ :: Read More :: ]

Cart #packbat_parametric_demo-0 | 2020-03-02 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

Pretty straightforward: uses the feature of line() that it will draw from the end of the previous line to connect the dots around a curve defined parametrically (i.e. x,y both written as functions of t). Uses whatever the current pen color set by color() is.

Tried to build in some basic politeness features - it resets the draw state when it's done, it sets the sign on deltat to make sure that it points from t0 towards t1, it chooses a default value if deltat is given as 0 or not given, and it will always draw a line that starts at t0 and ends at t1 whether or not deltat is an integer factor of (t1-t0). With all of those implemented, it works out to 82 tokens; cutting all of those out drops it to 33 tokens. Haven't given this a bulletproof QA treatment but it passed some basic checks.

Demo uses the parametric representation of the standard ellipse off Wikipedia.

P#73616 2020-03-02 17:39

[ :: Read More :: ]

I realize it's not applicable for all games, but would it be feasible to implement screen reader support for PICO-8? I don't know a lot about WAI-ARIA for web or anything about accessibility of standalone applications, so I don't know how such a thing is to be done, but we already have printh() commands sending data to the "@clip"board - adding something like "@tts" (text to speech) seems PICO-8-ish.

PICO-8 already does a lot of accessibility-positive things - the color palette includes a lot of nicely contrasting brightnesses and hues, the controls can already be remapped freely, carts can implement optional mouse and keyboard input - and setting up for speech reader integration would give programmers another way to make their carts playable by everyone.

P#73131 2020-02-15 20:29

[ :: Read More :: ]

Cart #gipuwesuno-0 | 2020-02-12 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

We were fiddling with another project and realized that, because we were using sspr() to resize images, we didn't actually know where all the pixels were - and we wanted to know, because we wanted to cast a shadow that was the colors of what was below but in shadow. And we realized that PICO-8 lets you look at the screen in the code, and then the concept of green screen popped into our head, and then we got to work.

It's not minimized and it's not optimized - if you tell it to chromakey the whole screen, it'll chew through the entire CPU budget with change (ask us how we know!) - but I think it's readable enough that people can hack on it. We haven't tested it extensively, but we made sure it respected the current clipping rectangle and restored it before it exited, because that seemed like the correct thing to do.

function chromakey(drawfunc,x,y,w,h,c)
    -- replaces pixels of c in rectangle x,y,w,h with output of drawfunc
    -- c defaults to 3
    if type(c) == "nil" then
        c = 3
    -- load prior clip state
    local clip_x0=peek(0x5f20)
    local clip_y0=peek(0x5f21)
    local clip_x1=peek(0x5f22)
    local clip_y1=peek(0x5f23)

    -- squeeze chromakey rectangle into clipping region
    x = max(x,clip_x0)
    y = max(y,clip_y0)
    w = min(w,clip_x1-x)
    h = min(h,clip_y1-y)

    for xs = x,x+w-1 do
        local ys = y
        local y0 = y
        local chroma = false
        while ys < y+h do
            if not chroma and pget(xs,ys) == c then
                -- start detecting
                chroma = true
                y0 = ys
            if chroma and pget(xs,ys) ~= c then
                -- stop detecting and draw rectangle
                chroma = false
            ys += 1
        if chroma then
            -- c extended to bottom of column

    -- reset clipping region
P#73022 2020-02-12 16:36

[ :: Read More :: ]

I noticed after I updated the cart on my Screen and Draw Palettes Demo thread that the post got bumped in the forum thread list with the message "[last person to post] replied 9 hours ago", when (a) they replied two months ago and (b) I updated the cart on the original post 9 hours ago.

(Yeah, that was really really belated. I was kind of embarrassed about the initial mistake.)

I'm not sure what's going on behind the scenes to display these timestamps but something like "Packbat updated a cartridge 9 hours ago" would be more accurate.

P#72035 2020-01-17 00:19

[ :: Read More :: ]

Cart #packbat_rain_gif-8 | 2023-03-08 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

No big story here - I'm a big fan of the bot rain.gif and wanted to make something in PICO-8 that would produce similar images. Is configured to loop seamlessly for 8-second gifs (PICO-8's default), but the parameter should be obvious and easy to change. Rain noise is implemented in a fairly basic form (the point of the cart is generating GIFs, not a full simulation) and disabled by default.

Currently has 16 palettes, all chosen to have reasonably good contrast ratio - should be clear how to edit those as well.

Edit 2023-03-08: Set gif duration to 16 seconds, matching new default gif length.

Edit 2019-12-15: Replaced green palettes, tweaked drop rendering, added more drop pattern randomization, added controls:

←/→ = change palette

[x]/[o] = reroll drops

The number that displays on the left is proportional to how many raindrops there are, the number that displays on the right is the palette index.

Edit 2020-01-18: Added some basic rain sounds with modes for light, medium, and heavy rain - option is stored in cart data and can be accessed via the menu when you first load or through the pause menu. Should be moderately quiet.

(9:29 p.m. UTC-05:00: Did a small tweak to the first-load menu to indicate how to change the setting.)

Edit 2020-01-22: Added granular volume control. ↑/↓ to adjust.

Edit 2020-04-06: Added pause menu items to (a) reroll both palette and rain pattern together and (b) automatically wait the loop length and save a GIF that will loop correctly.

Edit 2020-04-25: New graphical effect - raindrops now vary in length and in contrast with sky, which produces a bit of an illusion of depth.

P#70455 2019-12-01 17:11 ( Edited 2023-03-08 21:47)

[ :: Read More :: ]

Cart #packbat_palettes_demo-1 | 2020-01-16 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

revision 1: use pal(#,#,1) commands instead of poke() commands to set colors on screen palette.

I spent a while being very, very confused about screen palettes and draw palettes, but I've started to feel like I've got a hang of it? And I decided to make a visual expression of how these work as a PICO-8 cart.

In the default state, the screen palette contains the sixteen colors of the default palette - 0-15 - and the draw palette links the sixteen draw colors 0-15 directly to these default colors.

When the draw palette changes, different colors from the screen palette are loaded into the draw color slots. The sprite still refers to the same draw colors - in this case, 0-3 across the top, 4-7 in the second row, 8-11 in the second-to-bottom row, and 12-15 in the bottom row - but these draw colors have been redefined in terms of the screen palette.

...and the screen palette in turn can be redefined by changing the colors in its 16 slots.

I think how this is intended to be understood is:

  • 4 components: video output hardware (5-bit input, RGB output), screen palette RAM (4-bit address, 8-bit output), screen data RAM (14-bit address, 4-bit output), draw palette lookup table RAM (4-bit address, 4-bit output)
  • Every sixtieth of a second, one frame of graphics is sent to the monitor. As the hardware scans through the 128x128 pixels of the screen data, the 4-bit screen data output addresses the screen palette RAM, and the high-order bit plus lower 4 bits of the screen palette output goes to the video output hardware. (I can attest from having bugs in my screen palette code for a while that the other bits in the screen palette are ignored.)
  • When commands are run to draw graphics on the screen, the 4-bit addresses to be drawn go through the draw palette lookup table to set the 4 bits actually stored in the corresponding addresses of the screen data RAM.

Or, in less technical terms: the data stored for the sprite (or whatever) chooses a row in the draw palette (center) which chooses a row in the screen palette (left) which chooses a color on the screen. Changing the draw palette doesn't change what the screen shows until PICO-8 draws something new to the screen data; changing the screen palette does.

What honestly made it click for me, though, was thinking in practical terms.

The draw palette, for example, is a tool for recoloring images as I draw them. For example, if I have this little gray box stored as a sprite in my sprite sheet:

then I can use the draw palette to declare that I'm drawing it as an orange sprite instead:

The screen palette, on the other hand, I think of as 16 meanings. Rather than thinking of the, say, 11th color in the screen palette as a color, when I am using the screen palette to change the colors of images, I think of it by its purpose: in this case, the color of the character's shirt.

Then, changing that color represents a change corresponding to that meaning: to put the value 136 in screen palette slot 11, then, is to say that the color of the character's shirt is not green, it is red.

These ideas can obviously be combined - perhaps screen palette 1 is the color of player 1's vehicle and screen palette 2 is the color of player 2's vehicle, and which color is drawn when a vehicle sprite is drawn is selected using the draw palette. Thus, both players can select the colors of their vehicles and both vehicles can be drawn using the same sprites.

This is very long and probably well-known in the community, but I figured I'd make the tutorial anyway because I'm proud of figuring this out.

P#70054 2019-11-20 03:02 ( Edited 2020-01-16 15:51)

[ :: Read More :: ]

Cart #packbat_colorinfo_v1-2 | 2020-01-18 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

Revised 2020-01-17 to use and describe pal() commands instead of poke() commands.

One of the things that's been very much on my mind as I've been learning PICO-8 and making my first full game project in it has been how to use the console's limited palette, and particularly how to ensure that everything I put on screen is clear and straightforward to read. It is very easy when designing user interfaces to make something that looks fine in a screenshot viewed in a well-lit room on a bright monitor by someone who knows where and what everything is, but obscure or confusing in actual play under the varied conditions which your audience plays in.

Nothing substitutes for testing, but the number one idea that I've used to try to make sure I start with something easily-read or close to it is contrast ratios - essentially, how much difference in brightness there is between two colors.

The hex codes and names for colors 0-15 I copied from the wiki page on Graphics - the names being from Roman Zolotarev's PICO-8 palette reference. The hex codes for colors 128-143 (the extended palette - see the draw state section of the wiki page on Memory) I measured myself with paint.net from screenshots, and I picked names for them using https://klaash.github.io/xkcdcolorpicker/ as guide/inspiration.

The brightness numbers and the guidelines for difference of brightness need a little bit of explaining.

WCAG2 and contrast ratios

The Web Contrast Accessibility Guidelines (WCAG) 2.0 have a section on the visual presentation of text and images of text. There are no guidelines as far as I am aware for purely graphical elements - how could there be? - but I use the three thresholds that WCAG2 describes as the basis for how I do this.

  • For large-scale text, WCAG2 minimum contrast requires a contrast ratio of at least 3:1.
  • For ordinary text, WCAG2 minimum contrast requires a contrast ratio of at least 4.5:1.
  • For large-scale text, WCAG2 enhanced contrast requires a contrast ratio of at least 4.5:1.
  • For ordinary text, WCAG2 enhanced contrast requires a contrast ratio of at least 7:1.

So: what is a contrast ratio? Click the link on the page, and it gives a formula in terms of relative luminance:

contrast ratio = (L1 + 0.05) / (L2 + 0.05)

What is relative luminance? Click the link on the page, and it gives ... a collection of equations that are more complex than I want to get into, but trace all the way back to the 8-bit R, G, and B values used in sRGB color spaces, which I'm pretty sure is what most everything uses, including PICO-8. I plugged the formulae into a spreadsheet and managed to get out the contrast ratio data I wanted, which I then compared to these three thresholds from WCAG2 and used conditional formatting to display them all nicely.

...then I spent a long time thinking about how to make this easier to use as a general reference, because the difficulty of accessing information is probably what makes these things less commonly thought about.

What I've got right now probably isn't perfect. But I remembered a bit of math that might make things more straightforward: logarithms.

ln contrast ratio = ln (L1 + 0.05) - ln (L2 + 0.05)

By recording brightness as a logarithm of the relative luminance term in the equation (kind of like how magnitudes of stars in the sky are recorded), I turn a ratio into a simple subtraction. For a pair of colors to have sufficient contrast, these log-L brightnesses merely need to differ by enough to meet the necessary contrast ratio:

  • ln 3 = 1.10
  • ln 4.5 = 1.50
  • ln 7 = 1.95

...and thus I have a possible workflow: find your color in the list sorted by index, read out its brightness, add and/or subtract to calculate what brightnesses contrast at the level you need them to (a judgment call, but practice should help), and look at the list sorted by luminance to see what groups of colors at the ends of the lists hit that contrast level.

I hope this is useful to people, or at least a fun toy to explore the full list of PICO-8 colors with.

Edit: For convenience, I've also uploaded a text file with substantially the same information - probably more convenient to work with for many purposes, even if it's less colorful.

P#69781 2019-11-10 17:55 ( Edited 2020-01-18 00:46)