Log In  
Audio synthesis: plucked string and delay
by luchak
Simple smoke sim
by luchak
RP-8 1.1 - now with pattern randomizer!
by luchak
[ :: Read More :: ]

Note: this does not work properly in the web player, but it should work fine on desktop. Perhaps using larger buffers will fix the web version? I'll have to try that later.

Cart #lazy_procedural_acid_picotron-0 | 2024-04-11 | Embed ▽ | License: CC4-BY-NC-SA

Just a short little proof-of-concept for streaming PCM output in Picotron. Could stand to be streamlined and cleaned up a fair bit (and commented better!), but hopefully this gets the idea across. The idea is to set up a few different sfx instruments playing different wavetables on adjacent rows of a pattern, use stat() calls to track the currently playing row, and update non-playing rows with new samples. There are a few gotchas to work around - setting up the instruments is a little fiddly, and Picotron crossfades instruments between rows so some samples need to be written to multiple instruments.

P#146405 2024-04-11 23:12 ( Edited 2024-04-11 23:36)

[ :: Read More :: ]

The poster of https://www.lexaloffle.com/bbs/?tid=141370 seems to have discovered that posting a short reply to their own thread un-hides their original post after it was flagged for spam review. Not 100% sure this is a bug, but it seems like one.

P#145429 2024-04-02 02:34 ( Edited 2024-04-02 04:52)

[ :: Read More :: ]

Just a few notes from playing around with userdata. These notes assume 8M VM cycles/sec and large enough arrays to avoid substantial overhead and fully realize economies of scale.

  • Fast ops - add/mul/copy/etc. - cost 1/16 cycle.
  • Slow ops - div/convert/etc. - cost 1/4 cycle.
  • matmul is charged as a fast op based on the size of the output matrix. I'm a little suspicious that the answer seems to be so simple, so I'm wondering if I missed something.
  • copy and memmap/memcpy are approximately the same speed for 64 bit datatypes. For smaller datatypes, memcpy is proportionally faster, though of course you then have to manage strides/spans yourself. memcpy should also enable reinterpret_cast type shenanigans.
  • There is substantial overhead for small spans. If you use spans of length 1 you pay 1/4 cycle/span, same as a slow op. It looks like this may be a flat cost per span, but I'm not sure. Using the full/strided forms of the ops does not seem to have noticeable additional costs beyond the per-span cost.
  • For full-screen rendering, you have about 1 cycle/pixel at 480x270x60Hz. This includes whatever scaling/conversion you need to do at the end of the process. So realistically, you'll get in the neighborhood of 10 additions/multiplications per pixel. Exact numbers depend on whether you need a divide at the end, and whether or not you can work in u8.
  • userdata flat access w/ locals seems to cost 1 cycle/element including the assignment.
  • userdata get is 1/4 cycle / element at scale ... but each explicit assignment will cost you 1 cycle on top of this.
  • userdata set is 1 cycle / element at scale.

There also seems to be some interesting behavior happening where multivals, even very large multivals, do not seem to noticeably increase CPU usage when passed to pack or set. While I'm enjoying taking advantage of this for f64 to u8 conversions at the same cost as convert, I'm worried this might not last.

P#144791 2024-03-28 06:26 ( Edited 2024-03-28 15:40)

[ :: Read More :: ]

Now much more screensaver-y!

load #clock_life-1
// to load from inside Picotron

After seeing some of the other Game of Life posts today, I thought I'd take a shot at the problem. This implementation runs at 480x270 / 60fps using a little over 50% CPU. Userdata ops are used to compute neighbor counts, and I initially tried using userdata ops to apply the changes to the grid as well - but that was just a little too slow (~90% CPU, so Picotron wouldn't run it full speed). This version uses color tables to apply the updates instead, which is much faster.

Color tables seem incredibly powerful. I'm curious to see what else people do with them.


  • 0.2 - now the clock moves
P#144494 2024-03-26 04:31 ( Edited 2024-03-28 19:12)

[ :: Read More :: ]

load #wavepaper-0
// to load from inside Picotron

Please enjoy this very necessary desktop wallpaper.

This is 0.1 and not 1.0 because performance is a major issue: this will run at 60fps if you have no windows open, but will drop to 30fps or possibly lower if anything at all is running on top of it. When that happens, (WARNING) you may get some flickering, especially if some windows overlap desktop icons.

Despite the fact that this is basically just a reskin of the other wave sim I posted - using a different rendering effect to keep it compatible with the default palette - I still think it's kind of neat. Hopefully I'll find ways to get this running more smoothly over time. Or, failing that, maybe just ways to pause it if anything else is open.

