luchak [Lexaloffle Blog Feed]https://www.lexaloffle.com/bbs/?uid=24137
Little Drummer Toy
<p>
<table><tr><td>
<a href="/bbs/?pid=158930#p">
<img src="/bbs/thumbs/pico8_little_drummer_toy-7.png" style="height:256px"></a>
</td><td width=10></td><td valign=top>
<a href="/bbs/?pid=158930#p">
little_drummer_toy</a><br><br>
by <a href="/bbs/?uid=24137">
luchak</a>
<br><br><br>
<a href="/bbs/?pid=158930#p">
[Click to Play]</a>
</td></tr></table>
</p>
<p>Play music with a collection of holiday-themed samples! Or make chaotic sound collages, your choice. Export your creations and share them with others! Made for the <a href="https://www.lexaloffle.com/bbs/?tid=145806">2024 PICO-8 Advent Calendar</a>.</p>
<p>And, yes, this is a shameless demake of the <a href="https://thirtydollar.website/">Thirty Dollar Website</a>.</p>
<h2>Instructions</h2>
<p>This cart uses console controls, so use the arrows, β (x on keyboard), and π ΎοΈ (z/c on keyboard) to navigate. </p>
<p>The first two rows of icons are sounds. Press β to preview a sound, or π ΎοΈ to add it to the song.</p>
<p>Icons in the third row control playback, and can be used to change pitch or tempo, jump to different points in the song, control looping, etc.</p>
<p>The bottom row of icons contains non-song actions like play, save, and undo. It also contains a help icon if you want a reminder of this information.</p>
<p>Use β and π ΎοΈ on icons in your song for more options. β options are more navigation-related, and π ΎοΈ options are more editing-related.</p>
<h3>Saving and Loading</h3>
<p>The big blue up arrow icon lets you save songs to the clipboard. If you're doing this on the web, you'll need to also press ctrl-c to finish the process. Share your songs anywhere - like in this thread!</p>
<p>To load a song, just paste it into PICO-8. Try this one: βββββββ2ββ2ββββββββ1β-1β1ββ-1βaβ β π±</p>
<p>To reload the last song you saved or loaded, use the big blue down arrow icon. This lets you use the clipboard as a sort of checkpoint if you want.</p>
<p><em>Mac users:</em> If you're doing this in the web player on a Mac, you'll probably have to use both ctrl-c and cmd-c (in some order) to copy, and both ctrl-v and cmd-v to paste. I think ctrl-c then cmd-c to copy, and cmd-v then ctrl-v to paste should probably work? Good luck!</p>
<h3>Advanced Usage</h3>
<ul>
<li>
<p>Loops and targets: each "go to target" command will only fire once per loop. Every time you hit a loop point, all targets will refresh. You can use this behavior for complex structure in your song. Loops cannot be refreshed once they are out of iterations.</p>
</li>
<li>
<p>Chords: chord is a prefix command, that is, it comes before the sounds that should play simultaneously. (Instead of between them, like the simultaneous sound command on thirtydollar.website.) You can change the value of the chord command so it plays more or fewer sounds at once.</p>
</li>
<li>Scrolling: you should be able to use β plus up and down arrows to scroll through longer songs. I haven't tested long songs and scrolling very thoroughly, so you might encounter bugs. Please let me know if so!</li>
</ul>
<p>Also, many tips from <a href="https://www.youtube.com/watch?v=WzzFgloXoZo">this Thirty Dollar Website video</a> apply to this cart as well.</p>
<h2>Credits</h2>
<p>Thanks to <a href="https://www.lexaloffle.com/bbs/?uid=49583"> @bikibird</a> for organizing the Advent Calendar, <a href="https://www.lexaloffle.com/bbs/?uid=42184"> @SmellyFishstiks</a> for the calendar cart, <a href="https://thirtydollar.website/">https://thirtydollar.website/</a> for providing pretty much of all the ideas and structure, and <a href="https://www.lexaloffle.com/bbs/?uid=9770"> @Siapran</a> for pointing me to the Thirty Dollar Website in the first place.</p>
<h3>Sounds</h3>
<p>All sounds were trimmed, faded, normalized, and encoded with <a href="https://www.lexaloffle.com/bbs/?tid=53933">QPA</a> for use in this cart. Please feel free to re-use this attribution block if you need to credit this sound set elsewhere. Most sounds are CC0, but there are a few that are CC-BY.</p>
<ul>
<li>(snare drum) <a href="https://freesound.org/people/laffik/sounds/645211/">mapex snare 14"x7".wav</a> by <a href="https://freesound.org/people/laffik/">laffik</a> | License: <a href="http://creativecommons.org/publicdomain/zero/1.0/">Creative Commons 0</a></li>
<li>(bell) <a href="https://freesound.org/people/SamuelGremaud/sounds/572437/">SMALL BELL - 3</a> by <a href="https://freesound.org/people/SamuelGremaud/">SamuelGremaud</a> | License: <a href="http://creativecommons.org/publicdomain/zero/1.0/">Creative Commons 0</a></li>
<li>(cookie) <a href="https://freesound.org/people/black_trillium/sounds/752128/">Eating A Biscuit or Cookie</a> by <a href="https://freesound.org/people/black_trillium/">black_trillium</a> | License: <a href="http://creativecommons.org/publicdomain/zero/1.0/">Creative Commons 0</a></li>
<li>(cork) <a href="https://freesound.org/people/Kate_is_yellow/sounds/708201/">Popping the cork off a champagne bottle: Loud pop, light bubbles. Slight reverb tail. Recorded with ZoomH6, with Rifle Microphone, a compressor was used to ensure no clipping.</a> by <a href="https://freesound.org/people/Kate_is_yellow/">Kate_is_yellow</a> | License: <a href="http://creativecommons.org/publicdomain/zero/1.0/">Creative Commons 0</a></li>
<li>(ho, ho) <a href="https://freesound.org/people/SoundBiterSFX/sounds/740760/">Santa Claus - Ho ho ho, Merry Christmas!</a> by <a href="https://freesound.org/people/SoundBiterSFX/">SoundBiterSFX</a> | License: <a href="https://creativecommons.org/licenses/by/4.0/">Attribution 4.0</a></li>
<li>(jingle) <a href="https://freesound.org/people/dj997/sounds/493755/">Jingle Bells</a> by <a href="https://freesound.org/people/dj997/">dj997</a> | License: <a href="http://creativecommons.org/licenses/by/3.0/">Attribution 3.0</a></li>
<li>(unwrapping) <a href="https://freesound.org/people/221098HariPotter/sounds/655645/">Paper - single tear - 221098_AshtiHari_SD100_Term4.wav</a> by <a href="https://freesound.org/people/221098HariPotter/">221098HariPotter</a> | License: <a href="https://creativecommons.org/licenses/by/4.0/">Attribution 4.0</a></li>
<li>(glasses) <a href="https://freesound.org/people/waheegle/sounds/738633/">glasses clinking</a> by <a href="https://freesound.org/people/waheegle/">waheegle</a> | License: <a href="http://creativecommons.org/publicdomain/zero/1.0/">Creative Commons 0</a></li>
<li>(piano) <a href="https://freesound.org/people/Casonika/sounds/496393/">christmas carols on piano.wav</a> by <a href="https://freesound.org/people/Casonika/">Casonika</a> | License: <a href="https://creativecommons.org/licenses/by/4.0/">Attribution 4.0</a></li>
<li>(duck) <a href="https://freesound.org/people/Slothfully_So/sounds/685067/">Rubber Duck</a> by <a href="https://freesound.org/people/Slothfully_So/">Slothfully_So</a> | License: <a href="http://creativecommons.org/publicdomain/zero/1.0/">Creative Commons 0</a></li>
<li>(glockenspiel) <a href="https://freesound.org/people/MitchellSloan/sounds/723167/">VINNIES GLOCK C2</a> by <a href="https://freesound.org/people/MitchellSloan/">MitchellSloan</a> | License: <a href="http://creativecommons.org/publicdomain/zero/1.0/">Creative Commons 0</a></li>
<li>(ahh) <a href="https://freesound.org/people/random_intruder/sounds/392172/">AHHHH</a> by <a href="https://freesound.org/people/random_intruder/">random_intruder</a> | License: <a href="http://creativecommons.org/publicdomain/zero/1.0/">Creative Commons 0</a></li>
<li>(wow) <a href="https://freesound.org/people/NicknameLarry/sounds/487514/">Kid Says "Wow"</a> by <a href="https://freesound.org/people/NicknameLarry/">NicknameLarry</a> | License: <a href="http://creativecommons.org/publicdomain/zero/1.0/">Creative Commons 0</a></li>
<li>(wind-up) <a href="https://freesound.org/people/alexyquest42/sounds/630459/">Wind Up Toy</a> by <a href="https://freesound.org/people/alexyquest42/">alexyquest42</a> | License: <a href="https://creativecommons.org/licenses/by/4.0/">Attribution 4.0</a></li>
<li>(silverware) <a href="https://freesound.org/people/Erbsland-Music/sounds/634147/">Knife on Ceramic Plate</a> by <a href="https://freesound.org/people/Erbsland-Music/">Erbsland-Music</a> | License: <a href="http://creativecommons.org/publicdomain/zero/1.0/">Creative Commons 0</a></li>
<li>(laugh) <a href="https://freesound.org/people/Nox_Sound/sounds/752711/">Voice_Crowd_Small_Expression_Laugh_Stereo</a> by <a href="https://freesound.org/people/Nox_Sound/">Nox_Sound</a> | License: <a href="http://creativecommons.org/publicdomain/zero/1.0/">Creative Commons 0</a></li>
<li>(bass drum) <a href="https://freesound.org/people/karolist/sounds/371192/">acoustic kick.wav</a> by <a href="https://freesound.org/people/karolist/">karolist</a> | License: <a href="http://creativecommons.org/publicdomain/zero/1.0/">Creative Commons 0</a></li>
<li>(music box) <a href="https://freesound.org/people/sonically_sound/sounds/625234/">Music Box 2 (G)</a> by <a href="https://freesound.org/people/sonically_sound/">sonically_sound</a> | License: <a href="http://creativecommons.org/publicdomain/zero/1.0/">Creative Commons 0</a></li>
<li>(meow) <a href="https://freesound.org/people/Kinoton/sounds/584895/">Cat Meows 7x</a> by <a href="https://freesound.org/people/Kinoton/">Kinoton</a> | License: <a href="http://creativecommons.org/publicdomain/zero/1.0/">Creative Commons 0</a></li>
<li>(bark) <a href="https://freesound.org/people/exe2be/sounds/535458/">Husky dog barking and "talking"</a> by <a href="https://freesound.org/people/exe2be/">exe2be</a> | License: <a href="http://creativecommons.org/publicdomain/zero/1.0/">Creative Commons 0</a></li>
<li>(snow step) <a href="https://freesound.org/people/AUDIO_ANOMALIES_SOUND_DESIGN/sounds/555234/">FOLEY_SNOW FOOTSTEPS_STEREO_EQ BOOST_LIGHT COMPRESSION.wav</a> by <a href="https://freesound.org/people/AUDIO_ANOMALIES_SOUND_DESIGN/">AUDIO_ANOMALIES_SOUND_DESIGN</a> | License: <a href="http://creativecommons.org/publicdomain/zero/1.0/">Creative Commons 0</a></li>
<li>(hoofstep) <a href="https://freesound.org/people/snapssound/sounds/751204/">Horse walk_concrete_people talk</a> by <a href="https://freesound.org/people/snapssound/">snapssound</a> | License: <a href="https://creativecommons.org/licenses/by/4.0/">Attribution 4.0</a></li>
<li>(horn) <a href="https://freesound.org/people/TaranP/sounds/362183/">horn_tone_A3.wav</a> by <a href="https://freesound.org/people/TaranP/">TaranP</a> | License: <a href="https://creativecommons.org/licenses/by/4.0/">Attribution 4.0</a></li>
<li>(tambourine) <a href="https://freesound.org/people/alexanderdanner/sounds/625607/">Toy Tambourine 1 - Impacts.wav</a> by <a href="https://freesound.org/people/alexanderdanner/">alexanderdanner</a> | License: <a href="http://creativecommons.org/publicdomain/zero/1.0/">Creative Commons 0</a></li>
</ul>
https://www.lexaloffle.com/bbs/?tid=145980
https://www.lexaloffle.com/bbs/?tid=145980Sun, 08 Dec 2024 15:45:03 UTCWavepaint
<p>This probably has a bunch of bugs and I'm not sure what it's good for, but, well, here it is anyway.</p>
<p>
<table><tr><td>
<a href="/bbs/?pid=155992#p">
<img src="/bbs/thumbs/pico64_wavepaint-1.png" style="height:256px"></a>
</td><td width=10></td><td valign=top>
<a href="/bbs/?pid=155992#p">
wavepaint</a><br><br>
by <a href="/bbs/?uid=24137">
luchak</a>
<br><br><br>
<a href="/bbs/?pid=155992#p">
[Click to Play]</a>
</td></tr></table>
</p>
<h2>Controls</h2>
<ul>
<li>Left click to paint with foreground color, right click to paint with background color. Same buttons to choose colors from the palette on the right.</li>
<li>Slider on the right controls brush size.</li>
<li>"x" button at bottom of right toolbar replaces whole scene with background color.</li>
<li>Ctrl-z to undo. Undo stack size is 16. (edit: This seems to be broken on the web build for some reason?? Works okay locally.)</li>
<li>Ctrl-p to replace canvas with wave-warped canvas. This is undo-able.</li>
<li><code>x</code> key swaps foreground/background color.</li>
<li>Brush size affects wave height. </li>
</ul>
https://www.lexaloffle.com/bbs/?tid=144840
https://www.lexaloffle.com/bbs/?tid=144840Sun, 20 Oct 2024 18:29:45 UTCFilter Testbench
<p>
<table><tr><td>
<a href="/bbs/?pid=151611#p">
<img src="/bbs/thumbs/pico64_picotron_filter_testbench-0.png" style="height:256px"></a>
</td><td width=10></td><td valign=top>
<a href="/bbs/?pid=151611#p">
picotron_filter_testbench</a><br><br>
by <a href="/bbs/?uid=24137">
luchak</a>
<br><br><br>
<a href="/bbs/?pid=151611#p">
[Click to Play]</a>
</td></tr></table>
</p>
<p>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.</p>
<p>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.</p>
<p>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>
https://www.lexaloffle.com/bbs/?tid=143253
https://www.lexaloffle.com/bbs/?tid=143253Sun, 21 Jul 2024 20:53:40 UTCweird transpose/matmul interaction
<p>I was trying to compute the outer product of a column vector <code>x</code> with itself, like so:</p>
<div>
<div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px">
<table style="width:100%" cellspacing=0 cellpadding=0>
<tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td>
<td background=/gfx/code_bg0.png>
<div style="font-family : courier; color: #000000; display:absolute; padding-left:10px;
padding-top:4px; padding-bottom:4px;
">
<pre>xxt=x:matmul(x:transpose())</pre></div></td>
<td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td>
</tr></table></div></div>
<p>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>
https://www.lexaloffle.com/bbs/?tid=142745
https://www.lexaloffle.com/bbs/?tid=142745Tue, 18 Jun 2024 04:07:36 UTCVectorized FFT
<p>
<table><tr><td>
<a href="/bbs/?pid=150033#p">
<img src="/bbs/thumbs/pico64_vectorized_fft-1.png" style="height:256px"></a>
</td><td width=10></td><td valign=top>
<a href="/bbs/?pid=150033#p">
vectorized_fft</a><br><br>
by <a href="/bbs/?uid=24137">
luchak</a>
<br><br><br>
<a href="/bbs/?pid=150033#p">
[Click to Play]</a>
</td></tr></table>
</p>
<p>(This is slower after 0.1.0h but still fairly quick overall - maybe I'll update these numbers eventually.)</p>
<p>Here's some fairly fast FFT code for Picotron. It abuses the fact that <code>matmul()</code> is extremely cheap right now, and also decomposes shuffles into either matrix transposes or large-stride vector shuffles - so even if <code>matmul()</code> gets more expensive this should remain fairly efficient. A 256-point complex FFT currently takes about 1.4% CPU at 60fps.</p>
<p>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!</p>
<p>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.</p>
<p>General FFT code is in <code>fft.lua</code>, demo-specific code is in <code>main.lua</code>. 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>
https://www.lexaloffle.com/bbs/?tid=142736
https://www.lexaloffle.com/bbs/?tid=142736Sun, 16 Jun 2024 21:57:25 UTCscalar-userdata subtraction commutes
<p>Division has the same problem.</p>
<div>
<div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px">
<table style="width:100%" cellspacing=0 cellpadding=0>
<tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td>
<td background=/gfx/code_bg0.png>
<div style="font-family : courier; color: #000000; display:absolute; padding-left:10px;
padding-top:4px; padding-bottom:4px;
">
<pre>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</pre></div></td>
<td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td>
</tr></table></div></div>
https://www.lexaloffle.com/bbs/?tid=142723
https://www.lexaloffle.com/bbs/?tid=142723Sat, 15 Jun 2024 19:24:50 UTCmouselock issues on Mac
<p>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.</p>
<ol>
<li>Slow drags downward don't do anything - <code>mouselock</code> just returns 0 for <code>dy</code>, regardless of how far you drag. Slow drags upward do not have this problem.</li>
<li>Effective sensitivity is lower on Mac than on other platforms. I haven't verified this myself, but based on <a href="https://www.lexaloffle.com/bbs/?uid=5951"> @drakmaniso</a>'s description of how the sliders <em>should</em> be acting in #visitrack, this seems to be true.</li>
</ol>
https://www.lexaloffle.com/bbs/?tid=141785
https://www.lexaloffle.com/bbs/?tid=141785Wed, 17 Apr 2024 17:02:00 UTCPicotron PCM: Yet Another Lazy Procedural Acid Cart
<p>Now in stereo!</p>
<p>
<table><tr><td>
<a href="/bbs/?pid=146405#p">
<img src="/bbs/thumbs/pico64_lazy_procedural_acid_picotron-3.png" style="height:256px"></a>
</td><td width=10></td><td valign=top>
<a href="/bbs/?pid=146405#p">
lazy_procedural_acid_picotron</a><br><br>
by <a href="/bbs/?uid=24137">
luchak</a>
<br><br><br>
<a href="/bbs/?pid=146405#p">
[Click to Play]</a>
</td></tr></table>
</p>
<p>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 <code>stat()</code> 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>
<p>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.</p>
<p>IF YOU WANT TO USE PCM IN YOUR OWN CODE, here is a version of <code>pcm.lua</code> you can use (tested as of v0.1.1c, if it's not working for you please let me know):</p>
<div>
<div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px">
<table style="width:100%" cellspacing=0 cellpadding=0>
<tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td>
<td background=/gfx/code_bg0.png>
<div style="font-family : courier; color: #000000; display:absolute; padding-left:10px;
padding-top:4px; padding-bottom:4px;
">
<pre>-- Picotron PCM driver
--
--
-- 8-bit 44.1 kHz stereo PCM for Picotron
--
-- Entrypoint is pcm_init(), call it in update to set up.
--
-- You can pass {n_bufs = 4} to pcm_init for lower latency, but this will probably result in audio breakups in the web
-- player.
--
-- Uses these resources:
-- * memory from 0xe00000-0xe08000 (or more, if you want more than 8 buffers)
-- * instruments 56-63 (or more instruments, going lower as you add more buffers)
-- * sfx 63
-- * output channel 15
--
--
-- EXAMPLE
-- =======
--[[
include 'pcm.lua'
function gen_sine(n_samples)
local sound_left = userdata('f64', n_samples)
local sound_right = userdata('f64', n_samples)
for i = 0, n_samples - 1 do
sound_left[i] = sin(sine_phase_left)
sound_right[i] = sin(sine_phase_right)
sine_phase_left += 0.01
sine_phase_right += 0.015
end
-- output range is -128 to 127
return sound_left * 32, sound_right * 32
end
function _init()
sine_phase_left = 0
sine_phase_right = 0
pcm_callback, pcm_start, pcm_stop = pcm_init()
pcm_start()
end
function _update()
-- call pcm_callback() every frame until you call pcm_stop()
pcm_callback(gen_sine)
end
--]]
--
-- LICENSE
-- =======
--
-- Copyright (C) 2024 by luchak <[email protected]>
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby
-- granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
-- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
-- AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-- PERFORMANCE OF THIS SOFTWARE.
function pcm_init(options)
options = options or {}
local n_bufs = options.n_bufs or 8
local base_addr = options.base_addr or 0xe00000
local sfx_id = 63
local channel_id = 15
local sfx_spd = 2
local buf_size = 10
local buf_len = 2<<buf_size
local first_instrument=64-n_bufs
local play_queue={}
local buf_addrs_left={}
local buf_addrs_right={}
for i=0,n_bufs-1 do
local buf_addr_left = base_addr + 2*i*buf_len
local buf_addr_right = buf_addr_left + buf_len
add(buf_addrs_left, buf_addr_left)
add(buf_addrs_right, buf_addr_right)
-- fill output buffer with enough 0s to avoid startup glitches
add(play_queue, userdata('f64',buf_len))
add(play_queue, userdata('f64',buf_len))
_pcm_make_stream_instrument(first_instrument+i, buf_addr_left, buf_addr_right, buf_size)
end
_pcm_setup_sfx(sfx_id, sfx_spd, first_instrument, n_bufs)
local next_row=0
local function pcm_callback(gen_chunk)
local row = stat(400+channel_id, 9)
local tick_len = stat(400+channel_id, 21)
if #play_queue<n_bufs*2 then
local chunk_left, chunk_right = gen_chunk(sfx_spd*tick_len)
add(play_queue, chunk_left)
add(play_queue, chunk_right or chunk_left)
end
while next_row ~= row and #play_queue>0 do
local chunk_left = deli(play_queue, 1)
local chunk_right = deli(play_queue, 1)
local prev_idx = (next_row + n_bufs - 1) % n_bufs + 1
local this_idx = next_row + 1
local prev_base_left = buf_addrs_left[prev_idx]
local prev_base_right = buf_addrs_right[prev_idx]
local base_left = buf_addrs_left[this_idx]
local base_right = buf_addrs_right[this_idx]
local chunk_size = #chunk_left
poke(prev_base_left + chunk_size, chunk_left:get(0, chunk_size))
poke(prev_base_right + chunk_size, chunk_right:get(0, chunk_size))
poke(base_left, chunk_left:get(0, chunk_size))
poke(base_right, chunk_right:get(0, chunk_size))
next_row = (next_row + 1) % n_bufs
end
end
local function pcm_start()
sfx(sfx_id, channel_id)
end
local function pcm_stop(channel)
sfx(-1, channel_id)
end
return pcm_callback, pcm_start, pcm_stop
end
function _pcm_make_stream_instrument(instrument_id, wave_addr_left, wave_addr_right, wave_size)
-- instr comments here mostly copied from sfx.p64/data.lua
poke(0x40000 + (instrument_id * 512) + 0x1df, 2) -- wide
-- node 0: root
poke(0x40000 + (instrument_id * 512) + (0 * 32), -- instrument 63, node 0
0, -- parent (0x7) op (0xf0)
1, -- kind (0x0f): 1 root kind_p (0xf0): 0 -- wavetable_index
0, -- flags
0, -- unused extra
-- MVALs: kind/flags, val0, val1, envelope_index
0x0,0x40,0,0, -- volume: mult. 0x40 is max (-0x40 to invert, 0x7f to overamp)
0x1,0,0,0, -- pan: add. center
0x1,0,0,0, -- tune: +0 -- 0,48,0,0 absolute for middle c (c4) 261.6 Hz
0x1,0,0,0, -- bend: none
-- following shouldn't be in root
0x0,0,0,0, -- wave: use wave 0
0x0,0,0,0 -- phase
)
-- node 1: custom wt (left)
poke(0x40000 + (instrument_id * 512) + (1 * 32), -- instrument 63, node 1
0, -- parent (0x7) op (0xf0)
0x20|2, -- kind (0x0f): 2 osc kind_p (0xf0): 2 -- wavetable_index
0x0, -- flags
0, -- unused extra
-- MVALs: kind/flags, val0, val1, envelope_index
0x0,0x40,0,0, -- volume: mult. 0x40 is max (-0x40 to invert, 0x7f to overamp)
0x0,-128,0,0, -- pan: left
0x0,77,0,0x20|0x80, -- tune: +0 -- 0,5,0,0 absolute for f0
-- tune is quantized to semitones with 0x20
0x0,0,0,0x20|0x80, -- can fine-tune here but unsure of exact setting
0x0,0,0,0, -- wave: 0
0x0,-128,0,0 -- phase
)
-- node 2: custom wt (right)
poke(0x40000 + (instrument_id * 512) + (2 * 32), -- instrument 63, node 2
0, -- parent (0x7) op (0xf0)
0x30|2, -- kind (0x0f): 2 osc kind_p (0xf0): 3 -- wavetable_index
0x0, -- flags
0, -- unused extra
-- MVALs: kind/flags, val0, val1, envelope_index
0x0,0x40,0,0, -- volume: mult. 0x40 is max (-0x40 to invert, 0x7f to overamp)
0x0,127,0,0, -- pan: right
0x0,77,0,0x20|0x80, -- tune: +0 -- 0,5,0,0 absolute for f0
-- tune is quantized to semitones with 0x20
0x0,0,0,0x20|0x80, -- can fine-tune here but unsure of exact setting
0x0,0,0,0, -- wave: 0
0x0,-128,0,0 -- phase
)
-- wavetable 2 (node 1)
poke(0x40000 + 0x200*instrument_id + 0x1e0 + 4*2,
(wave_addr_left >> 8) & 255, -- address (low) in 256 byte increments
(wave_addr_left >> 16) & 255, -- address (high) in 64k increments
wave_size, -- samples (1 << n)
0x1 -- wt_height 256(0); wave mval points at one of the entries
)
-- wavetable 3 (node 2)
poke(0x40000 + 0x200*instrument_id + 0x1e0 + 4*3,
(wave_addr_right >> 8) & 255, -- address (low) in 256 byte increments
(wave_addr_right >> 16) & 255, -- address (high) in 64k increments
wave_size, -- samples (1 << n)
0x1 -- wt_height 256(0); wave mval points at one of the entries
)
memset(wave_addr_left, 2 << wave_size, 0)
memset(wave_addr_right, 2 << wave_size, 0)
end
function _pcm_setup_sfx(sfx_id, sfx_spd, first_instrument, n_bufs)
local base = 0x50000 + 328*sfx_id
poke2(base, 64) -- track length
poke(base + 2, sfx_spd) -- spd
poke(base + 3, 0) -- loop to 0
poke(base + 4, n_bufs) -- loop len n_bufs
poke(base + 5, 0)
poke(base + 6, 0)
for i=0,n_bufs-1 do
poke(base + 8 + i,0) -- note
poke(base + 8 + 64 + i, first_instrument + i) -- instrument
poke(base + 8 + 128 + i, 64) -- volume
end
end</pre></div></td>
<td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td>
</tr></table></div></div>
https://www.lexaloffle.com/bbs/?tid=141655
https://www.lexaloffle.com/bbs/?tid=141655Thu, 11 Apr 2024 23:12:48 UTCReappearing spam posts
<p>The poster of <a href="https://www.lexaloffle.com/bbs/?tid=141370">https://www.lexaloffle.com/bbs/?tid=141370</a> 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>
https://www.lexaloffle.com/bbs/?tid=141371
https://www.lexaloffle.com/bbs/?tid=141371Tue, 02 Apr 2024 02:34:28 UTCUserdata performance notes
<p>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.</p>
<ul>
<li>Fast ops - <code>add</code>/<code>mul</code>/<code>copy</code>/etc. - cost 1/16 cycle.</li>
<li>Slow ops - <code>div</code>/<code>convert</code>/etc. - cost 1/4 cycle.</li>
<li><code>matmul</code> 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.</li>
<li><code>copy</code> and <code>memmap</code>/<code>memcpy</code> are approximately the same speed for 64 bit datatypes. For smaller datatypes, <code>memcpy</code> is proportionally faster, though of course you then have to manage strides/spans yourself. <code>memcpy</code> should also enable <code>reinterpret_cast</code> type shenanigans.</li>
<li>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.</li>
<li>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 <code>u8</code>.</li>
<li>userdata flat access w/ locals seems to cost 1 cycle/element including the assignment.</li>
<li>userdata <code>get</code> is 1/4 cycle / element at scale ... but each explicit assignment will cost you 1 cycle on top of this.</li>
<li>userdata <code>set</code> is 1 cycle / element at scale.</li>
</ul>
<p>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 <code>pack</code> or <code>set</code>. While I'm enjoying taking advantage of this for <code>f64</code> to <code>u8</code> conversions at the same cost as <code>convert</code>, I'm worried this might not last.</p>
https://www.lexaloffle.com/bbs/?tid=141181
https://www.lexaloffle.com/bbs/?tid=141181Thu, 28 Mar 2024 06:26:22 UTCClock Life
<p>Now much more screensaver-y!</p>
<p>
<table><tr><td>
<a href="/bbs/?pid=144494#p">
<img src="/bbs/thumbs/pico64_clock_life-1.png" style="height:256px"></a>
</td><td width=10></td><td valign=top>
<a href="/bbs/?pid=144494#p">
clock_life</a><br><br>
by <a href="/bbs/?uid=24137">
luchak</a>
<br><br><br>
<a href="/bbs/?pid=144494#p">
[Click to Play]</a>
</td></tr></table>
</p>
<p>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 <del>50%</del> 30% CPU (on 0.1.1d). 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 <em>little</em> 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.</p>
<p>Color tables seem incredibly powerful. I'm curious to see what else people do with them.</p>
<h3>Changelog</h3>
<ul>
<li>0.2 - now the clock moves</li>
</ul>
https://www.lexaloffle.com/bbs/?tid=141098
https://www.lexaloffle.com/bbs/?tid=141098Tue, 26 Mar 2024 04:31:54 UTCWavepaper
<p>
<table><tr><td>
<a href="/bbs/?pid=143925#p">
<img src="/bbs/thumbs/pico64_wavepaper-0.png" style="height:256px"></a>
</td><td width=10></td><td valign=top>
<a href="/bbs/?pid=143925#p">
wavepaper</a><br><br>
by <a href="/bbs/?uid=24137">
luchak</a>
<br><br><br>
<a href="/bbs/?pid=143925#p">
[Click to Play]</a>
</td></tr></table>
</p>
<p>Please enjoy this very necessary desktop wallpaper.</p>
<p>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) <em>you may get some flickering</em>, especially if some windows overlap desktop icons.</p>
<p>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.</p>
<p>To install:</p>
<div>
<div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px">
<table style="width:100%" cellspacing=0 cellpadding=0>
<tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td>
<td background=/gfx/code_bg0.png>
<div style="font-family : courier; color: #000000; display:absolute; padding-left:10px;
padding-top:4px; padding-bottom:4px;
">
<pre>load #wavepaper
save /appdata/system/wallpapers/wavepaper.p64.png</pre></div></td>
<td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td>
</tr></table></div></div>
https://www.lexaloffle.com/bbs/?tid=140928
https://www.lexaloffle.com/bbs/?tid=140928Wed, 20 Mar 2024 03:46:47 UTCPico Ripples
<p>
<table><tr><td>
<a href="/bbs/?pid=143450#p">
<img src="/bbs/thumbs/pico64_pico_ripples-1.png" style="height:256px"></a>
</td><td width=10></td><td valign=top>
<a href="/bbs/?pid=143450#p">
pico_ripples</a><br><br>
by <a href="/bbs/?uid=24137">
luchak</a>
<br><br><br>
<a href="/bbs/?pid=143450#p">
[Click to Play]</a>
</td></tr></table>
</p>
<p>A nice little raindrops-on-a-pond type of effect. Though perhaps with more of a shifting rainbow color scheme than most ponds.</p>
<h3>Changelog</h3>
<p>v1.01: Fixed initial splash height so smaller ripples should be more visible. This, to my eye, adds some pleasing visual variety.</p>
https://www.lexaloffle.com/bbs/?tid=140778
https://www.lexaloffle.com/bbs/?tid=140778Sun, 17 Mar 2024 06:42:27 UTCPicotron Smoke Sim
<p>Updated: much higher res at 60fps, thanks to indexed userdata copies!</p>
<p>
<table><tr><td>
<a href="/bbs/?pid=143262#p">
<img src="/bbs/thumbs/pico64_picotron_smoke-2.png" style="height:256px"></a>
</td><td width=10></td><td valign=top>
<a href="/bbs/?pid=143262#p">
picotron_smoke</a><br><br>
by <a href="/bbs/?uid=24137">
luchak</a>
<br><br><br>
<a href="/bbs/?pid=143262#p">
[Click to Play]</a>
</td></tr></table>
</p>
<p>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.</p>
<p>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.</p>
<p>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>
https://www.lexaloffle.com/bbs/?tid=140717
https://www.lexaloffle.com/bbs/?tid=140717Sat, 16 Mar 2024 05:20:19 UTCTunnel effect w/ userdata
<p>
<table><tr><td>
<a href="/bbs/?pid=143044#p">
<img src="/bbs/thumbs/pico64_userdata_tunnel-0.png" style="height:256px"></a>
</td><td width=10></td><td valign=top>
<a href="/bbs/?pid=143044#p">
userdata_tunnel</a><br><br>
by <a href="/bbs/?uid=24137">
luchak</a>
<br><br><br>
<a href="/bbs/?pid=143044#p">
[Click to Play]</a>
</td></tr></table>
</p>
<p>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>
https://www.lexaloffle.com/bbs/?tid=140665
https://www.lexaloffle.com/bbs/?tid=140665Fri, 15 Mar 2024 02:39:43 UTCuserdata methods and type conversions
<p>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.</p>
<ol>
<li>
<p>Do math on <code>f64</code> 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.</p>
</li>
<li>
<p>Do math on <code>f64</code> userdata, then cast that to <code>u8</code> in a loop. This is a <em>lot</em> of time spend calling getters/setters in a loop, and may not be any better than <code>pset</code>, not sure.</p>
</li>
<li>Do math on integer userdata, then cast to <code>u8</code> using the <code>userdata:convert()</code> 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.</li>
</ol>
<p>I think my preferred resolution would be to allow <code>convert()</code> to go from <code>f64</code> to <code>u8</code>, but anything that allows any kind of math with fractional quantities, either floating-point or fixed-point, would be great. Drawing <code>f64</code> userdata with <code>spr</code> with implicit casting would be great too. Just trying to avoid explicit loops to get to <code>u8</code>....</p>
<p>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.</p>
<p>Other requests:</p>
<ul>
<li>
<p>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.</p>
</li>
<li>
<p>trig would be amazing but I assume that's probably off-limits, or at least would be quite expensive.</p>
</li>
<li>Even more amazing would be the ability to index userdata with userdata (and at this point built-in trig is probably not necessary)</li>
</ul>
https://www.lexaloffle.com/bbs/?tid=140654
https://www.lexaloffle.com/bbs/?tid=140654Thu, 14 Mar 2024 23:31:55 UTC(outdated) instrument editor confusion
<p>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.</p>
<p>Is there any way to flip the sign of this modulation? Am I thinking about this the wrong way?</p>
https://www.lexaloffle.com/bbs/?tid=140648
https://www.lexaloffle.com/bbs/?tid=140648Thu, 14 Mar 2024 20:25:34 UTCWave Designer
<p>This is a tool to allow both time-domain and frequency-domain editing of the new custom sfx waveforms.</p>
<p>
<table><tr><td>
<a href="/bbs/?pid=142178#p">
<img src="/bbs/thumbs/pico8_wave_designer-5.png" style="height:256px"></a>
</td><td width=10></td><td valign=top>
<a href="/bbs/?pid=142178#p">
wave_designer</a><br><br>
by <a href="/bbs/?uid=24137">
luchak</a>
<br><br><br>
<a href="/bbs/?pid=142178#p">
[Click to Play]</a>
</td></tr></table>
</p>
<p><strong>Note for existing users:</strong> use the buttons at the top-right to import/export to clipboard, it doesn't happen automatically on every change now.</p>
<p>Controls:</p>
<ul>
<li>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.</li>
<li>Left/right arrow: switch between a bunch of preset waves.</li>
<li>Tracker keys: play notes!</li>
</ul>
<p>Buttons:</p>
<ul>
<li>Play/pause</li>
<li>Undo and redo</li>
<li>Set phase to 0 or 0.25 (<code>sin</code> and <code>cos</code> buttons)</li>
<li>Keep odd harmonics only (<code>odd</code> button)</li>
</ul>
<p>Features:</p>
<ul>
<li>Time and frequency domain editing, always kept in sync.</li>
<li>Mouse drags are interpolated so fast drags work as expected.</li>
<li>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.</li>
<li>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).</li>
<li>NOTE: if you are using the web version on a Mac, you will have to press some combination of <em>both</em> cmd-C and ctrl-C to copy, and <em>both</em> 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.</li>
</ul>
<p>Limitations:</p>
<ul>
<li>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.</li>
</ul>
<p>Feedback is welcome!</p>
https://www.lexaloffle.com/bbs/?tid=140438
https://www.lexaloffle.com/bbs/?tid=140438Thu, 29 Feb 2024 04:59:40 UTCsfxp - sfx design tool and generator
<p>Want sfx fast? sfxp can help! Adjust the sliders for new sounds, or click "random" for instant inspiration.</p>
<p>
<table><tr><td>
<a href="/bbs/?pid=134424#p">
<img src="/bbs/thumbs/pico8_sfxp-1.png" style="height:256px"></a>
</td><td width=10></td><td valign=top>
<a href="/bbs/?pid=134424#p">
sfxp</a><br><br>
by <a href="/bbs/?uid=24137">
luchak</a>
<br><br><br>
<a href="/bbs/?pid=134424#p">
[Click to Play]</a>
</td></tr></table>
</p>
<p>Controls:</p>
<ul>
<li>click/drag sliders and buttons</li>
<li>right click the random button to mutate the current sound instead of fully randomizing it</li>
<li>z/x to replay the current sound</li>
<li>left arrow to undo</li>
<li>right arrow to redo</li>
</ul>
<p>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.</p>
<p>You can also find <a href="https://luchak.itch.io/sfxp">sfxp on Itch</a>.</p>
<p>Made for the PICO-1K Jam.</p>
<p>Update 2023-09-20: undo bug fixed and redo added.</p>
https://www.lexaloffle.com/bbs/?tid=54142
https://www.lexaloffle.com/bbs/?tid=54142Sat, 16 Sep 2023 08:24:00 UTCQPA: Lossy Audio Compression for PICO-8
<p>I am pleased to announce Questionable PICO-8 Audio (QPA), a compressed audio format for PICO-8.</p>
<p>(Compatibility note: if you encoded any QPA files before 2023-09-07, please see the Changes section below.)</p>
<h2>DON'T YOU LECTURE ME WITH YOUR 30K PICO-8 CART</h2>
<p>Oh, but I will!</p>
<p>
<table><tr><td>
<a href="/bbs/?pid=133755#p">
<img src="/bbs/thumbs/pico8_qpa_demo-0.png" style="height:256px"></a>
</td><td width=10></td><td valign=top>
<a href="/bbs/?pid=133755#p">
qpa_demo</a><br><br>
by <a href="/bbs/?uid=24137">
luchak</a>
<br><br><br>
<a href="/bbs/?pid=133755#p">
[Click to Play]</a>
</td></tr></table>
</p>
<p>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 <em>before</em> the thing they modify. Hopefully you can figure out the rest!</p>
<p>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.</p>
<p>You can also check out this song by me:</p>
<div>
<div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px">
<table style="width:100%" cellspacing=0 cellpadding=0>
<tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td>
<td background=/gfx/code_bg0.png>
<div style="font-family : courier; color: #000000; display:absolute; padding-left:10px;
padding-top:4px; padding-bottom:4px;
">
<pre>7β¬οΈβ½0β¬οΈβ½β½β½β¬οΈβ½β¬οΈβ½3β¬οΈβΆβ€μβ¬οΈ7β0β¬οΈΛΛβͺβͺβͺβ‘∧7β¬οΈ∧7β¬οΈ7β¬οΈ∧</pre></div></td>
<td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td>
</tr></table></div></div>
<p>Or this much better song by <a href="https://www.lexaloffle.com/bbs/?uid=40166"> <a href="https://www.lexaloffle.com/bbs/?uid=40166"> @packbat</a></a>:</p>
<div>
<div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px">
<table style="width:100%" cellspacing=0 cellpadding=0>
<tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td>
<td background=/gfx/code_bg0.png>
<div style="font-family : courier; color: #000000; display:absolute; padding-left:10px;
padding-top:4px; padding-bottom:4px;
">
<pre>β‘οΈβΆβ7ββΆ…6βββΆβ7ββΆ…6ββ βΆβ7ββΆ…6βββΆπ7ββΆβ3ββ¬οΈβΆβ3ββ¬οΈβΆβ7ββΆ…6βββΆβ7ββΆ…6ββ βΆβ7ββΆ…6ββ¬οΈβ β¬οΈβ¬οΈβ¬οΈβ¬οΈβΆβ 7ββΆβ₯β₯β¬οΈ2β∧</pre></div></td>
<td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td>
</tr></table></div></div>
<p>(<a href="https://thirtydollar.website/">The reference</a>, if you're not familiar)</p>
<h2>Why QPA?</h2>
<p>Because you want to put fun sound samples in your carts, but don't want to use too many tokens or too much space!</p>
<p>QPA provides three main benefits:</p>
<ul>
<li>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.</li>
<li>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.</li>
<li>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.</li>
</ul>
<p>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!</p>
<h2>What Is QPA?</h2>
<p>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 <a href="https://phoboslab.org/log/2023/02/qoa-time-domain-audio-compression">this introduction to QOA</a>. QPA changes some details, but the basic structure remains the same.</p>
<p>So what changed to put the PICO in QPA?</p>
<ul>
<li><strong>Multiple quality levels.</strong> 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.</li>
<li><strong>Minimal metadata.</strong> 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.</li>
<li><strong>8-bit audio only.</strong> QPA assumes an 8-bit output resolution, and is tuned to avoid single-bit output noise.</li>
<li><strong>PICO-8 numbers.</strong> 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.</li>
<li><strong>Smarter encoder.</strong> 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.</li>
</ul>
<h2>Resources</h2>
<p>To use QPA in your own carts, please check out the following resources:</p>
<ul>
<li><a href="https://www.lexaloffle.com/bbs/?uid=49583"> <a href="https://www.lexaloffle.com/bbs/?uid=49583"> @bikibird</a></a>'s <a href="https://bikibird.itch.io/defy">Defy encoder</a> and <a href="https://www.lexaloffle.com/bbs/?tid=47089">Defy cart</a>. You can use these to encode and play back your own audio files.</li>
<li>The <a href="https://raw.githubusercontent.com/luchak/qpa-format/main/pico8/qpa_util.p8">QPA utility cart</a>. This cart makes it easy to convert QPA files to strings, and contains low-token decoding functions to copy into your own carts.</li>
<li>The <a href="https://github.com/luchak/qpa-format/">QPA repository on Github</a>. Here you can find more documentation, a format spec, and a CLI encoder/decoder for QPA files. (CLI requires Node and NPM.)</li>
</ul>
<h2>Changes</h2>
<p>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.</p>
<h2>Future Work</h2>
<p>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.</p>
<p>But first - I think it's time to use QPA to build a tracker. No promises on release date!</p>
<h2>Acknowledgements</h2>
<ul>
<li>Dominic Szablewski for the QOA audio format</li>
<li><a href="https://www.lexaloffle.com/bbs/?uid=40166"> <a href="https://www.lexaloffle.com/bbs/?uid=40166"> @packbat</a></a> for discussions and a whole lot of listening tests</li>
<li><a href="https://www.lexaloffle.com/bbs/?uid=49583"> <a href="https://www.lexaloffle.com/bbs/?uid=49583"> @bikibird</a></a> for Defy and being willing to integrate QPA</li>
<li>ferris for providing design feedback and pointers for future work</li>
<li>MissMouse for the demo cart suggestion</li>
<li><a href="https://www.lexaloffle.com/bbs/?uid=25898"> @NerdyTeachers</a> and <a href="https://www.lexaloffle.com/bbs/?uid=66501"> @Heracleum</a> for help with the cookie/coffee test</li>
<li>The entire #music-sfx channel on the Discord for discussions and for putting up with a whole lot of QPA chatter</li>
</ul>
https://www.lexaloffle.com/bbs/?tid=53933
https://www.lexaloffle.com/bbs/?tid=53933Thu, 31 Aug 2023 01:00:55 UTC