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

Cart #picotron_filter_testbench-0 | 2024-07-21 | Embed ▽ | No License
1

This is a tool for visualizing the impulse response (top) and frequency magnitude response (bottom) of different filters. The frequency magnitude response is determined by taking the magnitude of the FFT of the impulse response.

It's a pretty messy tool that's accumulated a few different features from various things I've been trying to do, but I want to refer to the cart in a Mastodon post so I'm posting it here. Ignore the blue and orange lines, those are from a different experiment; just focus on green and red.

Basic usage: left/right changes cutoff, up/down changes resonance, Z/X rotate through filter modes. There are some modes labeled "zep" that are more relevant to the Picotron filter. The ladder filters work fine at 0 resonance, the zep modes need at least a tiny bit of resonance to produce a signal (so you'll need to hit the up arrow a bit to get them to work).

P#151611 2024-07-21 20:53 ( Edited 2024-07-22 01:05)

[ :: Read More :: ]

I was trying to compute the outer product of a column vector x with itself, like so:

xxt=x:matmul(x:transpose())

The resulting matrix had the right shape, but the only nonzero entries were on the diagonal. If I explicitly create a row vector and copy, then I get the correct result.

P#150067 2024-06-18 04:07

[ :: Read More :: ]

Cart #vectorized_fft-1 | 2024-06-17 | Embed ▽ | License: CC4-BY-NC-SA
4

(This is slower after 0.1.0h but still fairly quick overall - maybe I'll update these numbers eventually.)

Here's some fairly fast FFT code for Picotron. It abuses the fact that matmul() is extremely cheap right now, and also decomposes shuffles into either matrix transposes or large-stride vector shuffles - so even if matmul() gets more expensive this should remain fairly efficient. A 256-point complex FFT currently takes about 1.4% CPU at 60fps.

Hit Z or X to switch to the visualizer mode, which demonstrates feeding tracker output into the FFT to get frequency visualization. You could try taking the FFT code to use this with your own music!

This works on complex signals only at the moment, but since it's quite fast and accepts separate real and imaginary vectors as inputs, using it for real signals shouldn't be too bad - just provide a zero imaginary part and ignore the upper half of the returned frequencies.

General FFT code is in fft.lua, demo-specific code is in main.lua. I’ll probably stick a MIT license or similar on the FFT code soonish - if you want to use it and I haven’t done that by the time you read this (and the CC license won’t work for you), get in touch and remind me!

P#150033 2024-06-16 21:57 ( Edited 2024-07-20 20:05)

[ :: Read More :: ]

Division has the same problem.

print((vec(1)-2)[0])    -- prints -1
print((2-vec(1))[0])    -- prints -1
print((vec(1)/2)[0])    -- prints 0.5
print((2/vec(1))[0])    -- prints 0.5
P#149991 2024-06-15 19:24

[ :: Read More :: ]

I've seen this behavior in both sfx.p64 and #visitrack. I'm on a M1 Macbook Pro, OS X 14.3, running Picotron 0.1.0f.

  1. Slow drags downward don't do anything - mouselock just returns 0 for dy, regardless of how far you drag. Slow drags upward do not have this problem.
  2. Effective sensitivity is lower on Mac than on other platforms. I haven't verified this myself, but based on @drakmaniso's description of how the sliders should be acting in #visitrack, this seems to be true.
P#146834 2024-04-17 17:02 ( Edited 2024-04-17 17:02)

[ :: Read More :: ]

Now in stereo! (Stereo appears to work only on desktop for some reason.)

Cart #lazy_procedural_acid_picotron-3 | 2024-07-25 | Embed ▽ | License: CC4-BY-NC-SA
7

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.

Use the left and right arrows to change the audio buffer size. Values >=8 seem to work well on web, and it looks like I can go as low as 3 on desktop Picotron on my machine.

0.1.0h seems to have broken part of the visualization here - probably just an API change I haven't caught up with yet.

IF YOU WANT TO USE PCM IN YOUR OWN CODE: copy pcm.lua from this cart, and check out how main.lua uses pcm_init, pcm_callback, pcm_start, and pcm_stop. I'll probably document this better at some point.

P#146405 2024-04-11 23:12 ( Edited 2024-07-25 06:22)

[ :: 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!

Cart #clock_life-1 | 2024-03-28 | Embed ▽ | License: CC4-BY-NC-SA
8

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.

Changelog

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

[ :: Read More :: ]

Cart #wavepaper-0 | 2024-03-20 | Embed ▽ | License: CC4-BY-NC-SA
22

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 :: ]

Cart #pico_ripples-1 | 2024-03-17 | Embed ▽ | License: CC4-BY-NC-SA
21

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

Changelog

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 :: ]

Cart #picotron_smoke-0 | 2024-03-16 | Embed ▽ | License: CC4-BY-NC-SA
16

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 :: ]

Cart #userdata_tunnel-0 | 2024-03-15 | Embed ▽ | License: CC4-BY-NC-SA
14

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
31

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.

Controls:

  • 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!

Buttons:

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

Features:

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

Limitations:

  • 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
19

Controls:

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

DON'T YOU LECTURE ME WITH YOUR 30K PICO-8 CART

Oh, but I will!

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

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:

7⬆️✽0⬆️✽✽✽⬇️✽⬆️✽3⬇️▶▤웃⬆️7◀0⬆️ˇˇ♪♪♪□∧7⬆️∧7⬇️7⬇️∧

Or this much better song by @packbat:

➡️▶▒7◀▶…6◀░▶▒7◀▶…6◀★▶▒7◀▶…6◀█▶😐7◀▶⁘3◀⬆️▶⁘3◀⬇️▶▒7◀▶…6◀░▶▒7◀▶…6◀★▶▒7◀▶…6◀⬆️★⬇️⬇️⬇️⬇️▶★7◀▶▥▥⬆️2◀∧

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

Resources

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

Changes

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!

Acknowledgements

  • 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
9

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.

Features

  • 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
5

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

View Older Posts