To install:

load #wavepaper
save /appdata/system/wallpapers/wavepaper.p64.png
P#143925 2024-03-20 03:46 ( Edited 2024-03-20 05:27)

[ :: Read More :: ]

load #pico_ripples-1
// to load from inside Picotron

A nice little raindrops-on-a-pond type of effect. Though perhaps with more of a shifting rainbow color scheme than most ponds.


v1.01: Fixed initial splash height so smaller ripples should be more visible. This, to my eye, adds some pleasing visual variety.

P#143450 2024-03-17 06:42 ( Edited 2024-03-17 19:09)

[ :: Read More :: ]

load #picotron_smoke-0
// to load from inside Picotron

Yup, it's a smoke simulation toy for Picotron! Use your mouse to play. Should run at a nice steady 60fps after the first ~1s.

Uses userdata to make pressure projection steps almost free. That's great, since those steps were super-expensive in the PICO-8 version! Unfortunately, advection still has to be done with loops in userspace Lua code, and so that part didn't really see any speed increases, and is the new bottleneck.

There might still be some rendering tricks to speed this up, and I need to see whether some tricks I gave up on in the PICO-8 sim, like a MAC grid or vorticity confinement, might be viable here. Unfortunately higher-order interpolation, which I know would help a lot, is probably not happening without major new userdata features.

P#143262 2024-03-16 05:20

[ :: Read More :: ]

load #userdata_tunnel-0
// to load from inside Picotron

Here's a simple tunnel effect I did to test out doing pixel effects with userdata. Not super inspired, but it does run at 60fps at full resolution!

P#143044 2024-03-15 02:39

[ :: Read More :: ]

I've been thinking about ways to execute fast pixel effects on Picotron, and so I'm looking for ways to perform the effect entirely in userdata-land, and display the results as sprites. I've tried a few approaches, none of which seems to be quite ideal.

  1. Do math on f64 userdata, then render that to the screen. This gets me fast math operations, but of course the rendering result is completely garbled. I suppose some pack-ints-into-floats type nonsense could be attempted? But with just basic arithmetic ops this seems like a bad time.

  2. Do math on f64 userdata, then cast that to u8 in a loop. This is a lot of time spend calling getters/setters in a loop, and may not be any better than pset, not sure.

  3. Do math on integer userdata, then cast to u8 using the userdata:convert() method and display that. This would work fine if I could figure out how to get fast fixed-point going. Unfortunately, div and mod are slow, and shifts don't seem to work.

I think my preferred resolution would be to allow convert() to go from f64 to u8, but anything that allows any kind of math with fractional quantities, either floating-point or fixed-point, would be great. Drawing f64 userdata with spr with implicit casting would be great too. Just trying to avoid explicit loops to get to u8....

Or perhaps there's some other way to get what I want with what's in Picotron already and I've just missed it. Not quite sure what's doable with the 10-15ish ops/pixel this would get best case but I'm sure something neat can happen.

Other requests:

  • I would love to have min/max/abs or similar "sharp" functions available for userdata, to allow masking off out-of-range data or other similar conditional-ish use cases.

  • trig would be amazing but I assume that's probably off-limits, or at least would be quite expensive.

  • Even more amazing would be the ability to index userdata with userdata (and at this point built-in trig is probably not necessary)
P#143013 2024-03-14 23:31 ( Edited 2024-03-15 07:42)

[ :: Read More :: ]

I'm trying to make a classic synth pluck sound: a couple of detuned saw oscillators, plus a resonant lowpass filter that quickly opens up when a note starts, then closes back down. Unfortunately, I can't quite figure it out. I have an ADSR envelope modulating the filter cutoff, but since the envelope has a positive polarity and higher lowpass knob values = lower frequencies, the effect is exactly the opposite of what I want: the filter immediately closes down on note onset, then more slowly opens back up.

Is there any way to flip the sign of this modulation? Am I thinking about this the wrong way?

P#142951 2024-03-14 20:25 ( Edited 2024-03-15 06:51)

[ :: Read More :: ]

This is a tool to allow both time-domain and frequency-domain editing of the new custom sfx waveforms.

Cart #wave_designer-5 | 2024-04-01 | Code ▽ | Embed ▽ | No License

