As usual with PCM audio, be careful of your volume, and if this doesn't play back well in your browser, try it in desktop Pico-8. Earlier versions had filter instability problems that could result in loud sounds showing up out of nowhere - I think these are fixed now (I've let this play for over an hour with no problems) but I can't 100% promise it won't happen again.
An entry to the Pico-8 512-Char Jam. Kick, hat, acid.
Update 1: Moved the delay time slightly off the beat. Also saved a few characters thanks to @SmellyFishstiks, which let me thicken the oscilloscope line.
Update 2: Improved visualization and stability.
Update 3: More improvements to visualization and filter stability to avoid horrible loud sounds coming out of nowhere after letting the cart play for a long time. Hopefully the last update!
![](/gfx/set_like0.png)
![](/gfx/top_drop.png)
Really cool! I don't make "tweet"carts a lot so I find the use of the 2 labels really interesting having one for draw and one for grabbing the pcm
![](/gfx/set_like0.png)
![](/gfx/top_drop.png)
Thanks! Yeah, I needed to be able to skip synthesis if the buffer was full, and just sticking a label right before flip()
seemed like the most expedient approach.
But now that you've got me looking at it, I've realized that just using a big if
block actually saves a few characters, so if I make any more changes to this I should probably do that instead. Thanks for making me think about this a bit more!
![](/gfx/set_like0.png)
![](/gfx/top_drop.png)
With enough sacrifices it's possible to get this down to tweetcart length! I'll probably noodle on this one a bit before actually posting to Twitter, I think I can get this down a few more chars and maybe use those to get some of the quality back?
Changes:
- No delay
- No hihat
- Tuning is less accurate and there are fewer possible notes
- Synth plays every 16th note instead of only sometimes
- No oversampling
- 2-pole instead of 4-pole filter
- No saturation in filter
- Simpler visualization (dots instead of lines, no color)
- Favoring shorter values over better-sounding values
The lessons learned from the tweet-shortening process also got the 512-char version down to 487 characters, which is probably enough space to do something interesting.
Update 1: visualization with dots instead of circles, less flickery display.
Update 2: slightly improved visualization and sound.
![](/gfx/set_like0.png)
![](/gfx/top_drop.png)
Whoa this is super cool! Do you fill the audio buffer manually or how do you manage to do this? Oh and do you have somewhere where I can read about it if I want to do it myself?
![](/gfx/set_like0.png)
![](/gfx/top_drop.png)
So so cool!
THAT is my jam @luchak
I had a go at the tweetcart version and brought it down to 268bytes
x=0y=0p=0g=0::_::n=min(512-stat(108),188)for i=0,n-1do if(g%646<1)q=rnd({1,1.8,2,2.1})<<9e=.3+.1*sin(t()/6) if(g<1)k=62g=2584 k*=.999e*=.999g-=1o=p>>11o-=9*(y-o)x+=e*(o-x)y+=e*(x-y)p+=q v=y+sin((g>>10)^3)*k+64poke(i,v+64) ?"+",i,v,9 end serial(2056,0,n)?"⁶1⁶c0" goto _ |
![](/gfx/set_like0.png)
![](/gfx/top_drop.png)
@p01 Nice! I think I have some real ?
-blindness, I'll have to remember these tricks for next time. :) Glad you liked it!
Sending 1 byte at a time let me shave another 8 bytes:
x=0y=0p=0g=0::_::for i=0,min(511-stat(108),187)do if(g%646<1)q=rnd({1,1.8,2,2.1})<<9e=.3+.1*sin(t()/6) if(g<1)k=62g=2584 k*=.999e*=.999g-=1o=p>>11o-=9*(y-o)x+=e*(o-x)y+=e*(x-y)v=y+sin((g>>10)^3)*k+64poke(0,v+64)serial(2056,0,1)p+=q?"+",i,v,9 end?"⁶1⁶c0" goto _ |
![](/gfx/set_like0.png)
![](/gfx/top_drop.png)
@Wistpotion Yeah, this is using the PCM output - the last paragraph of this comment gives a summary of how to use it. Looking at the #pcm tag should also turn up a bunch more examples.
Here's an annotated version of the source (with @p01's improvements) if that helps:
x=0 y=0 -- Filter internal state p=0 -- Oscillator phase g=0 -- Samples left in beat ::_:: -- Label for start of frame -- Make sure we have at least 512 samples in the buffer, but also don't get -- too far ahead. Since we clear the screen every frame, the output will be -- very flickery if we skip synthesis too often. 5512.5/30=183.75, but I've -- found that adding a few extra samples helps avoid audio dropouts. for i=0,min(511-stat(108),187) do -- 646 samples per 16th note at 128 bpm. -- q is the oscillator phase increment, chosen from a bag of pitch ratios -- quantized to 1 decimal place. -- e is the filter envelope amount, which is modulated with an LFO. if(g%646<1)q=rnd({1,1.8,2,2.1})<<9e=.3+.1*sin(t()/6) -- 2584 samples per beat at 128 bpm. -- k is the kick drum amplitude. 62 is just what sounded best to me. if(g<1)k=62g=2584 k*=.999 e*=.999 -- apply decay to kick amplitude and filter env g-=1 -- advance sample counter o=p>>11 -- we use overflow for osc phase reset, so we need to scale down o-=9*(y-o) -- filter feedback (so we have some nice resonance) x+=e*(o-x) y+=e*(x-y) -- this filter is just 2 1-pole filters in series -- Mix in the kick drum. The cubic exponent was necessary to get a reasonable -- amount of pitch decay. 64 is added since we need offsets of 64 and 128 -- later and adding one offset here helps keep the char count down. v=y+sin((g>>10)^3)*k+64 poke(0,v+64) -- write output audio to memory serial(2056,0,1) -- send audio byte to output p+=q -- increment oscillator phase ?"+",i,v,9 -- draw oscilloscope dot end ?"⁶1⁶c0" -- end frame and clear screen goto _ -- next frame |
![](/gfx/set_like0.png)
![](/gfx/top_drop.png)
[Please log in to post a comment]