Note for existing users: use the buttons at the top-right to import/export to clipboard, it doesn't happen automatically on every change now.


  • Mouse: click and drag to edit the wave directly (green line), harmonic magnitudes (blue bars in the middle), or harmonic phases (maroon/lavender bars on the bottom). Or click the play button to toggle preview playback, which plays sfx 1 on loop. The waveform is stored in sfx 0.
  • Left/right arrow: switch between a bunch of preset waves.
  • Tracker keys: play notes!


  • Play/pause
  • Undo and redo
  • Set phase to 0 or 0.25 (sin and cos buttons)
  • Keep odd harmonics only (odd button)


  • Time and frequency domain editing, always kept in sync.
  • Mouse drags are interpolated so fast drags work as expected.
  • Import/export your creation to the clipboard as a PICO-8 sfx string, using the buttons at the upper right. You may need to press ctrl-c/cmd-c on the web for this to work; on desktop it should be automatic.
  • You should be able to paste PICO-8 format sfx strings into the cart to load waves (including waves you've designed in this tool).
  • NOTE: if you are using the web version on a Mac, you will have to press some combination of both cmd-C and ctrl-C to copy, and both cmd-V and ctrl-V in order to paste, since the web version is really really finicky about how copy/paste works. You may get a stuck note when this happens.


  • If you want to change the preview sound (sfx 1) you have to edit it in the tracker. You can replace sfx 1 with some other looping sfx if you want, it just needs to use wavetable instrument 0.

Feedback is welcome!

P#142178 2024-02-29 04:59 ( Edited 2024-04-01 04:51)

[ :: Read More :: ]

Want sfx fast? sfxp can help! Adjust the sliders for new sounds, or click "random" for instant inspiration.

Cart #sfxp-1 | 2023-09-21 | Code ▽ | Embed ▽ | No License


  • click/drag sliders and buttons
  • right click the random button to mutate the current sound instead of fully randomizing it
  • z/x to replay the current sound
  • left arrow to undo
  • right arrow to redo

You can export either to sfxp format (to share, or to edit more later) or to PICO-8 sfx format (to paste into the PICO-8 sfx editor and use in your PICO-8 games). Export works via the clipboard, which can be kind of weird on the web version of PICO-8. I highly recommend running sfxp on desktop PICO-8 if you have it.

You can also find sfxp on Itch.

Made for the PICO-1K Jam.

Update 2023-09-20: undo bug fixed and redo added.

P#134424 2023-09-16 08:24 ( Edited 2023-09-21 02:13)

[ :: Read More :: ]

I am pleased to announce Questionable PICO-8 Audio (QPA), a compressed audio format for PICO-8.

(Compatibility note: if you encoded any QPA files before 2023-09-07, please see the Changes section below.)


Oh, but I will!

Cart #qpa_demo-0 | 2023-08-31 | Code ▽ | Embed ▽ | No License

Your song is synced with the clipboard - you can copy your songs out to text files (or this thread!) and paste song strings back in to the cart. You can right-click samples to preview them, and you can right-click the play button to skip the intro sample. Numeric modifiers come before the thing they modify. Hopefully you can figure out the rest!

Copy/paste is a little annoying on the web player, and PCM audio can also be unreliable on the web, so consider trying this cart on desktop PICO-8 if you have it.

You can also check out this song by me:


Or this much better song by @packbat:


(The reference, if you're not familiar)

Why QPA?

Because you want to put fun sound samples in your carts, but don't want to use too many tokens or too much space!

QPA provides three main benefits:

  • Tiny decoder. A fully-featured decoder that reads from strings, does quality-level detection, and rejects invalid files is 228 tokens. A minimal decoder is just 175 tokens.
  • Reasonable quality at very low data rates. QPA encodes mostly-intelligible speech at just 1.14 bits/sample (788 bytes/second) - or completely-intelligible speech, as well as usable instrument samples, at twice that rate.
  • Faster-than-realtime decoding speed. Precise speed depends on the decoder implementation, but around 4x realtime is a reasonable expectation for a token-efficient decoder.

As for how this works in practice: the demo cart PNG is just under 32k. It contains about 18k of samples (split 17k in sprite/map/etc. space, 1k in code) and 2k of other compressed code; the rest of the space is taken up by the label and other overhead. That 18k of sample data stores almost 14 seconds of audio. The samples are stored at a variety of quality levels, but more audio could have been included at lower quality, or higher-quality audio could have been used in exchange for more space. There also could have been a lot more audio if I hadn't tried to keep the cart around 30k - there's another 12k of compressed code space that could have been filled with QPA data strings!

What Is QPA?

QPA is an adaptation of the Quite OK Audio Format (QOA) to PICO-8's constraints. As such, it is a lossy, constant-bitrate format, in which the decoder attempts to predict future sample values one at a time, and compressed files store approximate differences between these predictions and the real sample values. To better understand how this scheme works, I suggest reading this introduction to QOA. QPA changes some details, but the basic structure remains the same.

So what changed to put the PICO in QPA?

  • Multiple quality levels. QPA offers a choice of compression ratios so you can optimize your valuable cart space. The highest compression ratio (7x) is noisy, but is still suitable for some speech, percussion, and sound effects. The lowest two compression ratios (2.25x and 1.75x) are approximately transparent for most sounds.
  • Minimal metadata. The only pieces of metadata in a QPA file are a magic number and a sample count. QPA includes no sample rate information, and does not support multi-channel audio (PICO-8 is mono only) or seeking to arbitrary points in a file (PICO-8 carts are small). This makes the decoder simpler.
  • 8-bit audio only. QPA assumes an 8-bit output resolution, and is tuned to avoid single-bit output noise.
  • PICO-8 numbers. Almost all arithmetic works on PICO-8's native q16.16 number representation, slices are now 32 bits to match PICO-8's word size, and the format is little-endian instead of big-endian.
  • Smarter encoder. At PICO-8's low sample rate, it is critical to avoid high-frequency noise, so the encoder includes some light perceptual tuning to push noise into lower frequencies. It also expands QOA's use of exhaustive search for parameter selection, making it easier to incorporate new encoding strategies in the future.


To use QPA in your own carts, please check out the following resources:

  • @bikibird's Defy encoder and Defy cart. You can use these to encode and play back your own audio files.
  • The QPA utility cart. This cart makes it easy to convert QPA files to strings, and contains low-token decoding functions to copy into your own carts.
  • The QPA repository on Github. Here you can find more documentation, a format spec, and a CLI encoder/decoder for QPA files. (CLI requires Node and NPM.)


On 2023-09-06 and 2023-09-07, new versions of all tools, code, and encoders were released to fix problems identified by Packbat. These new versions broke compatibility with earlier versions - if you switch to the new code, please keep in mind that you will have to re-encode your files. Hopefully this is a one-time only sort of change.

Future Work

I'd like to focus on getting better quality at approximately 1 bit/sample. It's possible that VBR or variable sample rate enhancements to QPA could get there, or it might be necessary to explore frequency-domain techniques ... although that might bode poorly for keeping decoder token count low.

But first - I think it's time to use QPA to build a tracker. No promises on release date!


  • Dominic Szablewski for the QOA audio format
  • @packbat for discussions and a whole lot of listening tests
  • @bikibird for Defy and being willing to integrate QPA
  • ferris for providing design feedback and pointers for future work
  • MissMouse for the demo cart suggestion
  • @NerdyTeachers and @Heracleum for help with the cookie/coffee test
  • The entire #music-sfx channel on the Discord for discussions and for putting up with a whole lot of QPA chatter
P#133755 2023-08-31 01:00 ( Edited 2023-09-07 20:50)

[ :: Read More :: ]

Would you like to play the piano ... in PICO-8?

Cart #sample_piano-0 | 2023-08-05 | Code ▽ | Embed ▽ | No License

Well, I can't imagine why, but there you go. Use the tracker keys to play. It's polyphonic.

If you want to change the sample, you can convert a .WAV file to any of the QPA formats - and only the QPA formats - supported by this WIP tool: https://luchak.github.io/defy/ . Then drag and drop the resulting file on the running cart and play away. This will also save the new sample to the cart using cstore() so you can make new carts for new instruments! That URL has been updated with some format improvements, so it won't work here any more until I update these carts.

Usual caveats apply about PCM output not always working well on the web.

For this use case there's basically no reason not to use 3.2 bit QPA, the highest QPA quality, since the file will be truncated at ~32k samples (about 6 seconds) due to table indexing limits and my laziness, and at that length 3.2 bits fits easily in a cart. But for future use cases perhaps the 2.3 bit format will have a purpose, and maybe even the 1.1 bit...

Yes, the WIP conversion tool is a fork of @bikibird's Defy. There's a PR out to merge it into the official version!

And if you're curious, QPA is closely derived from https://qoaformat.org/ and currently stands for Questionable PICO-8 Audio. If you think it might stand for something else let me know, you could be correct.

P#132750 2023-08-05 05:43 ( Edited 2023-08-15 02:43)

[ :: Read More :: ]

This cart is a barebones Forth implementation in 279 tokens. It could be smaller, it could be more usable, but I haven't really touched it in weeks, so here you go. Probably full of bugs.


  • eval() function
  • Interpret and compile modes - extend syntax in Forth!
  • if/then (implemented in Forth)
  • A few useful comments, including some commented-out utility functions and basic smoke test.

Non-features / shortcomings

  • else (you can add this in Forth)
  • Arithmetic (you can add this for 11 tokens per binop, or 9 tokens if you're willing to use valid Lua identifiers as operator names)
  • Looping (you can probably add this for the cost of a few arithmetic/comparison operators, then add the syntax in Forth for no token cost)
  • Passing args or returning values when calling Lua functions from Forth (depending on how fancy you want to get, probably takes 20-50 tokens for a reasonable wrapper)
  • [ and ] are spelled _lb and _rb for some reason.
  • The inner interpreter works in a non-standard way that will make it hard to call arbitrary Forth from Lua outside of eval - this is probably worth rethinking.

A few ways to make it smaller

  • A few functions could probably be moved from Lua to the initial Forth definitions as-is, although since : and ; depend on them this will be a bit messy.
  • With the right Forth/Lua binding util, even more functions could probably be moved.
  • With some internal representation changes, even _interp and eval could probably be (mostly) boostrapped.

Anyway, enjoy. This may not be useful for very much, but the question of how little material you need to bootstrap a reasonable eval is still kind of a fun question.

Cart #bbforth-1 | 2023-07-11 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

P#131852 2023-07-11 21:17 ( Edited 2023-07-11 21:23)

[ :: Read More :: ]

Cart #raccoon_ball-1 | 2023-06-04 | Code ▽ | Embed ▽ | No License

Does what it says on the box.

(updated to fix a major oversight: now it bounces)

Photo by Toan Chu

P#130491 2023-06-04 06:02 ( Edited 2023-06-05 21:46)

[ :: Read More :: ]

Copy/paste is super cumbersome when using web exports on Mac. It appears that to reliably copy, I have to press ctrl-C and then cmd-C after text is written to the clipboard, and to get pastes to work I have to press cmd-V and then ctrl-V for PICO-8 to pick up the paste.

Is there a better way to do this? If not, can Mac exports be updated to only need cmd-C and cmd-V? I'd love to cut down the instructions on Tiny Drops...

It looks like this also may be affecting folks using the EDU edition: https://www.lexaloffle.com/bbs/?tid=51921

P#130011 2023-05-22 05:29 ( Edited 2023-05-22 05:58)

[ :: Read More :: ]

Bounce tiny drops that fall from the sky to make music! This began as a 500-character cart for TweetTweetJam 8. Or check this cart out on Itch!

Cart #tiny_drops-3 | 2023-05-22 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

The latest version adds many more editing features, as well as save/load and audio export.


  • Click and drag the mouse to draw lines, or to move line endpoints.
  • Z/X deletes a line. If your mouse is over a line endpoint, it'll delete that line, otherwise it'll delete the newest line.
  • Left/right changes line sounds. If your mouse is over a line endpoint, it'll change that line's sound, otherwise it'll change the sound for new lines.
  • Up/down changes the interval between drops.
  • E to start/stop audio export.
  • S to save to the clipboard. In the web version, you'll need to press ctrl-C after this to complete the copy. (Or, on a Mac, ctrl-C so PICO-8 notices and then cmd-C.)
  • R to restore from the clipboard.
  • N to get a new empty scene.
  • When you paste a saved scene into PICO-8, Tiny Drops will automatically load it. In the web version on Mac, you'll need to press cmd-V to paste and then ctrl-V to get PICO-8 to notice the paste.

Known issues

  • Can get slow if you trap too many drops on the screen.
  • When you start to create a line, you may hear "phantom" bounces until you start dragging the mouse. (Happens with 0-length lines.)

500-Character Jam Version

This version has no save/load, no audio export, and no concept of line hovering or editing existing lines:

Cart #tiny_drops-0 | 2023-05-13 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

P#129682 2023-05-13 00:11 ( Edited 2023-05-22 05:13)

[ :: Read More :: ]

Cart #boundary_peaks_tweetcart-0 | 2023-05-10 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

Just a small tweetcart.

P#129585 2023-05-10 02:02 ( Edited 2023-05-10 02:03)

[ :: Read More :: ]

The following code block prints only the number 4, though I would expect it to print 2, 3, and 4.

 if (false) if (true) print(1)

Only the outer if has this property, the inner if's scoping is fine. Similar behavior happens without the do block, I just included it to demonstrate the interaction of this behavior with "normal" Lua scopes.

P#129196 2023-04-30 19:56 ( Edited 2023-04-30 19:59)

View Older Posts
Follow Lexaloffle:          
Generated 2024-04-14 13:16:42 | 0.135s | Q:78