jaxter184
https://wiki.jaxter184.net
ZolaenWed, 26 Feb 2025 00:00:00 +0000Monthly Log 2025Wed, 26 Feb 2025 00:00:00 +0000Unknown
https://wiki.jaxter184.net/log/2025/
https://wiki.jaxter184.net/log/2025/<h2 id="01-january">01 - January</h2>
<ul>
<li>Learned to play the first bit of <a href="https://soundcloud.com/street/verrat-ep">Verrat</a> on the <a href="/mus-tec/hxcstr">HXCSTR</a>
(the piano part in the first 20 seconds).
Surprisingly easy! The rest has been much harder. TODO: upload a quick video</li>
<li>Started working with a designer to make crofu look less ugly. Their current progress is available at
<a href="https://ghostcatte.codeberg.page/crofu-design/">https://ghostcatte.codeberg.page/crofu-design/</a></li>
<li>Started working on updating <a href="https://github.com/TheLostLambda/knus"><code>knus</code></a> (forked from the unmaintained <code>knuffel</code>)
to KDL 2.0.
Keep up with my progress <a href="https://github.com/TheLostLambda/knus/pull/21">here</a>.
I've still got a long way to go, but a lot of the "main" features are implemented (bare identifier string arguments,
new keywords).
I'm still a bit bummed that <a href="https://github.com/kdl-org/kdl/discussions/494">the proposal to remove <code>/</code> from reserved identifier
characters</a> didn't pan out, but such is life.</li>
<li>tlature (apparently people pronounce it as "lature" when I format it as ↹lature)
<ul>
<li>Bumped <code>kdl</code> dependency to 6.0.0 (implementing KDL 2.0)</li>
<li>Lots of refactoring, as usual</li>
<li>Added some audio and event metering functionality</li>
<li>Added a plugin logger view</li>
<li>Wrote my first proc macro to make a "splittable" trait derive that implements a trait allowing a structure to be
split into main and audio thread versions.</li>
</ul>
</li>
</ul>
<h2 id="02-february">02 - February</h2>
<ul>
<li>Been exploring a game concept for a turn-based tower defence.
Explored a couple different game engines because I was tired of bevy compile times.
Ended up settling on macroquad.
Revived tacticum because it seemed to be a good fit.</li>
<li>more crofu legal studying</li>
<li>built a cheese press</li>
<li>made pho and kkwabaegi</li>
<li>got a bowl at a local makers market</li>
</ul>
Making crofuWed, 25 Dec 2024 00:00:00 +0000Unknown
https://wiki.jaxter184.net/icc/crofu/making-of/
https://wiki.jaxter184.net/icc/crofu/making-of/<p>crofu is a platform designed to test out <a href="/icc">incremental converse crowdfunding</a>.</p>
<h2 id="databases">Databases</h2>
<p>Databases have been a new thing that I've had to learn for crofu.
I've got a lot of experience in static webdev (mostly from this wiki), but I wasn't able to think of a good way to
implement crofu as a static site.
It seemed like even the very bare minimum implementation of crofu would require a web API backed by some sort of
database.</p>
<p>On the other hand, making the decision to just make a dynamic website has saved me a lot of weird mental gymnastics I
otherwise would've had to do.
Accounts and login?
Just stick em in the database.
Fediverse integration?
Database.
How do I securely generate page layouts?
Load from the database client-side.
API?
Just a thin wrapper around the database.
I can see now why web developers are the way they are: everything is just so easy when you give up on performance and
simplicity.</p>
<p>Right now, I'm using Postgres.
I don't remember why, I think someone just recommended it at some point and I just haven't run into any issues with it.
Some aspects of SurrealDB seemed cool, but I really don't trust the project, and it seems like it would've been a
hassle to use.</p>
<h2 id="note-everything-below-this-line-is-outdated-i-m-now-using-axum">NOTE: everything below this line is outdated. I'm now using axum.</h2>
<!--
this article is really dry. i think its because i basically go
step-by-step through my process, and its just a "this happened, then
this happened, so i did this, then this happened" sort of process with
no narrative through-line. is it even worth writing such an article?
maybe shorten it to its minimal length, and just include it in the
monthly log.
-->
<p>Web development has historically disgusted me.
So many frameworks, so much cruft spread across various W3C standards,
so much to know about, and an unappealing problem space.
That, combined with cultural elements of corporate minimalism, the rise
of Elektron-based webapps, and the infamous "move fast, break things"
approach have kept me mostly in the realm of desktop and embedded
software.
Which isn't to say I've never made a website. In fact, some of the
earliest software I've ever made were websites (the <em>actual</em> earliest
are probably games, but the timeline is hazy and the definition can
sometimes be blurry).</p>
<p>One of the projects that's been on my frontburner recently is
<a href="/icc">incremental converse crowdfunding</a>.
This is one of the few projects I've worked on that practically
<em>requires</em> a dynamic website.
I considered implementing it with email, or ActivityPub, or a TUI that
connects to a REST backend, but while none of those would require a
web frontend, I think it would be a challenge to make them comfortable
for a non-technical audience to use, and since I have to use web
technologies anyway, might as well just make a regular website.</p>
<p>I've been putting off actually working on it, out of a fear
of certificate authorities, nginx, DDOS attacks, authentication
(especially password storage), and handling money, just to name the
big ones.
But a friend recently made a website where he plans to sell software
directly to his customers (rather than through Gumroad, which is what he
currently uses).
Software politics aside, it was interesting to me that he was doing this
because it seemed pretty slick, and he seemed to lack the fear that I
had with my own website.
I assumed this was based on the fact that he was using a bunch of
existing services and frameworks, and the part of his stack that caught
my attention was Supabase.
I eventually realized that it didn't actually solve most of the more
prominent issues I was worried about, but at the time, the particular
features of Supabase that seemed appealing to me were that it handled
authentication, had a free tier, and was self-hostable.</p>
<p>Currently, I use <a href="https://rocket.rs">Rocket</a> for my entire stack.
I initially started with <a href="https://yew.rs">Yew</a>, before realizing that
it's intended for frontends.
I might return to it later if I figure out that the frontend is
actually something where I need to have a lot of dynamic stuff going
on, because under no condition am I going to write any JavaScript or
TypeScript.</p>
<p>Without a cloud service like Supabase, my options are pretty
limited.
Either I can roll my own authentication (almost certainly a bad idea,
if years of pwns are any indicator), or I can hook into an existing
website's authentication, through something like OAuth.
One such standard that came up in my research was
<a href="https://indieauth.net/">IndieAuth</a>, which, as I understand it, runs
on top of OAuth to log people into sites using their logins from, for
example, their social media accounts.
This is an improvement from OpenID Connect, which requires each website
to explicitly allow logins from each provider it chooses to support.
Twitter used to support it, but after the API closures, it no longer
works.
There are some smaller tools listed on
the IndieAuth website, including a plugin for
<a href="https://indieweb.org/Wordpress_IndieAuth_Plugin">WordPress</a>, but the
biggest IndieAuth provider is probably GitHub.
I think moving forward, Fediverse projects will probably start to
support it, but right now, support for IndieAuth is pretty sparse.
That being said, it seems to be my best option at the moment.</p>
<p><em>to be continued</em></p>
<!--
* indieauth
* https://indieauth.com/
* https://aaronparecki.com/2021/04/13/26/indieauth
* https://indieweb.org/IndieAuth
* https://aaronparecki.com/2018/07/07/7/oauth-for-the-open-web
* https://brainbaking.com/post/2021/03/the-indieweb-mixed-bag/
* https://docs.rs/indieweb/latest/indieweb/standards/indieauth/index.html
* databases
* using the ko-fi api
-->
🏮 PS06 - Impulse RegulatorSat, 14 Dec 2024 00:00:00 +0000Unknown
https://wiki.jaxter184.net/3d/ps06/
https://wiki.jaxter184.net/3d/ps06/<figure >
<img
class="fig"
src="/images/ps-06-header.jpg"
alt="close-up image of a sculpture featuring an LED ring, being refracted by a clear tube and chaotically reflected off the inner walls of the cube enclosing it. Thin steel cables are faintly visible coming out of the black plastic corners of the cube."
>
</figure>
<p>I've almost certainly forgotten something, so feel free to email me for clarification (or if you want me to build you
something): <a href="mailto:[email protected]?subject=Shiny%20Cube%20Sculpture">[email protected]</a></p>
<h2 id="build-guide">Build guide</h2>
<h3 id="laser-cut-3mm-clear-acrylic">Laser cut 3mm clear acrylic</h3>
<ul>
<li>Export the laser cutting files from <a href="/files/06-panel.FCStd" download><code>06-panel.FCStd</code></a>
<ul>
<li>Select the <code>Page</code> in the tree view</li>
<li>File -> Export (Ctrl+E)</li>
<li>Filetype: Technical Drawing (*.svg)</li>
<li>Save</li>
</ul>
</li>
<li>Might need to be manually cleaned up</li>
<li>6 pieces</li>
<li>Thanks to the friends who let me borrow their laser cutter</li>
</ul>
<h3 id="one-way-tint-film"><a href="https://www.ebay.com/itm/375391865439">One-way tint film</a></h3>
<ul>
<li>This particular product was shipped without a cardboard roll, or any outside protection, so there were some scuffs
and wrinkles</li>
<li>Is often adhesive backed, even if it doesn't seem like it</li>
<li>Spritz down some water while applying, it makes bubbles easier to move, and doesn't affect the adhesive as far as I
can tell</li>
<li>Apply to acrylic a bit too large, then cut away the excess</li>
<li>The holes are a little weird, but you can just roughly cut inside them with a craft knife</li>
</ul>
<h3 id="3d-printed-corner-brackets">3D-printed corner brackets</h3>
<ul>
<li>Export from <a href="/files/06-corner.FCStd" download><code>06-corner.FCStd</code></a></li>
<li>Print with the inner side down, outer corner facing up</li>
<li>Get the ethernet hole version by setting the tip to <code>Pocket008</code> (in the treeview right click menu as <code>Set tip</code>)</li>
<li>May need to use a drill to clean up the holes depending on your printer</li>
<li>I used PETG, but most "regular" materials are probably fine</li>
</ul>
<h3 id="bits-and-bobs">Bits and bobs</h3>
<ul>
<li><a href="https://www.aliexpress.us/item/3256804148366694.html">Ethernet keystone jack</a></li>
<li><a href="https://www.aliexpress.us/item/3256805702729664.html">LED Ring (80mm outer diameter)</a></li>
<li><a href="https://www.aliexpress.us/item/3256805513296299.html">T Plates</a></li>
<li>M3*8mm machine screws (to attach panels to corners)</li>
<li>M5*8mm machine screws (for aluminum extrusion)</li>
<li><a href="https://www.aliexpress.us/item/3256802092736214.html">M3 eye bolts</a></li>
<li><a href="https://www.aliexpress.us/item/3256804244839839.html">M3*OD5mm*6mm Heat-set inserts</a></li>
<li><a href="https://www.aliexpress.us/item/3256805435856022.html">M3 and M5 Slot T-nuts</a></li>
<li><a href="https://www.aliexpress.us/item/3256805290944526.html">2mm*125mm Steel rods</a></li>
<li><a href="https://www.aliexpress.us/item/3256805090996111.html">ID80mm*100mm Acrylic tube</a>
<ul>
<li>TODO: drilling procedure</li>
</ul>
</li>
<li><a href="https://www.aliexpress.us/item/3256806163927298.html">0.5mm cable</a></li>
<li><a href="https://www.aliexpress.us/item/3256806083804441.html">Cable clamps</a> (I just bought the smallest ones I could find)
<ul>
<li>I also used crimp-style ones, but they require a special tool, so if you're just doing a one-off, it's probably
better to just use the screw-style ones</li>
</ul>
</li>
<li>Aluminum extrusion
<ul>
<li>TODO: lengths</li>
</ul>
</li>
</ul>
<p>TODO: document electrical crimes</p>
December Adventure 2024Sun, 01 Dec 2024 00:00:00 +0000Unknown
https://wiki.jaxter184.net/log/dec-adv-2024/
https://wiki.jaxter184.net/log/dec-adv-2024/<p>Time for my second annual <a href="https://eli.li/december-adventure">December Adventure</a>!</p>
<p><a href="/log/dec-adv-2023">Last year</a>, I did some work on <a href="https://git.sr.ht/~jaxter184/tlature">↹lature</a>, and I learned a lot about which parts of December Adventure work for me
(and which don't).
I go over why I enjoy December Adventure so much in the <a href="/log/dec-adv-2023#retrospective">"retrospective" section</a> of the article, but in short, I think
the main benefits are feeling free to spend as little time as you want on it (as long as you do <em>something</em>) and having
the flexibility to go in whatever direction you feel motivated to go.</p>
<p>I encourage y'all to check out other participants as well!
There's a pretty comprehensive list at <a href="https://eli.li/december-adventure#section">https://eli.li/december-adventure#section</a>, and the <code>#DecemberAdventure</code>
hashtag is usually pretty busy this time of year (mostly on the Fediverse, but maybe BlueSky has some going on too?).</p>
<h2 id="this-year-my-project-is">This year, my project is:</h2>
<div>
<a href="/icc/crofu">
<div class="list-entry outer">
<span class="link">crofu</span>
- Incremental converse crowdfunding platform
</div>
</a>
</div>
<p>In short, crofu is a weird crowdfunding platform I've been working on for the last two months (and ideating for the
last two years), and it happens to be the project I have the most momentum on right now.
While it would be cool to work on ↹lature again, I think it would be really good synergy to use ↹lature development
as a proof-of-concept/<a href="https://en.wikipedia.org/wiki/Eating_your_own_dog_food">dogfooding</a> opportunity for crofu, so I'm trying to work to get crofu in at least a testable
state before I move on to more ↹lature stuff.</p>
<h2 id="day-1">Day 1</h2>
<p>I forgot December Adventure was starting until like the night before the first, so I was in the middle of a refactor.
I was gonna finish it up today, but I got distracted with some type safety improvements around IDs.
Pretty much everything in the database (Profiles, projects, tasks) is indexed by a UUID, and I want to prevent myself
from using the wrong UUID in the wrong situation, so I wrapped each of them with a distinct type (for example: <code>struct IdProject(uuid::Uuid)</code>) and impl'd a bunch of traits for them using a macro.</p>
<pre data-lang="rust" class="language-rust z-code"><code class="language-rust" data-lang="rust"><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-support z-function z-rust">macro_rules!</span> <span class="z-entity z-name z-macro z-rust">id_type</span> <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span></span></span><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust">
</span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"> <span class="z-meta z-group z-macro-matcher z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-variable z-parameter z-macro z-rust">$name</span><span class="z-punctuation z-separator z-rust">:</span><span class="z-storage z-type z-rust">ident</span><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-keyword z-operator z-rust">=></span> <span class="z-meta z-block z-macro-body z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust"> <span class="z-meta z-annotation z-rust"><span class="z-punctuation z-definition z-annotation z-rust">#</span><span class="z-punctuation z-section z-group z-begin z-rust">[</span><span class="z-variable z-annotation z-rust">derive</span><span class="z-meta z-annotation z-parameters z-rust"><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span></span><span class="z-meta z-annotation z-parameters z-rust"><span class="z-meta z-group z-rust">sqlx::Type<span class="z-punctuation z-separator z-rust">,</span> serde::Serialize</span></span><span class="z-meta z-annotation z-parameters z-rust"><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span></span><span class="z-punctuation z-section z-group z-end z-rust">]</span></span>
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust"> <span class="z-comment z-line z-double-slash z-rust"><span class="z-punctuation z-definition z-comment z-rust">//</span> This lets me use this type directly when interfacing with `sqlx`
</span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust"> <span class="z-comment z-line z-double-slash z-rust"><span class="z-punctuation z-definition z-comment z-rust">//</span> instead of having to unwrap it with `id.0` every time
</span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust"> <span class="z-meta z-annotation z-rust"><span class="z-punctuation z-definition z-annotation z-rust">#</span><span class="z-punctuation z-section z-group z-begin z-rust">[</span><span class="z-variable z-annotation z-rust">sqlx</span><span class="z-meta z-annotation z-parameters z-rust"><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span></span><span class="z-meta z-annotation z-parameters z-rust"><span class="z-meta z-group z-rust">transparent</span></span><span class="z-meta z-annotation z-parameters z-rust"><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span></span><span class="z-punctuation z-section z-group z-end z-rust">]</span></span>
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust"> <span class="z-meta z-annotation z-rust"><span class="z-punctuation z-definition z-annotation z-rust">#</span><span class="z-punctuation z-section z-group z-begin z-rust">[</span><span class="z-variable z-annotation z-rust">derive</span><span class="z-meta z-annotation z-parameters z-rust"><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span></span><span class="z-meta z-annotation z-parameters z-rust"><span class="z-meta z-group z-rust">Debug<span class="z-punctuation z-separator z-rust">,</span> Clone<span class="z-punctuation z-separator z-rust">,</span> Copy<span class="z-punctuation z-separator z-rust">,</span> PartialEq<span class="z-punctuation z-separator z-rust">,</span> Eq</span></span><span class="z-meta z-annotation z-parameters z-rust"><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span></span><span class="z-punctuation z-section z-group z-end z-rust">]</span></span>
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust"> <span class="z-meta z-struct z-rust"><span class="z-storage z-modifier z-rust">pub</span> <span class="z-storage z-type z-struct z-rust">struct</span> </span><span class="z-variable z-other z-rust">$name</span>(<span class="z-meta z-struct z-rust"><span class="z-entity z-name z-struct z-rust">Uuid</span>)</span><span class="z-punctuation z-terminator z-rust">;</span>
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust">
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust"> <span class="z-comment z-line z-double-slash z-rust"><span class="z-punctuation z-definition z-comment z-rust">//</span> This is necessary beacuse I use the id sometimes when generating HTML, mostly in forms.
</span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust"> <span class="z-meta z-impl z-rust"><span class="z-storage z-type z-impl z-rust">impl</span> </span><span class="z-meta z-impl z-rust"><span class="z-meta z-path z-rust">std<span class="z-punctuation z-accessor z-rust">::</span></span><span class="z-meta z-path z-rust">fmt<span class="z-punctuation z-accessor z-rust">::</span></span>Display <span class="z-keyword z-other z-rust">for</span></span><span class="z-meta z-impl z-rust"> <span class="z-variable z-other z-rust">$name</span> </span><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"> <span class="z-meta z-function z-rust"><span class="z-meta z-function z-rust"><span class="z-storage z-type z-function z-rust">fn</span> </span><span class="z-entity z-name z-function z-rust">fmt</span></span><span class="z-meta z-generic z-rust"><span class="z-punctuation z-definition z-generic z-begin z-rust"><</span><span class="z-storage z-modifier z-lifetime z-rust">'a</span><span class="z-punctuation z-definition z-generic z-end z-rust">></span></span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-begin z-rust">(</span><span class="z-keyword z-operator z-rust">&</span><span class="z-variable z-parameter z-rust">self</span>, <span class="z-variable z-parameter z-rust">f</span><span class="z-punctuation z-separator z-rust">:</span> <span class="z-keyword z-operator z-rust">&</span><span class="z-storage z-modifier z-rust">mut</span> <span class="z-meta z-path z-rust">std<span class="z-punctuation z-accessor z-rust">::</span></span><span class="z-meta z-path z-rust">fmt<span class="z-punctuation z-accessor z-rust">::</span></span><span class="z-meta z-generic z-rust">Formatter<span class="z-punctuation z-definition z-generic z-begin z-rust"><</span><span class="z-storage z-modifier z-lifetime z-rust">'a</span><span class="z-punctuation z-definition z-generic z-end z-rust">></span></span></span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-end z-rust">)</span></span></span></span><span class="z-meta z-function z-rust"> <span class="z-meta z-function z-return-type z-rust"><span class="z-punctuation z-separator z-rust">-></span> <span class="z-meta z-generic z-rust"><span class="z-support z-type z-rust">Result</span><span class="z-punctuation z-definition z-generic z-begin z-rust"><</span><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-punctuation z-section z-group z-end z-rust">)</span>, <span class="z-meta z-path z-rust">std<span class="z-punctuation z-accessor z-rust">::</span></span><span class="z-meta z-path z-rust">fmt<span class="z-punctuation z-accessor z-rust">::</span></span>Error<span class="z-punctuation z-definition z-generic z-end z-rust">></span></span></span> </span><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> <span class="z-support z-macro z-rust">write!</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust">f,</span><span class="z-meta z-group z-rust"> <span class="z-string z-quoted z-double z-rust"><span class="z-punctuation z-definition z-string z-begin z-rust">"</span><span class="z-constant z-other z-placeholder z-rust">{}</span><span class="z-punctuation z-definition z-string z-end z-rust">"</span></span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-separator z-rust">,</span> <span class="z-variable z-language z-rust">self</span><span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-constant z-numeric z-integer z-decimal z-rust">0</span><span class="z-punctuation z-section z-group z-end z-rust">)</span></span>
</span></span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span></span>
</span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span></span>
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust">
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust"> <span class="z-meta z-impl z-rust"><span class="z-storage z-type z-impl z-rust">impl</span> </span><span class="z-meta z-impl z-rust"><span class="z-meta z-generic z-rust"><span class="z-support z-type z-rust">From</span><span class="z-punctuation z-definition z-generic z-begin z-rust"><</span>Uuid<span class="z-punctuation z-definition z-generic z-end z-rust">></span></span> <span class="z-keyword z-other z-rust">for</span></span><span class="z-meta z-impl z-rust"> <span class="z-variable z-other z-rust">$name</span> </span><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"> <span class="z-meta z-function z-rust"><span class="z-meta z-function z-rust"><span class="z-storage z-type z-function z-rust">fn</span> </span><span class="z-entity z-name z-function z-rust">from</span></span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-begin z-rust">(</span><span class="z-variable z-parameter z-rust">uuid</span><span class="z-punctuation z-separator z-rust">:</span> Uuid</span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-end z-rust">)</span></span></span></span><span class="z-meta z-function z-rust"> <span class="z-meta z-function z-return-type z-rust"><span class="z-punctuation z-separator z-rust">-></span> <span class="z-storage z-type z-rust">Self</span></span> </span><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> <span class="z-storage z-type z-rust">Self</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>uuid</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span>
</span></span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span></span>
</span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span></span>
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust">
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust"> <span class="z-meta z-impl z-rust"><span class="z-storage z-type z-impl z-rust">impl</span> </span><span class="z-meta z-impl z-rust"><span class="z-meta z-generic z-rust"><span class="z-support z-type z-rust">From</span><span class="z-punctuation z-definition z-generic z-begin z-rust"><</span><span class="z-variable z-other z-rust">$name</span><span class="z-punctuation z-definition z-generic z-end z-rust">></span></span> <span class="z-keyword z-other z-rust">for</span></span><span class="z-meta z-impl z-rust"> <span class="z-entity z-name z-impl z-rust">Uuid</span> </span><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"> <span class="z-meta z-function z-rust"><span class="z-meta z-function z-rust"><span class="z-storage z-type z-function z-rust">fn</span> </span><span class="z-entity z-name z-function z-rust">from</span></span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-begin z-rust">(</span><span class="z-variable z-parameter z-rust">id</span><span class="z-punctuation z-separator z-rust">:</span> <span class="z-variable z-other z-rust">$name</span></span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-end z-rust">)</span></span></span></span><span class="z-meta z-function z-rust"> <span class="z-meta z-function z-return-type z-rust"><span class="z-punctuation z-separator z-rust">-></span> Uuid</span> </span><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> id<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-constant z-numeric z-integer z-decimal z-rust">0</span>
</span></span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span></span>
</span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span></span>
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust">
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust"> <span class="z-comment z-line z-double-slash z-rust"><span class="z-punctuation z-definition z-comment z-rust">//</span> these let me check for equality between this wrapper type and a bare UUID
</span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust"> <span class="z-meta z-impl z-rust"><span class="z-storage z-type z-impl z-rust">impl</span> </span><span class="z-meta z-impl z-rust"><span class="z-meta z-generic z-rust"><span class="z-support z-type z-rust">PartialEq</span><span class="z-punctuation z-definition z-generic z-begin z-rust"><</span>Uuid<span class="z-punctuation z-definition z-generic z-end z-rust">></span></span> <span class="z-keyword z-other z-rust">for</span></span><span class="z-meta z-impl z-rust"> <span class="z-variable z-other z-rust">$name</span> </span><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"> <span class="z-meta z-function z-rust"><span class="z-meta z-function z-rust"><span class="z-storage z-type z-function z-rust">fn</span> </span><span class="z-entity z-name z-function z-rust">eq</span></span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-begin z-rust">(</span><span class="z-keyword z-operator z-rust">&</span><span class="z-variable z-parameter z-rust">self</span>, <span class="z-variable z-parameter z-rust">uuid</span><span class="z-punctuation z-separator z-rust">:</span> <span class="z-keyword z-operator z-rust">&</span>Uuid</span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-end z-rust">)</span></span></span></span><span class="z-meta z-function z-rust"> <span class="z-meta z-function z-return-type z-rust"><span class="z-punctuation z-separator z-rust">-></span> <span class="z-storage z-type z-rust">bool</span></span> </span><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> <span class="z-variable z-language z-rust">self</span><span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-constant z-numeric z-integer z-decimal z-rust">0</span> <span class="z-keyword z-operator z-comparison z-rust">==</span> <span class="z-keyword z-operator z-arithmetic z-rust">*</span>uuid
</span></span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span></span>
</span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span></span>
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust">
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust"> <span class="z-meta z-impl z-rust"><span class="z-storage z-type z-impl z-rust">impl</span> </span><span class="z-meta z-impl z-rust"><span class="z-meta z-generic z-rust"><span class="z-support z-type z-rust">PartialEq</span><span class="z-punctuation z-definition z-generic z-begin z-rust"><</span><span class="z-variable z-other z-rust">$name</span><span class="z-punctuation z-definition z-generic z-end z-rust">></span></span> <span class="z-keyword z-other z-rust">for</span></span><span class="z-meta z-impl z-rust"> <span class="z-entity z-name z-impl z-rust">Uuid</span> </span><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"> <span class="z-meta z-function z-rust"><span class="z-meta z-function z-rust"><span class="z-storage z-type z-function z-rust">fn</span> </span><span class="z-entity z-name z-function z-rust">eq</span></span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-begin z-rust">(</span><span class="z-keyword z-operator z-rust">&</span><span class="z-variable z-parameter z-rust">self</span>, <span class="z-variable z-parameter z-rust">id</span><span class="z-punctuation z-separator z-rust">:</span> <span class="z-keyword z-operator z-rust">&</span><span class="z-variable z-other z-rust">$name</span></span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-end z-rust">)</span></span></span></span><span class="z-meta z-function z-rust"> <span class="z-meta z-function z-return-type z-rust"><span class="z-punctuation z-separator z-rust">-></span> <span class="z-storage z-type z-rust">bool</span></span> </span><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> <span class="z-keyword z-operator z-arithmetic z-rust">*</span><span class="z-variable z-language z-rust">self</span> <span class="z-keyword z-operator z-comparison z-rust">==</span> id<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-constant z-numeric z-integer z-decimal z-rust">0</span>
</span></span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span></span>
</span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span></span>
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-macro-body z-rust"> <span class="z-punctuation z-section z-block z-end z-rust">}</span></span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"></span></span><span class="z-meta z-macro z-rust"><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span></span>
</span><span class="z-source z-rust">
</span><span class="z-source z-rust"><span class="z-storage z-modifier z-rust">pub</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-keyword z-other z-rust">crate</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-keyword z-other z-rust">use</span> id_type<span class="z-punctuation z-terminator z-rust">;</span>
</span></code></pre>
<p>A fairly modest start to my December Adventure, but a good start nonetheless!</p>
<h2 id="day-2">Day 2</h2>
<p>Finished up the refactor I mentioned yesterday.
The basic idea was to make accessing the database a little more ergonomic:
instead of big methods like <code>database.get_tasks_by_project_id(project_id)</code> and <code>database.edit_project_by_id(project_id, edits)</code>, I'd instead call <code>database.project(project_id).tasks()</code> and <code>database.project(project_id).edit(edits)</code>.
This not only makes it clearer what table is being edited, but also makes naming functions a lot easier.</p>
<p>One of the things I didn't really concern myself with was dealing with indexing based on slugs (for example, when
using a URL endpoint like <code>crofu.net/my-profile/a-project</code> and performing some operation on the corresponding project
(usually getting info), but using the slug (<code>a-project</code> in this case) since it doesn't have access to the UUID.
The way I have it now, I'm just using the old method of having a top-level <code>database.get_project_by_slug()</code>
method, but I think I might have a <code>database.project_by_slug(project_slug)</code> that returns the same thing as
<code>database.project(project_id)</code>.</p>
<h2 id="day-3">Day 3</h2>
<p>Spent the evening doing some CSS and layout improvements, mostly around the pledge editing box.
My two main inspirations when it comes to website style are <a href="https://sr.ht">https://sr.ht</a> and <a href="https://comradery.co">https://comradery.co</a>, and it really
shows.
I'm not <em>too</em> worried about aesthetics or anything, and the only goal right now is to make it clear how to use the
website.
As a result, I don't have much to say about it, but here's a screenshot:</p>
<figure style="width:80%">
<img
class="fig"
src="/images/crofu/pledge-v0.png"
alt="A webpage titled 'add cool feature XYZ to frontend'. In the center is a dark box listing the current pledge amount, an input box with the current pledge amount again, a checkbox labelled 'Pledge anonymously', and two buttons: a blue one labelled 'make pledge' and a red one labelled 'retract pledge'. Under the pledge entry box is a table listing the current pledges, of which there is currently only one."
>
</figure>
<p>Hopefully it's obvious which parts of the page do what.</p>
<h2 id="day-4">Day 4</h2>
<p>Today was mostly a cleanup day:</p>
<ul>
<li>Fixed an issue where <code>/foo</code> resolves correctly, but <code>/foo/</code> does not</li>
<li>Fixed project and task editing</li>
<li>Messed with the CSS on the login page, and added links to <code>/register</code> in <code>/login</code>, and vice versa</li>
<li>Added a little icon for private projects</li>
</ul>
<h2 id="day-5">Day 5</h2>
<p>Added dashboard for general profile operations.
The first page I added was to show all the pledges you've made so far:</p>
<figure >
<img
class="fig"
src="/images/crofu/my-pledges-v0.png"
alt="A webpage titled 'All pledges', featuring a table listing tasks, their projects, and the amounts pledged to each task. Above the table is a total listing '$36'."
>
</figure>
<p>I also changed the URL format from <code>crofu.net/my-profile</code> to <code>crofu.net/~my-profile</code>, like how sourcehut does it.
With the previous method, I had to block out a bunch of profile names like "dashboard" and "profile" and "login" so
they wouldn't collide with the existing URLs.
I might still make a list of forbidden usernames, but I'll probably stick to terms like "admin" that might be confusing.
Since I had a single <code>url.rs</code> that I put all of my URL definitions in, this change was pretty quick and easy:
<a href="https://git.sr.ht/~jaxter184/crofu/commit/5e24615bf468fce79fdd0d56277c57f72356e4be#src/url.rs">(see diff)</a></p>
<p>I think tomorrow is going to be a documentation day, since I've got a few emails and info forms that I want to write.</p>
<h2 id="day-6">Day 6</h2>
<p>Wrote two emails, one to a local co-op and one to a lawyer specializing in co-ops.
The one to the local co-op was pretty straightforward, just a "hi please help" sort of thing.
I might publish the one I sent to the lawyer if I get their permission (technically, I think I'm allowed to do
whatever, but I think it would be courteous to at least let them know first).</p>
<p>Wanted to write the info sheet that I'm sending out to creators for the trial run, but I didn't get around to it. I've
also got one more email to write.</p>
<h2 id="day-7">Day 7</h2>
<p>Spent most of the day researching cooperatives and legal resources, and didn't actually put pen to paper.
I think this sort of activity, while helpful, kinda goes against the spirit of December Adventure.
Rather than planning every last detail, I feel like it's more about being able to shake off the stun and create
something, no matter how minor or ill-conceived.
Much of the stuff I worked on last December Adventure was refactoring work, and it's likely that I'll refactor it
again, but that doesn't mean that it's wasted.
A lot of the time, it's much faster and easier to try something out and see what goes wrong than to try to plan ahead
for every hypothetical.</p>
<p>Plus, that's the main problem I've been having with crofu development so far: I've been doing so much planning and
worrying and hypothesizing, and I have very little actual outcome to show for it, and I think the best way forward is
to just put something together and put it out there, and lay out the tracks as I'm riding the train.
There are a couple caveats, namely that I'm working with money here, and I'd like to be careful on that front, but even
there, I should focus more on having conversations and reaching out to more people who have actually gone through this
before rather than scouring the internet for blog posts and videos.</p>
<p>The goal tomorrow is to get back to coding.</p>
<h2 id="day-8">Day 8</h2>
<p>Added markdown support for descriptions.
A super straightforward change, just <code>cargo add</code>ed a markdown package and wrapped a
<code>maud::PreEscaped(markdown::to_html(description))</code> around the existing description.</p>
<p>Faffed around a bit trying to figure out how object storage works, then I realized I'm probably overengineering, and
decided just to store files on the local filesystem for the short term.
We'll see how that pans out.</p>
<h2 id="day-9">Day 9</h2>
<p>Another quick day: added a "complete task" button and reorganized some files.
Gave up on image uploads for now because they seem like a hassle and I don't really need them for the initial test.</p>
<h2 id="day-10-11">Day 10,11</h2>
<p>Totally missed day 10 because I was working on a sculpture (article incoming soonish), but day 11 was pretty
successful.
I'm still in the "haphazard improvisational slapping together" phase of the project (which frankly might never end),
but I've got a basic payment flow working, which is one of the last parts that I actually need to run my trial.
It's using an internal wallet with fake money, which definitely won't be how it works for the final project, but for
now, it lets me run my self-funded trial phase.
Looking forward, I think the best thing to do would be to make this a formal payment process so that people pay for
pledges individually, but if it's not to legally fraught, I think a wallet would be worth considering.
It allows people to distribute "free credits", either at a project level or at the site level, which could be a neat
way for projects to interact with their communities.
No clue what the tax implications are there though, so the most complicated I think I'll get is a cart system
where people can pay for multiple pledges in a single transaction (though under the hood, it'd still be separate
transactions, like how Bandcamp works).</p>
<p>Apparently Overwatch is doing a 6v6 test thing next week, so I'm trying to get to a solid foundation on this project by
then so I don't have to be working on any big sweeping changes.</p>
<h2 id="day-12">Day 12</h2>
<p>Spent the evening getting an account set up at <a href="https://mythic-beasts.com">https://mythic-beasts.com</a>.
They put my order on hold for manual confirmation, but other than that, the setup was actually pretty straightforward.
I ran into a lot of errors, but they all had clear error messages, and I learned a lot while fixing them.
This is in stark contrast to my experience with ovhcloud, where I had no idea what was going wrong (sometimes I wasn't
even aware something was wrong), and it was always something stupid, and usually their fault rather than mine.
Also, mythic beasts uses a pretty functional html-driven site, again in contrast to ovhcloud, which is like 4 different
single page webapps composed primarily of javascript.</p>
<p>Also tested <a href="https://lettre.rs/">lettre</a>, an email library.
I was kinda surprised how easy it was to just send an email.
To be fair, I had already done a lot of the config inputs at arms length because I've been using
<a href="https://meli-email.org/">meli</a> and sending from a custom domain.
But even so, its cool that old internet protocols like SMTP just kinda work.</p>
<h2 id="day-13">Day 13</h2>
<p>Short day, just finished setting up things on <a href="https://mythic-beasts.com">https://mythic-beasts.com</a>.
Got crofu running on my virtual server and verified that email is working, and was able to access it through the URL I
bought for it.
Mythic beasts continues to be excellently clear about capabilities and available services (and their costs).</p>
<p>Documentation is hit or miss though:</p>
<ul>
<li>I thought I just needed to set up a mail address to be able to send emails via SMTP, but apparently I also needed
a mailbox. Once I knew that's what I needed to do, though, it was really easy to set up.</li>
<li>Setting up SPF records and DKIM (email security stuff) were literally a single click each.
Setting it up for this site (jaxter184.net) was 25 minutes of trial and error.</li>
<li>Still haven't gotten HTTPS, but I think I'm fine just using HTTP for the initial tests.
They have clear documentation for HTTPS on their web hosting service, but not for their VPS service.</li>
</ul>
<p>My total costs so far:</p>
<ul>
<li>mythic-beasts
<ul>
<li>VPS (~$7/mo)</li>
<li>email (~$2/mo)</li>
</ul>
</li>
<li>crofu.org domain registration ($15/yr)</li>
</ul>
<p>Certainly not nothing, but it's wild to me that I can get someone to manage my server and email stuff for less than the
cost of a Netflix or Spotify subscription.
Though that maybe says more about Netflix and Spotify than it does about VPSes.</p>
<p>Tommorrow I'll be back to actual crofu development.
The current plan is to get the login flow working smoothly, with clear error messages.</p>
<h2 id="day-14">Day 14</h2>
<p>Actually, I send credentials with the URL in a POST message, so I actually need to use HTTPS. I'll do that later though.</p>
<p>Today I wrote some shell scripts for testing web interaction.
One of the things that the test revealed is that I've been using content tags when I should've been using void tags in
a few cases (ex: <code><br></br></code> instead of just <code><br></code>), so I spent a few minutes fixing that.</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">paths</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>/ /register /login /fake /~jaxter184 /~jaxter184/tlature /~jaxter184/tlature/task/plugin-iir-eq<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-keyword z-control z-loop z-for z-shell">for</span><span class="z-meta z-group z-for z-shell"> ea_path <span class="z-keyword z-control z-in z-shell">in</span> <span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">paths</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-keyword z-control z-loop z-do z-shell">do</span>
</span><span class="z-source z-shell z-bash"> <span class="z-meta z-function-call z-shell"><span class="z-support z-function z-echo z-shell">echo</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>=================<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span> <span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">ea_path</span></span> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>=================<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"> <span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">curl</span></span><span class="z-meta z-function-call z-arguments z-shell"> http://0.0.0.0:3000<span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">ea_path</span></span><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> --</span>silent</span> <span class="z-keyword z-operator z-assignment z-redirection z-shell">></span> out.html</span>
</span><span class="z-source z-shell z-bash"> <span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">xmllint</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> --</span>html</span> out.html <span class="z-keyword z-operator z-assignment z-redirection z-shell">></span> /dev/null</span>
</span><span class="z-source z-shell z-bash"><span class="z-keyword z-control z-loop z-end z-shell">done</span>
</span></code></pre>
<p>It seems to be falsely flagging my <code><nav></code> blocks, apparently because they're an HTML5 feature.
Gonna ignore for now.</p>
<p>Also forgot to add money to the wallet of the task owner when a pledge is paid so I did that too.
Did some weird stuff with postgresql transactions that I frankly don't fully understand.
I'll have to come back to this before I start processing other peoples' money.</p>
<p>Didn't get around to improving login error messages, but I'll kick the can on that one.</p>
<h2 id="day-15">Day 15</h2>
<p>Finally improved the login error messages.
I think it looks a little simple, but I'll do some trials and see how usable it is before making any more changes to
how it looks.
Also implemented email registration.
Basically how it works is that there's a SQL table of user profiles and
secret codes (<a href="https://en.wikipedia.org/wiki/Unix_time">UNIX timestamp</a> plus a
<a href="https://en.wikipedia.org/wiki/Universally_unique_identifier">UUID</a>):</p>
<pre data-lang="sql" class="language-sql z-code"><code class="language-sql" data-lang="sql"><span class="z-source z-sql"><span class="z-meta z-create z-sql"><span class="z-keyword z-other z-create z-sql">create</span> <span class="z-keyword z-other z-sql">table</span> </span><span class="z-meta z-toc-list z-full-identifier z-sql">if not exists </span><span class="z-meta z-toc-list z-full-identifier z-sql"><span class="z-entity z-name z-function z-sql">email_verify</span></span>
</span><span class="z-source z-sql">(
</span><span class="z-source z-sql"> id UUID <span class="z-storage z-modifier z-sql">primary key</span> <span class="z-keyword z-operator z-logical z-sql">not</span> <span class="z-constant z-language z-sql">null</span> <span class="z-storage z-modifier z-sql">references</span> <span class="z-string z-quoted z-double z-sql"><span class="z-punctuation z-definition z-string z-begin z-sql">"</span>profiles<span class="z-punctuation z-definition z-string z-end z-sql">"</span></span>(id),
</span><span class="z-source z-sql"> code <span class="z-storage z-type z-sql">text</span> <span class="z-keyword z-operator z-logical z-sql">not</span> <span class="z-constant z-language z-sql">null</span> unique
</span><span class="z-source z-sql">);
</span></code></pre>
<p>When the user registers, it creates an entry in the database, generating a new code.
The code is overwritten whenever a new verification email is resent (which also happens whenever the email changes).
The body of the email has a link that looks like <code>https://crofu.org/verify-email?v=<insert secret code here></code>, which is
a page that just checks whether the logged-in user's secret code in the table matches the one that's given in the URL
parameter, and if it is, then it marks a column in the <code>profiles</code> table indicating that its email is verified.</p>
<p>Verifying your email doesn't do anything yet, but in the future, I think it'll be a prerequisite for donating and
creating projects (pledging should have as few barriers as possible though).</p>
<h2 id="day-16">Day 16</h2>
<p>Literally spent my entire evening playing
<a href="https://magnesiumninja.itch.io/nomia/devlog/851879/alpha-v040-released-mac-linux-builds-available">Nomia</a> (very fun
game despite being fairly early in development).</p>
<p>To abide by the December Adventure spirit, I added a few registration tests using the <code>curl</code> -> <code>xmllint</code> process I
showed on Day 14. It involved using some XPath queries, which can get pretty gnarly depending on the website:</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">xmllint</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> --</span>html</span> out.html<span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> --</span>xpath</span> <span class="z-string z-quoted z-single z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">'</span>/html/body/div/form/fieldset/table/tr/td/label[@for="username"]/../../following-sibling::tr[1]/td[1]/text()<span class="z-punctuation z-definition z-string z-end z-shell">'</span></span></span>
</span></code></pre>
<h3 id="xpath-breakdown">XPath Breakdown</h3>
<p><code>/html/body/div/form/fieldset/table/tr/td/label</code>: The first part is pretty straightforward, and just goes down the tree looking for all the heirarchies that fit the
pattern:</p>
<pre data-lang="html" class="language-html z-code"><code class="language-html" data-lang="html"><span class="z-text z-html z-basic"><span class="z-meta z-tag z-structure z-any z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"><</span><span class="z-entity z-name z-tag z-structure z-any z-html">html</span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span>
</span><span class="z-text z-html z-basic"> <span class="z-meta z-tag z-structure z-any z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"><</span><span class="z-entity z-name z-tag z-structure z-any z-html">body</span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span>
</span><span class="z-text z-html z-basic"> <span class="z-meta z-tag z-block z-any z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"><</span><span class="z-entity z-name z-tag z-block z-any z-html">div</span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span>
</span><span class="z-text z-html z-basic"> <span class="z-meta z-tag z-block z-form z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"><</span><span class="z-entity z-name z-tag z-block z-form z-html">form</span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span>
</span><span class="z-text z-html z-basic"> <span class="z-meta z-tag z-block z-form z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"><</span><span class="z-entity z-name z-tag z-block z-form z-html">fieldset</span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span>
</span><span class="z-text z-html z-basic"> <span class="z-comment z-block z-html"><span class="z-punctuation z-definition z-comment z-begin z-html"><!--</span> etc <span class="z-punctuation z-definition z-comment z-end z-html">--></span></span>
</span></code></pre>
<p><code>[@for="username"]</code>: This part checks the attributes of the <code><label></code> tags to find one that looks like <code><label for="username"></code></p>
<p><code>/../..</code>: This just goes up the tag heirarchy the same way it works in a file path</p>
<p><code>/following-sibling::tr[1]</code>: This is a pattern I found that just looks for sibling nodes (nodes that are at the same
heirarchy level and share the same parent) and picks the first one</p>
<p><code>/td[1]</code>: Picks the first <code><td></code> child node</p>
<p><code>text()</code>: Prints the text contents, i.e. <code><tag>this part of the xml data, excluding the tags</tag></code></p>
<h2 id="day-17">Day 17</h2>
<p>Added an account page for checking email verification status</p>
<h2 id="day-18">Day 18</h2>
<p>Added a button to the account page to resend the verification email.
It's one of the slowest parts of the website since it has to do the whole email sending thing in the background.
There's probably something I could do on that front, but I'll leave that for later.</p>
<h2 id="day-19">Day 19</h2>
<p>Added a rudimentary version of the expected donation score (a more complete version is described here:
<a href="https://wiki.jaxter184.net/icc/scoring/">https://wiki.jaxter184.net/icc/scoring/</a>).
While doing this, I ran into a bunch of annoying SQL bugs.
At one point, I converted all the <code>sqlx</code> macros into regular function calls, which skips some convenient checks on the
SQL queries.
I had originally done it to try to get it to compile on fly.io, but since I'm not using fly.io anymore, I think I'll
convert them back.</p>
<h2 id="day-20">Day 20</h2>
<p>Despite spending 2.5 hours of my day playing Overwatch right after work, I ended up accomplishing a lot today.</p>
<p>The main thing I did today was, as I foreshadowed, changing all the <code>sqlx</code> function calls back into macros.
There were a couple weird things to do with the enum types, but I just tossed a <code>as "task_state: _"</code> after every
<code>SELECT</code> field in the SQL command and an <code>as _</code> after every query argument in the Rust code, and that seemed to solve
pretty much all the issues I was having with it.</p>
<ul>
<li>Added error messages when you fail a login</li>
<li>Fixed a few miscellaneous bugs and refactored some currently unused task deletion code</li>
<li>Implemented HTTPS (basically just had to copy the <a href="https://github.com/tokio-rs/axum/blob/main/examples/tls-rustls/src/main.rs">axum rustls example</a> and run <code>certbot certonly --standalone</code>)</li>
</ul>
<figure style="width:40%">
<img
class="fig"
src="/images/crofu/https.png"
alt="A partial screenshot of a login page. The URL bar at the top shows a lock icon, indicating successful TLS encryption."
>
<figcaption>Note the 's' in the URL bar</figcaption>
</figure>
<h2 id="day-21">Day 21</h2>
<p>Cleaned up some code, mostly in <code>main.rs</code> around using axum.
Something about graceful shutdown and redirecting HTTP to HTTPS.
Mostly copying and pasting from axum examples.</p>
<h2 id="day-22">Day 22</h2>
<p>Added a field for changing your email.
There are a lot of cases where the server panics (in an async thread, so it doesn't bring down the whole site),
including when the email is changed to one that is already in use.
I might do some research into ways to do error handling in axum so I can improve both the cases where it happens, as
well as the ergonomics of the code itself.</p>
<h2 id="day-23">Day 23</h2>
<p>Added an error message when changing the email to an existing value</p>
<h2 id="day-24">Day 24</h2>
<p>Did some testing on the website and found/fixed a few minor issues, mostly just blank pages when there should be an
error message, or rephrasing text.</p>
<p>Also implemented a stipend system so that each user gets $5 when verifying their email, but with a limit that I can
update regularly to mitigate bot registrations.
Things that I probably won't have to worry about, but addressing early can save me future headache.
I might also gamify it so that you get a dollar a day just for visiting.</p>
<h2 id="day-25">Day 25</h2>
<p>Did a lot of stuff today!
The site is finally ready to start seeing test use by people who are not me.
If you want to check out what's going on so far, the site is available at <a href="https://crofu.org">https://crofu.org</a>.
I'd love to hear any feedback, even if it's just "I opened it and didn't understand what was going on".
In the few hours that it's been up, I've gotten a few people who visited it, but no one ever got past the home screen.
I have no idea why this is, so if you have any thoughts on the subject, please reach out at
<a href="mailto:[email protected]">[email protected]</a>.</p>
<p>I think if I really wanted to, I could start my official trial run in January, but while it would be cool to start at
the literal beginning of the year, I don't want to rush into anything, so I'll probably start a week or two into the
month, assuming I keep pace.</p>
<p>Things I did today:</p>
<ul>
<li>Just added a file output and picked a filter level that makes sense.
<ul>
<li>I think there are some additions I can make that will make logging cleaner (like implementing my own <code>Filter</code> as
recommended <a href="https://users.rust-lang.org/t/use-tracing-subscriber-to-log-only-debug-to-file/104453">here</a>), but I'll worry about that when the current logging becomes a problem.</li>
</ul>
</li>
<li>Made some scripts to make deploying and testing ever so slightly faster and easier</li>
<li>Change <code>lettre</code> (email sending library) to use <code>rustls</code> instead of <code>openssl</code>, which lets me compile to <code>musl</code>, which
I had to do because my server is running Ubuntu, which uses a much older glib version than my development machine,
<a href="https://knowyourmeme.com/memes/btw-i-use-arch">which uses arch btw</a>.
Before, I was pulling the repo and recompiling <code>crofu</code> each time on the server, but now I can just build locally and
<code>scp</code> the binary over, which is a lot quicker and doesn't require me to put the whole Rust toolchain on the tiny server
that I've rented.</li>
<li>Fix a bug with project visibility, and show the current visibility setting of profiles/projects/tasks that the
currently logged in user controls.</li>
<li>Add some server-side processing to reduce the complexity of the redirect information in login/registration URLs</li>
</ul>
<h2 id="day-26">Day 26</h2>
<p>Improved the logging.
Trying to not get too invasive with it, but adding the user agent and requested path really helped.
It turns out a lot of the visits to my site are either Fediverse servers, scrapers, or bots looking for
vulnerabilities, such as:</p>
<ul>
<li><code>/wp-admin/setup-config.php</code> - WordPress admin portal</li>
<li><code>/cgi-bin/luci/;stok=/locale</code></li>
<li><code>/actuator/gateway/routes</code></li>
<li><code>/.env</code></li>
<li><code>/api/v2/static/not.found</code></li>
<li><code>/remote/logincheck</code></li>
<li><code>/fonts/ftnt-icons.woff</code></li>
<li><code>/cgi-bin/luci/;stok=/locale</code></li>
<li><code>/geoserver/web</code></li>
<li><code>/query</code></li>
<li><code>/solr/admin/info/system</code></li>
<li><code>/solr/admin/cores</code></li>
<li><code>/v2/_catalog</code></li>
<li><code>/libs/js/iframe.js</code></li>
</ul>
<h2 id="day-27">Day 27</h2>
<p>Got some really good feedback today, and spent basically the whole day addressing it.</p>
<ul>
<li>Fixed a logic bug that displays the logged in user's projects on every profile page</li>
<li>Replaced all uses of the unpopulated displayname field with the slug</li>
<li>Changed default font to sans-serif</li>
<li>Put an ICC summary on the landing page so people don't have to immediately leave the website</li>
<li>Autogenerate project/task slug based on the displayname and put it in an "Additional settings" collapsible menu</li>
<li>Improve styling of new/edit project/task page</li>
<li>Add PostgreSQL constraints that prevent multiple tasks with the same slug, and relax the constraint that requires a
project slug be globally unique (instead, it now must be unique within the profile).</li>
</ul>
<p>Just goes to show that if you want to motivate me, just look at something I did and complain about it.</p>
<h2 id="day-28">Day 28</h2>
<p>Another pretty packed day</p>
<ul>
<li>Allow username login</li>
<li>Add PostgreSQL constraint requiring that usernames be case-insensitively unique</li>
<li>Update curl tests to use HTTPS</li>
<li>Refactor database interface to be a little more modular, at the cost of potentially worse performance.
I'll try to benchmark it later, and likely refactor this again, maybe using <code>diesel</code> since I'm not really loving <code>sqlx</code>.</li>
<li>Refactor the way that I organize functions on pages that need to be loaded with errors, like registration and new
project/task creation</li>
</ul>
<p>The way I implemented that last item was a bit hacky.
I'm using <a href="https://github.com/tokio-rs/axum">axum</a>, which uses "extractor"s to load the items in a function signature
using the HTTP request data.</p>
<p>For example, the function signature for the <code>https://crofu.org/~profile/new-project</code> route is:</p>
<pre data-lang="rust" class="language-rust z-code"><code class="language-rust" data-lang="rust"><span class="z-source z-rust">async <span class="z-meta z-function z-rust"><span class="z-meta z-function z-rust"><span class="z-storage z-type z-function z-rust">fn</span> </span><span class="z-entity z-name z-function z-rust">get_new_project</span></span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-begin z-rust">(</span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"> Path<span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-variable z-parameter z-rust">profile</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span>: <span class="z-meta z-generic z-rust">Path<span class="z-punctuation z-definition z-generic z-begin z-rust"><</span><span class="z-support z-type z-rust">String</span><span class="z-punctuation z-definition z-generic z-end z-rust">></span></span>,
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"> <span class="z-variable z-parameter z-rust">db</span><span class="z-punctuation z-separator z-rust">:</span> <span class="z-meta z-generic z-rust">Extension<span class="z-punctuation z-definition z-generic z-begin z-rust"><</span>Database<span class="z-punctuation z-definition z-generic z-end z-rust">></span></span>,
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"> <span class="z-variable z-parameter z-rust">auth_session</span><span class="z-punctuation z-separator z-rust">:</span> AuthSession,
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"> <span class="z-variable z-parameter z-rust">uri</span><span class="z-punctuation z-separator z-rust">:</span> Uri,
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"></span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-end z-rust">)</span></span></span></span><span class="z-meta z-function z-rust"> <span class="z-meta z-function z-return-type z-rust"><span class="z-punctuation z-separator z-rust">-></span> <span class="z-meta z-generic z-rust"><span class="z-support z-type z-rust">Result</span><span class="z-punctuation z-definition z-generic z-begin z-rust"><</span><span class="z-meta z-generic z-rust">Html<span class="z-punctuation z-definition z-generic z-begin z-rust"><</span><span class="z-support z-type z-rust">String</span><span class="z-punctuation z-definition z-generic z-end z-rust">></span></span>, <span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-punctuation z-section z-group z-end z-rust">)</span><span class="z-punctuation z-definition z-generic z-end z-rust">></span></span></span></span><span class="z-punctuation z-terminator z-rust">;</span>
</span></code></pre>
<ul>
<li><code>Path(profile): Path<String></code> - populated according to the route
(<code>.route(&url::new_project(":profile"), get(get_new_project))</code>), which specifies one matchable path parameter: the
profile name.</li>
<li><code>db: Extension<Database></code> - I'm honestly not totally solid on how exactly this one works since I basically just
copy/pasted it from an example, but my assumption is that the <code>Extension</code> type is what lets me access it like a global
state by just adding it as a <code>.layer(db)</code> when initializing the <code>Router</code>.</li>
<li><code>auth_session: AuthSession</code> - Another copy/paste, but this one is probably from the <code>AuthManagerLayer</code> provided by
the <code>axum_login</code> library.</li>
<li><code>uri: Uri</code> - The URI, which I use to generate the nav bar.</li>
</ul>
<p>Notably, all of these types have to implement <code>FromRequest</code> or <code>FromRequestParts</code> (I think), which is what makes them
extractors and allows the router to use them to generate web pages.
While these functions are normally called by the router (the <code>get(get_new_project))</code> in the <code>.route(...)</code> call
above is where that connection is made), I can also, of course, use this as a regular function, for example by the
<code>post::new_project</code> function that I use to process the form generated by <code>get_new_project</code> (TODO: draw a flow chart on
how these calls work).
However, if I add a parameter like <code>error_state: Option<NewProjectErrorState></code>, then I get a compilation error complaining that
<code>the trait `Handler<_, _>` is not implemented for fn item <function signature of get_new_project></code>, since
<code>Option<NewProjectErrorState></code> is not a proper extractor.</p>
<p>My solution to this problem was to make <code>NewProjectErrorState</code> an extractor.
Extractors should generally be based on request data, but I chose to ignore that because it makes my life easier.
I chose <code>FromRequestParts</code> over <code>FromRequest</code> because the half-second of reading documentation I did suggested that the
former has fewer restrictions, and can be optimized more efficiently than the latter.</p>
<pre data-lang="rust" class="language-rust z-code"><code class="language-rust" data-lang="rust"><span class="z-source z-rust"><span class="z-meta z-annotation z-rust"><span class="z-punctuation z-definition z-annotation z-rust">#</span><span class="z-punctuation z-section z-group z-begin z-rust">[</span><span class="z-variable z-annotation z-rust">axum</span>::<span class="z-variable z-annotation z-rust">async_trait</span><span class="z-punctuation z-section z-group z-end z-rust">]</span></span>
</span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-storage z-type z-impl z-rust">impl</span></span><span class="z-meta z-impl z-rust"><span class="z-meta z-generic z-rust"><span class="z-punctuation z-definition z-generic z-begin z-rust"><</span>S<span class="z-punctuation z-definition z-generic z-end z-rust">></span></span></span><span class="z-meta z-impl z-rust"> <span class="z-meta z-generic z-rust">FromRequestParts<span class="z-punctuation z-definition z-generic z-begin z-rust"><</span>S<span class="z-punctuation z-definition z-generic z-end z-rust">></span></span> <span class="z-keyword z-other z-rust">for</span></span><span class="z-meta z-impl z-rust"> <span class="z-entity z-name z-impl z-rust">NewProjectState</span> </span><span class="z-meta z-impl z-rust"><span class="z-keyword z-other z-rust">where</span> S<span class="z-punctuation z-separator z-rust">:</span> Send + Sync, </span><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"> <span class="z-storage z-type z-type z-rust">type</span> <span class="z-entity z-name z-type z-rust">Rejection</span> <span class="z-keyword z-operator z-assignment z-rust">=</span> <span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-terminator z-rust">;</span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"> async <span class="z-meta z-function z-rust"><span class="z-meta z-function z-rust"><span class="z-storage z-type z-function z-rust">fn</span> </span><span class="z-entity z-name z-function z-rust">from_request_parts</span></span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-begin z-rust">(</span><span class="z-keyword z-operator z-rust">_</span>: <span class="z-keyword z-operator z-rust">&</span><span class="z-storage z-modifier z-rust">mut</span> Parts, <span class="z-keyword z-operator z-rust">_</span>: <span class="z-keyword z-operator z-rust">&</span>S</span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-end z-rust">)</span></span></span></span><span class="z-meta z-function z-rust"> <span class="z-meta z-function z-return-type z-rust"><span class="z-punctuation z-separator z-rust">-></span> <span class="z-meta z-generic z-rust"><span class="z-support z-type z-rust">Result</span><span class="z-punctuation z-definition z-generic z-begin z-rust"><</span><span class="z-storage z-type z-rust">Self</span>, <span class="z-meta z-path z-rust"><span class="z-storage z-type z-rust"><span class="z-storage z-type z-rust">Self</span><span class="z-punctuation z-accessor z-rust">::</span></span></span>Rejection<span class="z-punctuation z-definition z-generic z-end z-rust">></span></span></span> </span><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span> <span class="z-support z-type z-rust">Ok</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-storage z-type z-rust">Self</span><span class="z-meta z-path z-rust"><span class="z-punctuation z-accessor z-rust">::</span></span>default<span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span></span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"></span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span></span>
</span></code></pre>
<p>My implementation basically does nothing and always populates <code>NewProjectErrorState</code> with its default values.
This works in my case because I want it to always load from the router using its defaults, since errors should only be
displayed when <code>get_new_project()</code> gets used by <code>post::new_project()</code>:</p>
<pre data-lang="rust" class="language-rust z-code"><code class="language-rust" data-lang="rust"><span class="z-source z-rust"><span class="z-meta z-module z-rust"><span class="z-storage z-type z-module z-rust">mod</span> <span class="z-entity z-name z-module z-rust">post</span> <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-module z-rust"><span class="z-meta z-block z-rust"> <span class="z-storage z-modifier z-rust">pub</span> async <span class="z-meta z-function z-rust"><span class="z-meta z-function z-rust"><span class="z-storage z-type z-function z-rust">fn</span> </span><span class="z-entity z-name z-function z-rust">new_project</span></span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-begin z-rust">(</span>
</span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-module z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"> <span class="z-comment z-block z-rust"><span class="z-punctuation z-definition z-comment z-rust">/*</span> other parameters <span class="z-punctuation z-definition z-comment z-rust">*/</span></span>
</span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-module z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"> Form<span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-variable z-parameter z-rust">form</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span>: <span class="z-meta z-generic z-rust">Form<span class="z-punctuation z-definition z-generic z-begin z-rust"><</span>EditProjectForm<span class="z-punctuation z-definition z-generic z-end z-rust">></span></span>,
</span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-module z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"> </span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-end z-rust">)</span></span></span></span><span class="z-meta z-function z-rust"> <span class="z-meta z-function z-return-type z-rust"><span class="z-punctuation z-separator z-rust">-></span> <span class="z-meta z-generic z-rust"><span class="z-support z-type z-rust">Result</span><span class="z-punctuation z-definition z-generic z-begin z-rust"><</span>Response, <span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-punctuation z-section z-group z-end z-rust">)</span><span class="z-punctuation z-definition z-generic z-end z-rust">></span></span></span> </span><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-module z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> <span class="z-comment z-line z-double-slash z-rust"><span class="z-punctuation z-definition z-comment z-rust">//</span> ...
</span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-module z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> <span class="z-keyword z-control z-rust">if</span> db<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">session_profile</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-keyword z-operator z-bitwise z-rust">&</span>auth_session</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">unwrap</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span>
</span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-module z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> <span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">new_project</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-keyword z-operator z-bitwise z-rust">&</span>form<span class="z-punctuation z-accessor z-dot z-rust">.</span>slug<span class="z-punctuation z-separator z-rust">,</span> <span class="z-keyword z-operator z-bitwise z-rust">&</span>form<span class="z-punctuation z-accessor z-dot z-rust">.</span>name<span class="z-punctuation z-separator z-rust">,</span> <span class="z-keyword z-operator z-bitwise z-rust">&</span>form<span class="z-punctuation z-accessor z-dot z-rust">.</span>description<span class="z-punctuation z-separator z-rust">,</span> form<span class="z-punctuation z-accessor z-dot z-rust">.</span>page_state</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-accessor z-dot z-rust">.</span>await
</span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-module z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> <span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">is_err</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-module z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> <span class="z-keyword z-control z-rust">return</span> <span class="z-support z-type z-rust">Ok</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-support z-function z-rust">get_new_project</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>
</span></span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-module z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-group z-rust"><span class="z-meta z-group z-rust"> path<span class="z-punctuation z-separator z-rust">,</span> db<span class="z-punctuation z-separator z-rust">,</span> auth_session<span class="z-punctuation z-separator z-rust">,</span> uri<span class="z-punctuation z-separator z-rust">,</span>
</span></span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-module z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-group z-rust"><span class="z-meta z-group z-rust"> NewProjectErrorState <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span> form<span class="z-punctuation z-separator z-rust">,</span> slug_taken<span class="z-punctuation z-separator z-rust">:</span> <span class="z-constant z-language z-rust">true</span> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span><span class="z-punctuation z-separator z-rust">,</span>
</span></span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-module z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-group z-rust"><span class="z-meta z-group z-rust"> </span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-accessor z-dot z-rust">.</span>await<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">into_response</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-terminator z-rust">;</span>
</span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-module z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span>
</span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-module z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> <span class="z-comment z-line z-double-slash z-rust"><span class="z-punctuation z-definition z-comment z-rust">//</span> ...
</span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-module z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span></span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-module z-rust"><span class="z-meta z-block z-rust"></span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span></span>
</span></code></pre>
<p>All that work just so I don't have to have an intermediary <code>fn render_new_project()</code> that gets called by both
<code>post::new_project()</code> and <code>get_new_project()</code>.</p>
<h2 id="day-29">Day 29</h2>
<p>Did a little more database API refactoring, just a continuation of what I did yesterday.
The general idea is that instead of having the <code>get_project_by_slug()</code> function in the main database struct return a
<code>Project</code>, I instead have it return a <code>ProjectDatabase</code>, which is an API structure that exposes more controls.
Put another way, I used to return a read-only structure, but now I return something that can be used to modify the
project, and getting the original read-only structure now requires an additional function call.</p>
<h2 id="day-30">Day 30</h2>
<p>I was busy with other stuff, so I missed the day</p>
<h2 id="day-31">Day 31</h2>
<p>Added error messages to the POST request handlers for the task. Basically just more continuation of the stuff I did on
the 28th.</p>
<h2 id="retrospective">Retrospective</h2>
<p>As it usually is with my December Adventures, I ended up getting a lot of stuff done (despite that not being the direct
goal).
While I'm pretty happy with the current state of the project, I think there's still a lot more stuff to do, and I've
become a little less confident in both the idea itself and my ability to execute on it.
I am, however, now in a much better place to understand where the shortcomings are and fix them.</p>
<p>Last year, the things I wanted to do better were keep up with other people's adventures and do less refactoring.
I think it's just the nature of working on a brand new project, but I didn't spend nearly as long refactoring as I did
last year.
There was still a bit, but in retrospect, I think it's just the nature of the way I do things to refactor often.
Plus, my main reason for doing less refactoring was that it was less exploratory, but thinking about it more,
frequent refactoring is the natural result of a very exploratory process.
Rust's ability to let me confidently change my approach allows me to try many more things than if I had to stick to
whatever design I make, and knowing that I'll change it if it's bad lets me make decisions much quicker.</p>
<p>As for keeping up with other peoples' adventures, I didn't do it as much as I probably should have.
Early on, I saw a few, but I found myself not keeping up with them because they didn't really have a narrative
through-line.</p>
<p>Which brings me to my goals for next year:</p>
<ul>
<li>Have a narrative through-line.
The adventure is much more interesting and compelling when it's presented as a story, even if that story is mundane.
Not sure yet exactly what this will entail, but I have a whole 11 months to think about it.</li>
<li>Instead of just consuming the firehose, pick a few adventures to keep up with.
I think it'll be much easier to keep up with other peoples' adventures if I just stick to a few from people I like, or
people who are doing things I'm interested in.</li>
</ul>
<p>2024 was not the year of the tracker on the Linux desktop, but hopefully 2025 will be the advent of ICC, thus enabling
the subsequent year of the tracker on the Linux desktop.</p>
Graph Layout AlgorithmThu, 05 Sep 2024 00:00:00 +0000Unknown
https://wiki.jaxter184.net/graph-layout/
https://wiki.jaxter184.net/graph-layout/<figure >
<img
class="fig"
src="/images/graph-layout/blender-nodes.jpg"
alt="Blender UI showing many interconnected nodes"
>
<figcaption>Blender's node editor UI</figcaption>
</figure>
<p>Node editors are really neat! They're a very intuitive way to visualize the dependent relationships of the various parts
of a process, from graphics rendering pipelines to audio patches.
I've been doing a lot of keyboard-driven TUI stuff for the last few years, and while node editors are a very GUI-centric
interface, I think with a few tweaks, a TUI node editor could be a very
reasonable thing to do.</p>
<p>The main challenge that makes node editors so GUI-centric is that there is a lot of mouse interaction.
While you can use a mouse in a TUI, keyboards are by far the preferred input method, and the spatial nature of a node
editor makes it tough to navigate with just a keyboard.
Some node editors solve this by tying navigation to the topology of the graph: up arrow key selects the parent, down
arrow key selects the first child, left and right navigate siblings. If all the nodes are connected (they usually are,
and even if they aren't, there are workarounds), this allows you to navigate to any other node with ease.</p>
<p>However, even if you solve navigation, a lot of the clicking in a node editor is just moving nodes around to tidy the
connections, and having to use arrow keys for that would greatly slow down workflows, which basically nullifies 80% of
the benefit of a keyboard-only input method.</p>
<p>Which brings me to my solution: node placement should be determined only by topology.
No more clicking around reorganizing nodes, they'll be placed mostly in the right place automatically, no more manually
routing connections, plus you won't need to store extra data in files to keep track of positions.</p>
<h2 id="prior-art">prior art</h2>
<p><a href="http://mermaid.js.org/syntax/flowchart.html">mermaid</a> and <a href="https://graphviz.org/doc/info/lang.html">dot</a> have some way of placing nodes by topology that i didnt bother looking into</p>
<h2 id="vocabulary">vocabulary</h2>
<ul>
<li><code>graph</code> - a set of nodes with connections between them</li>
<li><code>directed graph</code> - a graph where the connections are directional</li>
<li><code>directed acyclic graph</code> - a directed graph where following a series of connections from node to node will never result
in a loop</li>
<li><code>node</code> - an object in a graph that connects to other nodes</li>
<li><code>connection</code> - a relationship between two nodes, generally represented with a line connecting the two (footnote:
choosing this over the classical graph theory term "edge" because it's a little confusing since we're talking about the
geometry of how things get drawn)</li>
<li><code>line segment</code> - when drawing a connection, these are the straight parts between bends</li>
<li><code>joining</code> - the place where two connections with the same destination represented by separate lines come together into one line</li>
<li><code>splitting</code> - the place where a connection between a source and two destinations splits apart into separate lines</li>
</ul>
<h2 id="background">background</h2>
<ul>
<li>why are we using a DAG?</li>
<li>how is it related to a tree?</li>
</ul>
<h2 id="goals">goals</h2>
<p>For simplicity, we will assume rectangular nodes and orthogonal connections that only travel horizontally and
vertically, with right angled turns.</p>
<p>For clarity, we will assume all data flows in one direction. In some cases, this may be left to right, in our case, we
will pick bottom to top.</p>
<h3 id="requirements">requirements</h3>
<p>Connections cannot pass behind nodes, as they would be much harder to follow.</p>
<p>Connections can only overlap when perpendicular. Put another way, only two connections can overlap at any point, and one
must be travelling horizontally while the other travels vertically.</p>
<p>it must be clear whether segments are intersecting at a junction or passing over each other</p>
<h3 id="nice-to-haves">nice-to-haves</h3>
<p>Positioning should be predictable. If I know how two nodes are connected, and I know where one of the nodes is, I should
be able to know where the other is as accurately as possible.</p>
<p>should be as compact as possible</p>
<p>minimize connection intersections, both when passing over each other and when splitting/joining at a junction</p>
<p>if it doesn't interfere with any of the above heuristics, the segments of a line between bends should try to be similar
to each other in length.</p>
<p>Try to draw the least amount of lines possible.
This primarily means that connected nodes should be placed close to each other.
Also, if two connections join, they should do so at the earlist point possible, so that the majority of their travel is
as a combined line.</p>
<p>willing to allow minor space inefficiencies for a simpler or faster algorithm</p>
<h2 id="our-use-case">our use case</h2>
<p>The actual data that the node is representing is arbitrary, but I've chosen my favorite <a href="https://cooking.nytimes.com/recipes/1023855-classic-kouign-amann">kouign amann recipe</a>.</p>
<p>Assuming we have a loose list of which components have which ingrendients (these are the directed connections):</p>
<pre class="z-code"><code><span class="z-text z-plain">dough requires salt
</span><span class="z-text z-plain">butter sheet requires salt
</span><span class="z-text z-plain">butter sheet requires sugar
</span><span class="z-text z-plain">dough requires water
</span><span class="z-text z-plain">syrup requires sugar
</span><span class="z-text z-plain">syrup requires spices
</span><span class="z-text z-plain">dough requires butter
</span><span class="z-text z-plain">kouign amann requires syrup
</span><span class="z-text z-plain">dough requires flour
</span><span class="z-text z-plain">laminated dough requires dough
</span><span class="z-text z-plain">butter sheet requires butter
</span><span class="z-text z-plain">laminated dough requires butter sheet
</span><span class="z-text z-plain">dough requires yeast
</span><span class="z-text z-plain">kouign amann requires laminated dough
</span></code></pre>
<p>We can sort that list and build a heirarchical representation of the ingredients is as follows (in order of amount):</p>
<pre class="z-code"><code><span class="z-text z-plain">* kouign amann
</span><span class="z-text z-plain"> * laminated dough
</span><span class="z-text z-plain"> * dough
</span><span class="z-text z-plain"> * flour
</span><span class="z-text z-plain"> * water
</span><span class="z-text z-plain"> * butter
</span><span class="z-text z-plain"> * yeast
</span><span class="z-text z-plain"> * salt
</span><span class="z-text z-plain"> * butter sheet
</span><span class="z-text z-plain"> * butter
</span><span class="z-text z-plain"> * sugar
</span><span class="z-text z-plain"> * salt
</span><span class="z-text z-plain"> * syrup
</span><span class="z-text z-plain"> * sugar
</span><span class="z-text z-plain"> * spices
</span></code></pre>
<p>We're basically 80% of the way there already!
Though now comes the tough part, where we figure out how to display these with connections between them so that items
(like butter and salt) aren't repeated.</p>
<p>You could imagine that if this were a graphics pipeline, it would be much more efficient to just calculate the <code>butter</code>
and <code>salt</code> processes once, and reuse that output for the inputs to both the <code>dough</code> and the <code>butter sheet</code> processes.</p>
<h2 id="the-algorithm-is-as-follows">the algorithm is as follows</h2>
<h3 id="place-nodes-and-their-children">Place nodes and their children</h3>
<p>Before we even draw any connections, let's roughly arrange all of the nodes first, based on their relationships to each
other, starting with the root:</p>
<figure >
<img
class="fig"
src="/images/graph-layout/build/00.kdl.svg"
alt="node labelled 'kouign amann' with two inputs"
>
</figure>
<p>Note the two inputs at the bottom of the node, which are for <code>laminated dough</code> and <code>syrup</code>.
We'll follow those two inputs and place their corresponding nodes:</p>
<figure >
<img
class="fig"
src="/images/graph-layout/build/01.kdl.svg"
alt="same as previous image, but with two more nodes under labelled 'laminated dough' and 'syrup'"
>
</figure>
<p>So far so good. <code>laminated dough</code> was placed in a way that automatically connects it to the corresponding input on the <code>kouign amann</code>
node, but <code>syrup</code> wasn't. Later when we draw connections, we'll bump the lower row down one unit so that we can make the
connection, but for now, we'll leave them where they are for simplicity.</p>
<h3 id="horizontal-nudging">Horizontal nudging</h3>
<figure >
<img
class="fig"
src="/images/graph-layout/build/02.kdl.svg"
alt=""
>
</figure>
<p>Quick pause: it's not immediately obvious now, but if we try to connect <code>syrup</code>, <code>butter sheet</code> is going to make the
connections a bit more roundabout than necessary, even if we do the vertical bumping mentioned earlier:</p>
<figure >
<img
class="fig"
src="/images/graph-layout/build/03.kdl.svg"
alt=""
>
</figure>
<p>It's not too bad, but there are similar cases that we could run into in the future that are much worse:</p>
<figure >
<img
class="fig"
src="/images/graph-layout/build/04.kdl.svg"
alt=""
>
</figure>
<p>Recall one of our goals: "Try to draw the least amount of lines possible".</p>
<p>To address these cases, let's adjust our algorithm to nudge parent nodes to their leftmost child. This will pull it further from its
parent, but since there is one connection to the parent and two to its children, putting it closer to its children
draws fewer lines:</p>
<figure >
<img
class="fig"
src="/images/graph-layout/build/05.kdl.svg"
alt=""
>
</figure>
<p>We'll have to do the same horizontal nudging to <code>butter sheet</code> in the next step (which will move <code>syrup</code> again, poor
fella).</p>
<p>However, it happens to be the case that <code>butter</code> and <code>salt</code>, the last two ingredients of <code>dough</code>, are also ingredients
in <code>butter sheet</code>, so for more compactness, we can modify our nudging algorithm to only move parent nodes when <em>all</em>
of their children are to the right.</p>
<figure >
<img
class="fig"
src="/images/graph-layout/build/06.kdl.svg"
alt=""
>
</figure>
<h3 id="vertical-nudging">Vertical nudging</h3>
<p>Now all of the nodes have been placed, but there's one more adjustment that we should make before drawing connections.
It seems that <code>sugar</code> is on the same row as <code>butter sheet</code> even though one is an ingredient of the other.
A quick fix is to nudge it down one row.
In this case, we don't have to move it anymore, but if it collided with something on its row, we'd nudge it (and its
parents) to the right.</p>
<p>Also, once we bump <code>sugar</code> down one row, we can tuck in <code>spices</code>. This is our first time nudging something to the left,
and the mechanics of that are a little weird, so I'll leave that as a bonus in the appendix (TODO).</p>
<figure >
<img
class="fig"
src="/images/graph-layout/build/07.kdl.svg"
alt=""
>
</figure>
<h3 id="easy-connections">Easy connections</h3>
<p>Alright! How exciting! All the nodes are placed and we're ready to draw connections. Let's start with the easy ones:
anywhere where we can just draw an unobstructed horizontal line between inputs and outputs, let's do that:</p>
<figure >
<img
class="fig"
src="/images/graph-layout/build/08.kdl.svg"
alt=""
>
</figure>
<p>This is another place where the exact mechanics of the algorithm are up for grabs.
Should the ingredients on the bottom row stay aligned?
To do so would make connections longer, but would maybe be more readable.
Since our original goals emphasize compactness, I'll leave it as-is for now, but maybe I'll return to it later.</p>
<p>We could also mode <code>syrup</code> and its children to the left by one unit, which would make one line one unit shorter, but
tracking when that optimization could be made would greatly increase the complexity of the algorithm, which is no fun
for anyone, so we can ignore that.</p>
<h3 id="harder-connections">Harder connections</h3>
<p>The only connections left are the inputs of <code>butter sheet</code>.
There is no way to lay out the connections without at least one intersection, so now is when we need to start worrying
about all the rules regarding connections and create a measurable heuristic for how good a connection is. To review (and
simplify), our rules for connections are:</p>
<ul>
<li>All ingredients flow from bottom to top</li>
<li>Connections cannot pass behind nodes</li>
<li>Unrelated connections can only overlap when perpendicular</li>
<li>Minimize connection intersections, both junctions and overlaps</li>
<li>Try to draw the least amount of lines possible.</li>
</ul>
<p>First, let's take a closer look at what we're working with:</p>
<figure >
<img
class="fig"
src="/images/graph-layout/build/09.kdl.svg"
alt=""
>
</figure>
<p>The inputs of <code>butter sheet</code>, in order, are <code>butter</code>, <code>sugar</code>, <code>salt</code>.</p>
<p>The way I went about doing this is creating a pathing algorithm (similar to <a href="https://en.wikipedia.org/wiki/A*_search_algorithm">A*</a>) that tries to make its way to the
other end of the connection.</p>
<p>TODO: the rest of the article</p>
<figure >
<img
class="fig"
src="/images/graph-layout/build/10.kdl.svg"
alt=""
>
</figure>
<figure >
<img
class="fig"
src="/images/graph-layout/build/11.kdl.svg"
alt=""
>
</figure>
<h2 id="appendix">appendix</h2>
<h3 id="box-drawing-characters">box drawing characters</h3>
<p>what are they</p>
<p>what are the caveats (output pipe can't match both block wall and connection type/style)</p>
Nokia Battery Comparison ChartThu, 08 Aug 2024 00:00:00 +0000Unknown
https://wiki.jaxter184.net/nokia-battery-comparison-chart/
https://wiki.jaxter184.net/nokia-battery-comparison-chart/<style>
table {
font-size: 15px;
}
table td {
padding: 3px;
}
table th {
cursor: pointer;
padding: 3px;
}
th.no-sort {
pointer-events: none;
}
th::after,
th::before {
transition: color 0.2s ease-in-out;
font-size: 1.2em;
color: transparent;
}
th::after {
margin-left: 3px;
content: '\025B8';
}
th:hover::after {
color: inherit;
}
th.dir-d::after {
color: inherit;
content: '\025BE';
}
th.dir-u::after {
color: inherit;
content: '\025B4';
}
.container {
max-width: 80rem
}
</style>
<p>Recommended sizes (mostly due to popularity/availability):</p>
<ol>
<li>BL-5C</li>
<li>BP-4L</li>
</ol>
<table><thead><tr><th>Name</th><th>Capacity (mAh)</th><th>Voltage</th><th>Capacity (Wh)</th><th>Weight (g)</th><th>BSI (kOhm)</th><th>Pin Count</th><th>Footprint (mm)</th><th>Thickness (mm)</th><th>Chemistry</th><th>Connector</th></tr></thead><tbody>
<tr><td>BL-6X</td><td> 700</td><td>3.7</td><td></td><td></td><td></td><td></td><td></td><td></td><td>Lithium</td><td></td></tr>
<tr><td>BL-4YW</td><td>2000</td><td>3.7</td><td></td><td></td><td></td><td></td><td></td><td></td><td>Lithium</td><td>harness</td></tr>
<tr><td>BML-3</td><td></td><td>2.4</td><td></td><td></td><td>no BSI pin</td><td></td><td></td><td></td><td>Ni-Mh</td><td></td></tr>
<tr><td>BLK-4S</td><td> 750</td><td>7.2</td><td></td><td></td><td></td><td></td><td></td><td></td><td>Lithium</td><td></td></tr>
<tr><td>BLS-2N</td><td>1050</td><td>3.8</td><td></td><td></td><td></td><td></td><td></td><td></td><td>Lithium</td><td></td></tr>
<tr><td>BV-4D</td><td>1320</td><td>3.7</td><td></td><td></td><td></td><td></td><td></td><td></td><td>Lithium</td><td>pinch</td></tr>
<tr><td>BBT-3S</td><td></td><td>6</td><td></td><td></td><td></td><td></td><td></td><td></td><td>Ni-MH</td><td></td></tr>
<tr><td>BL-5F</td><td> 950</td><td>3.7</td><td></td><td></td><td>27</td><td></td><td>46 x 40</td><td>5</td><td>Lithium</td><td></td></tr>
<tr><td>BPS-2</td><td>1100</td><td>3.8</td><td></td><td></td><td></td><td></td><td></td><td></td><td>Lithium</td><td></td></tr>
<tr><td>BL-5B</td><td> 820-890</td><td>3.7</td><td></td><td>24</td><td>75</td><td></td><td>46 x 33.9</td><td>5.65</td><td>Lithium</td><td></td></tr>
<tr><td>BL-6C</td><td>1070-1150</td><td>3.7</td><td></td><td></td><td></td><td></td><td>53 x 34</td><td>?</td><td>Lithium</td><td>side pad</td></tr>
<tr><td>BR-5C</td><td> 970</td><td>3.7</td><td></td><td></td><td></td><td></td><td>53 x 34</td><td>6</td><td>Lithium</td><td></td></tr>
<tr><td>BL-6P</td><td> 830</td><td>3.7</td><td></td><td></td><td>75</td><td></td><td>37 x 37</td><td>6</td><td>Lithium</td><td></td></tr>
<tr><td>BL-5X</td><td> 600</td><td>3.7</td><td></td><td></td><td></td><td></td><td>32.9 x 37.7</td><td>5.4</td><td>Lithium</td><td></td></tr>
<tr><td>BBH-1H</td><td>1700</td><td>6</td><td></td><td></td><td></td><td></td><td></td><td></td><td>Ni-MH</td><td>bottom pad</td></tr>
<tr><td>BL-4D</td><td>1200</td><td>3.7</td><td></td><td>24</td><td>100</td><td></td><td>60 x 44</td><td>4</td><td>Lithium</td><td>pinch</td></tr>
<tr><td>BLD-3</td><td> 780</td><td>3.7</td><td></td><td>19</td><td>44</td><td></td><td>52.3 x 32.4</td><td>5.3</td><td>Lithium</td><td></td></tr>
<tr><td>BBH-1SF</td><td> 800</td><td>6</td><td></td><td></td><td></td><td></td><td></td><td></td><td>Ni-MH</td><td></td></tr>
<tr><td>BP-4GW</td><td>2000</td><td>3.7</td><td></td><td></td><td></td><td></td><td></td><td></td><td>Lithium</td><td>harness</td></tr>
<tr><td>BL-4J</td><td>1200</td><td>3.7</td><td></td><td></td><td>100</td><td></td><td>59 x 37.5</td><td>4</td><td>Lithium</td><td>pinch</td></tr>
<tr><td>BP-6X</td><td> 700</td><td>3.7</td><td></td><td></td><td></td><td></td><td></td><td></td><td>Lithium</td><td></td></tr>
<tr><td>BL-8N</td><td> 700</td><td>3.7</td><td></td><td></td><td>no BSI pin</td><td></td><td></td><td></td><td>Lithium</td><td></td></tr>
<tr><td>BMH-1V</td><td> 550</td><td>6</td><td></td><td>89</td><td></td><td></td><td>120 x 42</td><td>14</td><td>Ni-MH</td><td></td></tr>
<tr><td>BL-5CA</td><td> 700</td><td>3.7</td><td></td><td>18</td><td>47</td><td></td><td>53 x 33.9</td><td>5.6</td><td>Lithium</td><td></td></tr>
<tr><td>BV-5JW</td><td>1450</td><td>3.8</td><td>5.5</td><td>28</td><td>7.3</td><td></td><td>58.5 x 38</td><td>5.6</td><td>Lithium</td><td>cable</td></tr>
<tr><td>BP-4L</td><td>1500</td><td>3.7</td><td></td><td></td><td>120</td><td></td><td>66 x 44</td><td>5</td><td>Lithium</td><td>pinch</td></tr>
<tr><td>BLL-2</td><td></td><td>3.6</td><td></td><td></td><td></td><td></td><td></td><td></td><td>Lithium</td><td></td></tr>
<tr><td>BMC-3</td><td>1100</td><td>3.6</td><td></td><td></td><td></td><td></td><td></td><td></td><td>Ni-Mh</td><td></td></tr>
<tr><td>BL-4CT</td><td> 860</td><td>3.7</td><td>3.2</td><td></td><td>75</td><td></td><td>54 x 34</td><td>4</td><td>Lithium</td><td></td></tr>
<tr><td>BL-5BT</td><td> 870</td><td>3.7</td><td></td><td></td><td>75</td><td></td><td>45 x 33</td><td>5</td><td>Lithium</td><td></td></tr>
<tr><td>BLB-3</td><td> 920</td><td>3.7</td><td></td><td></td><td></td><td></td><td></td><td></td><td>Lithium</td><td></td></tr>
<tr><td>BL-4B</td><td> 700</td><td>3.7</td><td></td><td></td><td>68</td><td></td><td>46 x 34</td><td>4.3</td><td>Lithium</td><td></td></tr>
<tr><td>BBH-1S</td><td> 600</td><td>6</td><td></td><td></td><td></td><td></td><td></td><td></td><td>Ni-MH</td><td></td></tr>
<tr><td>BLJ-1</td><td> 400</td><td>7.2</td><td></td><td>48</td><td></td><td></td><td>120 x 42</td><td>10</td><td>Lithium</td><td></td></tr>
<tr><td>BV-5XW</td><td>2000</td><td>3.7</td><td></td><td></td><td></td><td></td><td></td><td></td><td>Lithium</td><td>harness</td></tr>
<tr><td>BP-4W</td><td>1800</td><td>3.7</td><td></td><td></td><td></td><td></td><td>84 x 48</td><td>3.8</td><td>Lithium</td><td>pinch</td></tr>
<tr><td>BV-4NW</td><td>2000</td><td>3.7</td><td></td><td></td><td></td><td></td><td></td><td></td><td>Lithium</td><td>harness</td></tr>
<tr><td>BLJ-4</td><td>1500</td><td>7.2</td><td></td><td>106</td><td></td><td></td><td>120 x 42</td><td>22</td><td>Lithium</td><td></td></tr>
<tr><td>BLN-3</td><td></td><td>3.6</td><td></td><td></td><td></td><td></td><td></td><td></td><td>Lithium</td><td></td></tr>
<tr><td>BL-8L</td><td> 700</td><td>3.7</td><td></td><td></td><td></td><td></td><td></td><td></td><td>Lithium</td><td></td></tr>
<tr><td>BL-5K</td><td>1200</td><td>3.7</td><td></td><td></td><td></td><td></td><td></td><td></td><td>Lithium</td><td>pinch</td></tr>
<tr><td>BL-5C</td><td> 970-1050</td><td>3.7</td><td>3.885</td><td>23</td><td>82</td><td></td><td>53 x 34</td><td>6</td><td>Lithium</td><td>side pad</td></tr>
<tr><td>BL-5J</td><td>1320</td><td>3.7</td><td>4.9</td><td>22.15</td><td>100</td><td></td><td>60 x 38</td><td>5.5</td><td>Lithium</td><td>pinch</td></tr>
<tr><td>BPS-1</td><td> 600</td><td>3.7</td><td></td><td></td><td></td><td></td><td></td><td></td><td>Lithium</td><td></td></tr>
<tr><td>BP-5L</td><td>1300-1500</td><td>3.7</td><td></td><td></td><td></td><td></td><td></td><td></td><td>Lithium</td><td>side pad</td></tr>
<tr><td>BLC-1</td><td> 950</td><td>3.6</td><td></td><td></td><td></td><td></td><td></td><td></td><td>Lithium</td><td></td></tr>
<tr><td>BL-4BA</td><td> 630</td><td>3.7</td><td></td><td></td><td>33</td><td></td><td>46 x 34</td><td>4.3</td><td>Lithium</td><td></td></tr>
<tr><td>BBH-2H</td><td>1100</td><td>6</td><td></td><td></td><td></td><td></td><td></td><td></td><td>Ni-CD</td><td></td></tr>
<tr><td>BL-4S</td><td> 860</td><td>3.7</td><td></td><td></td><td>75</td><td></td><td>50 x 38</td><td>4.8</td><td>Lithium</td><td></td></tr>
<tr><td>BP-3L</td><td>1300</td><td>3.7</td><td>4.8</td><td>28</td><td></td><td></td><td>66 x 44</td><td>4.1</td><td>Lithium</td><td>pinch</td></tr>
<tr><td>BL-5CB</td><td> 800</td><td>3.7</td><td></td><td>21</td><td>30</td><td></td><td>53 x 34</td><td>5.8</td><td>Lithium</td><td></td></tr>
<tr><td>BP-5M</td><td> 900</td><td>3.7</td><td>3.3</td><td></td><td>75</td><td></td><td>41 x 40</td><td>6</td><td>Lithium</td><td></td></tr>
<tr><td>BP-6EW</td><td>1830</td><td>3.7</td><td></td><td></td><td></td><td></td><td></td><td></td><td>Lithium</td><td>harness</td></tr>
<tr><td>BLB-2</td><td> 650-750</td><td>3.7</td><td></td><td></td><td>46</td><td></td><td>53 x 33</td><td>7</td><td>Lithium</td><td></td></tr>
<tr><td>BBT-1XV</td><td> 600</td><td>6</td><td></td><td></td><td></td><td></td><td></td><td></td><td>Ni-MH</td><td></td></tr>
<tr><td>BL-6Q</td><td> 970</td><td>3.7</td><td></td><td></td><td></td><td></td><td></td><td></td><td>Lithium</td><td></td></tr>
<tr><td>BMH-1</td><td> 550</td><td>6</td><td></td><td>85</td><td></td><td></td><td>120 x 42</td><td>14</td><td>Ni-MH</td><td></td></tr>
<tr><td>BP-5Z</td><td>1080</td><td>3.7</td><td>4</td><td></td><td></td><td></td><td>41.6 x 43.9</td><td>5.6</td><td>Lithium</td><td>pinch</td></tr>
<tr><td>BP-3001L</td><td>1240</td><td>3.7</td><td></td><td></td><td></td><td></td><td></td><td></td><td>Lithium</td><td>bottom pad</td></tr>
<tr><td>BL-6F</td><td>1200</td><td>3.7</td><td></td><td></td><td></td><td></td><td></td><td></td><td>Lithium</td><td>pinch</td></tr>
<tr><td>BL-5CT</td><td>1050</td><td>3.7</td><td></td><td></td><td>82</td><td></td><td>54 x 34</td><td>5</td><td>Lithium</td><td></td></tr>
<tr><td>BLL-3</td><td></td><td>3.6</td><td></td><td></td><td></td><td></td><td></td><td></td><td>Lithium</td><td></td></tr>
<tr><td>BP-6M</td><td>1070-1100</td><td>3.7</td><td></td><td></td><td>91.5</td><td></td><td>40 x 41</td><td>6</td><td>Lithium</td><td></td></tr>
<tr><td>BP-6MT</td><td>1050</td><td>3.7</td><td></td><td></td><td>91</td><td></td><td>40 x 41</td><td>6.5</td><td>Lithium</td><td></td></tr>
<tr><td>BL-4U</td><td>1000</td><td>3.7</td><td></td><td></td><td>82</td><td></td><td>61 x 38</td><td>4</td><td>Lithium</td><td></td></tr>
<tr><td>BMP-1A</td><td> 700</td><td>3.6</td><td></td><td></td><td></td><td></td><td></td><td></td><td>Ni-MH</td><td></td></tr>
<tr><td>BP-5T</td><td>1650</td><td>3.7</td><td></td><td></td><td></td><td></td><td></td><td></td><td>Lithium</td><td>pinch</td></tr>
<tr><td>BLC-2</td><td></td><td>3.6</td><td></td><td></td><td>74.1-75</td><td></td><td>53 x 38.3</td><td>7.4</td><td>Lithium</td><td></td></tr>
<tr><td>BL-4C</td><td> 860</td><td>3.7</td><td></td><td>24</td><td>75</td><td></td><td>53 x 34</td><td>4.5</td><td>Lithium</td><td></td></tr>
</tbody></table>
<p>BLB-2 may have a different BSI resistance for the 650 mAh model. The wiki was unclear.</p>
<p>BSI resistance is generally measured between the GND and BSI pin</p>
<p>Datasheet for a BL-5C clone: <a href="https://docs.rs-online.com/ab5a/0900766b80cf37fc.pdf">https://docs.rs-online.com/ab5a/0900766b80cf37fc.pdf</a></p>
<p>Good part for the side pad-style connector: <a href="https://www.aliexpress.us/item/3256804968015691.html">https://www.aliexpress.us/item/3256804968015691.html</a>.
The important part is that the pitch is roughly 3mm and the point of contact is roughly 3mm high.
For this reason, I like the <code>4.6H</code> variant.
Some taller alternatives for you sickos out there: <a href="https://www.aliexpress.us/item/3256804706549757.html">https://www.aliexpress.us/item/3256804706549757.html</a></p>
<p>Discontinued part for pinch connector: <a href="https://jlcpcb.com/partdetail/DCHAIN-BA030637042059/C5827004">https://jlcpcb.com/partdetail/DCHAIN-BA030637042059/C5827004</a>.
A bit of a stretch, but it may be possible to use parts from here:
<a href="https://www.lcsc.com/products/PCB-Welding-Terminal_11512.html">https://www.lcsc.com/products/PCB-Welding-Terminal_11512.html</a>, with the caveat that they're all through-hole and all
the ones I've looked at are too big.</p>
<figure >
<div style="height: 360px; overflow: hidden">
<img
class="fig"
style="margin: -300px 0px; overflow: hidden"
src="https://www.batteries4pro.com/22082-pos_thickbox/batterie-38v-2ah-lipo-bv-4nw-pour-nokia-lumia-928.jpg"
alt="a battery connector mounted to a flex PCB cable harness.
it is mostly plastic with 7 metal bits, 3 of which are soldered to the flex PCB."
>
</div>
<figcaption>Close-up of the harness-style battery connector</figcaption>
</figure>
<p>There's probably a similar part somewhere in this list: <a href="https://www.lcsc.com/products/Board-to-Board-and-Backplane-Connector_960.html">https://www.lcsc.com/products/Board-to-Board-and-Backplane-Connector_960.html</a></p>
<script>
document.addEventListener('click', function (e) {
try {
// allows for elements inside TH
function findElementRecursive(element, tag) {
return element.nodeName === tag ? element : findElementRecursive(element.parentNode, tag);
}
var ascending_table_sort_class = 'asc';
var no_sort_class = 'no-sort';
var null_last_class = 'n-last';
var alt_sort_1 = e.shiftKey || e.altKey;
var element = findElementRecursive(e.target, 'TH');
var tr = element.parentNode;
var thead = tr.parentNode;
var table = thead.parentNode;
function getValue(element) {
var _a;
var value = alt_sort_1 ? element.dataset.sortAlt : (_a = element.dataset.sort) !== null && _a !== void 0 ? _a : element.textContent;
return value;
}
if (thead.nodeName === 'THEAD' && // sortable only triggered in `thead`
!element.classList.contains(no_sort_class) // .no-sort is now core functionality, no longer handled in CSS
) {
var column_index_1;
var nodes = tr.cells;
var tiebreaker_1 = +element.dataset.sortTbr;
// Reset thead cells and get column index
for (var i = 0; i < nodes.length; i++) {
if (nodes[i] === element) {
column_index_1 = +element.dataset.sortCol || i;
}
else {
nodes[i].setAttribute('aria-sort', 'none');
}
}
var direction = 'descending';
if (element.getAttribute('aria-sort') === 'descending' ||
(table.classList.contains(ascending_table_sort_class) && element.getAttribute('aria-sort') !== 'ascending')) {
direction = 'ascending';
}
// Update the `th` class accordingly
element.setAttribute('aria-sort', direction);
var reverse_1 = direction === 'ascending';
var sort_null_last_1 = table.classList.contains(null_last_class);
var compare_1 = function (a, b, index) {
var x = getValue(b.cells[index]);
var y = getValue(a.cells[index]);
if (sort_null_last_1) {
if (x === '' && y !== '') {
return -1;
}
if (y === '' && x !== '') {
return 1;
}
}
var temp = +x - +y;
var bool = isNaN(temp) ? x.localeCompare(y) : temp;
return reverse_1 ? -bool : bool;
};
// loop through all tbodies and sort them
for (var i = 0; i < table.tBodies.length; i++) {
var org_tbody = table.tBodies[i];
// Put the array rows in an array, so we can sort them...
var rows = [].slice.call(org_tbody.rows, 0);
// Sort them using Array.prototype.sort()
rows.sort(function (a, b) {
var bool = compare_1(a, b, column_index_1);
return bool === 0 && !isNaN(tiebreaker_1) ? compare_1(a, b, tiebreaker_1) : bool;
});
// Make an empty clone
var clone_tbody = org_tbody.cloneNode();
// Put the sorted rows inside the clone
clone_tbody.append.apply(clone_tbody, rows);
// And finally replace the unsorted tbody with the sorted one
table.replaceChild(clone_tbody, org_tbody);
}
}
// eslint-disable-next-line no-unused-vars
}
catch (error) {
// console.log(error)
}
});
</script>
<p>TODO:</p>
<ul>
<li>implement natural sorting so I can remove the underscores from the capacity section</li>
<li>put arrows in header row so its more obvious that the table is sortable</li>
<li>missing
<ul>
<li>BN-02 <a href="https://www.aliexpress.us/item/2251832781466285.html">https://www.aliexpress.us/item/2251832781466285.html</a></li>
<li>BN-06 <a href="https://www.aliexpress.us/item/2251832781466285.html">https://www.aliexpress.us/item/2251832781466285.html</a></li>
<li>BV-T5A <a href="https://www.aliexpress.us/item/2251832781466285.html">https://www.aliexpress.us/item/2251832781466285.html</a></li>
</ul>
</li>
</ul>
<h2 id="sources">Sources</h2>
<ul>
<li>Basically everything scraped from the cpkb.org wiki pages on Nokia batteries (it's down at time of writing, which is
a bit concerning)
<ul>
<li>The cpkb wiki is under the GNU Free Documentation License 1.3, which disallows editing, but as I understand it,
everything I've copied is technical information, and holds the same copyright status as a phone book.
If anyone knows more about law than me and thinks this is technically illegal, I'd appreciate if you let me know.</li>
</ul>
</li>
<li>BP-4W dimensions from: <a href="https://www.batterymart.com/p-bli-1322-1-4-nokia-bp-4w-battery.html">https://www.batterymart.com/p-bli-1322-1-4-nokia-bp-4w-battery.html</a></li>
<li>Table sorting code adapted from: <a href="https://github.com/tofsjonas/sortable/blob/main/sortable.js">https://github.com/tofsjonas/sortable/blob/main/sortable.js</a></li>
<li>TODO: add info from here: <a href="https://pinoutguide.com/CellularPhones-A-N/nokia_bl-4j_battery_pinout.shtml">https://pinoutguide.com/CellularPhones-A-N/nokia_bl-4j_battery_pinout.shtml</a></li>
</ul>
PCB swatchMon, 05 Aug 2024 00:00:00 +0000Unknown
https://wiki.jaxter184.net/pcb-swatch/
https://wiki.jaxter184.net/pcb-swatch/<p>The main goal of this swatch PCB is to use as a visual reference when creating PCB art, specifically for <a href="/life-counter">this life
counter project</a>.</p>
<p>A similar project: <a href="https://arx.wtf/blog/1-front-panels-tips">https://arx.wtf/blog/1-front-panels-tips</a></p>
<figure >
<img
class="fig"
src="/images/pcb-swatch/front.jpg"
alt="Three square printed circuit boards with rounded corners in a row with an almost plaid pattern.
More details on the pattern are outlined later in the blog.
Each board has the same pattern in different colors, except the one on the right has silkscreen in some areas that do
not have solder mask, while the left and center ones have silkscreen only where there is also solder mask.
From left to right, the colors are: white silkscreen on green mask, white silkscreen on black mask, black silkscreen on
white mask."
>
</figure>
<figure >
<img
class="fig"
src="/images/pcb-swatch/back.jpg"
alt="The back of the same circuit boards as the above image.
This side is completely bare (no silkscreen, mask, or copper), except the second of four rows, where there are various
test patterns, and a link to this page and a logo in copper with a HASL finish.
On the left side, the tests are: A hatched grid where the holes get smaller, horizontal lines where the lines get
thinner as you go right and further apart as you go down, two long lines that start about a millimeter apart and get
closer as you go right until they touch.
On the right side, tthe tests are: a grid of dots, getting smaller as you go right, and further apart as you go down,
and a grid of donuts, where the holes get smaller as you go down."
>
</figure>
<figure >
<img
class="fig"
src="/images/pcb-swatch/illuminated.jpg"
alt="The back of the PCBs again, but illuminated to show that the fiberglass substrate is translucent, and the copper
on the other side blocks light, and the solder mask adds a tint."
>
</figure>
<p><kicanvas-embed
src="/kicad/pcb-swatch.kicad_pcb"
controls="full"
theme="kicad"
>
</kicanvas-embed></p>
<p>I encourage you to <a href="https://git.sr.ht/~jaxter184/pcb-swatch/tree/main/item/pcb-swatch.kicad_pcb">download the file</a>!</p>
<h2 id="design">Design</h2>
<p>To show how different layers interact, the front side features every combination of copper, silkscreen, and mask (and
lack thereof), plus two sizes of hatching.
My favorite combo is probably the one where every layer has small haching.</p>
<p>The back features a couple test patterns to let me know how small my features can be.
According to the fab house's support team, the reason that the black silkscreen on white mask came out differently than
the other two is because it was processed by a different engineer who skipped a step assuming that I was putting
silkscreen over the holes in the mask on purpose. I'm honestly kinda glad that I got a few different versions because
I now have references for what the end result is like in both cases.</p>
<p>TODO: closeup pics</p>
Life CounterSun, 30 Jun 2024 00:00:00 +0000Unknown
https://wiki.jaxter184.net/life-counter/
https://wiki.jaxter184.net/life-counter/<p>A friend had a cool abacus life counter that he made with his favorite card, so I made a feeble imitation of it using
processes and components that were more familiar to me.
It's hypothetically usable for keeping track of any integer value in any game, such as a <a href="https://mtg.fandom.com/wiki/Planeswalker">planeswalker</a>'s loyalty count
or any resource in a board game or tabletop roleplaying game.</p>
<p>I'm not selling them yet, but I'm planning to.
To be notified when they're available, <a href="mailto:[email protected]?subject=Life%20counter%20notification%20request&body=I%20would%20like%20to%20be%20emailed%20once%20the%20life%20counter%20is%20available.%20Thanks.">click here</a>, fill in your email (if it isn't already done so automatically),
and hit "send".
Alternatively, you can try to make one yourself using the <a href="https://git.sr.ht/~jaxter184/life-counter">published schematics and code</a>, in which case I'm happy to
help!</p>
<div class="multi-fig">
<figure class="half-width">
<img
class="fig"
src="/images/oko/stand_front.jpg"
alt="A life counter on a stand that orients it diagonally, seen from the front, showing the Oko, Thief of Crowns card
and an e-ink display through a cutout where the rules text used to be"
>
<figcaption>Front</figcaption>
</figure>
<figure class="half-width">
<img
class="fig"
src="/images/oko/stand_back.jpg"
alt="A life counter on a stand that orients it diagonally, seen from the back, showing all the electrical components
and the clear acrylic back"
>
<figcaption>Back</figcaption>
</figure>
</div>
<h2 id="layers">Layers</h2>
<figure >
<img
class="fig"
src="/images/oko/stack.jpg"
alt="A life counter disassembled showing each of the five layers laid on top of each other like a spread of playing cards"
>
<figcaption>Stacked layer view</figcaption>
</figure>
<ul>
<li>3D printed frame with little corner indents to keep everything together</li>
<li>Thin, hard plastic sheet to protect the card and display</li>
<li>The card "<a href="https://scryfall.com/card/eld/197/oko-thief-of-crowns">Oko, Thief of Crowns</a>" from the trading card game, <a href="https://en.wikipedia.org/wiki/Magic%3A_The_Gathering">Magic, the Gathering</a>.
I bought a bunch of <a href="https://scryfall.com/card/cmm/851/jace-architect-of-thought">this significantly cheaper card with a similar layout</a> to practice the cutout on.</li>
<li>PCB with an <a href="https://en.wikipedia.org/wiki/Electronic_paper">e-ink</a> display, STM32F030C8T6, and side-mounted switches</li>
<li>Two pieces of laser-cut ~1.5mm acrylic welded together with solvent.
Pattern created by manually editing the courtyard output of KiCAD.</li>
</ul>
<p><figure >
<img
class="fig"
src="/images/oko/spread_front.jpg"
alt="A life counter disassembled with four of the layers (the plastic cover is omitted) and the battery removed, laid
out flat, each layer about a centimeter apart, face-up"
>
<figcaption>Flat layer view (front)</figcaption>
</figure>
<br>
<figure >
<img
class="fig"
src="/images/oko/spread_back.jpg"
alt="Same as the previous image, but from the back"
>
<figcaption>Flat layer view (back)</figcaption>
</figure>
</p>
<figure >
<img
class="fig"
src="/images/oko/lr_back.jpg"
alt="Two different life counter circuit boards, one with the display connector on the left and the other with it on th
e right"
>
<figcaption>The two versions I've made, with the e-ink display mirrored.</figcaption>
</figure>
<p>The programming pads are designed for <a href="https://en.wikipedia.org/wiki/Pogo_pin">pogo pins</a>, but it's a hassle to hold them on by hand when I'm iterating on
code, so I've soldered a header on the one on the right.</p>
<h2 id="display-considerations">Display considerations</h2>
<p><figure class="half-width">
<img
class="fig"
src="/images/oko/numbers_l.jpg"
alt="A close-up of the e-ink display on an assembled life counter, with the numbers on the left covering up a brown
kapton portion of the display"
>
<figcaption>The numbers on the left conveniently cover up the weird bit at the base of the cable connector</figcaption>
</figure>
<figure class="half-width">
<img
class="fig"
src="/images/oko/numbers_r.jpg"
alt="Same as the previous image, but with the brown kapton clearly visible on the right"
>
<figcaption>When the display is flipped, I can put the buttons on the same side as the numbers, but at the cost of seeing
the weird bit</figcaption>
</figure>
</p>
<figure style="width:60%">
<img
class="fig"
src="/images/oko/fresh.jpg"
alt="Same as the previous image, but with the display looking much clearer and less noisy"
>
<figcaption>the noise in the other pictures are a result of the displays being turned off for a long period of time</figcaption>
</figure>
<p>The e-paper circuit primarily references <a href="https://www.good-display.com/companyfile/623.html">the manufacturer's documentation</a>, but I also looked at the
<a href="https://shop.pimoroni.com/products/badger-2040">Pimoroni Badger 2040</a> (<a href="https://cdn.shopify.com/s/files/1/0174/1800/files/badger_2040_schematic.pdf">schematic</a>) and the <a href="https://www.waveshare.com/wiki/E-Paper_Driver_HAT#Resources">Waveshare E-Paper Driver HAT</a>.</p>
<h2 id="old-version">Old version</h2>
<figure >
<img
class="fig"
src="/images/oko/old.jpg"
alt="An older version on a green circuit board, with an image of the card etched into the PCB itself, prominently
displaying the card art. The rules text area has two 3x5 matrices of LEDs displaying digits. There are also switches
on the left of the LED matrices."
>
<figcaption>The first version. It's much more readable in the dark.</figcaption>
</figure>
<p>I really liked the rear-illuminated icons, but unfortunately, the LEDs greatly increase the power draw.
I'm not sure by exactly how much, but my estimate is that it takes the power on duration from a few days to a few
hours.
I wanted to use a coin cell in the final version so I wouldn't have to worry about charging or other lithium polymer
battery considerations, and the LEDs were pretty much the only thing that drew a lot of power, so they were the first
thing to go.</p>
<p>I also thought the PCB art was really cool, and I want to experiment with stuff like <a href="https://youtu.be/VckU9UXI_XE">halftone</a>/dither and outlines and
compensating for the silkscreen leakage, but I'll probably do that on a project that I'm not planning to sell, because
I'd prefer to avoid having <a href="https://www.polygon.com/23695923/mtg-aftermath-pinkerton-raid-leaked-cards">Wizards of the Coast sic the Pinkertons</a> on me.</p>
<h2 id="things-to-try-in-the-future">Things to try in the future</h2>
<ul>
<li>USB connector at the top? or the side? or the bottom?</li>
<li>Add optional LEDs for those who want to use it as an "on" indicator (the e-ink display doesn't have time to change
it's display when the unit is powered off)</li>
<li>See how it fits with Pokémon, Digimon, and Yu-Gi-Oh cards (and maybe others?)</li>
<li>Make the case on a resin printer.
Should be thinner and more precise, and maybe let me make clips like on injection molded parts.
If I do the clips, I can probably also redesign the acrylic back to make the clips flush with the back.</li>
<li>https://github.com/klepas/open-baskerville/tree/master/specimens</li>
</ul>
<!-- fuck you to Doombringer1331, the rando i met in the makerspace, for telling
me you were taking a picture to "show your friends" and then posting it to reddit
https://www.reddit.com/r/magicTCG/comments/f3w98w/someone_at_my_university_made_this_life_counter/ -->
Open Hardware SummitWed, 12 Jun 2024 00:00:00 +0000Unknown
https://wiki.jaxter184.net/open-hardware-summit/
https://wiki.jaxter184.net/open-hardware-summit/<p><a href="https://2024.oshwa.org/">Open Hardware Summit</a> in Montréal</p>
<p>Went up with some friends who are building a digital theremin</p>
<p>Canada does a really good job supporting cooperatives compared to the US (at least based on my anecdotal experience in
both cases).</p>
<h2 id="kouign-amann">Kouign Amann</h2>
<p>The bagels sucked. I liked the bagels I had in New York better.</p>
<p>I did, however, have a very good pastry called Kouign Amann
Delicious cake made of butter, sugar, and bread (in order of amount).
One of the people in the group I was staying with wanted to go to a café named after the pastry one morning, and after
eating it, I was thinking about it for the entire rest of my trip.</p>
<p>After I came back, I tried making the pastry using <a href="https://cooking.nytimes.com/recipes/1023855-classic-kouign-amann">this recipe</a> (conveniently based on the exact same patisserie I
happened to visit), and it only took one failure to successfully make one.</p>
<h2 id="hackerspaces">Hackerspaces</h2>
<p>visited several very cool hackerspaces.
don't remember the names of the places i visited, but they were all incredible.
saw a cool sculpture.</p>
<p>Also visited ResistorNYC while i was in New York and it was very impressive.</p>
<h2 id="individuals-and-organizations">Individuals and organizations</h2>
<ul>
<li><a href="https://openhvac.io/">OpenHVAC</a></li>
<li><a href="https://forum.openhardware.science/">Global Open Science Hardware</a>
<ul>
<li><a href="https://www.flickr.com/photos/goshcommunity/albums/72177720316719345/">Some notes from the unconference talk</a></li>
</ul>
</li>
<li><a href="https://www.erikcontreras.com/">Erik Contreras</a></li>
</ul>
State machinesSat, 25 May 2024 00:00:00 +0000Unknown
https://wiki.jaxter184.net/game-dev/spell-scripting/state-machines/
https://wiki.jaxter184.net/game-dev/spell-scripting/state-machines/<p>During development of a character combat game, it is important to be able to materialize and iterate on ideas quickly.
Some games use XML files, others use <a href="https://youtu.be/nGaajB8m5Q0?t=91">Microsoft Excel</a>, and games like Balatro and Hades 2 are written mostly in Lua
(to the delight of their modding communities).
Overwatch uses a visual programming language called "Statescript", described in <a href="https://youtu.be/ScyZjcjTlA4?t=154">Dan Reed's GDC 2017 talk</a> (<a href="https://www.gdcvault.com/play/1024041/Networking-Scripted-Weapons-and-Abilities">alt
link</a>), and it is a particularly exciting example of how such a system can be designed.
According to the talk, Statescript is used to manage all the game logic, animations, and sounds that comprise a hero
ability (including their primary fire weapons), as well as things like HUD UI elements (ex: ultimate meter, character
portrait, health bar, ability icons).</p>
<h2 id="a-whirlwind-primer-on-finite-state-machines">A whirlwind primer on finite state machines</h2>
<details open>
<summary>
Feel free to skip this section if you are familiar with the term "finite state machine"
</summary>
<br>
<figure >
<img
class="fig"
src="/images/fsm/build/sm-00.dot.svg"
alt="A state machine with two unconnected states 'A' and 'B'"
>
<figcaption>A state machine has states (duh?)</figcaption>
</figure>
<figure >
<img
class="fig"
src="/images/fsm/build/sm-01.dot.svg"
alt="A state machine with starting state 'A' and ending state 'B', where 'A' transitions into 'B'."
>
<figcaption>Each state can transition into another.
The arrow on the left indicates that that is the "starting" state, and the double circle on the right indicates that it
is a valid "ending" state.</figcaption>
</figure>
<figure >
<img
class="fig"
src="/images/fsm/build/sm-02.dot.svg"
alt="A state machine with state 'A' transitioning to ending state 'B' on condition `var`, and to 'C' on condition `!var`.
'C' transtitions to 'B' unconditionally."
>
<figcaption>Transitions can have conditions.
In this case, if <code>var</code> is true, then the state machine goes <code>A -> B</code>, and if <code>var</code> is
false, then it goes <code>A -> C -> B</code>.</figcaption>
</figure>
<figure >
<img
class="fig"
src="/images/fsm/build/sm-03.dot.svg"
alt="A state machine with lots of states and transitions as a non-trivial example"
>
<figcaption>State machines can be arbitrarily complex.</figcaption>
</figure>
<p>Note that for every node (that is not an ending node) <em>always</em> transitions to exactly one another node. For example, the
conditions of the outgoing edges of node <code>C</code> are a bit weird, but no matter what the values of <code>u</code> and <code>w</code> are, <code>C</code> will
always transition to <strong>only</strong> one of <code>F</code>, <code>B</code>, or <code>D</code>:</p>
<style>
table {
width: fit-content;
margin: auto;
}
th { width: fit-content; }
</style>
<table><thead><tr><th style="text-align: center"><code>u</code></th><th style="text-align: center"><code>w</code></th><th></th><th style="text-align: center"><code>!u</code> (to <code>F</code>)</th><th style="text-align: center"><code>u && w</code> (to <code>B</code>)</th><th style="text-align: center"><code>u && !w</code> (to <code>D</code>)</th><th></th><th style="text-align: center">node</th></tr></thead><tbody>
<tr><td style="text-align: center">❌</td><td style="text-align: center">❌</td><td></td><td style="text-align: center">✅</td><td style="text-align: center">❌</td><td style="text-align: center">❌</td><td></td><td style="text-align: center"><code>F</code></td></tr>
<tr><td style="text-align: center">✅</td><td style="text-align: center">❌</td><td></td><td style="text-align: center">❌</td><td style="text-align: center">❌</td><td style="text-align: center">✅</td><td></td><td style="text-align: center"><code>D</code></td></tr>
<tr><td style="text-align: center">❌</td><td style="text-align: center">✅</td><td></td><td style="text-align: center">✅</td><td style="text-align: center">❌</td><td style="text-align: center">❌</td><td></td><td style="text-align: center"><code>F</code></td></tr>
<tr><td style="text-align: center">✅</td><td style="text-align: center">✅</td><td></td><td style="text-align: center">❌</td><td style="text-align: center">✅</td><td style="text-align: center">❌</td><td></td><td style="text-align: center"><code>B</code></td></tr>
</tbody></table>
</details>
<p>Now that we're on the same page about what a finite state machine is, it's time for something completely different
because:</p>
<h2 id="actually-i-don-t-want-a-finite-state-machine">Actually, I don't want a finite state machine</h2>
<p>Though finite state machines are one of the most common types of state machines in the context of programming, they
aren't the only one.
In the aforementioned Statescript, for example, multiple states can be "active" at once, and the flow of states
interacts with the "tick" timing system used to synchronize game logic in Overwatch.
Loosening the rules like this allows for a more flexible system and (hopefully) cleaner state machines.</p>
<!--
Also, the inputs (ex: button presses) and outputs (ex: animations, sounds) can be connected to any state in the state
machine, and aren't necessarily limited to the beginning and ending states.
-->
<h2 id="current-design">Current design</h2>
<ul>
<li>Each spell/skill is its own state machine containing:
<ul>
<li>states</li>
<li>attributes - in-game variables turning speed, position</li>
<li>resources - skill-specific variables such as ammo and fire rate, as well as timers</li>
</ul>
</li>
<li>States can transition between each other</li>
<li>States can be triggered to be "active" even without a preceding state</li>
<li>When activated, states perform actions, such as starting a timer or dealing damage</li>
</ul>
<p>Here is an extremely simple example of a "dash" skill that moves the player quickly in the direction they are currently
facing:</p>
<pre data-lang="ron" class="language-ron z-code"><code class="language-ron" data-lang="ron"><span class="z-source z-ron"><span class="z-comment z-line z-double-slash z-ron"><span class="z-punctuation z-definition z-comment z-ron">//</span> skill
</span></span><span class="z-source z-ron"><span class="z-string z-quoted z-double z-ron"><span class="z-punctuation z-definition z-string z-begin z-ron">"</span>dash<span class="z-punctuation z-definition z-string z-end z-ron">"</span></span>: <span class="z-meta z-structure z-dictionary z-ron"><span class="z-punctuation z-section z-dictionary z-begin z-ron">{</span>
</span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron">
</span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"> <span class="z-comment z-line z-double-slash z-ron"><span class="z-punctuation z-definition z-comment z-ron">//</span> state that triggers on button press
</span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"> <span class="z-string z-quoted z-double z-ron"><span class="z-punctuation z-definition z-string z-begin z-ron">"</span>do<span class="z-punctuation z-definition z-string z-end z-ron">"</span></span><span class="z-punctuation z-separator z-dictionary z-key-value z-ron">:</span> <span class="z-meta z-structure z-entity z-ron"><span class="z-punctuation z-section z-dictionary z-begin z-ron">(</span>
</span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"> <span class="z-entity z-name z-tag z-ron">activate_on</span><span class="z-punctuation z-separator z-dictionary z-key-value z-ron">:</span> <span class="z-entity z-name z-class z-ron">All</span><span class="z-meta z-structure z-entity z-ron"><span class="z-punctuation z-section z-dictionary z-begin z-ron">(</span><span class="z-meta z-structure z-array z-ron"><span class="z-punctuation z-section z-array z-begin z-ron">[</span>
</span></span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"> <span class="z-comment z-line z-double-slash z-ron"><span class="z-punctuation z-definition z-comment z-ron">//</span> on button input
</span></span></span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"> <span class="z-entity z-name z-class z-ron">Ext</span><span class="z-meta z-structure z-entity z-ron"><span class="z-punctuation z-section z-dictionary z-begin z-ron">(</span><span class="z-entity z-name z-class z-ron">Input</span><span class="z-meta z-structure z-entity z-ron"><span class="z-punctuation z-section z-dictionary z-begin z-ron">(</span><span class="z-entity z-name z-class z-ron">Rise</span><span class="z-punctuation z-section z-dictionary z-end z-ron">)</span></span><span class="z-punctuation z-section z-dictionary z-end z-ron">)</span></span><span class="z-punctuation z-separator z-array z-ron">,</span>
</span></span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"> <span class="z-comment z-line z-double-slash z-ron"><span class="z-punctuation z-definition z-comment z-ron">//</span> if cooldown is not active
</span></span></span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"> <span class="z-entity z-name z-class z-ron">TimerExpired</span><span class="z-meta z-structure z-entity z-ron"><span class="z-punctuation z-section z-dictionary z-begin z-ron">(</span><span class="z-string z-quoted z-double z-ron"><span class="z-punctuation z-definition z-string z-begin z-ron">"</span>dash::cooldown<span class="z-punctuation z-definition z-string z-end z-ron">"</span></span><span class="z-punctuation z-section z-dictionary z-end z-ron">)</span></span><span class="z-punctuation z-separator z-array z-ron">,</span>
</span></span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"> <span class="z-punctuation z-section z-array z-end z-ron">]</span></span><span class="z-punctuation z-section z-dictionary z-end z-ron">)</span></span><span class="z-punctuation z-separator z-dictionary z-ron">,</span>
</span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"> <span class="z-entity z-name z-tag z-ron">action</span><span class="z-punctuation z-separator z-dictionary z-key-value z-ron">:</span> <span class="z-meta z-structure z-array z-ron"><span class="z-punctuation z-section z-array z-begin z-ron">[</span>
</span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"> <span class="z-entity z-name z-class z-ron">StartTimer</span><span class="z-meta z-structure z-entity z-ron"><span class="z-punctuation z-section z-dictionary z-begin z-ron">(</span><span class="z-string z-quoted z-double z-ron"><span class="z-punctuation z-definition z-string z-begin z-ron">"</span>dash::cooldown<span class="z-punctuation z-definition z-string z-end z-ron">"</span></span><span class="z-punctuation z-separator z-dictionary z-ron">,</span> <span class="z-constant z-numeric z-ron">0.5</span><span class="z-punctuation z-section z-dictionary z-end z-ron">)</span></span><span class="z-punctuation z-separator z-array z-ron">,</span>
</span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"> <span class="z-entity z-name z-class z-ron">StartTimer</span><span class="z-meta z-structure z-entity z-ron"><span class="z-punctuation z-section z-dictionary z-begin z-ron">(</span><span class="z-string z-quoted z-double z-ron"><span class="z-punctuation z-definition z-string z-begin z-ron">"</span>dash::duration<span class="z-punctuation z-definition z-string z-end z-ron">"</span></span><span class="z-punctuation z-separator z-dictionary z-ron">,</span> <span class="z-constant z-numeric z-ron">0.05</span><span class="z-punctuation z-section z-dictionary z-end z-ron">)</span></span><span class="z-punctuation z-separator z-array z-ron">,</span>
</span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"> <span class="z-entity z-name z-class z-ron">Ext</span><span class="z-meta z-structure z-entity z-ron"><span class="z-punctuation z-section z-dictionary z-begin z-ron">(</span><span class="z-entity z-name z-class z-ron">SelfEffect</span><span class="z-meta z-structure z-entity z-ron"><span class="z-punctuation z-section z-dictionary z-begin z-ron">(</span><span class="z-meta z-structure z-array z-ron"><span class="z-punctuation z-section z-array z-begin z-ron">[</span>
</span></span></span></span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"> <span class="z-comment z-line z-double-slash z-ron"><span class="z-punctuation z-definition z-comment z-ron">//</span> forces player to move in the direction it is facing (i.e. not strafing)
</span></span></span></span></span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"> <span class="z-entity z-name z-class z-ron">SetMoveControl</span><span class="z-meta z-structure z-entity z-ron"><span class="z-punctuation z-section z-dictionary z-begin z-ron">(</span><span class="z-entity z-name z-class z-ron">Forwards</span><span class="z-punctuation z-section z-dictionary z-end z-ron">)</span></span><span class="z-punctuation z-separator z-array z-ron">,</span>
</span></span></span></span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"> <span class="z-comment z-line z-double-slash z-ron"><span class="z-punctuation z-definition z-comment z-ron">//</span> "Stats" are game variables that directly control some aspect of game logic
</span></span></span></span></span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"> <span class="z-entity z-name z-class z-ron">AddStatMod</span><span class="z-meta z-structure z-entity z-ron"><span class="z-punctuation z-section z-dictionary z-begin z-ron">(</span>
</span></span></span></span></span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"> <span class="z-comment z-line z-double-slash z-ron"><span class="z-punctuation z-definition z-comment z-ron">//</span> Set turn speed to 0, preventing turning
</span></span></span></span></span></span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"> <span class="z-entity z-name z-class z-ron">TurnLimit</span><span class="z-punctuation z-separator z-dictionary z-ron">,</span>
</span></span></span></span></span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"> <span class="z-comment z-line z-double-slash z-ron"><span class="z-punctuation z-definition z-comment z-ron">//</span> Information for order of operations. The name is for when we want to delete the modifier later.
</span></span></span></span></span></span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"> <span class="z-meta z-structure z-entity z-ron"><span class="z-punctuation z-section z-dictionary z-begin z-ron">(</span><span class="z-entity z-name z-tag z-ron">priority</span><span class="z-punctuation z-separator z-dictionary z-key-value z-ron">:</span> <span class="z-constant z-numeric z-ron">2</span><span class="z-punctuation z-separator z-dictionary z-ron">,</span> <span class="z-entity z-name z-tag z-ron">modifier</span><span class="z-punctuation z-separator z-dictionary z-key-value z-ron">:</span> <span class="z-entity z-name z-class z-ron">UpperLimit</span><span class="z-punctuation z-separator z-dictionary z-ron">,</span> <span class="z-entity z-name z-tag z-ron">name</span><span class="z-punctuation z-separator z-dictionary z-key-value z-ron">:</span> <span class="z-string z-quoted z-double z-ron"><span class="z-punctuation z-definition z-string z-begin z-ron">"</span>dash::<span class="z-punctuation z-definition z-string z-end z-ron">"</span></span><span class="z-punctuation z-section z-dictionary z-end z-ron">)</span></span><span class="z-punctuation z-separator z-dictionary z-ron">,</span>
</span></span></span></span></span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"> <span class="z-constant z-numeric z-ron">0</span><span class="z-punctuation z-separator z-dictionary z-ron">,</span>
</span></span></span></span></span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"> <span class="z-punctuation z-section z-dictionary z-end z-ron">)</span></span><span class="z-punctuation z-separator z-array z-ron">,</span>
</span></span></span></span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"> <span class="z-entity z-name z-class z-ron">AddStatMod</span><span class="z-meta z-structure z-entity z-ron"><span class="z-punctuation z-section z-dictionary z-begin z-ron">(</span>
</span></span></span></span></span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"> <span class="z-entity z-name z-class z-ron">MoveSpeed</span><span class="z-punctuation z-separator z-dictionary z-ron">,</span>
</span></span></span></span></span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"> <span class="z-meta z-structure z-entity z-ron"><span class="z-punctuation z-section z-dictionary z-begin z-ron">(</span><span class="z-entity z-name z-tag z-ron">priority</span><span class="z-punctuation z-separator z-dictionary z-key-value z-ron">:</span> <span class="z-constant z-numeric z-ron">2</span><span class="z-punctuation z-separator z-dictionary z-ron">,</span> <span class="z-entity z-name z-tag z-ron">modifier</span><span class="z-punctuation z-separator z-dictionary z-key-value z-ron">:</span> <span class="z-entity z-name z-class z-ron">Add</span><span class="z-punctuation z-separator z-dictionary z-ron">,</span> <span class="z-entity z-name z-tag z-ron">name</span><span class="z-punctuation z-separator z-dictionary z-key-value z-ron">:</span> <span class="z-string z-quoted z-double z-ron"><span class="z-punctuation z-definition z-string z-begin z-ron">"</span>dash::<span class="z-punctuation z-definition z-string z-end z-ron">"</span></span><span class="z-punctuation z-section z-dictionary z-end z-ron">)</span></span><span class="z-punctuation z-separator z-dictionary z-ron">,</span>
</span></span></span></span></span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"> <span class="z-constant z-numeric z-ron">80</span>.<span class="z-punctuation z-separator z-dictionary z-ron">,</span>
</span></span></span></span></span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"> <span class="z-punctuation z-section z-dictionary z-end z-ron">)</span></span><span class="z-punctuation z-separator z-array z-ron">,</span>
</span></span></span></span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"> <span class="z-punctuation z-section z-array z-end z-ron">]</span></span><span class="z-punctuation z-section z-dictionary z-end z-ron">)</span></span><span class="z-punctuation z-section z-dictionary z-end z-ron">)</span></span><span class="z-punctuation z-separator z-array z-ron">,</span>
</span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"> <span class="z-punctuation z-section z-array z-end z-ron">]</span></span><span class="z-punctuation z-separator z-dictionary z-ron">,</span>
</span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"> <span class="z-punctuation z-section z-dictionary z-end z-ron">)</span></span><span class="z-punctuation z-separator z-dictionary z-ron">,</span>
</span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron">
</span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"> <span class="z-comment z-line z-double-slash z-ron"><span class="z-punctuation z-definition z-comment z-ron">//</span> state that runs at end of dash to reset the player
</span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"> <span class="z-string z-quoted z-double z-ron"><span class="z-punctuation z-definition z-string z-begin z-ron">"</span>end<span class="z-punctuation z-definition z-string z-end z-ron">"</span></span><span class="z-punctuation z-separator z-dictionary z-key-value z-ron">:</span> <span class="z-meta z-structure z-entity z-ron"><span class="z-punctuation z-section z-dictionary z-begin z-ron">(</span>
</span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"> <span class="z-entity z-name z-tag z-ron">activate_on</span><span class="z-punctuation z-separator z-dictionary z-key-value z-ron">:</span> <span class="z-entity z-name z-class z-ron">TimerExpired</span><span class="z-meta z-structure z-entity z-ron"><span class="z-punctuation z-section z-dictionary z-begin z-ron">(</span><span class="z-string z-quoted z-double z-ron"><span class="z-punctuation z-definition z-string z-begin z-ron">"</span>dash::duration<span class="z-punctuation z-definition z-string z-end z-ron">"</span></span><span class="z-punctuation z-section z-dictionary z-end z-ron">)</span></span><span class="z-punctuation z-separator z-dictionary z-ron">,</span>
</span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"> <span class="z-comment z-line z-double-slash z-ron"><span class="z-punctuation z-definition z-comment z-ron">//</span> `pre_state` is the list of states that this "end" state transitions from.
</span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"> <span class="z-comment z-line z-double-slash z-ron"><span class="z-punctuation z-definition z-comment z-ron">//</span> These states must be active in order for "end" to trigger, and will all be deactivated when that happens.
</span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"> <span class="z-entity z-name z-tag z-ron">pre_states</span><span class="z-punctuation z-separator z-dictionary z-key-value z-ron">:</span> <span class="z-meta z-structure z-array z-ron"><span class="z-punctuation z-section z-array z-begin z-ron">[</span><span class="z-string z-quoted z-double z-ron"><span class="z-punctuation z-definition z-string z-begin z-ron">"</span>do<span class="z-punctuation z-definition z-string z-end z-ron">"</span></span><span class="z-punctuation z-section z-array z-end z-ron">]</span></span><span class="z-punctuation z-separator z-dictionary z-ron">,</span>
</span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"> <span class="z-comment z-line z-double-slash z-ron"><span class="z-punctuation z-definition z-comment z-ron">//</span> Deactivate this state ("end") after triggering
</span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"> <span class="z-entity z-name z-tag z-ron">transient</span><span class="z-punctuation z-separator z-dictionary z-key-value z-ron">:</span> <span class="z-entity z-name z-tag z-ron">true</span><span class="z-punctuation z-separator z-dictionary z-ron">,</span>
</span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"> <span class="z-entity z-name z-tag z-ron">action</span><span class="z-punctuation z-separator z-dictionary z-key-value z-ron">:</span> <span class="z-meta z-structure z-array z-ron"><span class="z-punctuation z-section z-array z-begin z-ron">[</span>
</span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"> <span class="z-entity z-name z-class z-ron">Ext</span><span class="z-meta z-structure z-entity z-ron"><span class="z-punctuation z-section z-dictionary z-begin z-ron">(</span><span class="z-entity z-name z-class z-ron">SelfEffect</span><span class="z-meta z-structure z-entity z-ron"><span class="z-punctuation z-section z-dictionary z-begin z-ron">(</span><span class="z-meta z-structure z-array z-ron"><span class="z-punctuation z-section z-array z-begin z-ron">[</span>
</span></span></span></span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"> <span class="z-entity z-name z-class z-ron">RemoveStatMod</span><span class="z-meta z-structure z-entity z-ron"><span class="z-punctuation z-section z-dictionary z-begin z-ron">(</span>
</span></span></span></span></span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"> <span class="z-entity z-name z-class z-ron">TurnLimit</span><span class="z-punctuation z-separator z-dictionary z-ron">,</span>
</span></span></span></span></span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"> <span class="z-meta z-structure z-entity z-ron"><span class="z-punctuation z-section z-dictionary z-begin z-ron">(</span><span class="z-entity z-name z-tag z-ron">priority</span><span class="z-punctuation z-separator z-dictionary z-key-value z-ron">:</span> <span class="z-constant z-numeric z-ron">2</span><span class="z-punctuation z-separator z-dictionary z-ron">,</span> <span class="z-entity z-name z-tag z-ron">modifier</span><span class="z-punctuation z-separator z-dictionary z-key-value z-ron">:</span> <span class="z-entity z-name z-class z-ron">UpperLimit</span><span class="z-punctuation z-separator z-dictionary z-ron">,</span> <span class="z-entity z-name z-tag z-ron">name</span><span class="z-punctuation z-separator z-dictionary z-key-value z-ron">:</span> <span class="z-string z-quoted z-double z-ron"><span class="z-punctuation z-definition z-string z-begin z-ron">"</span>dash::<span class="z-punctuation z-definition z-string z-end z-ron">"</span></span><span class="z-punctuation z-section z-dictionary z-end z-ron">)</span></span><span class="z-punctuation z-separator z-dictionary z-ron">,</span>
</span></span></span></span></span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"> <span class="z-punctuation z-section z-dictionary z-end z-ron">)</span></span><span class="z-punctuation z-separator z-array z-ron">,</span>
</span></span></span></span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"> <span class="z-entity z-name z-class z-ron">RemoveStatMod</span><span class="z-meta z-structure z-entity z-ron"><span class="z-punctuation z-section z-dictionary z-begin z-ron">(</span>
</span></span></span></span></span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"> <span class="z-entity z-name z-class z-ron">MoveSpeed</span><span class="z-punctuation z-separator z-dictionary z-ron">,</span>
</span></span></span></span></span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"> <span class="z-meta z-structure z-entity z-ron"><span class="z-punctuation z-section z-dictionary z-begin z-ron">(</span><span class="z-entity z-name z-tag z-ron">priority</span><span class="z-punctuation z-separator z-dictionary z-key-value z-ron">:</span> <span class="z-constant z-numeric z-ron">2</span><span class="z-punctuation z-separator z-dictionary z-ron">,</span> <span class="z-entity z-name z-tag z-ron">modifier</span><span class="z-punctuation z-separator z-dictionary z-key-value z-ron">:</span> <span class="z-entity z-name z-class z-ron">Add</span><span class="z-punctuation z-separator z-dictionary z-ron">,</span> <span class="z-entity z-name z-tag z-ron">name</span><span class="z-punctuation z-separator z-dictionary z-key-value z-ron">:</span> <span class="z-string z-quoted z-double z-ron"><span class="z-punctuation z-definition z-string z-begin z-ron">"</span>dash::<span class="z-punctuation z-definition z-string z-end z-ron">"</span></span><span class="z-punctuation z-section z-dictionary z-end z-ron">)</span></span><span class="z-punctuation z-separator z-dictionary z-ron">,</span>
</span></span></span></span></span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"> <span class="z-punctuation z-section z-dictionary z-end z-ron">)</span></span><span class="z-punctuation z-separator z-array z-ron">,</span>
</span></span></span></span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"> <span class="z-comment z-line z-double-slash z-ron"><span class="z-punctuation z-definition z-comment z-ron">//</span> return movement to the default
</span></span></span></span></span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"> <span class="z-entity z-name z-class z-ron">RevertMoveControl</span>
</span></span></span></span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"> <span class="z-punctuation z-section z-array z-end z-ron">]</span></span><span class="z-punctuation z-section z-dictionary z-end z-ron">)</span></span><span class="z-punctuation z-section z-dictionary z-end z-ron">)</span></span><span class="z-punctuation z-separator z-array z-ron">,</span>
</span></span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"><span class="z-meta z-structure z-array z-ron"> <span class="z-punctuation z-section z-array z-end z-ron">]</span></span><span class="z-punctuation z-separator z-dictionary z-ron">,</span>
</span></span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-meta z-structure z-entity z-ron"> <span class="z-punctuation z-section z-dictionary z-end z-ron">)</span></span><span class="z-punctuation z-separator z-dictionary z-ron">,</span>
</span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron">
</span></span><span class="z-source z-ron"><span class="z-meta z-structure z-dictionary z-ron"><span class="z-punctuation z-section z-dictionary z-end z-ron">}</span></span>,
</span></code></pre>
<p>Note that all the variables are prefixed with <code>dash::</code>. Variables are all global, and the prefix serves as a sort of
namespace so that the dash "cooldown" timer doesn't interfere with the shoot "cooldown" timer.</p>
<h2 id="next-time">next time</h2>
<p>why is ammo a non-integer value</p>
<p>meters</p>
<p>implementation details:</p>
<ul>
<li>colliders</li>
<li>variables</li>
<li>serialization</li>
<li>stats (move speed, etc)</li>
</ul>
<p>Considering wiping the slate and reimplementing it as Lua scripts.
depends on how much i need the Lua functionality, and how consise the scripts end up looking.</p>
Circle PhysicsThu, 09 May 2024 00:00:00 +0000Unknown
https://wiki.jaxter184.net/game-dev/spell-scripting/circle-physics/
https://wiki.jaxter184.net/game-dev/spell-scripting/circle-physics/<!--
By far, my favorite game as of late has been Pikmin 4.
Something about the classic Pikmin game loop is really appealing to me, and while the current "dandori" ethos
originated in Pikmin 3 (where they added the ability to move captains as a background action), Pikmin 4 really refined
the concept down to its purest essence.
some good reference material here: https://joonaa.dev/blog/06/avian-0-1
-->
<p>Real-time strategy (RTS) games like StarCraft and Pikmin are characterized by their ability to simulate large numbers of
units.
One of the interesting things that I noticed about Pikmin is that it seems that the Pikmin <a href="https://youtu.be/KpwrWjxvEOg?t=302">almost always organize into
circles when carrying objects</a>, and almost all the enemies have round hitboxes and
hurtboxes.
Similarly, when calculating how they push each other around, all of the units in StarCraft 2 (and <a href="https://youtu.be/PoH0KNeVOjo?t=96">similar
games</a>) are also calculated as circles.
All this made me wonder if there was some trait of real-time strategy games that made it advantageous to limit all
moving objects to circles in their physics simulation.</p>
<p>While I have not spoken to any developers of these games to confirm that RTS games indeed tend to use circles, the
following are my observations that potentially explain this:</p>
<ul>
<li>RTS games have tons of dynamic units, so even a tiny savings in processing per unit can result in a large overall
performance improvement</li>
<li>Circle collisions are easy to calculate. Circles <code>a</code> and <code>b</code> are colliding if:
<code>distance_between(a.position, b.position) < a.radius + b.radius</code></li>
<li>Because the objects are circles, their rotation does not change physics behavior, and physics does not have to change
rotation. Contrast that to a square, where hitting its corner at an angle will cause it to spin, and it can hit other
objects while spinning and move them as a result.</li>
</ul>
<!-- maybe the following is unnecessary and all i need is a cheat sheet -->
<h2 id="a-prelude-axis-aligned-halfplane">A prelude: axis-aligned halfplane</h2>
<p>Assuming 2D space for now, the absolute simplest shape we can define collision detection for is the axis-aligned
<a href="https://en.wikipedia.org/wiki/Half-space_(geometry)">halfplane</a>.
Given a horizontal line that intercepts the Y axis at height <code>y</code>, lets define our halfplane as being the space above
that line.
We can tell whether a point collides with this halfplane by simply comparing <code>y</code> to the point's Y value:</p>
<pre data-lang="rust" class="language-rust z-code"><code class="language-rust" data-lang="rust"><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-function z-rust"><span class="z-storage z-type z-function z-rust">fn</span> </span><span class="z-entity z-name z-function z-rust">is_above</span></span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-begin z-rust">(</span><span class="z-variable z-parameter z-rust">p</span><span class="z-punctuation z-separator z-rust">:</span> Point, <span class="z-variable z-parameter z-rust">y_intercept</span><span class="z-punctuation z-separator z-rust">:</span> <span class="z-storage z-type z-rust">f32</span></span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-end z-rust">)</span></span></span></span><span class="z-meta z-function z-rust"> <span class="z-meta z-function z-return-type z-rust"><span class="z-punctuation z-separator z-rust">-></span> <span class="z-storage z-type z-rust">bool</span></span> </span><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> p<span class="z-punctuation z-accessor z-dot z-rust">.</span>y <span class="z-keyword z-operator z-comparison z-rust">></span> y_intercept
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"></span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span></span>
</span></code></pre>
<p>and given the following points and halplane:</p>
<p>[TODO: plot]</p>
<p>we can easily calculate that <code>is_above(a, y) == true</code> and <code>is_above(b, y) == false</code>, so we know <code>a</code> collides and
<code>b</code> does not. Easy peasy!</p>
<!--
While this alone might not seem useful for anything other than keeping objects above a ground plane, keep in mind that
we can also compare lines to each other, or combine multiple lines to create more complex shapes like the [axis-aligned
bounding box] (with a few caveats).
[axis-aligned bounding box]: https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_collision_detection
-->
<h2 id="the-main-event-circles">The main event: circles</h2>
<p>To start off, let's do the same thing we did with the halfplane and check for whether a point collides with a circle:</p>
<pre data-lang="rust" class="language-rust z-code"><code class="language-rust" data-lang="rust"><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-function z-rust"><span class="z-storage z-type z-function z-rust">fn</span> </span><span class="z-entity z-name z-function z-rust">is_inside</span></span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-begin z-rust">(</span><span class="z-variable z-parameter z-rust">p</span><span class="z-punctuation z-separator z-rust">:</span> Point, <span class="z-variable z-parameter z-rust">c</span><span class="z-punctuation z-separator z-rust">:</span> Circle</span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-end z-rust">)</span></span></span></span><span class="z-meta z-function z-rust"> <span class="z-meta z-function z-return-type z-rust"><span class="z-punctuation z-separator z-rust">-></span> <span class="z-storage z-type z-rust">bool</span></span> </span><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> <span class="z-storage z-type z-rust">let</span> distance <span class="z-keyword z-operator z-assignment z-rust">=</span> <span class="z-support z-function z-rust">sqrt</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>c<span class="z-punctuation z-accessor z-dot z-rust">.</span>position<span class="z-punctuation z-accessor z-dot z-rust">.</span>x <span class="z-keyword z-operator z-arithmetic z-rust">-</span> p<span class="z-punctuation z-accessor z-dot z-rust">.</span>x</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">powi</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-constant z-numeric z-integer z-decimal z-rust">2</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-keyword z-operator z-arithmetic z-rust">+</span> <span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>c<span class="z-punctuation z-accessor z-dot z-rust">.</span>position<span class="z-punctuation z-accessor z-dot z-rust">.</span>y <span class="z-keyword z-operator z-arithmetic z-rust">-</span> p<span class="z-punctuation z-accessor z-dot z-rust">.</span>y</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">powi</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-constant z-numeric z-integer z-decimal z-rust">2</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-terminator z-rust">;</span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> distance <span class="z-keyword z-operator z-comparison z-rust"><</span> c<span class="z-punctuation z-accessor z-dot z-rust">.</span>radius
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"></span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span></span>
</span></code></pre>
<p>Eek, seems like there are a few nasty operations in there.
According to <a href="https://latkin.org/blog/2014/11/09/a-simple-benchmark-of-various-math-operations/">latkin</a>, square roots are about 6 times more computationally intensive than additions or
subtractions, and exponents are even worse.
Fortunately, since ${a^2 = a * a}$, we can replace all of these operations with well-placed multiplications, at the
cost of making the code a bit harder to read:</p>
<pre data-lang="rust" class="language-rust z-code"><code class="language-rust" data-lang="rust"><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-function z-rust"><span class="z-storage z-type z-function z-rust">fn</span> </span><span class="z-entity z-name z-function z-rust">is_inside</span></span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-begin z-rust">(</span><span class="z-variable z-parameter z-rust">p</span><span class="z-punctuation z-separator z-rust">:</span> Point, <span class="z-variable z-parameter z-rust">c</span><span class="z-punctuation z-separator z-rust">:</span> Circle</span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-end z-rust">)</span></span></span></span><span class="z-meta z-function z-rust"> <span class="z-meta z-function z-return-type z-rust"><span class="z-punctuation z-separator z-rust">-></span> <span class="z-storage z-type z-rust">bool</span></span> </span><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> <span class="z-storage z-type z-rust">let</span> dx <span class="z-keyword z-operator z-assignment z-rust">=</span> c<span class="z-punctuation z-accessor z-dot z-rust">.</span>position<span class="z-punctuation z-accessor z-dot z-rust">.</span>x <span class="z-keyword z-operator z-arithmetic z-rust">-</span> p<span class="z-punctuation z-accessor z-dot z-rust">.</span>x<span class="z-punctuation z-terminator z-rust">;</span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> <span class="z-storage z-type z-rust">let</span> dy <span class="z-keyword z-operator z-assignment z-rust">=</span> c<span class="z-punctuation z-accessor z-dot z-rust">.</span>position<span class="z-punctuation z-accessor z-dot z-rust">.</span>y <span class="z-keyword z-operator z-arithmetic z-rust">-</span> p<span class="z-punctuation z-accessor z-dot z-rust">.</span>y<span class="z-punctuation z-terminator z-rust">;</span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> <span class="z-storage z-type z-rust">let</span> d_squared <span class="z-keyword z-operator z-assignment z-rust">=</span> dx <span class="z-keyword z-operator z-arithmetic z-rust">*</span> dx <span class="z-keyword z-operator z-arithmetic z-rust">+</span> dy <span class="z-keyword z-operator z-arithmetic z-rust">*</span> dy<span class="z-punctuation z-terminator z-rust">;</span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> d_squared <span class="z-keyword z-operator z-comparison z-rust"><</span> c<span class="z-punctuation z-accessor z-dot z-rust">.</span>radius <span class="z-keyword z-operator z-arithmetic z-rust">*</span> c<span class="z-punctuation z-accessor z-dot z-rust">.</span>radius
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"></span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span></span>
</span></code></pre>
<p>(<a href="https://stackoverflow.com/questions/2940367/what-is-more-efficient-using-pow-to-square-or-just-multiply-it-with-itself%3Cdetails">Extra reading</a> on when <code>pow</code> is more expensive than multiplication)</p>
<p>I'm not too worried about readability; people used to do <a href="https://en.wikipedia.org/wiki/Fast_inverse_square_root">much less readable things</a> in the name of
computational efficiency.
Plus, the libraries I've used usually have <a href="https://docs.rs/bevy/latest/bevy/math/struct.Vec2.html#method.distance_squared">functions for ergonomically calculating <code>d_squared</code></a>, so it ends up looking
more like:</p>
<pre data-lang="rust" class="language-rust z-code"><code class="language-rust" data-lang="rust"><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-function z-rust"><span class="z-storage z-type z-function z-rust">fn</span> </span><span class="z-entity z-name z-function z-rust">is_inside</span></span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-begin z-rust">(</span><span class="z-variable z-parameter z-rust">p</span><span class="z-punctuation z-separator z-rust">:</span> Point, <span class="z-variable z-parameter z-rust">c</span><span class="z-punctuation z-separator z-rust">:</span> Circle</span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-end z-rust">)</span></span></span></span><span class="z-meta z-function z-rust"> <span class="z-meta z-function z-return-type z-rust"><span class="z-punctuation z-separator z-rust">-></span> <span class="z-storage z-type z-rust">bool</span></span> </span><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> c<span class="z-punctuation z-accessor z-dot z-rust">.</span>position<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">distance_squared</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>p</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-keyword z-operator z-comparison z-rust"><</span> c<span class="z-punctuation z-accessor z-dot z-rust">.</span>radius <span class="z-keyword z-operator z-arithmetic z-rust">*</span> c<span class="z-punctuation z-accessor z-dot z-rust">.</span>radius
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"></span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span></span>
</span></code></pre>
<p>Probably the most common thing we would need to calculate when simulating physics for a bunch of circles is figuring out
when circles are colliding with each other.
For that, we can just use the check from the beginning of the article
(<code>distance_between(a.position, b.position) < a.radius + b.radius</code>),
using the tricks we know to make things more efficient and clean:</p>
<pre data-lang="rust" class="language-rust z-code"><code class="language-rust" data-lang="rust"><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-function z-rust"><span class="z-storage z-type z-function z-rust">fn</span> </span><span class="z-entity z-name z-function z-rust">is_colliding</span></span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-begin z-rust">(</span><span class="z-variable z-parameter z-rust">a</span><span class="z-punctuation z-separator z-rust">:</span> Circle, <span class="z-variable z-parameter z-rust">b</span><span class="z-punctuation z-separator z-rust">:</span> Circle</span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-end z-rust">)</span></span></span></span><span class="z-meta z-function z-rust"> <span class="z-meta z-function z-return-type z-rust"><span class="z-punctuation z-separator z-rust">-></span> <span class="z-storage z-type z-rust">bool</span></span> </span><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> <span class="z-storage z-type z-rust">let</span> rad <span class="z-keyword z-operator z-assignment z-rust">=</span> a<span class="z-punctuation z-accessor z-dot z-rust">.</span>radius <span class="z-keyword z-operator z-arithmetic z-rust">+</span> b<span class="z-punctuation z-accessor z-dot z-rust">.</span>radius<span class="z-punctuation z-terminator z-rust">;</span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> a<span class="z-punctuation z-accessor z-dot z-rust">.</span>position<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">distance_squared</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>b<span class="z-punctuation z-accessor z-dot z-rust">.</span>position</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-keyword z-operator z-comparison z-rust"><</span> rad <span class="z-keyword z-operator z-arithmetic z-rust">*</span> rad
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"></span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span></span>
</span></code></pre>
<p>So given the two shapes we've talked about so far, the halfplane and the circle, plus the points that we've tested them with,
we can calculate the following collisions:</p>
<style>
table {
width: fit-content;
margin: auto;
}
th { width: fit-content; }
</style>
<table><thead><tr><th style="text-align: right"></th><th style="text-align: center">point</th><th style="text-align: center">halfplane</th><th style="text-align: center">circle</th></tr></thead><tbody>
<tr><td style="text-align: right"><strong>point</strong></td><td style="text-align: center">❌</td><td style="text-align: center">✅</td><td style="text-align: center">✅</td></tr>
<tr><td style="text-align: right"><strong>halfplane</strong></td><td style="text-align: center">✅</td><td style="text-align: center">✅</td><td style="text-align: center">❌</td></tr>
<tr><td style="text-align: right"><strong>circle</strong></td><td style="text-align: center">✅</td><td style="text-align: center">❌</td><td style="text-align: center">✅</td></tr>
</tbody></table>
<p>Comparing whether two points are colliding is pretty trivial, since they are only touching if they're exactly the
same, so to complete the matrix, we really just need to figure out how halfplanes and circles collide, which is pretty
straightforward.</p>
<pre data-lang="rust" class="language-rust z-code"><code class="language-rust" data-lang="rust"><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-function z-rust"><span class="z-storage z-type z-function z-rust">fn</span> </span><span class="z-entity z-name z-function z-rust">is_colliding</span></span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-begin z-rust">(</span><span class="z-variable z-parameter z-rust">c</span><span class="z-punctuation z-separator z-rust">:</span> Circle, <span class="z-variable z-parameter z-rust">y_intercept</span><span class="z-punctuation z-separator z-rust">:</span> <span class="z-storage z-type z-rust">f32</span></span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-end z-rust">)</span></span></span></span><span class="z-meta z-function z-rust"> <span class="z-meta z-function z-return-type z-rust"><span class="z-punctuation z-separator z-rust">-></span> <span class="z-storage z-type z-rust">bool</span></span> </span><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> <span class="z-support z-function z-rust">abs</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>c<span class="z-punctuation z-accessor z-dot z-rust">.</span>position<span class="z-punctuation z-accessor z-dot z-rust">.</span>y <span class="z-keyword z-operator z-arithmetic z-rust">-</span> y_intercept</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-keyword z-operator z-comparison z-rust"><</span> c<span class="z-punctuation z-accessor z-dot z-rust">.</span>radius
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"></span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span></span>
</span></code></pre>
<h2 id="more-halfplanes">More halfplanes</h2>
<p>Let's go back to the halfplane example from earlier for a bit.
Why was it necessary to limit it to perfectly horizontal and vertical halfplanes?
What if we try to generalize to halfplanes of any slope rather than just the ones aligned with the X and Y axes?</p>
<p>Assuming that the edge of the halfplane is defined by two points <code>p1</code> and <code>p2</code>, we can tell which side of the edge
point <code>d</code> is on by finding the terms to the slope intercept definition <code>y = mx + b</code>.
Hopefully you're brushed up on your grade school geometry/algebra:</p>
<pre data-lang="rust" class="language-rust z-code"><code class="language-rust" data-lang="rust"><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-function z-rust"><span class="z-storage z-type z-function z-rust">fn</span> </span><span class="z-entity z-name z-function z-rust">is_above</span></span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-begin z-rust">(</span><span class="z-variable z-parameter z-rust">d</span><span class="z-punctuation z-separator z-rust">:</span> Point, <span class="z-variable z-parameter z-rust">p1</span><span class="z-punctuation z-separator z-rust">:</span> Point, <span class="z-variable z-parameter z-rust">p2</span><span class="z-punctuation z-separator z-rust">:</span> Point</span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-end z-rust">)</span></span></span></span><span class="z-meta z-function z-rust"> <span class="z-meta z-function z-return-type z-rust"><span class="z-punctuation z-separator z-rust">-></span> <span class="z-storage z-type z-rust">bool</span></span> </span><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> <span class="z-storage z-type z-rust">let</span> slope <span class="z-keyword z-operator z-assignment z-rust">=</span> <span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>p2<span class="z-punctuation z-accessor z-dot z-rust">.</span>y <span class="z-keyword z-operator z-arithmetic z-rust">-</span> p1<span class="z-punctuation z-accessor z-dot z-rust">.</span>y</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-keyword z-operator z-arithmetic z-rust">/</span> <span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>p2<span class="z-punctuation z-accessor z-dot z-rust">.</span>x <span class="z-keyword z-operator z-arithmetic z-rust">-</span> p1<span class="z-punctuation z-accessor z-dot z-rust">.</span>x</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-terminator z-rust">;</span> <span class="z-comment z-line z-double-slash z-rust"><span class="z-punctuation z-definition z-comment z-rust">//</span> m = Δy / Δx
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> <span class="z-storage z-type z-rust">let</span> intercept <span class="z-keyword z-operator z-assignment z-rust">=</span> p2<span class="z-punctuation z-accessor z-dot z-rust">.</span>y <span class="z-keyword z-operator z-arithmetic z-rust">-</span> slope <span class="z-keyword z-operator z-arithmetic z-rust">*</span> p2<span class="z-punctuation z-accessor z-dot z-rust">.</span>x<span class="z-punctuation z-terminator z-rust">;</span> <span class="z-comment z-line z-double-slash z-rust"><span class="z-punctuation z-definition z-comment z-rust">//</span> b = y - mx
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> <span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>d<span class="z-punctuation z-accessor z-dot z-rust">.</span>y <span class="z-keyword z-operator z-arithmetic z-rust">-</span> slope <span class="z-keyword z-operator z-arithmetic z-rust">*</span> d<span class="z-punctuation z-accessor z-dot z-rust">.</span>x</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-keyword z-operator z-comparison z-rust">></span> intercept
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"></span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span></span>
</span></code></pre>
<p>[TODO: plot]</p>
<p>Which, in this case, is [TODO: calculation], meaning <code>d</code> is below the edge.
As you can see, this takes quite a few more operations than the axis-aligned case, and the division is particularly
nasty, as it takes about [4 times as long] as adding or subtracting.
If you're clever, you may also be worried about the case where the edge is vertical, when <code>p2.x - p1.x == 0</code>, which will
result in a division by zero when calculating slope.
To fix this, we can rearrange things to reduce calculations to two multiplications and four subtractions:</p>
<p>$${
\begin{align*}
d_y - \text{slope} * d_x &> p_{2y} - \text{slope} * p_{2x} && \text{our original equation} \\
d_y - \frac{p_{2y} - p_{1y}}{p_{2x} - p_{1x}}d_x &> p_{2y} - \frac{p_{2y} - p_{1y}}{p_{2x} - p_{1x}}p_{2x} && \text{slope} = \frac{p_{2y} - p_{1y}}{p_{2x} - p_{1x}}\\
d_y - p_{2y} &> \frac{p_{2y} - p_{1y}}{p_{2x} - p_{1x}}d_x - \frac{p_{2y} - p_{1y}}{p_{2x} - p_{1x}}p_{2x} && \text{add terms to both sides}\\
d_y - p_{2y} &> \frac{p_{2y} - p_{1y}}{p_{2x} - p_{1x}}(d_x - p_{2x}) && \text{factor out slope}\\
(p_{2x} - p_{1x})(d_y - p_{2y}) &> (p_{2y} - p_{1y})(d_x - p_{2x}) && \text{this is actually illegal?}\\
\end{align*}
}$$</p>
<p>So it turns out you're not supposed to do that last part because multiplying both sides of an inequality with a
negative number will flip the inequality, and since we don't know whether ${p_{2x} - p_{1x}}$ is positive or negative,
we can't know whether or not we have to flip the inequality.
My solution to this is to simply invert the result when ${p_{2x} - p_{1x}}$ is negative, resulting in the following
function:</p>
<pre data-lang="rust" class="language-rust z-code"><code class="language-rust" data-lang="rust"><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-function z-rust"><span class="z-storage z-type z-function z-rust">fn</span> </span><span class="z-entity z-name z-function z-rust">is_above</span></span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-begin z-rust">(</span><span class="z-variable z-parameter z-rust">d</span><span class="z-punctuation z-separator z-rust">:</span> Point, <span class="z-variable z-parameter z-rust">p1</span><span class="z-punctuation z-separator z-rust">:</span> Point, <span class="z-variable z-parameter z-rust">p2</span><span class="z-punctuation z-separator z-rust">:</span> Point</span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-end z-rust">)</span></span></span></span><span class="z-meta z-function z-rust"> <span class="z-meta z-function z-return-type z-rust"><span class="z-punctuation z-separator z-rust">-></span> <span class="z-storage z-type z-rust">bool</span></span> </span><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> <span class="z-storage z-type z-rust">let</span> check <span class="z-keyword z-operator z-assignment z-rust">=</span> <span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>p2<span class="z-punctuation z-accessor z-dot z-rust">.</span>x <span class="z-keyword z-operator z-arithmetic z-rust">-</span> p1<span class="z-punctuation z-accessor z-dot z-rust">.</span>x</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-keyword z-operator z-arithmetic z-rust">*</span> <span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>d<span class="z-punctuation z-accessor z-dot z-rust">.</span>y <span class="z-keyword z-operator z-arithmetic z-rust">-</span> p2<span class="z-punctuation z-accessor z-dot z-rust">.</span>y</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-keyword z-operator z-comparison z-rust">></span> <span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>p2<span class="z-punctuation z-accessor z-dot z-rust">.</span>y <span class="z-keyword z-operator z-arithmetic z-rust">-</span> p1<span class="z-punctuation z-accessor z-dot z-rust">.</span>y</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-keyword z-operator z-arithmetic z-rust">*</span> <span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>d<span class="z-punctuation z-accessor z-dot z-rust">.</span>x <span class="z-keyword z-operator z-arithmetic z-rust">-</span> p2<span class="z-punctuation z-accessor z-dot z-rust">.</span>x</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-terminator z-rust">;</span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> check <span class="z-keyword z-operator z-comparison z-rust">!=</span> <span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>p2<span class="z-punctuation z-accessor z-dot z-rust">.</span>x <span class="z-keyword z-operator z-comparison z-rust">></span> p1<span class="z-punctuation z-accessor z-dot z-rust">.</span>x</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-comment z-line z-double-slash z-rust"><span class="z-punctuation z-definition z-comment z-rust">//</span> != is functionally identical to a boolean XOR
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"></span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span></span>
</span></code></pre>
<p>That was a lot more work than the axis-aligned case, and even after all that, it's still quite a bit more
computationally intensive.</p>
<h2 id="for-the-future">for the future</h2>
<p>I do all of my game development these days using Bevy, which has <a href="https://bevyengine.org/assets/#physics">plenty of physics extensions</a>, but I was under the
impression that my use case was esoteric enough that it would make sense to implement my own in order to have fine
control over efficiency.
However, it seems that many of the existing libraries use <a href="https://www.ncollide.org/bounding_volumes/">similar ideas</a> under the hood.</p>
<ul>
<li>collisions
<ul>
<li>all the fun collision shapes and the stackoverflow posts I got them from</li>
</ul>
</li>
<li>nudging
<ul>
<li>i want to refactor this.</li>
</ul>
</li>
<li>im a little concerned about the unused height axis, but itll probably be fine.
but what if I want an attack used on a bridge to not affect any units under the bridge?
and what if I do want it to affect them?</li>
<li>turn speed limiting <a href="https://youtu.be/K1bQuMnMqKY?t=546">https://youtu.be/K1bQuMnMqKY?t=546</a>
<ul>
<li>quaternions</li>
</ul>
</li>
</ul>
<h2 id="up-next">Up next:</h2>
<div>
<a href="/game-dev/spell-scripting/state-machines">
<div class="list-entry outer">
<span class="link">🔀 State machines</span>
- Creating a state machine scripting language to implement spells for a MOBA
</div>
</a>
</div>
↹latureTue, 26 Mar 2024 00:00:00 +0000Unknown
https://wiki.jaxter184.net/mus-tec/tlature/
https://wiki.jaxter184.net/mus-tec/tlature/<p>From the README:</p>
<p>↹lature is a tracker-style DAW. Its most unique features include:</p>
<ul>
<li>First-class support for Open Sound Control</li>
<li>CLAP plugin support</li>
<li>Terminal UI</li>
<li>Keyboard-focused workflow with Kakoune/vim style keybindings</li>
<li>Headless mode for portable playback on a <a href="https://en.wikipedia.org/wiki/Single-board_computer" title="wikipedia article for single board computers">SBC</a> or dedicated live
performance machine</li>
<li>A directly editable, git-friendly file format</li>
</ul>
<details>
<summary>An optional aside regarding Bitwig's modular environment</summary>
Around 2017, I was reverse-engineering the Bitwig Studio internal device files to try and create my own version of the
modular environment that they had been teasing since before the 1.0 release.
Screenshots of it are hard to find, but there's one at the bottom of [this
article](https://sevish.com/2016/microtonal-music-in-bitwig-studio).
<p>Bitwig stores its device files in <code>/opt/bitwig-studio/Library/devices</code> on Linux, and in some old macOS versions, they
stored them in JSON format.
Though the binary format is (as far as I know) completely bespoke, it is vaguely reminiscent of
<a href="https://bsonspec.org/">BSON</a>.</p>
<p>One of the other interesting things that they were doing before 3.0 was embedding their own (JIT?) compiled language
called Nitro.
As far as I know, there exists no documentation for it online, and I am hesitant to post any example code since
basically all existing Nitro code is copyrighted by Bitwig, but it basically looks like C with some special plumbing
keywords.</p>
<p>Anyway, I had at some point reverse-engineered it to the point that I could create my own devices with their own UIs!
The import process was a bit hacky, and there was a tedious process of dragging and dropping every time I opened Bitwig
to ensure that the devices were properly loaded when opening existing files, but I had achieved my goal of having a
modular environment where I could make Bitwig devices (though UIs had to be written manually in JSON).
My first device was a "notepad" device that was basically just a text box for writing notes in a track.</p>
<p>Fast forward a few months, and Bitwig 3.0 is released, along with some very cool new devices, including "The Grid".
The Grid was a more abstract, "friendly" interface that I assume is meant to act as a substitute for access to the
real modular environment that they use internally to create their native devices, with the target audience leaning more
toward musicians than programmers.
Notably, it lacked any tools for creating inline UIs or doing frequency-domain processing (though I have heard rumors
that these features are coming at some point in the near future).</p>
<p>But worse (for me), the devices files in the new version were all encrypted (I think).
This meant that any new devices released from then on were completely inaccessible to me.
While this wasn't too big of a deal since I wanted to make my own devices anyway, this reminded me that this sort
of introspection into the inner workings of Bitwig Studio isn't necessarily something Bitwig Gmbh the corporation
appreciates.
The company's goal is, by definition, to make money, and one of the main ways they do that is by creating new devices
that users need to buy additional updates to access.
If there were some pack of homebrew devices, this would make it harder for them to sell updates.</p>
<p>I don't harbor too much ill will towards Bitwig, and there are myriad potential reasons for doing this that aren't in
conflict with my goals (and are, in many cases, in sync):</p>
<ul>
<li>They want to prevent their future DSP from being accessible by competitors like Ableton</li>
<li>They might have been getting support requests from confused people trying to use my tool without realizing it wasn't
an official Bitwig tool (I tried to match the Bitwig color scheme with my device editor tool, which in retrospect, was a
poor choice)</li>
</ul>
<p>The true reason(s) I can only speculate about, but either way, I had learned a lot about programming practices and
tools from this adventure.
<code>pytwig</code> (the library I made that I assume Bitwig is OK with because they haven't asked me to take it down yet) was my
first "real" programming project used by people other than just me, and while there's nothing specific to this project
that enabled that (perhaps I could have pursued VST development instead to the same effect), I think it gave me the
confidence I needed to execute on a project that better represented my own vision.</p>
<p>Fast forward a few more years: free-audio (backed by Bitwig and u-he) releases the <a href="https://cleveraudio.org/">CLAP</a>
specification, a C-based API for creating audio processing devices.
(That's a surprise tool that can help us later)</p>
<p>Honorary mention (but not technically relevant to ↹lature (yet)): <a href="https://github.com/bitwig/dawproject">dawproject</a>,
a DAW interchange format.</p>
</details>
<h2 id="motivation">motivation</h2>
<p>Features that I want in a DAW:</p>
<ul>
<li>text-based save files
<ul>
<li>better git-friendliness</li>
<li>readable/editable in a text editor</li>
</ul>
</li>
<li>modal editing
<ul>
<li>first-class keyboard-only workflow</li>
<li>ideally in a TUI</li>
</ul>
</li>
<li>can play audio on a raspberry pi</li>
<li>the features listed at the top of this article</li>
<li>screen reader compatible</li>
<li>package manager for parts that are non-redistributable due to copyright</li>
<li>documentation for music (comments in the file, UI elements inline with the plugin chain where you can write stuff)</li>
<li>capable of integrating a voice synthesizer into the software</li>
</ul>
<p>Reaper comes really close, but I think only technically.
I feel like it doesn't fulfill the spirit of text-editability, and the modal editing only comes with a plugins like
<a href="https://github.com/gwatcha/reaper-keys">reaper-keys</a> or <a href="https://github.com/madskjeldgaard/reaper-nvim">reaper-nvim</a>.
I think in order for a keyboard-only workflow to be actually usable, you have to really rethink what exactly the needs
of a DAW are.</p>
<p>The subtext for all of these features is that fundamentally, I want this DAW to be one that panders to my every whim.
No matter how ridiculous the feature, if it's something I want, it should be something this DAW does.
If these requirements don't speak to you the way they speak to me, you're probably better off using
Bitwig or Reaper (both very good DAWs that I still use!).</p>
<h3 id="general-design-philosophy">general design philosophy</h3>
<ul>
<li>UI should be limited in scope to a few of the most heavily used parts of the interface</li>
<li>similarly, common actions should be bound to the easiest keybinds, and uncommon actions should only be accessible by
command line</li>
<li>all actions should be accessible by a "scripting" language</li>
<li>actions should be easy to type quickly for the "normal" case, but should be configurable (with optional parameters)
for esoteric cases</li>
</ul>
<h2 id="prior-art">prior art</h2>
<h3 id="foss-daws">FOSS DAWs</h3>
<ul>
<li><a href="https://warmplace.ru/soft/sunvox/">SunVox</a></li>
<li><a href="https://github.com/ameobea/web-synth/">web-synth</a></li>
<li><a href="https://www.bespokesynth.com/">BespokeSynth</a></li>
<li><a href="https://www.zrythm.org/en/index.html">Zrythm</a></li>
<li><a href="https://ardour.org/">Ardour</a></li>
<li><a href="https://github.com/WeirdConstructor/HexoSynth">HexoSynth</a></li>
<li><a href="https://ossia.io/">ossia</a></li>
<li><a href="http://users.notam02.no/~kjetism/radium/index.php">Radium</a></li>
<li><a href="https://git.sr.ht/~ft/neindaw">neindaw</a></li>
<li><a href="https://github.com/codybloemhard/termdaw">termdaw</a></li>
</ul>
<!-- meadowlark does not exist yet -->
<!--
### Audio routing and plugin hosting software
carla
ingen
-->
<h3 id="tui-music-software">TUI music software</h3>
<ul>
<li><a href="https://wiki.xxiivv.com/site/orca.html">Orca</a></li>
<li><a href="https://subalterngames.com/cacophony/">Cacophony</a> (not technically TUI, but certainly TUI-esque)</li>
<li><a href="https://github.com/zuggamasta/midiTracker">midiTracker</a></li>
<li>any livecoding tool with a CLI REPL or a plaintext file format + hot reloading</li>
</ul>
<h3 id="trackers">trackers</h3>
<ul>
<li>1987 - the ultimate soundtracker</li>
<li>1989 - OctaMED</li>
<li>1990 - <a href="https://www.exotica.org.uk/wiki/Future_Composer_(amiga)">Future Composer</a></li>
<li>1990 - <a href="https://archive.org/details/digital-mugician-amiga-music-editor">Digital Mugician</a></li>
<li>1993 - <a href="https://en.wikipedia.org/wiki/FastTracker_2">FastTracker 2</a></li>
<li>1994 - ScreamTracker 3</li>
<li>1995 - Impulse Tracker</li>
<li>1997 - <a href="https://openmpt.org/">OpenMPT</a> (<a href="https://github.com/OpenMPT/openmpt/"></a>)</li>
<li>2002 - <a href="https://www.renoise.com/">Renoise</a></li>
<li>2005 - <a href="https://milkytracker.org/">MilkyTracker</a> (<a href="https://github.com/milkytracker/MilkyTracker"></a>)</li>
<li>2005-2015 - <a href="http://www.famitracker.com/">FamiTracker</a> (<a href="http://www.famitracker.com/downloads.php">source</a>)</li>
<li>2006 - <a href="https://schismtracker.org/">SchismTracker</a> (<a href="https://github.com/schismtracker/schismtracker"></a>)</li>
<li>2008 - <a href="https://warmplace.ru/soft/sunvox/">SunVox</a> (open core?)</li>
<li>2009 - <a href="https://www.pouet.net/prod.php?which=53615">Sonant</a></li>
<li>2011 - <a href="https://deflemask.net/">DefleMask</a></li>
<li>2015-2018 - 0CC-FamiTracker (<a href="https://github.com/HertzDevil/0CC-FamiTracker"></a>)</li>
<li>2018 - zytrax (<a href="https://github.com/reduz/zytrax"></a>)</li>
<li>2018 - sointu (<a href="https://github.com/vsariola/sointu"></a>)</li>
<li>2018-2020 - j0CC-Famitracker (<a href="https://github.com/nyanpasu64/j0CC-FamiTracker"></a>)</li>
<li>2018 - <a href="https://www.3eality.com/productions/reality-adlib-tracker">Reality Adlib Tracker</a> (<a href="https://realityproductions.itch.io/rad"></a>)</li>
<li>2019 - ft2-clone (clone of FastTracker 2) (<a href="https://github.com/8bitbubsy/ft2-clone"></a>)</li>
<li>2020 - <a href="https://dirtywave.com/">Dirtywave M8</a></li>
<li>2020 - <a href="https://bintracker.org/">bintracker</a> (<a href="https://github.com/bintracker/bintracker"></a>)</li>
<li>2020 - Dn-FamiTracker (<a href="https://github.com/Dn-Programming-Core-Management/Dn-FamiTracker"></a>)</li>
<li>2021 - Furnace (<a href="https://github.com/tildearrow/furnace"></a>)</li>
<li>2021 - protrekkr (<a href="https://github.com/hitchhikr/protrekkr"></a>)</li>
<li>2024 - <a href="https://wavetracker.org">WaveTracker</a> (<a href="https://github.com/squiggythings/WaveTracker"></a>)</li>
<li>2024 - <a href="https://phoboslab.org/log/2025/01/synth">pl_synth</a> (<a href="https://github.com/phoboslab/pl_synth"></a>)</li>
</ul>
<h2 id="devlog">devlog</h2>
<h3 id="controller-support">Controller support</h3>
<p>Controller support is implemented using JACK.
I initially used <a href="https://github.com/Boddlnagg/midir"><code>midir</code></a>, but I couldn't figure out how to synchronize notes
within the buffer.
While the approach that minimizes average latency would be for all the notes to trigger at the beginning of the audio
frame, I have heard that psychoacoustically, stable jitter is more important to playability than latency.
Plus, at large buffer sizes, having all the messages in a frame trigger at the beginning would likely be noticably
distracting.</p>
<p>To implement this, I first add an additional port to the <code>JackProcess</code> struct:</p>
<pre data-lang="rust" class="language-rust z-code"><code class="language-rust" data-lang="rust"><span class="z-source z-rust"><span class="z-meta z-struct z-rust"><span class="z-storage z-modifier z-rust">pub</span> <span class="z-storage z-type z-struct z-rust">struct</span> </span><span class="z-meta z-struct z-rust"><span class="z-entity z-name z-struct z-rust">JackProcess</span> </span><span class="z-meta z-struct z-rust"><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-struct z-rust"><span class="z-meta z-block z-rust"> <span class="z-comment z-line z-double-slash z-rust"><span class="z-punctuation z-definition z-comment z-rust">//</span> ...
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-struct z-rust"><span class="z-meta z-block z-rust"> <span class="z-variable z-other z-member z-rust">in_port_midi</span><span class="z-punctuation z-separator z-type z-rust">:</span> <span class="z-meta z-path z-rust">jack<span class="z-punctuation z-accessor z-rust">::</span></span><span class="z-meta z-generic z-rust">Port<span class="z-punctuation z-definition z-generic z-begin z-rust"><</span><span class="z-meta z-path z-rust">jack<span class="z-punctuation z-accessor z-rust">::</span></span>MidiIn<span class="z-punctuation z-definition z-generic z-end z-rust">></span></span>,
</span></span></span><span class="z-source z-rust"><span class="z-meta z-struct z-rust"><span class="z-meta z-block z-rust"></span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span></span>
</span></code></pre>
<p>Then search for a controller and connect it to the port:</p>
<pre data-lang="rust" class="language-rust z-code"><code class="language-rust" data-lang="rust"><span class="z-source z-rust"><span class="z-keyword z-control z-rust">for</span> ea_port <span class="z-keyword z-operator z-rust">in</span> active_client<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">as_client</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">ports</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-support z-type z-rust">None</span><span class="z-punctuation z-separator z-rust">,</span> <span class="z-support z-type z-rust">Some</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-string z-quoted z-double z-rust"><span class="z-punctuation z-definition z-string z-begin z-rust">"</span>8 bit raw midi<span class="z-punctuation z-definition z-string z-end z-rust">"</span></span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-separator z-rust">,</span> <span class="z-meta z-path z-rust">jack<span class="z-punctuation z-accessor z-rust">::</span></span><span class="z-meta z-path z-rust">PortFlags<span class="z-punctuation z-accessor z-rust">::</span></span><span class="z-constant z-other z-rust">IS_OUTPUT</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"> <span class="z-keyword z-control z-rust">if</span> ea_port<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">contains</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-string z-quoted z-double z-rust"><span class="z-punctuation z-definition z-string z-begin z-rust">"</span>HXCSTR<span class="z-punctuation z-definition z-string z-end z-rust">"</span></span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> <span class="z-meta z-path z-rust">log<span class="z-punctuation z-accessor z-rust">::</span></span>debug<span class="z-keyword z-operator z-logical z-rust">!</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-string z-quoted z-double z-rust"><span class="z-punctuation z-definition z-string z-begin z-rust">"</span>found port {ea_port:?}<span class="z-punctuation z-definition z-string z-end z-rust">"</span></span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-terminator z-rust">;</span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> active_client<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">as_client</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">connect_ports_by_name</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-keyword z-operator z-bitwise z-rust">&</span>ea_port<span class="z-punctuation z-separator z-rust">,</span> <span class="z-keyword z-operator z-bitwise z-rust">&</span><span class="z-support z-macro z-rust">format!</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-string z-quoted z-double z-rust"><span class="z-punctuation z-definition z-string z-begin z-rust">"</span><span class="z-constant z-other z-placeholder z-rust">{}</span>:midi_in<span class="z-punctuation z-definition z-string z-end z-rust">"</span></span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-separator z-rust">,</span> active_client<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">as_client</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">name</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-section z-group z-end z-rust">)</span></span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> <span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">expect</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-keyword z-operator z-bitwise z-rust">&</span><span class="z-support z-macro z-rust">format!</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-string z-quoted z-double z-raw z-rust"><span class="z-storage z-type z-string z-rust">r</span><span class="z-punctuation z-definition z-string z-begin z-rust">#</span>"Could not connect ↹lature midi input to "<span class="z-constant z-other z-placeholder z-rust">{:?}</span>".<span class="z-punctuation z-definition z-string z-end z-rust">"#</span></span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-separator z-rust">,</span> ea_port<span class="z-punctuation z-section z-group z-end z-rust">)</span></span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-terminator z-rust">;</span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span>
</span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"></span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span>
</span></code></pre>
<p>Lastly, read the input port in the process loop, adapting the <code>push_event</code> function that I use for auditioning cells:</p>
<pre data-lang="rust" class="language-rust z-code"><code class="language-rust" data-lang="rust"><span class="z-source z-rust"><span class="z-keyword z-control z-rust">for</span> <span class="z-meta z-path z-rust">jack<span class="z-punctuation z-accessor z-rust">::</span></span>RawMidi <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span> time<span class="z-punctuation z-separator z-rust">,</span> bytes </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span> <span class="z-keyword z-operator z-rust">in</span> <span class="z-variable z-language z-rust">self</span><span class="z-punctuation z-accessor z-dot z-rust">.</span>in_port_midi<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">iter</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>ps</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"> <span class="z-keyword z-control z-rust">if</span> bytes<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">len</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-keyword z-operator z-comparison z-rust">==</span> <span class="z-constant z-numeric z-integer z-decimal z-rust">3</span> <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> <span class="z-storage z-type z-rust">let</span> msg <span class="z-keyword z-operator z-assignment z-rust">=</span> OscMessage <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> addr<span class="z-punctuation z-separator z-rust">:</span> <span class="z-string z-quoted z-double z-rust"><span class="z-punctuation z-definition z-string z-begin z-rust">"</span>/HXCSTR<span class="z-punctuation z-definition z-string z-end z-rust">"</span></span><span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">to_string</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-separator z-rust">,</span>
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> args<span class="z-punctuation z-separator z-rust">:</span> <span class="z-support z-macro z-rust">vec!</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">[</span><span class="z-meta z-path z-rust">OscType<span class="z-punctuation z-accessor z-rust">::</span></span>Midi<span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>OscMidiMessage <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-group z-rust"><span class="z-meta z-group z-rust"><span class="z-meta z-block z-rust"> port<span class="z-punctuation z-separator z-rust">:</span> <span class="z-constant z-numeric z-integer z-decimal z-rust">1</span><span class="z-punctuation z-separator z-rust">,</span>
</span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-group z-rust"><span class="z-meta z-group z-rust"><span class="z-meta z-block z-rust"> status<span class="z-punctuation z-separator z-rust">:</span> bytes<span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">[</span><span class="z-constant z-numeric z-integer z-decimal z-rust">0</span><span class="z-punctuation z-section z-group z-end z-rust">]</span></span><span class="z-punctuation z-separator z-rust">,</span>
</span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-group z-rust"><span class="z-meta z-group z-rust"><span class="z-meta z-block z-rust"> data1<span class="z-punctuation z-separator z-rust">:</span> bytes<span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">[</span><span class="z-constant z-numeric z-integer z-decimal z-rust">1</span><span class="z-punctuation z-section z-group z-end z-rust">]</span></span><span class="z-punctuation z-separator z-rust">,</span>
</span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-group z-rust"><span class="z-meta z-group z-rust"><span class="z-meta z-block z-rust"> data2<span class="z-punctuation z-separator z-rust">:</span> bytes<span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">[</span><span class="z-constant z-numeric z-integer z-decimal z-rust">2</span><span class="z-punctuation z-section z-group z-end z-rust">]</span></span><span class="z-punctuation z-separator z-rust">,</span>
</span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-group z-rust"><span class="z-meta z-group z-rust"><span class="z-meta z-block z-rust"> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-section z-group z-end z-rust">]</span></span><span class="z-punctuation z-separator z-rust">,</span>
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span><span class="z-punctuation z-terminator z-rust">;</span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> pd<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">push_event_root</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>time <span class="z-keyword z-operator z-rust">as</span> <span class="z-storage z-type z-rust">u64</span><span class="z-punctuation z-separator z-rust">,</span> msg</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-terminator z-rust">;</span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span>
</span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"></span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span>
</span></code></pre>
<p>Note that the controller search and OSC message are hardcoded with the string "<a href="/prj/hxcstr/">HXCSTR</a>". Ideally, I'd have both a
configurable method to automatically connect controllers as well as a manual menu system in the TUI to set up such
connections.</p>
<p>I added controller support so I could practice playing HXCSTR, which unfortunately only supports MIDI at this point in
time, but OSC support would likely be similarly trivial.</p>
<h3 id="chain-view">Chain view</h3>
<p>Hypothetically, the entire audio graph can be represented as a list of nodes and the connections between them, where
you can see each sound processor as a little "block", and their ports are routed explicitly into each other.
However, most DAWs instead have a channel-based workflow, where sound processors are on one of many channels, and the
audio from one flows into the next in the series.
One of the driving forces that I think about when designing interfaces is to try to make the common use cases easy and
clear, and relegate the uncommon use cases to more laborious and obscure parts of the interface.
In the case of the audio graph, it is very common for signal chains to be considered on a per-instrument basis, where
one set of notes is piped into an instrument processor followed by effect processors.
This maps very neatly to the channel-based topology, and as such, I chose to try to preserve that in ↹lature.</p>
<p>The solution I came up with was the "chain", where a <code>Block</code> is a container for a series of <code>Processor</code>s.
I was struggling with a good way to draw the chain in a way that was scrollable, and this is one of the parts where
making ↹lature TUI-based caused some friction in the implementation.</p>
<p>I mostly copied the default behavior of panning in [niri], but for those not familiar, in broad strokes, the
requirements were these:</p>
<ul>
<li>The selected processor should always be fully visible (if possible)</li>
<li>The chain view should not pan if it does not need to (including when blocks are deleted or resized)</li>
</ul>
<p>I faffed about a bit for a working solution, but ended up with this:</p>
<pre data-lang="rust" class="language-rust z-code"><code class="language-rust" data-lang="rust"><span class="z-source z-rust"><span class="z-storage z-type z-rust">let</span> frame_width <span class="z-keyword z-operator z-assignment z-rust">=</span> f<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">size</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-accessor z-dot z-rust">.</span>width <span class="z-keyword z-operator z-rust">as</span> <span class="z-storage z-type z-rust">i32</span><span class="z-punctuation z-terminator z-rust">;</span>
</span><span class="z-source z-rust"><span class="z-comment z-line z-double-slash z-rust"><span class="z-punctuation z-definition z-comment z-rust">//</span> clamp cursor to chain length
</span></span><span class="z-source z-rust"><span class="z-variable z-language z-rust">self</span><span class="z-punctuation z-accessor z-dot z-rust">.</span>cursor <span class="z-keyword z-operator z-assignment z-rust">=</span> <span class="z-variable z-language z-rust">self</span><span class="z-punctuation z-accessor z-dot z-rust">.</span>cursor<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">min</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>constraints<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">len</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-keyword z-operator z-arithmetic z-rust">-</span> <span class="z-constant z-numeric z-integer z-decimal z-rust">1</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-terminator z-rust">;</span>
</span><span class="z-source z-rust"><span class="z-comment z-line z-double-slash z-rust"><span class="z-punctuation z-definition z-comment z-rust">//</span> get the x offset and width of currently selected processor
</span></span><span class="z-source z-rust"><span class="z-storage z-type z-rust">let</span> cursor_x_offset <span class="z-keyword z-operator z-assignment z-rust">=</span> constraints<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">iter</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">take</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-variable z-language z-rust">self</span><span class="z-punctuation z-accessor z-dot z-rust">.</span>cursor</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-meta z-path z-rust">sum<span class="z-punctuation z-accessor z-rust">::</span></span><span class="z-meta z-generic z-rust"><span class="z-punctuation z-definition z-generic z-begin z-rust"><</span><span class="z-storage z-type z-rust">u16</span><span class="z-punctuation z-definition z-generic z-end z-rust">></span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-keyword z-operator z-rust">as</span> <span class="z-storage z-type z-rust">i32</span><span class="z-punctuation z-terminator z-rust">;</span>
</span><span class="z-source z-rust"><span class="z-storage z-type z-rust">let</span> cursor_width <span class="z-keyword z-operator z-assignment z-rust">=</span> constraints<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">get</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-variable z-language z-rust">self</span><span class="z-punctuation z-accessor z-dot z-rust">.</span>cursor</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span>
</span><span class="z-source z-rust"> <span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">expect</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-string z-quoted z-double z-rust"><span class="z-punctuation z-definition z-string z-begin z-rust">"</span>bounds check should clamp cursor to a valid index of constraints<span class="z-punctuation z-definition z-string z-end z-rust">"</span></span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-terminator z-rust">;</span>
</span><span class="z-source z-rust"><span class="z-comment z-line z-double-slash z-rust"><span class="z-punctuation z-definition z-comment z-rust">//</span> this calculation determines the view offset
</span></span><span class="z-source z-rust"><span class="z-variable z-language z-rust">self</span><span class="z-punctuation z-accessor z-dot z-rust">.</span>offset <span class="z-keyword z-operator z-assignment z-rust">=</span> <span class="z-variable z-language z-rust">self</span><span class="z-punctuation z-accessor z-dot z-rust">.</span>offset<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">clamp</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>cursor_x_offset <span class="z-keyword z-operator z-arithmetic z-rust">+</span> <span class="z-keyword z-operator z-arithmetic z-rust">*</span>cursor_width <span class="z-keyword z-operator z-rust">as</span> <span class="z-storage z-type z-rust">i32</span> <span class="z-keyword z-operator z-arithmetic z-rust">-</span> frame_width<span class="z-punctuation z-separator z-rust">,</span> cursor_x_offset</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-terminator z-rust">;</span>
</span><span class="z-source z-rust">
</span><span class="z-source z-rust"><span class="z-storage z-type z-rust">let</span> <span class="z-storage z-modifier z-rust">mut</span> idx_offset <span class="z-keyword z-operator z-assignment z-rust">=</span> <span class="z-constant z-numeric z-integer z-decimal z-rust">0</span><span class="z-punctuation z-terminator z-rust">;</span>
</span><span class="z-source z-rust"><span class="z-storage z-type z-rust">let</span> <span class="z-storage z-modifier z-rust">mut</span> x_offset <span class="z-keyword z-operator z-assignment z-rust">=</span> <span class="z-constant z-numeric z-integer z-decimal z-rust">0</span><span class="z-punctuation z-terminator z-rust">;</span>
</span><span class="z-source z-rust"><span class="z-comment z-line z-double-slash z-rust"><span class="z-punctuation z-definition z-comment z-rust">//</span> because it can be partially cut off, we must calculate the width of the leftmost visible processor
</span></span><span class="z-source z-rust"><span class="z-storage z-type z-rust">let</span> first_width <span class="z-keyword z-operator z-assignment z-rust">=</span> <span class="z-keyword z-control z-rust">loop</span> <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"> <span class="z-keyword z-control z-rust">match</span> constraints<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">get</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>idx_offset</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> <span class="z-support z-type z-rust">Some</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>width</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-keyword z-operator z-rust">=></span> <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> x_offset <span class="z-keyword z-operator z-assignment z-rust">+=</span> <span class="z-keyword z-operator z-arithmetic z-rust">*</span>width <span class="z-keyword z-operator z-rust">as</span> <span class="z-storage z-type z-rust">i32</span><span class="z-punctuation z-terminator z-rust">;</span>
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> <span class="z-keyword z-control z-rust">if</span> x_offset <span class="z-keyword z-operator z-comparison z-rust">></span> <span class="z-variable z-language z-rust">self</span><span class="z-punctuation z-accessor z-dot z-rust">.</span>offset <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> <span class="z-keyword z-control z-rust">break</span> <span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>x_offset <span class="z-keyword z-operator z-arithmetic z-rust">-</span> <span class="z-variable z-language z-rust">self</span><span class="z-punctuation z-accessor z-dot z-rust">.</span>offset</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-keyword z-operator z-rust">as</span> <span class="z-storage z-type z-rust">u16</span><span class="z-punctuation z-terminator z-rust">;</span>
</span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span>
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> idx_offset <span class="z-keyword z-operator z-assignment z-rust">+=</span> <span class="z-constant z-numeric z-integer z-decimal z-rust">1</span><span class="z-punctuation z-terminator z-rust">;</span>
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> <span class="z-support z-type z-rust">None</span> <span class="z-keyword z-operator z-rust">=></span> <span class="z-support z-macro z-rust">unreachable!</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-string z-quoted z-double z-rust"><span class="z-punctuation z-definition z-string z-begin z-rust">"</span>offset should not go past end of chain<span class="z-punctuation z-definition z-string z-end z-rust">"</span></span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span>
</span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"></span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span><span class="z-punctuation z-terminator z-rust">;</span>
</span></code></pre>
<h3 id="clap-tui-extension">CLAP TUI extension</h3>
<script src="https://asciinema.org/a/50G16WJML5WjPUZNLrm3NwPKV.js" id="asciicast-50G16WJML5WjPUZNLrm3NwPKV" async="true"></script>
<p><a href="https://git.sr.ht/~jaxter184/tlature-clap-extensions/tree/main/item/src/tui.rs">source code</a></p>
<p>TODO: elaborate</p>
RepliconThu, 01 Feb 2024 00:00:00 +0000Unknown
https://wiki.jaxter184.net/game-dev/bevy-netcode/replicon/
https://wiki.jaxter184.net/game-dev/bevy-netcode/replicon/<p><a href="https://github.com/projectharmonia/bevy_replicon">Replicon</a> is a
bevy-centric interface built on top of the <code>renet</code> networking library.
It provides some derivable traits and bevy plugins to apply to your
game in, ideally, a very modular and straightforward way.</p>
<p>NOTE: for the following, I'm using bevy version 0.11</p>
<h2 id="first-task-headless-dedicated-server">First task: Headless dedicated server</h2>
<p>In the various bevy networking libraries I've evaluated, the examples
tend to be for a "main" client that also performs the role of a server,
and "sub" clients that hook into that main client's server to connect
to each other.
While that's neat, in my case, I want my server to run headlessly on an
always-on computer, so for me, it would be nice for the server-side to
be in a separate binary with no UI or input functionality.
Adapting an example to do this also seems like a good way to learn more
about the library.</p>
<h2 id="turning-off-display">Turning off display</h2>
<p>The bevy repo has <a href="https://github.com/bevyengine/bevy/blob/main/examples/app/headless.rs">a nice example for headless operation</a>.</p>
<p>The important part is:</p>
<pre data-lang="rust" class="language-rust z-code"><code class="language-rust" data-lang="rust"><span class="z-source z-rust"><span class="z-storage z-type z-rust">let</span> refresh <span class="z-keyword z-operator z-assignment z-rust">=</span> <span class="z-meta z-path z-rust">Duration<span class="z-punctuation z-accessor z-rust">::</span></span>from_secs_f64<span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-constant z-numeric z-float z-rust">1.</span><span class="z-constant z-numeric z-float z-rust">0</span> <span class="z-keyword z-operator z-arithmetic z-rust">/</span> <span class="z-constant z-numeric z-float z-rust">60.</span><span class="z-constant z-numeric z-float z-rust">0</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-terminator z-rust">;</span>
</span><span class="z-source z-rust">MinimalPlugins<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">set</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-meta z-path z-rust">ScheduleRunnerPlugin<span class="z-punctuation z-accessor z-rust">::</span></span>run_loop<span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>refresh</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span>
</span></code></pre>
<p>Though there are some plugins in <code>DefaultPlugins</code> that are required
by some of the plugins like <code>rapier-3d</code> that I'm using for server-side
game logic checks.</p>
<pre data-lang="rust" class="language-rust z-code"><code class="language-rust" data-lang="rust"><span class="z-source z-rust"><span class="z-storage z-type z-rust">let</span> refresh <span class="z-keyword z-operator z-assignment z-rust">=</span> <span class="z-meta z-path z-rust">Duration<span class="z-punctuation z-accessor z-rust">::</span></span>from_secs_f64<span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-constant z-numeric z-float z-rust">1.</span><span class="z-constant z-numeric z-float z-rust">0</span> <span class="z-keyword z-operator z-arithmetic z-rust">/</span> <span class="z-constant z-numeric z-float z-rust">60.</span><span class="z-constant z-numeric z-float z-rust">0</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-terminator z-rust">;</span>
</span><span class="z-source z-rust"><span class="z-storage z-type z-rust">let</span> app <span class="z-keyword z-operator z-assignment z-rust">=</span> <span class="z-meta z-path z-rust">App<span class="z-punctuation z-accessor z-rust">::</span></span>new<span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span>
</span><span class="z-source z-rust"> <span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">add_plugins</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>DefaultPlugins<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">build</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-meta z-path z-rust">disable<span class="z-punctuation z-accessor z-rust">::</span></span><span class="z-meta z-generic z-rust"><span class="z-punctuation z-definition z-generic z-begin z-rust"><</span>WinitPlugin<span class="z-punctuation z-definition z-generic z-end z-rust">></span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span>
</span><span class="z-source z-rust"> <span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">add_plugins</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-meta z-path z-rust">ScheduleRunnerPlugin<span class="z-punctuation z-accessor z-rust">::</span></span>run_loop<span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>refresh</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span>
</span><span class="z-source z-rust"><span class="z-punctuation z-terminator z-rust">;</span>
</span></code></pre>
<h2 id="refactoring">Refactoring</h2>
<p>In preparation for networking <a href="/art/game-dev/#bevy">my existing game</a>,
I reorganized most of my game logic for better separation between the
parts that are going to be on the client and server.
In the end, of the systems I've implemented so far, there are going
to be:</p>
<ul>
<li>some that run in the client, ex: input handling</li>
<li>some than run in the server, ex: determining when the game is over</li>
<li>some that run in both, ex: most of the game logic</li>
</ul>
<p>For the most part, I use const generics so that I can keep the number
of structs relatively low:</p>
<pre data-lang="rust" class="language-rust z-code"><code class="language-rust" data-lang="rust"><span class="z-source z-rust"><span class="z-meta z-struct z-rust"><span class="z-storage z-modifier z-rust">pub</span> <span class="z-storage z-type z-struct z-rust">struct</span> </span><span class="z-meta z-struct z-rust"><span class="z-meta z-generic z-rust"><span class="z-entity z-name z-struct z-rust">PlayerPlugin</span><span class="z-meta z-generic z-rust"><span class="z-punctuation z-definition z-generic z-begin z-rust"><</span><span class="z-storage z-modifier z-rust">const</span> IS_SERVER<span class="z-punctuation z-separator z-rust">:</span> <span class="z-storage z-type z-rust">bool</span><span class="z-punctuation z-definition z-generic z-end z-rust">></span></span></span></span><span class="z-punctuation z-terminator z-rust">;</span>
</span><span class="z-source z-rust">
</span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-storage z-type z-impl z-rust">impl</span></span><span class="z-meta z-impl z-rust"><span class="z-meta z-generic z-rust"><span class="z-punctuation z-definition z-generic z-begin z-rust"><</span><span class="z-storage z-modifier z-rust">const</span> IS_SERVER<span class="z-punctuation z-separator z-rust">:</span> <span class="z-storage z-type z-rust">bool</span><span class="z-punctuation z-definition z-generic z-end z-rust">></span></span></span><span class="z-meta z-impl z-rust"> Plugin <span class="z-keyword z-other z-rust">for</span></span><span class="z-meta z-impl z-rust"> <span class="z-entity z-name z-impl z-rust">PlayerPlugin</span><span class="z-meta z-generic z-rust"><span class="z-punctuation z-definition z-generic z-begin z-rust"><</span>IS_SERVER<span class="z-punctuation z-definition z-generic z-end z-rust">></span></span> </span><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"> <span class="z-meta z-function z-rust"><span class="z-meta z-function z-rust"><span class="z-storage z-type z-function z-rust">fn</span> </span><span class="z-entity z-name z-function z-rust">build</span></span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-begin z-rust">(</span><span class="z-keyword z-operator z-rust">&</span><span class="z-variable z-parameter z-rust">self</span>, <span class="z-variable z-parameter z-rust">app</span><span class="z-punctuation z-separator z-rust">:</span> <span class="z-keyword z-operator z-rust">&</span><span class="z-storage z-modifier z-rust">mut</span> App</span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-end z-rust">)</span></span></span></span><span class="z-meta z-function z-rust"> </span><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> app
</span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> <span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">add_systems</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>
</span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-group z-rust"> Update<span class="z-punctuation z-separator z-rust">,</span>
</span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-group z-rust"> <span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>
</span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-group z-rust"><span class="z-meta z-group z-rust"> tick_player<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">before</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-meta z-path z-rust">bevy_mod_wanderlust<span class="z-punctuation z-accessor z-rust">::</span></span>movement</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-separator z-rust">,</span>
</span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-group z-rust"><span class="z-meta z-group z-rust"> ocean_kill<span class="z-punctuation z-separator z-rust">,</span>
</span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-group z-rust"><span class="z-meta z-group z-rust"> draw_gizmos<span class="z-punctuation z-separator z-rust">,</span>
</span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-group z-rust"><span class="z-meta z-group z-rust"> tick_unit<span class="z-punctuation z-separator z-rust">,</span>
</span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-group z-rust"><span class="z-meta z-group z-rust"> </span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">run_if</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-support z-function z-rust">in_state</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-meta z-path z-rust">GameState<span class="z-punctuation z-accessor z-rust">::</span></span>Playing</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-separator z-rust">,</span>
</span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-group z-rust"> </span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span>
</span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> <span class="z-comment z-line z-double-slash z-rust"><span class="z-punctuation z-definition z-comment z-rust">//</span> ...
</span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> <span class="z-punctuation z-terminator z-rust">;</span>
</span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust">
</span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> <span class="z-keyword z-control z-rust">if</span> <span class="z-constant z-other z-rust">IS_SERVER</span> <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> app
</span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> <span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">add_systems</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>
</span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-group z-rust"> Update<span class="z-punctuation z-separator z-rust">,</span>
</span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-group z-rust"> <span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>
</span></span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-group z-rust"><span class="z-meta z-group z-rust"> player_events_server<span class="z-punctuation z-separator z-rust">,</span>
</span></span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-group z-rust"><span class="z-meta z-group z-rust"> confirm_projectile<span class="z-punctuation z-separator z-rust">,</span>
</span></span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-group z-rust"><span class="z-meta z-group z-rust"> </span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">run_if</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-support z-function z-rust">in_state</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-meta z-path z-rust">GameState<span class="z-punctuation z-accessor z-rust">::</span></span>Playing</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-separator z-rust">,</span>
</span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-group z-rust"> </span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span>
</span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> <span class="z-punctuation z-terminator z-rust">;</span>
</span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span>
</span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> <span class="z-keyword z-control z-rust">else</span> <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> app
</span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> <span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">add_systems</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>
</span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-group z-rust"> Update<span class="z-punctuation z-separator z-rust">,</span>
</span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-group z-rust"> <span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>
</span></span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-group z-rust"><span class="z-meta z-group z-rust"> player_init_system<span class="z-punctuation z-separator z-rust">,</span>
</span></span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-group z-rust"><span class="z-meta z-group z-rust"> player_events_client<span class="z-punctuation z-separator z-rust">,</span>
</span></span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-group z-rust"><span class="z-meta z-group z-rust"> tick_player_spell<span class="z-punctuation z-separator z-rust">,</span>
</span></span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-group z-rust"><span class="z-meta z-group z-rust"> </span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">run_if</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-support z-function z-rust">in_state</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-meta z-path z-rust">GameState<span class="z-punctuation z-accessor z-rust">::</span></span>Playing</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-separator z-rust">,</span>
</span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-group z-rust"> </span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span>
</span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> <span class="z-punctuation z-terminator z-rust">;</span>
</span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span>
</span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span></span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-impl z-rust"><span class="z-meta z-block z-rust"></span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span></span>
</span></code></pre>
<p>After doing this, I was able to add replicon with relatively little
friction.
Of course, it didn't work at all straight out of the gate, but at the
very least, it compiled.
Usually, with a Rust program, that would mean it's mostly working, but
I think part of the tradeoff that Bevy makes is that it does a lot of
stuff (running systems, managing components) dynamically, so some of
Rust's strong typing advantages aren't quite so prominent in their API.</p>
<p>This results in a bit of a strange pattern:</p>
<pre data-lang="rust" class="language-rust z-code"><code class="language-rust" data-lang="rust"><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-function z-rust"><span class="z-storage z-type z-function z-rust">fn</span> </span><span class="z-entity z-name z-function z-rust">player_events_server</span></span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-begin z-rust">(</span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"> <span class="z-storage z-modifier z-rust">mut</span> <span class="z-variable z-parameter z-rust">commands</span><span class="z-punctuation z-separator z-rust">:</span> Commands,
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"> <span class="z-storage z-modifier z-rust">mut</span> <span class="z-variable z-parameter z-rust">player_events</span><span class="z-punctuation z-separator z-rust">:</span> <span class="z-meta z-generic z-rust">EventReader<span class="z-punctuation z-definition z-generic z-begin z-rust"><</span><span class="z-meta z-generic z-rust">FromClient<span class="z-punctuation z-definition z-generic z-begin z-rust"><</span>PlayerEvent<span class="z-punctuation z-definition z-generic z-end z-rust">></span></span><span class="z-punctuation z-definition z-generic z-end z-rust">></span></span>,
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"> <span class="z-storage z-modifier z-rust">mut</span> <span class="z-variable z-parameter z-rust">players</span><span class="z-punctuation z-separator z-rust">:</span> <span class="z-meta z-generic z-rust">Query<span class="z-punctuation z-definition z-generic z-begin z-rust"><</span><span class="z-punctuation z-section z-group z-begin z-rust">(</span>Entity, <span class="z-keyword z-operator z-rust">&</span>UnitId, <span class="z-keyword z-operator z-rust">&</span><span class="z-storage z-modifier z-rust">mut</span> PlayerState, <span class="z-keyword z-operator z-rust">&</span><span class="z-storage z-modifier z-rust">mut</span> UnitStateSync<span class="z-punctuation z-section z-group z-end z-rust">)</span><span class="z-punctuation z-definition z-generic z-end z-rust">></span></span>,
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"></span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-end z-rust">)</span></span></span></span><span class="z-meta z-function z-rust"> </span><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> <span class="z-keyword z-control z-rust">for</span> FromClient <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span> client_id<span class="z-punctuation z-separator z-rust">:</span> <span class="z-keyword z-operator z-rust">_</span><span class="z-punctuation z-separator z-rust">,</span> event </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span> <span class="z-keyword z-operator z-rust">in</span> player_events<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">iter</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> <span class="z-support z-function z-rust">player_events_helper</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-constant z-language z-rust">true</span><span class="z-punctuation z-separator z-rust">,</span> event<span class="z-punctuation z-separator z-rust">,</span> <span class="z-keyword z-operator z-bitwise z-rust">&</span><span class="z-storage z-modifier z-rust">mut</span> commands<span class="z-punctuation z-separator z-rust">,</span> <span class="z-keyword z-operator z-bitwise z-rust">&</span><span class="z-storage z-modifier z-rust">mut</span> players</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-terminator z-rust">;</span>
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"></span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span></span>
</span><span class="z-source z-rust">
</span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-function z-rust"><span class="z-storage z-type z-function z-rust">fn</span> </span><span class="z-entity z-name z-function z-rust">player_events_client</span></span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-begin z-rust">(</span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"> <span class="z-storage z-modifier z-rust">mut</span> <span class="z-variable z-parameter z-rust">commands</span><span class="z-punctuation z-separator z-rust">:</span> Commands,
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"> <span class="z-storage z-modifier z-rust">mut</span> <span class="z-variable z-parameter z-rust">player_events</span><span class="z-punctuation z-separator z-rust">:</span> <span class="z-meta z-generic z-rust">EventReader<span class="z-punctuation z-definition z-generic z-begin z-rust"><</span>PlayerEvent<span class="z-punctuation z-definition z-generic z-end z-rust">></span></span>,
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"> <span class="z-storage z-modifier z-rust">mut</span> <span class="z-variable z-parameter z-rust">players</span><span class="z-punctuation z-separator z-rust">:</span> <span class="z-meta z-generic z-rust">Query<span class="z-punctuation z-definition z-generic z-begin z-rust"><</span><span class="z-punctuation z-section z-group z-begin z-rust">(</span>Entity, <span class="z-keyword z-operator z-rust">&</span>UnitId, <span class="z-keyword z-operator z-rust">&</span><span class="z-storage z-modifier z-rust">mut</span> PlayerState, <span class="z-keyword z-operator z-rust">&</span><span class="z-storage z-modifier z-rust">mut</span> UnitStateSync<span class="z-punctuation z-section z-group z-end z-rust">)</span><span class="z-punctuation z-definition z-generic z-end z-rust">></span></span>,
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"></span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-end z-rust">)</span></span></span></span><span class="z-meta z-function z-rust"> </span><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> <span class="z-keyword z-control z-rust">for</span> event <span class="z-keyword z-operator z-rust">in</span> player_events<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">iter</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> <span class="z-support z-function z-rust">player_events_helper</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-constant z-language z-rust">false</span><span class="z-punctuation z-separator z-rust">,</span> event<span class="z-punctuation z-separator z-rust">,</span> <span class="z-keyword z-operator z-bitwise z-rust">&</span><span class="z-storage z-modifier z-rust">mut</span> commands<span class="z-punctuation z-separator z-rust">,</span> <span class="z-keyword z-operator z-bitwise z-rust">&</span><span class="z-storage z-modifier z-rust">mut</span> players</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-terminator z-rust">;</span>
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"></span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span></span>
</span><span class="z-source z-rust">
</span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-function z-rust"><span class="z-storage z-type z-function z-rust">fn</span> </span><span class="z-entity z-name z-function z-rust">player_events_helper</span></span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-begin z-rust">(</span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"> <span class="z-variable z-parameter z-rust">is_server</span><span class="z-punctuation z-separator z-rust">:</span> <span class="z-storage z-type z-rust">bool</span>,
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"> <span class="z-variable z-parameter z-rust">event</span><span class="z-punctuation z-separator z-rust">:</span> <span class="z-keyword z-operator z-rust">&</span>PlayerEvent,
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"> <span class="z-variable z-parameter z-rust">commands</span><span class="z-punctuation z-separator z-rust">:</span> <span class="z-keyword z-operator z-rust">&</span><span class="z-storage z-modifier z-rust">mut</span> Commands,
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"> <span class="z-variable z-parameter z-rust">players</span><span class="z-punctuation z-separator z-rust">:</span> <span class="z-keyword z-operator z-rust">&</span><span class="z-storage z-modifier z-rust">mut</span> <span class="z-meta z-generic z-rust">Query<span class="z-punctuation z-definition z-generic z-begin z-rust"><</span><span class="z-punctuation z-section z-group z-begin z-rust">(</span>Entity, <span class="z-keyword z-operator z-rust">&</span>UnitId, <span class="z-keyword z-operator z-rust">&</span><span class="z-storage z-modifier z-rust">mut</span> PlayerState, <span class="z-keyword z-operator z-rust">&</span><span class="z-storage z-modifier z-rust">mut</span> UnitStateSync<span class="z-punctuation z-section z-group z-end z-rust">)</span><span class="z-punctuation z-definition z-generic z-end z-rust">></span></span>,
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"></span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-end z-rust">)</span></span></span></span><span class="z-meta z-function z-rust"> </span><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> <span class="z-keyword z-control z-rust">match</span> event<span class="z-punctuation z-accessor z-dot z-rust">.</span>event_kind <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> <span class="z-meta z-path z-rust">PlayerEventKind<span class="z-punctuation z-accessor z-rust">::</span></span>Spawn<span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>id</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-keyword z-operator z-rust">=></span> <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> <span class="z-keyword z-control z-rust">if</span> is_server <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> commands
</span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> <span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">spawn</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>
</span></span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-group z-rust"><span class="z-meta z-group z-rust"> <span class="z-meta z-path z-rust">PlayerBundle<span class="z-punctuation z-accessor z-rust">::</span></span>new<span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>id</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-separator z-rust">,</span>
</span></span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-group z-rust"><span class="z-meta z-group z-rust"> Replication<span class="z-punctuation z-separator z-rust">,</span>
</span></span></span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-group z-rust"><span class="z-meta z-group z-rust"> </span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-terminator z-rust">;</span>
</span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span>
</span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span>
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> <span class="z-meta z-path z-rust">PlayerEventKind<span class="z-punctuation z-accessor z-rust">::</span></span>StartAction<span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>idx</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-keyword z-operator z-rust">=></span> <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> <span class="z-keyword z-control z-rust">if</span> idx <span class="z-keyword z-operator z-comparison z-rust">></span> <span class="z-constant z-numeric z-integer z-decimal z-rust">4</span> <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span> <span class="z-keyword z-control z-rust">return</span> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span>
</span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> <span class="z-keyword z-control z-rust">if</span> <span class="z-storage z-type z-rust">let</span> <span class="z-support z-type z-rust">Some</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-storage z-modifier z-rust">mut</span> player</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-keyword z-operator z-assignment z-rust">=</span> players<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">iter_mut</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">filter</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-meta z-function z-closure z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-begin z-rust">|</span></span></span><span class="z-meta z-function z-closure z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-variable z-parameter z-rust">ea</span><span class="z-punctuation z-section z-parameters z-end z-rust">|</span></span> </span><span class="z-meta z-function z-closure z-rust"><span class="z-keyword z-operator z-arithmetic z-rust">*</span>ea<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-constant z-numeric z-integer z-decimal z-rust">1</span> <span class="z-keyword z-operator z-comparison z-rust">==</span> event<span class="z-punctuation z-accessor z-dot z-rust">.</span>id</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">next</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> player<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-constant z-numeric z-float z-rust">2.</span>spells<span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">[</span>idx<span class="z-punctuation z-section z-group z-end z-rust">]</span></span><span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-constant z-numeric z-integer z-decimal z-rust">1</span> <span class="z-keyword z-operator z-assignment z-rust">=</span> <span class="z-meta z-path z-rust">SpellState<span class="z-punctuation z-accessor z-rust">::</span></span>Cast<span class="z-punctuation z-terminator z-rust">;</span>
</span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span>
</span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span>
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> <span class="z-meta z-path z-rust">PlayerEventKind<span class="z-punctuation z-accessor z-rust">::</span></span>FinishAction<span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>idx</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-keyword z-operator z-rust">=></span> <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> <span class="z-keyword z-control z-rust">if</span> idx <span class="z-keyword z-operator z-comparison z-rust">></span> <span class="z-constant z-numeric z-integer z-decimal z-rust">4</span> <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span> <span class="z-keyword z-control z-rust">return</span> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span>
</span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> <span class="z-keyword z-control z-rust">if</span> <span class="z-storage z-type z-rust">let</span> <span class="z-support z-type z-rust">Some</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-storage z-modifier z-rust">mut</span> player</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-keyword z-operator z-assignment z-rust">=</span> players<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">iter_mut</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">filter</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-meta z-function z-closure z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-begin z-rust">|</span></span></span><span class="z-meta z-function z-closure z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-variable z-parameter z-rust">ea</span><span class="z-punctuation z-section z-parameters z-end z-rust">|</span></span> </span><span class="z-meta z-function z-closure z-rust"><span class="z-keyword z-operator z-arithmetic z-rust">*</span>ea<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-constant z-numeric z-integer z-decimal z-rust">1</span> <span class="z-keyword z-operator z-comparison z-rust">==</span> event<span class="z-punctuation z-accessor z-dot z-rust">.</span>id</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">next</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> player<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-constant z-numeric z-float z-rust">2.</span>spells<span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">[</span>idx<span class="z-punctuation z-section z-group z-end z-rust">]</span></span><span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-constant z-numeric z-integer z-decimal z-rust">1</span> <span class="z-keyword z-operator z-assignment z-rust">=</span> <span class="z-meta z-path z-rust">SpellState<span class="z-punctuation z-accessor z-rust">::</span></span>Release<span class="z-punctuation z-terminator z-rust">;</span>
</span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span>
</span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span>
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> <span class="z-meta z-path z-rust">PlayerEventKind<span class="z-punctuation z-accessor z-rust">::</span></span>Moving<span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>dir</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-keyword z-operator z-rust">=></span> <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> <span class="z-keyword z-control z-rust">if</span> <span class="z-storage z-type z-rust">let</span> <span class="z-support z-type z-rust">Some</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-storage z-modifier z-rust">mut</span> player</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-keyword z-operator z-assignment z-rust">=</span> players<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">iter_mut</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">filter</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-meta z-function z-closure z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-begin z-rust">|</span></span></span><span class="z-meta z-function z-closure z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-variable z-parameter z-rust">ea</span><span class="z-punctuation z-section z-parameters z-end z-rust">|</span></span> </span><span class="z-meta z-function z-closure z-rust"><span class="z-keyword z-operator z-arithmetic z-rust">*</span>ea<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-constant z-numeric z-integer z-decimal z-rust">1</span> <span class="z-keyword z-operator z-comparison z-rust">==</span> event<span class="z-punctuation z-accessor z-dot z-rust">.</span>id</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">next</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> player<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-constant z-numeric z-float z-rust">3.</span>direction_input <span class="z-keyword z-operator z-assignment z-rust">=</span> dir<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">clamp_length</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-constant z-numeric z-float z-rust">0.</span><span class="z-constant z-numeric z-float z-rust">0</span><span class="z-punctuation z-separator z-rust">,</span> <span class="z-constant z-numeric z-float z-rust">1.</span><span class="z-constant z-numeric z-float z-rust">0</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-terminator z-rust">;</span>
</span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span>
</span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span>
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> <span class="z-meta z-path z-rust">PlayerEventKind<span class="z-punctuation z-accessor z-rust">::</span></span>Facing<span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>dir</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-keyword z-operator z-rust">=></span> <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> <span class="z-keyword z-control z-rust">if</span> <span class="z-storage z-type z-rust">let</span> <span class="z-support z-type z-rust">Some</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-storage z-modifier z-rust">mut</span> player</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-keyword z-operator z-assignment z-rust">=</span> players<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">iter_mut</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">filter</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-meta z-function z-closure z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-begin z-rust">|</span></span></span><span class="z-meta z-function z-closure z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-variable z-parameter z-rust">ea</span><span class="z-punctuation z-section z-parameters z-end z-rust">|</span></span> </span><span class="z-meta z-function z-closure z-rust"><span class="z-keyword z-operator z-arithmetic z-rust">*</span>ea<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-constant z-numeric z-integer z-decimal z-rust">1</span> <span class="z-keyword z-operator z-comparison z-rust">==</span> event<span class="z-punctuation z-accessor z-dot z-rust">.</span>id</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">next</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> player<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-constant z-numeric z-float z-rust">3.</span>direction_input_2 <span class="z-keyword z-operator z-assignment z-rust">=</span> dir<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">clamp_length</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-constant z-numeric z-float z-rust">0.</span><span class="z-constant z-numeric z-float z-rust">0</span><span class="z-punctuation z-separator z-rust">,</span> <span class="z-constant z-numeric z-float z-rust">1.</span><span class="z-constant z-numeric z-float z-rust">0</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-terminator z-rust">;</span>
</span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span>
</span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span>
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"></span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span></span>
</span></code></pre>
<h2 id="server-to-client">Server to client</h2>
<p>There are three things you have to do for a bevy <code>Entity</code> to be
replicated from your server to your client:</p>
<ul>
<li>Implement <code>serde::{Serialize, Deserialize}</code> for the <code>Component</code></li>
<li>Mark the compenent as replicatable by using <code>App::replicate()</code> when
instatiating your app</li>
<li>Instantiate the <code>Entity</code> on your server with Replicon's <code>Replication</code>
component</li>
</ul>
<p>To send events from the client to the server, you follow three similar
steps:</p>
<ul>
<li>Implement <code>serde::{Serialize, Deserialize}</code> for the <code>Event</code></li>
<li>Mark the event as one that should be sent using
<code>App::add_client_event(EventType)</code></li>
<li>The event can be sent using the usual <code>EventWriter<YourEvent></code>
parameter in your client system, but to read these from the server
system, you use <code>EventReader<FromClient<YourEvent>></code>, which wraps the
event and bundles it with the <code>client_id</code></li>
</ul>
<h2 id="client-to-server">Client to server</h2>
<p>For some game events, letting the client send an event and just waiting
until the server processes it and sends back a replicated entity is
fine, but there are many actions that should be much more responsive,
like movement and abilities.
Unfortunately, the server will not replicate entities created on the
client.
After spending an hour perusing through various Overwatch videos to
understand better how their netcode works, I found the solution in the
Replicon documentation after looking for just a few minutes.</p>
<p>The resource <a href="https://docs.rs/bevy_replicon/latest/bevy_replicon/server/struct.ClientEntityMap.html"><code>ClientEntityMap</code></a> allows you to connect
an entity on the client with an entity on the server to synchronize
with.</p>
<p>Usage is fairly straightforward:</p>
<ul>
<li>Create the entity on the client</li>
<li>Send the <code>Entity</code> (a simple ID for the entity for use locally in the
client's ECS) to the server, for example by using an event</li>
<li>Once it receives the <code>Entity</code>, the server creates a similar entity
with all of the components that should be replicated across the server
and all the clients</li>
<li>Use <code>ClientEntityMap::insert</code> to connect the client <code>Entity</code> with the
server <code>Entity</code> (you only have to do this for the client that created
the original entity, as the other clients should use the server's
entity instead)</li>
</ul>
<p>For example, I have a projectile that gets instantiated in certain
conditions.
It is implemented with a <code>cast</code> function that handles all the logic for
it, and returns a <code>SpellEvent::Projectile(Spell, UnitId, Entity)</code> where
<code>Spell</code> and <code>UnitId</code> is information that the server needs to identically
run the <code>cast</code> function on the server side, and the <code>Entity</code> that the
client created so that it can connect it to the <code>Entity</code> that the
server makes.</p>
<h2 id="todo">TODO:</h2>
<ul>
<li>I have a bug where two projectiles are created when two clients are
connected to the server when only one should be created</li>
<li>How should non-deterministic events be handled? For example, the
random spread of a shotgun-like projectile</li>
<li>Need to test on a high-latency connection to make sure that the
projectile is not rubber-banded on the client side (or at least, any
rubber-banding is minimal)</li>
<li>Use <code>replicon_snap</code> to interpolate instead of just hard snapping</li>
</ul>
Monthly Log 2024Thu, 01 Feb 2024 00:00:00 +0000Unknown
https://wiki.jaxter184.net/log/2024/
https://wiki.jaxter184.net/log/2024/<h2 id="01-january">01 - January</h2>
<h3 id="tlature">tlature</h3>
<p>Did some refactoring around <code>Command</code>s so that I could:</p>
<ul>
<li>Map commands like <code>:wq</code> to multiple edits</li>
<li>Make <code>App</code>-level commands, like <code>:?</code> to open the help dialog</li>
<li>Add tooltips to subcommands like <code>:add</code>, which are not commands in
themselves, but rather components of other commands like <code>:add plugin</code>
or <code>:add block</code></li>
</ul>
<h3 id="bevy-replicon">Bevy Replicon</h3>
<p>Did a cursory evaluation of a bunch of bevy networking crates.
Decided to use Replicon for my current game, and wrote about the process
of integrating it:</p>
<p><div>
<a href="/game-dev/bevy-netcode">
<div class="list-entry outer">
<span class="link">🌐 Bevy netcode</span>
- Exploring various online multiplayer solutions for the Bevy game engine
</div>
</a>
</div>
<div>
<a href="/game-dev/bevy-netcode/replicon">
<div class="list-entry outer">
<span class="link">Replicon</span>
</div>
</a>
</div>
</p>
<h2 id="02-february">02 - February</h2>
<h3 id="tlature-1">tlature</h3>
<p>I probably should've been taking notes on what I'm working on while I
was working on it, but in lieu of a more thorough description, here's a
summary of my <code>git log</code>:</p>
<ul>
<li>Add better RouteView and PluginView panels, including parameter editing</li>
<li>Add chain view scrolling: Instead of squeezing every processor on the
page, chain view panels now have a fixed width, and the ones that don't
fit aren't shown until the selection moves offscreen.</li>
<li>Add base64-encoded plugin state for non-UTF-8 plugin state bytestreams</li>
</ul>
<h3 id="fedicard">FediCard</h3>
<p><a href="https://labyrinth.social/@nash">Nash</a> and <a href="https://labyrinth.social/@hexephre">hexephre</a> from labyrinth.social made <a href="https://labyrinth.social/@nash/111961056428799897">a little tool for creating
FediCards</a>!
It's built using Godot, which is really neat, but in the comments,
there seem to be a few issues when running on some browsers and OSes.</p>
<p>To that end, I've been playing around a lot with web technologies, and
in particular, HTML and SVG.
In my naïveté, I figured I'd be able to whip up a quick browser-based
editor to provide a more compatible (albeit less functional)
companion application. <a href="https://fedicard.jaxter184.net/edit.html">Check out the proof-of-concept here</a></p>
<p>See also:
<div>
<a href="/log/2023#card-game">
<div class="list-entry outer">
<span class="link">Monthly Log 2023</span>
- Designing a card game?
</div>
</a>
</div>
</p>
<h4 id="problem-1">Problem 1</h4>
<p>My first instinct was to make an SVG file that gets edited, and
directly export that.
After hand-minimizing an SVG file and defining all of its style in CSS,
I ran into issues with HTML <code><input></code> tags embedded in the SVG data.</p>
<p>In retrospect, I could've probably rigged up some sort of Javascript
shenanigan, but at the time, I was hoping to get away with as little
Javascript as possible.
So I converted all of the SVG rectangles to <code><div></code> tags.</p>
<h4 id="problem-2">Problem 2</h4>
<p>There's no way to resize the <code><textarea></code> dynamically based on its
contents with just HTML.
My solution to this problem was to ignore it.</p>
<h4 id="problem-3">Problem 3</h4>
<p>After adding some controls for zooming into the image, I ran into an
issue where the zooming was causing the image to exceed the bounds.</p>
<p>I turned the img tag into a <code><div></code> because I have no idea what I'm
doing and the internet told me that would work.
And it did.</p>
<h3 id="rowing-machine-mod">Rowing machine mod</h3>
<p>A while back, I found a used rowing machine on craigslist.
It was in really good condition (still had the plastic screen
protector, and clearly hadn't been used that much).
I've been using it pretty regularly, and for the most part, I'm happy
with it, but there's one issue that's been bugging me: after ~3 minutes
of inactivity, the screen resets, and does so with almost no warning.</p>
<p>Obviously, the only solution is to reverse engineer the device so that
I can build my own distance tracking device.
And because the original head unit is mounted to the machine using a
VESA mount, this has the added benefit of letting me mount a screen
and speakers instead, so I can more comfortably watch stuff while I
exercise.</p>
<h4 id="finding-pinout">Finding pinout</h4>
<p>First step was to figure out what all the pins did.
I took apart the head unit, and looked at the board to figure out what
did what.</p>
<p>Then I probed the pins to check for voltages and any discernable
signals.
Under the assumption that the black wire was ground, I found pins with
9 volt and 3.3 volt signals, which I just assumed were power, but other
than that, the only other pins I could identify from the 10 total pins
were a pair of differential signals.</p>
<p>Next step was to open up the body of the machine.
In retrospect, I shouldn't have wasted any time with the head unit,
because everything I needed to know was in here.
There is a small cylindrical sensor pointing to the air resistance
wheel, and after following its wires back to the cable harness, I
probed it to find that its resistance dropped when it got close to a
magnet mounted on the air resistance wheel.
With this, I could just treat it like a pull-up switch and use it to
count revolutions.</p>
<h4 id="building-on-a-perfboard">Building on a perfboard</h4>
<p>These days, it's really easy and cheap to get a custom PCB made.
With excellent FOSS tools like [KiCad], there are very few excuses to
<em>not</em> make a PCB. My excuse in this case was that I wanted to get this
running within a few days.</p>
<p>TODO:</p>
<ul>
<li>add pics</li>
<li>adding power switch after building the board</li>
<li>alternate mcu pins</li>
<li>reverse mount headers</li>
<li>embassy i/o interrupts</li>
<li>48 revolutions with interleaved i2c</li>
<li>210 when doing one every 10 buttons (maybe because of debouncing or noise? should try with hysteresis)</li>
</ul>
<p>Next step is to get some sort of speed detection going, but in it's
current state, it does everything I need it to, so I suspect that I
won't get around to doing anything like that for a while.</p>
<h3 id="monthly-log">Monthly Log</h3>
<p>I don't want to complain too much because the fact that I've been able
to do 6 months (wow!) of monthly logs without missing any by more than
a day or two has far exceeded the expectations I had when I started,
but I think I still haven't quite found a good "voice" for blogging
yet.
Plus, my posts are a bit slapdash and dry, and tend to just say "I did
this, then I did this, so I had to do this", rather than outlining any
actually useful perspectives or processes, or building an interesting
narrative.
I had also hoped to use these logs as an opportunity to practice my
German, but I've yet to translate any of them yet.
Maybe next month.</p>
<h2 id="03-march">03 - March</h2>
<ul>
<li><a href="/mus-tec/tlature/#controller-support">Controller support for ↹lature</a></li>
</ul>
<h3 id="more-bevy-replicon-stuff">more bevy replicon stuff</h3>
<p>Tried a few libraries for rollback/snapshot netcode:</p>
<ul>
<li><a href="https://github.com/RJ/bevy_timewarp">bevy_timewarp</a>
<ul>
<li>no examples, documentation is pretty rough</li>
</ul>
</li>
<li><a href="https://github.com/Bendzae/bevy_replicon_snap">bevy_replicon_snap</a>
<ul>
<li>seems very immature, got a lot of weird behavior at high latencies.
(Tangentially, <code>tc qdisc add dev lo root netem delay 200ms</code> is a fun command)</li>
</ul>
</li>
</ul>
<p>Overall, I spent way too much time spinning my wheels and have very little to show for it.
I spent the last day of the month basically undoing most of my changes to revert it back to being a local-multiplayer
game.
I'm also trying to refactor it to make more sense, but seeing how I have very little experience, it's pretty tough.
I think I might just start from scratch on a simpler game, with more focus on game mechanics and playability rather
than spending so much time on high-complexity features like scriptable abilities and multiplayer.
Maybe even enter a game jam or something.</p>
<h3 id="blender-mod">blender mod</h3>
<ul>
<li>bought a $30 blender on craigslist because i was tired of holding down the cup on my smaller personal ninja blender</li>
<li>fool that I am, this one you have to hold down the button, so I still can't walk away from it while blending</li>
<li>so i soldered a toggle switch on it in parallel with the momentary switch</li>
<li>fortunately, it was easier to take apart than my old smaller blender</li>
<li>i once again forgot to take pics</li>
<li>super easy modification that i think anyone can do to almost any blender</li>
</ul>
<h2 id="04-april">04 - April</h2>
<p>Mostly game development this month:</p>
<div>
<a href="/game-dev/spell-scripting/">
<div class="list-entry outer">
<span class="link">🧙 Spell Scripting</span>
- Creating a state machine scripting language to implement spells for a MOBA
</div>
</a>
</div>
<div>
<a href="/game-dev/spell-scripting/circle-physics">
<div class="list-entry outer">
<span class="link">⭕ Circle physics</span>
- A physics engine specifically for real-time strategy games
</div>
</a>
</div>
<p>But also a bit of ↹lature:</p>
<div>
<a href="/mus-tec/tlature#chain-view">
<div class="list-entry outer">
<span class="link">↹lature</span>
- devlog - Implementing a niri-like signal chain viewer
</div>
</a>
</div>
<h3 id="monthly-log-1">Monthly log</h3>
<p>It is the 27th of May, and I am just now uploading my log entries for April.
I originally intended to write and post my logs while I was travelling at the beginning of May, but that just didn't
happen at all.
On the bright side, I spent basically the whole day yesterday revamping and reorganizing the wiki, hopefully in a way
that makes it easier to write for.
I'm also really proud of the two articles that I did end up with, and think that they're some of my best.</p>
<h2 id="05-may">05 - May</h2>
<p>Went to Montréal for <a href="https://2024.oshwa.org/">Open Hardware Summit</a>.</p>
<ul>
<li>Additional wiki features:
<div>
<a href="/website#markdown-shortcodes-in-zola">
<div class="list-entry outer">
<span class="link">This website</span>
- Adding shortcodes to my Zola template
</div>
</a>
</div>
</li>
<li>Wrote two small tools for using my HP ZBook X2 G4:
<ul>
<li><a href="https://git.sr.ht/~jaxter184/flourish">flourish</a>, a touchscreen gesture launcher. Currently needs to be run as root,
which makes it almost useless because then it can't launch things or modify <code>niri</code>.</li>
<li><a href="https://git.sr.ht/~jaxter184/hp-zbook-quick-keys">hp-quick-keys</a>, a launcher than listens to the "quick keys" on the
ZBook, which are exposed as a USB Human Interface Device. This works much better since it doesn't require root. TODO:
talk about the different gestures and how they're measured</li>
</ul>
</li>
<li>Added magnetic feet to my keyboard (TODO: pics)</li>
<li>I've been "falling behind" on my self-imposed monthly "schedule", which I think means I should be making smaller
posts and updating them more often</li>
</ul>
<h2 id="06-june">06 - June</h2>
<p>Documented an old project (mostly taking pictures):</p>
<div>
<a href="/life-counter">
<div class="list-entry outer">
<span class="link">❤️ Life Counter</span>
- Device for keeping track of an integer value in a tabletop game
</div>
</a>
</div>
<ul>
<li>research screenless computer</li>
<li>research persistence of vision vibration sculpture</li>
<li>start programming esp32s (again)</li>
<li>inline latch for cardboard boxes (downloadable 3D print to be added)</li>
<li>tlature transport refactor</li>
<li>planned out a more elaborate bed desk with overhead rails</li>
<li>increased the font size on my rowing machine display</li>
</ul>
<h2 id="07-july">07 - July</h2>
<ul>
<li>Got DAC output working on a Raspberry Pi Zero for screenless computer</li>
<li>Fixed my rowing machine: the strap broke, so I replaced it with some cheap webbing I got off Aliexpress, which also
broke.
Bought a nicer nylon webbing (also from Aliexpress), and that worked a lot better.</li>
<li>Made a Gaussian curvature visualization script for Blender.
My plan is to eventually adapt this into a plugin that flattens faces to make them zero curvature for papercraft and
acrylic bending purposes.</li>
<li><a href="https://git.sr.ht/~jaxter184/wifi-button">wifi-button</a>: made a PCB and played around with MDNS on the ESP32</li>
<li>Made a swatch to see how different layers interact on a PCB to use it in PCB art</li>
<li>Started working on a <a href="/mus-tec/tlature#clap-tui-extension">TUI extension for the CLAP plugin format</a></li>
</ul>
<h2 id="08-august">08 - August</h2>
<p>Swatch PCBs came in, turns out they decide whether to remove silkscreen from areas with no mask on a case-by-case
basis.
<div>
<a href="/pcb-swatch">
<div class="list-entry outer">
<span class="link">🟩️ PCB swatch</span>
- Color and pattern test for PCB art
</div>
</a>
</div>
</p>
<ul>
<li>Life counter
<ul>
<li>Touched up the PCB last month, and tested the new boards this month after they came in.</li>
<li>Turns out the STM32 has about a megaohm between its GPIO pins and ground, which messes with the enable input of the
<a href="https://www.diodes.com/assets/Datasheets/AP22816_17_18.pdf">load switch</a> im using.
I wanted the buttons to pull down and have the load switch input low enable, but it seems like that would require a
bunch more diodes.</li>
<li>Cleaned up the art file, will make a script that turns the cleaned-up version into a set of KiCad files.</li>
<li>Made an inkscape extension that I intended to use for this, but I ended up using the GIMP newspaper filter and
tracing the bitmap instead.
Looks very comic-y.</li>
<li>(TODO: elaborate, clarify, document in life counter article)</li>
</ul>
</li>
<li>Wifi button
<ul>
<li>Assembled, doesn't work very reliably, partially because of the wifi antenna.</li>
<li>LED driver was a bit of a pain; using the same one in the life counter</li>
</ul>
</li>
<li>Someone sent me some contributions to <a href="https://git.sr.ht/~jaxter184/tui-nodes"><code>tui-nodes</code></a>!
Didn't expect it, and the changes were well done and much needed, so it was a very pleasant surprise.
It was also my first time using <code>git-am</code>, which was very enlightening.
I now realize that I need to migrate to meli as soon as possible.
But also, the mail service I'm currently using doesn't seem to let me send emails from my domain (though receiving
seems to work fine, so git-am should work fine for now as long as I don't need to request changes).</li>
<li>Made a single DDR pad.
TODO: write an article about it sometime this month.
Will be tough because I neglected to take pictures.</li>
<li>Screenless computer
<ul>
<li>Couldn't get the hifi DAC that I was using on my Raspberry Pi Zero to work on my Orange Pi Zero 2W.</li>
<li>Gonna work on the software side for now while that simmers. Made a quick mockup with speech-dispatcher and jack. Next
step is to playback samples (probably wanna load them into memory first, for the alphabet).</li>
</ul>
</li>
<li>PoE
<ul>
<li>Got a 5V output using a dedicated PoE ethernet jack and a Silvertel-esque module.</li>
<li>Have been working with PoE injectors, which it turns out aren't actually compliant because they inject voltage
regardless of whether the device requests it.
Ordered a cheap PoE switch for testing.</li>
<li>Also want to try out dual rail generation (i.e. both positive and negative voltage), so I ordered a transformer for
that. I have no idea if it's going to work.</li>
<li>Messed around with inductors</li>
</ul>
</li>
<li>Tried to test some coils for reverse-engineering Wacom EMR pens, but it didn't work.
Ordered a headless oscilliscope that should make things a bit more convenient.
TODO: write article, or at least a linkdump</li>
</ul>
<h2 id="09-september">09 - September</h2>
<ul>
<li>wrote an article for how graph drawing works in <code>tui-nodes</code> to try to make things easier for contributors who want to
improve that part: <div>
<a href="/graph-layout">
<div class="list-entry outer">
<span class="link">Graph Layout Algorithm</span>
- Novel (i think?) algorithm for drawing a directed acyclic graph
</div>
</a>
</div>
<ul>
<li>made a little program for drawing in a tui, but used a svg output instead for better compatibility:
<a href="https://git.sr.ht/~jaxter184/woogie">https://git.sr.ht/~jaxter184/woogie</a></li>
</ul>
</li>
<li>Added modifier and release keybinds to niri.
currently testing it to make sure it's working right before upstreaming, and I've already run into a few issues.</li>
<li>building a dead bug circuit for PoE</li>
<li>flashed a ch32 riscv chip, excited to try out ethernet on it</li>
</ul>
<h2 id="10-october">10 - October</h2>
<ul>
<li>Set up author tags for this wiki
<ul>
<li><a href="https://adventures-with-pi.blogspot.com/2024/10/setting-up-author-attribution-for.html">https://adventures-with-pi.blogspot.com/2024/10/setting-up-author-attribution-for.html</a></li>
<li><a href="https://rknight.me/blog/setting-up-mastodon-author-tags/">https://rknight.me/blog/setting-up-mastodon-author-tags/</a></li>
</ul>
</li>
<li>Tried to learn Elixir so I could develop crofu with it, but failed miserably: <div>
<a href="/icc/crofu">
<div class="list-entry outer">
<span class="link">crofu</span>
- Incremental converse crowdfunding platform
</div>
</a>
</div>
TODO: update crofu article with my takeaways</li>
<li>Made a board for use with SFP connectors so I can have an inter-board connection format.
Would be nice if there was an inline connector version of it, but I think I've found a reasonable solution despite
that. TODO: small writeup</li>
<li>More PoE dead bug progress</li>
<li>Reverse engineering the Dell Canvas totem.
Turns out it literally just works by putting capacitive touch pads in a specific layout. TODO: small writeup</li>
</ul>
<h2 id="11-november">11 - November</h2>
<ul>
<li>Been playing an unconscionable amount of Overwatch because they've got a "classic" game mode replicating what the
game was like when it was first released.
I've missed Bastion's headshots and old ultimate so much.</li>
<li>Set up a RISCV microcontroller for a friend to use in their art project (different from the ethernet ch32 from
September, but same flashing process)</li>
<li>Been working on a sculpture.
Haven't documented it at all, but will probably try to by mid next week so I can put a little placard pointing to the
article when I put it in a local show.</li>
<li>Made more progress with crofu using <a href="https://github.com/tokio-rs/axum">Axum</a>: <div>
<a href="/icc/crofu">
<div class="list-entry outer">
<span class="link">crofu</span>
- Incremental converse crowdfunding platform
</div>
</a>
</div>
</li>
<li>Made an attachment for my pants to mount my keyboard to.
Still a work in progress.</li>
</ul>
December Adventure 2023Fri, 01 Dec 2023 00:00:00 +0000Unknown
https://wiki.jaxter184.net/log/dec-adv-2023/
https://wiki.jaxter184.net/log/dec-adv-2023/<h2 id="december-adventure">December Adventure</h2>
<p>I never really gel'd with <a href="https://adventofcode.com/">Advent of Code</a>.
I think it's just not really <em>for</em> me.
But while I didn't really enjoy the contents event itself, I think the
premise is fun, and it's nice to be able to do something with a bunch of
people who share similar interests with you.
It is perhaps because of that desire that I immediately found myself
on board with <a href="https://eli.li/december-adventure">December Adventure</a> as soon as I heard about it.</p>
<blockquote>
<p>The December Adventure is low key. The goal is to write a little bit of code every day in December.</p>
</blockquote>
<p>The project towards which I'm going to do my December Adventure
development is the <a href="https://cleveraudio.org/">CLAP</a> plugin library for <a href="https://git.sr.ht/~jaxter184/tlature">↹lature</a>.
While I'd love to do a brand new plugin each day (and I probably have
enough ideas to get through the month doing that), the premise of
the adventure seems to encourage low-key, consistent progress, so I'm
going to aim for something a little less manic, and set my end-goal as
developing <strong>a meaningful feature every day</strong>.</p>
<p>Watch my progress here: <a href="https://git.sr.ht/~jaxter184/tlature-plugins">https://git.sr.ht/~jaxter184/tlature-plugins</a></p>
<p>Current list of plugins:</p>
<ul>
<li>chord name to note generator</li>
<li>simple instruments
<ul>
<li>sampler</li>
</ul>
</li>
<li>simple fx
<ul>
<li>filter</li>
<li>compressor</li>
</ul>
</li>
<li>livestock auctioneer physical model
<ul>
<li>maybe even something as simple as a singing voice synth with a
southern drawl</li>
</ul>
</li>
<li>tools
<ul>
<li>osc output</li>
<li>automation</li>
<li>adsr</li>
<li>lfo</li>
<li>step sequencer</li>
<li>https://nimble.itch.io/catjammer</li>
</ul>
</li>
</ul>
<p>(Not intended to be exhaustive, just to show the general roadmap)</p>
<h3 id="day-01">day 01</h3>
<p>While this wasn't my initial plan, I spent basically my whole evening
reorganizing the ↹lature plugin repositories.
Today, I:</p>
<ul>
<li>Renamed structs to make the plugins consistent with each other</li>
<li>Moved structs to make code easier to navigate</li>
<li>Combined the <code>tlature-dev</code> and <code>tlature-std</code> plugin repositories</li>
<li>Made a <a href="https://github.com/cargo-generate/cargo-generate"><code>cargo-generate</code></a> template for creating new plugins</li>
</ul>
<p>While this is all a lot of really helpful preparatory work (renaming
and moving structs was particularly time-consuming), I'm particularly
excited about the template.
It was surprisingly easy to make, and I'm looking forward to both using
it myself and also asking people who expressed interest in the project
in the past to try it out to see how difficult it is for them to use.</p>
<h3 id="day-02">day 02</h3>
<p>Did some organization on the main ↹lature repository to make it more
sturdy.
The goal is to be able to easily point people to a specific commit or
tag where everything hopefully "just works".</p>
<p>The most on-track December Adventure thing I did was making an
<code>event-offset</code> plugin that offsets incoming events in time and pitch.
Well, not every event, just note events.
Also it doesn't do anything yet, it just passes events through.
That is, I think it does, but I can't tell because I haven't set up a
way to test it yet.
So I spent the rest of my day working on a ↹lature feature to be able
to define event output files.
It's not done yet, but once it is, it should be helpful for debugging
plugins in the future.</p>
<p>But it's December Adventure!
So falling off course and working on something completely different and
then not finishing it is still a success!</p>
<!--
Right now I don't really understand what mechanisms CLAP has for things
like Bitwig's modulators to output a parameter change message, but
I'd assume that since it's co-developed between Bitwig and u-he, there
would be some sort of functionality like that built-in.
-->
<h3 id="day-03">day 03</h3>
<p>Trying to create plugins for ↹lature has really pointed out all the
flaws of its current state.
I thought it would be easier to make event plugins instead of
audio plugins because it would be easier to debug and test for, but it
turns out that not only have I neglected to set up any tests, but I've
also completely underdesigned all my event handling.
I'm not totally sure what I'm doing going forward, but at the very
least, I added a feature to convert CLAP plugin event outputs back into
OSC messages, so I can confirm that the <code>event-offset</code> plugin does
indeed work as I would expect it to in its current form (i.e. it passes
the events through as-is)!</p>
<p>I'm really liking this whole December Adventure thing.
The flexibility of being able to explore things at my own pace gives
me space to breathe and give this stuff the time it deserves instead of
churning out solution after solution.</p>
<h3 id="day-04">day 04</h3>
<p>↹lature is, by design, an OSC-powered DAW.
Back when it was just a sequencer, that worked because all it had to do
was generate notes and spit them out somewhere.
But as the project grew into a more complete audio workstation, I
decided that I wanted the sequencer to be "aware" of the downstream
consumers of the messages, and once plugins were added, I also had to
add some sort of converter between OSC and various CLAP events.
Until now, I was able to get away with that because I was only doing the
lossy conversion from OSC, a very flexible, generic message format, to
CLAP events, which is more structured. So now that I want to convert
them back, I'm not sure how to do it.</p>
<p>To illustrate, there are multiple OSC messages that can convert to the
same CLAP event.
<code>/notes/v0 i 60</code> and <code>/ ii 0 60</code> both convert to middle C on the 0th voice.
So if a CLAP plugin outputs a middle C, which of those OSC messages
should it convert to?</p>
<p>The solution that I came up with is that it shouldn't.
OSC conversions should only go one way.
Maybe in the future it would make sense to set up some sort of
conversion, but for now, the plan is that any plugin that wants to
output events for ↹lature to send to other plugins should output OSC
events only.</p>
<p>To facilitate this, I created an OSC message extension that provides
<code>OscEvent</code> (that is, I copied the MIDI SysEx event format from regular
CLAP and changed some names).
It essentially consists of a buffer that is written to and read from
using the <code>rosc</code> encode/decode functions.</p>
<p>Behold!:</p>
<pre data-lang="rust" class="language-rust z-code"><code class="language-rust" data-lang="rust"><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-function z-rust"><span class="z-storage z-type z-function z-rust">fn</span> </span><span class="z-entity z-name z-function z-rust">process</span></span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-begin z-rust">(</span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"> <span class="z-keyword z-operator z-rust">&</span><span class="z-storage z-modifier z-rust">mut</span> <span class="z-variable z-parameter z-rust">self</span>,
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"> <span class="z-variable z-parameter z-rust">_process</span><span class="z-punctuation z-separator z-rust">:</span> <span class="z-meta z-generic z-rust">Process<span class="z-punctuation z-definition z-generic z-begin z-rust"><</span>'<span class="z-keyword z-operator z-rust">_</span><span class="z-punctuation z-definition z-generic z-end z-rust">></span></span>,
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"> <span class="z-variable z-parameter z-rust">_audio</span><span class="z-punctuation z-separator z-rust">:</span> <span class="z-meta z-generic z-rust">Audio<span class="z-punctuation z-definition z-generic z-begin z-rust"><</span>'<span class="z-keyword z-operator z-rust">_</span><span class="z-punctuation z-definition z-generic z-end z-rust">></span></span>,
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"> <span class="z-variable z-parameter z-rust">events</span><span class="z-punctuation z-separator z-rust">:</span> <span class="z-meta z-generic z-rust">Events<span class="z-punctuation z-definition z-generic z-begin z-rust"><</span>'<span class="z-keyword z-operator z-rust">_</span><span class="z-punctuation z-definition z-generic z-end z-rust">></span></span>,
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"></span><span class="z-meta z-function z-rust"><span class="z-meta z-function z-parameters z-rust"><span class="z-punctuation z-section z-parameters z-end z-rust">)</span></span></span></span><span class="z-meta z-function z-rust"> <span class="z-meta z-function z-return-type z-rust"><span class="z-punctuation z-separator z-rust">-></span> <span class="z-meta z-generic z-rust"><span class="z-support z-type z-rust">Result</span><span class="z-punctuation z-definition z-generic z-begin z-rust"><</span>ProcessStatus, PluginError<span class="z-punctuation z-definition z-generic z-end z-rust">></span></span></span> </span><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> <span class="z-keyword z-control z-rust">for</span> ea_event <span class="z-keyword z-operator z-rust">in</span> events<span class="z-punctuation z-accessor z-dot z-rust">.</span>input <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> events<span class="z-punctuation z-accessor z-dot z-rust">.</span>output<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">try_push</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>ea_event</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">unwrap</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-terminator z-rust">;</span>
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> <span class="z-storage z-type z-rust">let</span> packet <span class="z-keyword z-operator z-assignment z-rust">=</span> <span class="z-meta z-path z-rust">rosc<span class="z-punctuation z-accessor z-rust">::</span></span><span class="z-meta z-path z-rust">OscPacket<span class="z-punctuation z-accessor z-rust">::</span></span>Message<span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-meta z-path z-rust">rosc<span class="z-punctuation z-accessor z-rust">::</span></span>OscMessage <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span> addr<span class="z-punctuation z-separator z-rust">:</span> <span class="z-string z-quoted z-double z-rust"><span class="z-punctuation z-definition z-string z-begin z-rust">"</span>test<span class="z-punctuation z-definition z-string z-end z-rust">"</span></span><span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">to_string</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-separator z-rust">,</span> args<span class="z-punctuation z-separator z-rust">:</span> <span class="z-support z-macro z-rust">vec!</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">[</span><span class="z-punctuation z-section z-group z-end z-rust">]</span></span> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-terminator z-rust">;</span>
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> <span class="z-storage z-type z-rust">let</span> buf <span class="z-keyword z-operator z-assignment z-rust">=</span> <span class="z-meta z-path z-rust">rosc<span class="z-punctuation z-accessor z-rust">::</span></span><span class="z-meta z-path z-rust">encoder<span class="z-punctuation z-accessor z-rust">::</span></span>encode<span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-keyword z-operator z-bitwise z-rust">&</span>packet</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">unwrap</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-terminator z-rust">;</span>
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> <span class="z-storage z-type z-rust">let</span> <span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>ptr<span class="z-punctuation z-separator z-rust">,</span> len</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-keyword z-operator z-assignment z-rust">=</span> <span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>buf<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">as_ptr</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-separator z-rust">,</span> buf<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">len</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-terminator z-rust">;</span>
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> <span class="z-meta z-path z-rust">std<span class="z-punctuation z-accessor z-rust">::</span></span><span class="z-meta z-path z-rust">mem<span class="z-punctuation z-accessor z-rust">::</span></span>forget<span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>buf</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-terminator z-rust">;</span>
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> <span class="z-storage z-type z-rust">let</span> osc_event <span class="z-keyword z-operator z-assignment z-rust">=</span> <span class="z-meta z-path z-rust">OscEvent<span class="z-punctuation z-accessor z-rust">::</span></span>new<span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>
</span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-group z-rust"> <span class="z-meta z-path z-rust">EventHeader<span class="z-punctuation z-accessor z-rust">::</span></span>new<span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>ea_event<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">header</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">time</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-separator z-rust">,</span>
</span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-group z-rust"> <span class="z-constant z-numeric z-integer z-decimal z-rust">0</span><span class="z-punctuation z-separator z-rust">,</span>
</span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-group z-rust"> <span class="z-storage z-modifier z-rust">unsafe</span> <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span> <span class="z-meta z-path z-rust">std<span class="z-punctuation z-accessor z-rust">::</span></span><span class="z-meta z-path z-rust">slice<span class="z-punctuation z-accessor z-rust">::</span></span>from_raw_parts<span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>ptr<span class="z-punctuation z-separator z-rust">,</span> len</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span><span class="z-punctuation z-separator z-rust">,</span> <span class="z-comment z-line z-double-slash z-rust"><span class="z-punctuation z-definition z-comment z-rust">//</span> SAFETY: leaks are safe
</span></span></span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-group z-rust"> </span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-terminator z-rust">;</span>
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> events<span class="z-punctuation z-accessor z-dot z-rust">.</span>output<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">try_push</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>osc_event</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">unwrap</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-terminator z-rust">;</span>
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust">
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"> <span class="z-support z-type z-rust">Ok</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-meta z-path z-rust">ProcessStatus<span class="z-punctuation z-accessor z-rust">::</span></span>ContinueIfNotQuiet</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-function z-rust"><span class="z-meta z-block z-rust"></span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span></span>
</span></code></pre>
<pre data-lang="rust" class="language-rust z-code"><code class="language-rust" data-lang="rust"><span class="z-source z-rust"><span class="z-keyword z-control z-rust">if</span> <span class="z-storage z-type z-rust">let</span> <span class="z-support z-type z-rust">Some</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>ev</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-keyword z-operator z-assignment z-rust">=</span> ea_event<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-meta z-path z-rust">as_event<span class="z-punctuation z-accessor z-rust">::</span></span><span class="z-meta z-generic z-rust"><span class="z-punctuation z-definition z-generic z-begin z-rust"><</span>OscEvent<span class="z-punctuation z-definition z-generic z-end z-rust">></span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"> <span class="z-storage z-type z-rust">let</span> map <span class="z-keyword z-operator z-assignment z-rust">=</span> pd<span class="z-punctuation z-accessor z-dot z-rust">.</span>event_map<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">entry</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>ctx<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">clone</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">or_default</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-terminator z-rust">;</span>
</span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"> <span class="z-storage z-type z-rust">let</span> packet <span class="z-keyword z-operator z-assignment z-rust">=</span> <span class="z-meta z-path z-rust">rosc<span class="z-punctuation z-accessor z-rust">::</span></span><span class="z-meta z-path z-rust">decoder<span class="z-punctuation z-accessor z-rust">::</span></span>decode_udp<span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>ev<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">data</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">unwrap</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-constant z-numeric z-integer z-decimal z-rust">1</span><span class="z-punctuation z-terminator z-rust">;</span>
</span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"> <span class="z-keyword z-control z-rust">match</span> packet <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> <span class="z-meta z-path z-rust">OscPacket<span class="z-punctuation z-accessor z-rust">::</span></span>Message<span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>msg</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-keyword z-operator z-rust">=></span> <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> map<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">push</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span>ev<span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">header</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">time</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-accessor z-dot z-rust">.</span><span class="z-support z-function z-rust">into</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-separator z-rust">,</span> msg</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-terminator z-rust">;</span>
</span></span></span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> <span class="z-meta z-path z-rust">OscPacket<span class="z-punctuation z-accessor z-rust">::</span></span>Bundle<span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-keyword z-operator z-rust">_</span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span> <span class="z-keyword z-operator z-rust">=></span> <span class="z-support z-macro z-rust">todo!</span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-begin z-rust">(</span><span class="z-string z-quoted z-double z-rust"><span class="z-punctuation z-definition z-string z-begin z-rust">"</span>handle OSC bundles<span class="z-punctuation z-definition z-string z-end z-rust">"</span></span></span><span class="z-meta z-group z-rust"><span class="z-punctuation z-section z-group z-end z-rust">)</span></span><span class="z-punctuation z-separator z-rust">,</span>
</span></span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"><span class="z-meta z-block z-rust"> </span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span>
</span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"></span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span>
</span></code></pre>
<p>There are a lot of things that aren't great about the code above
(memory leaks, allocation on the audio thread, ignoring bundles), but
those are problems for future me to solve.</p>
<h3 id="day-05">day 05</h3>
<p>(When reading the following, it may help to know that <code>Location</code> is
more or less just a reimplementation of <code>PathBuf</code> that uses <code>String</code>
instead of <code>OsString</code>.
If that isn't clear, just think of <code>Location</code> as a string that
represents a path, like <code>/home/jaxter/music/ninja_tuna.flac</code>)</p>
<p>I think the most difficult thing with ↹lature so far has been deciding
how routing works. The general philosophy is as follows:</p>
<ul>
<li>routing should be easy to understand if explained properly</li>
<li><code>Location</code>s should be generally analagous to paths on a filesystem,
and the routing syntax should reflect that</li>
<li>for people editing the project file directly (for example, in a text
editor), opportunities for error should be minimized</li>
<li>the <a href="https://en.wiktionary.org/wiki/royal_road">"royal road"</a> should be encouraged by the syntax, generally by
making it short and simple compared to less common alternatives</li>
<li>the actual logical code for routing should be relatively simple, with
few edge cases</li>
</ul>
<p>I believe the current method achieves this, though perhaps not to the
extent I would like.
That being said, having added OSC outputs to plugins has shown me the
flaws of my current scheme, so I made a few additions to adjust the
routing behavior.
Currently, the process data is structured roughly like this:</p>
<pre data-lang="rust" class="language-rust z-code"><code class="language-rust" data-lang="rust"><span class="z-source z-rust">ProcessData <span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-begin z-rust">{</span>
</span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"> audio_map<span class="z-punctuation z-separator z-rust">:</span> <span class="z-meta z-generic z-rust">BTreeMap<span class="z-punctuation z-definition z-generic z-begin z-rust"><</span>Location, AudioBuffer<span class="z-punctuation z-definition z-generic z-end z-rust">></span></span><span class="z-punctuation z-separator z-rust">,</span>
</span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"> event_map<span class="z-punctuation z-separator z-rust">:</span> <span class="z-meta z-generic z-rust">BTreeMap<span class="z-punctuation z-definition z-generic z-begin z-rust"><</span>Location, <span class="z-meta z-generic z-rust"><span class="z-support z-type z-rust">Vec</span><span class="z-punctuation z-definition z-generic z-begin z-rust"><</span><span class="z-meta z-path z-rust">rosc<span class="z-punctuation z-accessor z-rust">::</span></span>OscMessage<span class="z-punctuation z-definition z-generic z-end z-rust">></span></span><span class="z-punctuation z-definition z-generic z-end z-rust">></span></span>
</span></span><span class="z-source z-rust"><span class="z-meta z-block z-rust"></span><span class="z-meta z-block z-rust"><span class="z-punctuation z-section z-block z-end z-rust">}</span></span>
</span></code></pre>
<p>In this case, there are two places that can represent the "path" of the
event: the <code>Location</code> key in <code>event_map</code>, or the <code>rosc::OscMessage</code>,
which contains an address field.
The changes I want to make are to resolve an ambiguity as to whether
the new path should be reflected in the <code>ProcessData</code> <code>event_map</code> key
or in the OSC message's address.
I've decided for now that the <code>event_map</code> key of any newly routed
message will simply the current context (that is, the path of the
parent block), and the rest of the path will go in the <code>OscMessage</code>
address.
I'm a little hesitant to split it up like that because it makes
matching message paths difficult later down the pipelite, but chances
are, it will need to be refactored again soon anyway, so I'm fine
leaving things a little messy and placeholder-esque for now.</p>
<p>All of that planning aside, the only change I made today was to reflect
this design in the loop that gets the events of <code>Processor::Plugin</code>.</p>
<h3 id="day-06">day 06</h3>
<p>After making the changes yesterday in the <code>Processor::Plugin</code>
processing, I had to change <code>Processor::Route</code> behavior to be
consistent with it, which broke some tests.
I think in cases like these when I make some weird change that might
affect behavior, I'm really appreciative of my past self for setting
up these tests to give me a more clear path to things working as they
should.
I wish I had the energy today to describe this particular case where I
benefitted from a test, but for now, I will just say what has been said
many times, and more persuasively by many others: <strong>write unit tests
for your code</strong>!</p>
<h3 id="day-07">day 07</h3>
<p>Honestly not sure why I thought I needed OSC support in order to make
an event offset event.
I can just make ↹lature pass note events to downstream processors.
Maybe I just wanted to not deal with implementing both an OSC channel
and a CLAP event channel, and just only dealing with OSC events?</p>
<p>Anyway, today I just added some logic for handling CLAP <code>NoteOnEvent</code>s
in addition to OSC events.</p>
<p>Things have been slowing down, and I'll probably skip tomorrow to do
some non-computer-touching stuff.
I'm a little sad that things haven't been as adventurous as I had hoped
when starting December Adventure, but I'll be ambiently thinking about
how I can shift my progress to be more exploratory going forward.</p>
<p><del>TODO:</del>
Implement CLAP event channel routing</p>
<h3 id="day-08">day 08</h3>
<p>(rest day)</p>
<h3 id="day-09">day 09</h3>
<p>As alluded to in day 07, the thing about December Adventure that I like
the most is the idea of exploring a little bit of a fun idea every day.
The most recent few days have felt like a lot of slogging in place, and
I was hoping to get out of that funk, so I've expanded the scope to be
anything ↹lature-related rather than just plugins, with a priority on
fun new features that I don't see in a lot of music software or TUI
programs.</p>
<p>First on my list is a file/string picker pop-up.
While common in music software, it's not so common in TUIs, so there
are a couple interesting things to think about when it comes to the
design.
But before working on the feature itself, I first need to figure out
how my GUI is going to work in general, in particular how various
elements are created/activated.
Two organization methods that I've run into in the past are stacks and
layers.
With stacks, you push and pop overlays, and with layers, you activate
and deactivate overlays.
The main difference is that with stacks, overlay order can be dynamic,
while with layers, overlay order is fixed.</p>
<p>When it comes to the interface, it should be easy for the end user
to be able to tell how their actions will translate to actions in the
software before they actually perform them.
A stack throws a wrench in this understandability, because with a given
screen, there may be 3 different overlays, plus something going on in
the status bar.
Rather than using something like color or highlighting to show what is
currently focused, I think making the layer order fixed would train
people to instinctually know what's focused by assigning a sort of
"priority" to each type of overlay.
For example, if a help dialog always has higher priority than the
numerical argument input, then you know that if you see a help
dialog, you can't perform any num arg inputs.
Things get a little tougher with the command bar, because it's pretty
subtle whether or not its active from just the command input itself,
so adding a command info dialog and autocomplete assistance menus will
make it clearer that the command entry is actually active.</p>
<p>I did a bit of messing around to see what things would be like in terms
of implementing each, and I decided that while both have their flaws,
I like layers slightly more.</p>
<p>One thing that makes it difficult is that I want overlay state like the
numerical argument and replacement values to be a dynamic "variable"
rather than a statically define Rust struct, with the goal of making it
more accessible from the hypothetical future scripting language.
I implemented rhai support in the past, but removed it because I didn't
like how I had to structure the application state to accommodate it,
so maybe it's worth considering removing that entirely.</p>
<ul>
<li><del>TODO:</del>
Implement layer UI</li>
<li><del>TODO:</del>
Add string picker pop-up</li>
</ul>
<h3 id="day-10">day 10</h3>
<p>Spent all day today spinning my wheels trying to figure out how I
should refactor layers to work.
I think I'm going to abandon this for now and come back to it after
December Adventure ends.
(Update: I did not abandon this, and it now works pretty ok)</p>
<h3 id="day-11">day 11</h3>
<p>Not even sure what counts as December Adventure anymore, but spent the
evening configuring <a href="https://zellij.dev">Zellij</a>.
It's a pretty neat piece of software, and while there are a couple
things that annoy me about it (mostly visual clutter), I learned a lot
of things that seem to be very helpful for higher-level planning for
↹lature.</p>
<p>While not intentional, it really helped me solidify a big decision with
↹lature: session management.
I have two choices when it comes to managing ↹lature sessions: I could
do it all internally, and have the program itself organize the various
panels and windows and tabs, or I could hook into the window/session
management of some other program.</p>
<p>Just to give a run down of what exactly I mean by session
management, there are various programs that manage different
workspaces/windows/sessions:</p>
<ul>
<li>Kakoune has a concept of "buffers", which are basically all the
documents you have open, plus a few extra scratch areas where you
can do things like peruse the various references of an identifier to
<code>goto</code>, or see debug information, and all of the buffers are associated
with a session, which can be used to connect a different client to the
current set of buffers.</li>
<li>Bitwig has tabs, which can each have a project, similar to a web
browser.</li>
<li>Sway, my window manager, can organize windows in various ways, but the
most basic method is tiling windows into various panes, which
show up side-by-side.</li>
<li>Zellij has pretty much all of these: panes fit into a tab, and tabs
are hosted in a session, which a seperate Zellij client can connect to.</li>
</ul>
<p>I hadn't really given too much consideration, and assumed that I would
be implementing this myself in ↹lature, but it seems like Zellij
has enough functionality (through things like <code>zellij action</code>) for
↹lature to be able to integrate pretty seamlessly with it (given an
appropriate configuration).</p>
<p>There were also a couple other things that exploring Zellij helped
with.
Being a TUI Rust program, it runs into many of the same restrictions
that ↹lature does, but compared to <a href="https://helix-editor.com">Helix</a> (another TUI Rust
program), there were a few aspects of Zellij's approach that I liked
more, particularly with regards to keymapping, mode management, and
plugins, all of which I'll probably talk about as December Adventure
continues.
Also, it uses <a href="https://kdl.dev">KDL</a>!</p>
<p>Despite not having even looked at any ↹lature code today, I made a
lot of progress on it!
While I'd like to say that it was all part of the plan, and that
December Adventure put me in an exploratory, casual mindset that led
to me making the decision to take a step back and just check out some
other stuff, that's not really what happened.
I just happened to check out Zellij on a whim, and it just happened to
be really helpful for getting a better idea of what I want ↹lature
to be.
That being said, I'm pretty sure I wouldn't have tried Zellij if I was
doing Advent of Code right now.</p>
<h3 id="day-12">day 12</h3>
<p>Recently, a friend started working on a Discord bot, and it made me
jealous, so I decided to go fix up the bot I made for ↹lature.
My bot was pretty simple: you'd send it a message, and it would reply
with a rendered <code>.wav</code> of the output.</p>
<p>The main thing I had to do to fix it was to migrate the rendering code
to use the new <code>ProcessData</code> structure, which greatly simplified the
setup.
Next, I just need to update the part that interfaces with Discord to
match.
There's also the concern with calculating the <code>tail</code>, but hopefully
that just works without too much fuss.</p>
<p>An example of the old message format:</p>
<blockquote>
<p>@jaxter184-bot-test<br />
sheet</p>
<pre data-lang="kdl" class="language-kdl z-code"><code class="language-kdl" data-lang="kdl"><span class="z-source z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl">ticks</span> 6985_0000<span class="z-punctuation z-terminator z-node z-kdl">
</span></span></span><span class="z-source z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-string z-kdl"><span class="z-string z-quoted z-double z-kdl"><span class="z-punctuation z-definition z-string z-begin z-kdl">"</span><span class="z-punctuation z-definition z-string z-end z-kdl">"</span></span></span> <span class="z-meta z-block z-child z-kdl"><span class="z-punctuation z-section z-mapping z-begin z-kdl">{</span>
</span></span></span></span><span class="z-source z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-block z-child z-kdl"> <span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl">bass</span> <span class="z-meta z-block z-child z-kdl"><span class="z-punctuation z-section z-mapping z-begin z-kdl">{</span>
</span></span></span></span></span></span><span class="z-source z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"><span class="z-meta z-block z-child z-kdl"> <span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-string z-kdl"><span class="z-string z-quoted z-double z-kdl"><span class="z-punctuation z-definition z-string z-begin z-kdl">"</span><span class="z-punctuation z-definition z-string z-end z-kdl">"</span></span></span> <span class="z-meta z-argument z-value z-kdl"><span class="z-meta z-mapping z-value z-kdl"><span class="z-meta z-annotation z-kdl"><span class="z-punctuation z-separator z-annotation z-begin z-kdl">(</span></span><span class="z-meta z-annotation z-kdl"><span class="z-entity z-name z-type z-kdl">i0</span><span class="z-punctuation z-separator z-annotation z-end z-kdl">)</span></span>"
</span></span></span></span></span></span></span></span></span></span><span class="z-source z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-argument z-value z-kdl"><span class="z-meta z-mapping z-value z-kdl"> ,21 [ _ ] [ ,21 _ ] . ,2D _ ,21 _ ,1F ,21 _ [ ,2D _ ] . ,1F _ [ ,21 _ ]
</span></span></span></span></span></span></span></span></span></span><span class="z-source z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-argument z-value z-kdl"><span class="z-meta z-mapping z-value z-kdl"> . [ ,21 _ ] ,21 . ,2B ,2D ,24 ,25 ,26 . [ ,32 _ ] [ ,24 _ ] . ,26 _ [ ,21 _ ]
</span></span></span></span></span></span></span></span></span></span><span class="z-source z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-argument z-value z-kdl"><span class="z-meta z-mapping z-value z-kdl"> . [ ,21 _ ] ,21 _ [ ,24 _ ] . ,21 . ,27 ,28 _ ,26 _ ,24 _ ,21
</span></span></span></span></span></span></span></span></span></span><span class="z-source z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-argument z-value z-kdl"><span class="z-meta z-mapping z-value z-kdl"> _ . ,21 . ,23 . [ ,2F _ ] ,23 ,24 . [ ,30 _ ] ,26 ,27 ,26 _ ,24
</span></span></span></span></span></span></span></span></span></span><span class="z-source z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-argument z-value z-kdl"><span class="z-meta z-mapping z-value z-kdl"> "
</span></span></span></span></span></span></span></span></span></span><span class="z-source z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-argument z-value z-kdl"><span class="z-meta z-mapping z-value z-kdl"> }
</span></span></span></span></span></span></span></span></span></span><span class="z-source z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-argument z-value z-kdl"><span class="z-meta z-mapping z-value z-kdl">
</span></span></span></span></span></span></span></span></span></span><span class="z-source z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-argument z-value z-kdl"><span class="z-meta z-mapping z-value z-kdl"> drums {
</span></span></span></span></span></span></span></span></span></span><span class="z-source z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-argument z-value z-kdl"><span class="z-meta z-mapping z-value z-kdl"> kick <span class="z-meta z-annotation z-kdl"><span class="z-punctuation z-separator z-annotation z-begin z-kdl">(</span></span><span class="z-meta z-annotation z-kdl"><span class="z-entity z-name z-type z-kdl">F</span><span class="z-punctuation z-separator z-annotation z-end z-kdl">)</span></span>",T ....... ,T ,T ..... ,T ,T . ,T ..... ,T ..... ,T . *6;20 ,T . *E;20 ,T . *8;20"
</span></span></span></span></span></span></span></span></span></span><span class="z-source z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-argument z-value z-kdl"><span class="z-meta z-mapping z-value z-kdl"> snare <span class="z-meta z-annotation z-kdl"><span class="z-punctuation z-separator z-annotation z-begin z-kdl">(</span></span><span class="z-meta z-annotation z-kdl"><span class="z-entity z-name z-type z-kdl">F</span><span class="z-punctuation z-separator z-annotation z-end z-kdl">)</span></span>".... ,T ... *38;8"
</span></span></span></span></span></span></span></span></span></span><span class="z-source z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-argument z-value z-kdl"><span class="z-meta z-mapping z-value z-kdl"> hat <span class="z-meta z-annotation z-kdl"><span class="z-punctuation z-separator z-annotation z-begin z-kdl">(</span></span><span class="z-meta z-annotation z-kdl"><span class="z-entity z-name z-type z-kdl">i0</span><span class="z-punctuation z-separator z-annotation z-end z-kdl">)</span></span>"*4. ,C0 ,50 *3A;2"
</span></span></span></span></span></span></span></span></span></span><span class="z-source z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-argument z-value z-kdl"><span class="z-meta z-mapping z-value z-kdl"> crash <span class="z-meta z-annotation z-kdl"><span class="z-punctuation z-separator z-annotation z-begin z-kdl">(</span></span><span class="z-meta z-annotation z-kdl"><span class="z-entity z-name z-type z-kdl">F</span><span class="z-punctuation z-separator z-annotation z-end z-kdl">)</span></span>",T *3F."
</span></span></span></span></span></span></span></span></span></span><span class="z-source z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-argument z-value z-kdl"><span class="z-meta z-mapping z-value z-kdl"> }
</span></span></span></span></span></span></span></span></span></span><span class="z-source z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-argument z-value z-kdl"><span class="z-meta z-mapping z-value z-kdl">}
</span></span></span></span></span></span></span></span></span></span></code></pre>
<p>graph</p>
<pre data-lang="kdl" class="language-kdl z-code"><code class="language-kdl" data-lang="kdl"><span class="z-source z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-string z-kdl"><span class="z-string z-quoted z-double z-kdl"><span class="z-punctuation z-definition z-string z-begin z-kdl">"</span><span class="z-punctuation z-definition z-string z-end z-kdl">"</span></span></span> <span class="z-meta z-block z-child z-kdl"><span class="z-punctuation z-section z-mapping z-begin z-kdl">{</span>
</span></span></span></span><span class="z-source z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-block z-child z-kdl"> <span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-string z-kdl"><span class="z-string z-quoted z-double z-kdl"><span class="z-punctuation z-definition z-string z-begin z-kdl">"</span><span class="z-punctuation z-definition z-string z-end z-kdl">"</span></span></span> "mixer" <span class="z-meta z-block z-child z-kdl"><span class="z-punctuation z-section z-mapping z-begin z-kdl">{</span>
</span></span></span></span></span></span></span><span class="z-source z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-block z-child z-kdl"> <span class="z-meta z-node z-kdl"><span class="z-meta z-annotation z-kdl"><span class="z-punctuation z-separator z-annotation z-begin z-kdl">(</span></span><span class="z-meta z-annotation z-kdl"><span class="z-entity z-name z-type z-kdl">ai</span><span class="z-punctuation z-separator z-annotation z-end z-kdl">)</span></span>a01 "../bass/"
</span></span></span></span></span></span></span></span><span class="z-source z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"> <span class="z-meta z-annotation z-kdl"><span class="z-punctuation z-separator z-annotation z-begin z-kdl">(</span></span><span class="z-meta z-annotation z-kdl"><span class="z-entity z-name z-type z-kdl">ai</span><span class="z-punctuation z-separator z-annotation z-end z-kdl">)</span></span>a02 "../drums/"
</span></span></span></span></span></span></span></span><span class="z-source z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"> }
</span></span></span></span></span></span></span></span><span class="z-source z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"> lead "sisqsa" state=<span class="z-meta z-annotation z-kdl"><span class="z-punctuation z-separator z-annotation z-begin z-kdl">(</span></span><span class="z-meta z-annotation z-kdl"><span class="z-entity z-name z-type z-kdl">inline</span><span class="z-punctuation z-separator z-annotation z-end z-kdl">)</span></span>"q"
</span></span></span></span></span></span></span></span><span class="z-source z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"> bass "sisqsa" state=<span class="z-meta z-annotation z-kdl"><span class="z-punctuation z-separator z-annotation z-begin z-kdl">(</span></span><span class="z-meta z-annotation z-kdl"><span class="z-entity z-name z-type z-kdl">inline</span><span class="z-punctuation z-separator z-annotation z-end z-kdl">)</span></span>"q"
</span></span></span></span></span></span></span></span><span class="z-source z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"> drums "beep-kit"
</span></span></span></span></span></span></span></span><span class="z-source z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl"><span class="z-entity z-name z-tag z-node z-kdl"><span class="z-meta z-block z-child z-kdl"><span class="z-meta z-node z-kdl">}
</span></span></span></span></span></span></span></span></code></pre>
</blockquote>
<h3 id="day-13">day 13</h3>
<p>The goal was to adapt the rest of the ↹lature discord bot to work,
and while that did get done, I unfortunately ran into an issue with the
tail where it would always return a length of 1.
While trying to figure that out, I adjusted the tests so that they
would dump the audio output into a file, and in doing so, I uncovered
another issue: the test audio output was not the same as the output
when opening the test projects with the TUI.
I'll hopefully figure it out tomorrow.</p>
<h3 id="day-14">day 14</h3>
<p>Spoiler: I didn't figure out yesterday's problem.
I had a few things going on today, and I didn't do as much work on
↹lature as I wanted to.
But, in the spirit of December Adventure, I at least took a look.
I've got a little TUI tool called <a href="https://git.sr.ht/~jaxter184/twv"><code>twv</code></a> that I use to inspect audio
files, and it seems that there's either a bug in <code>twv</code> or something
weird with the output, because I can't see it.
It's likely that the signal is entirely outside the -1.0 to 1.0 range,
but since I'm writing it to a WAV file as integers, it should clip?
I think?
I'll figure it out tomorrow.</p>
<h3 id="day-15">day 15</h3>
<p>We're already halfway through December Adventure!
The days have been whizzing by, and I haven't completed a single
plugin.
But that's OK, because I've been exploring tons!</p>
<p>I've refactored the <code>twv</code> code a bit, and added character markers for
zero, NaN, and end of file.</p>
<p>After fixing this and using it on the output, I found out that:</p>
<ul>
<li>Using <kbd>Ctrl</kbd>+<kbd>C</kbd> to close my recording program
results in a malformed WAV file that thinks it has a length of 0
despite being 188kB</li>
<li>When I gracefully shutdown the recording program, it seems that the
expected sine wave is mostly intact, except for a few discontinuities,
which I assume occur at buffer boundaries.</li>
</ul>
<p>To show this off, I want to make a little asciinema recording where I
create a WAV recording from ↹lature, and open it in <code>twv</code>.
To do so, I need to add a feature to my CLI recording utility to let
it connect to ports in the middle of its execution, and not just at
startup.
While working on that, I ran into a bug.
Did you know that a backgrounded program can't read a line from <code>stdio</code>
while starting an async JACK client?
Neither did I!
(Or maybe you did, in which case, why didn't you tell me‽)</p>
<p>After several hours, I think I've fixed everything I needed to make the
recording that demonstrates the problem I had yesterday.
While I could probably make the recording at this point, it's getting
late, so I'll leave learning <a href="https://github.com/k9withabone/autocast"><code>autocast</code></a> and making the recording for
tomorrow.</p>
<h3 id="day-16">day 16</h3>
<p>There were a couple extra things I had to do to get my utilities to
work with <code>autocast</code>.
The main incompatibility was that the point in the recording when
↹lature starts is not fixed. Sometimes it takes longer to start up,
so I couldn't repeatably zoom in on the exact point of the
discontinuity.
To fix this, I added the <kbd>[</kbd> keybind to <code>twv</code>, which pans the
waveform such that the first nonzero sample is at the very left of the
screen.
From the first nonzero point, the buffer discontinuity is always exactly
the buffer length.</p>
<p>But after sorting through all that, I present the first December
Adventure demo!
(Though it seems there's an <a href="https://github.com/asciinema/asciinema-player/issues/34">issue</a> with how box-drawing characters
are drawn in the asciinema player, so you may need to download the
asciicast and use the <a href="https://github.com/asciinema/asciinema"><code>asciinema</code> CLI</a> to play it back)</p>
<script async id="asciicast-627311" src="https://asciinema.org/a/627311.js"></script>
<p>(<a href="https://asciinema.org/a/627311">link to demo</a>)</p>
<p>Commands used:</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">$</span></span><span class="z-meta z-function-call z-arguments z-shell"> cat tlature-engine/projects/load/main.tlgr.kdl</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">$</span></span><span class="z-meta z-function-call z-arguments z-shell"> jaxrec<span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>p</span> 1 tlature:output_1<span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>e</span> <span class="z-keyword z-operator z-assignment z-redirection z-shell">&></span> jaxrec.log</span> <span class="z-keyword z-operator z-logical z-job z-shell">&</span> <span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">tlature</span></span><span class="z-meta z-function-call z-arguments z-shell"> tlature-engine/projects/load</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">$</span></span><span class="z-meta z-function-call z-arguments z-shell"> du out.wav</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">$</span></span><span class="z-meta z-function-call z-arguments z-shell"> twv out.wav</span>
</span></code></pre>
<p>To break down the chunky command in the middle:</p>
<ul>
<li><code>jaxrec</code>: my recording utility.
The recording ends automatically when all the input ports have been
disconnected.</li>
<li><code>-p 1</code>: open one port in the jack client, and write a single-channel
(mono) WAV file.</li>
<li><code>tlature:output_1</code>: the positional arguments to <code>jaxrec</code> match output
ports to each input using a regex, or simply the first port(s) that
start with this string.</li>
<li><code>-e</code>: ignores the stdin.
Usually, <code>jaxrec</code> ends when enter is pressed, but as described
yesterday, this prevents the JACK client from starting, so we use this
flag to disable that feature.</li>
<li><code>&> jaxrec.log</code>: pipe all stdout and stderr to the file <code>jaxrec.log</code>.</li>
<li><code>&</code>: run the previous command (<code>jaxrec</code>) in the background, and start
the next command immediately.
Contrast with <code class="z-keyword">;</code>, which waits until the previous command finishes
before running the next command.</li>
<li><code>tlature tlature-engine/projects/load</code>: open the project file in
↹lature.</li>
</ul>
<p>While I was getting this working, I realized why the output was not
consistent between the TUI and the tests: the tests have extremely
large buffers, such that the entire test runs in one buffer, so there
are no buffer edges for the test tone to be discontinuous across.
After fixing that, the tests seem to be pretty much in line with the
behavior of the TUI, so I think I can move forward with debugging why
the events aren't getting read in the tests, after which I can move on
to fixing the Discord bot.</p>
<h3 id="day-17">day 17</h3>
<p>Started a bit of work on a <a href="https://kdl.dev">KDL</a> highlighter for <a href="https://kakoune.org">kakoune</a>.
It's just a basic implementation that colors numeric values and quoted
strings, and sets the indentation on each newline.
Not directly relevant to ↹lature, but it'll at least make the files a
bit easier to read and write.</p>
<p>TODO:
Features that I plan to add are properly highlighting the types, node
name, and comments.</p>
<h3 id="day-18">day 18</h3>
<p>Did a little bit more diagnosis on why my tests are behaving strangely.
I made two findings:</p>
<ul>
<li>The OSC messages are not stored in chronological order.
My assumption was that <code>clack</code> sorts the order when it creates the
event buffer, but that could very well not be the case.
In fact, it may be better for performance for that to be done on
↹lature's side rather than on <code>clack</code>'s.</li>
<li>The <code>test-tone</code> plugin sees exactly one parameter change that sets
the frequency to 440 Hz.
It is likely that this is from the first event.
Potentially related to the above bullet point.</li>
<li>When adding an abridged version of the <code>ave-maria</code> example project as
a test, only the arpeggio voice is output in the test, and the reverb
plugin (michaelwillis' <a href="https://michaelwillis.github.io/dragonfly-reverb/">Dragonfly Reverb</a>) raises some sort of DPF
error.
<del>Possibly also due to out-of-order events.</del>
(Update: this was not due to the out-of-order events, and is still an
issue)</li>
</ul>
<p><del>TODO:</del>
Tomorrow I'll try actually putting the events in the right order before
they're added to <code>clack</code>'s event buffer.</p>
<h3 id="day-19">day 19</h3>
<p>Did a couple of minor fixes for event processing.
As alluded to yesterday, I fixed the event order, as well as an
additional bug where the root block sends a blank OSC message on note
off.</p>
<h3 id="day-20">day 20</h3>
<p>Missed one spot where transport events are sent after all of the other
messages instead of being interleaved with them.
The solution was calling <code>sort</code> on the event buffer (which I should
have been doing in the first place), so events being sent out of order
should no longer be an issue.</p>
<p><del>TODO:</del>
The plan tomorrow is to take a shorter day by organizing commits and
checking the behavior of all my tests and examples.</p>
<h3 id="day-21">day 21</h3>
<p>Organized the commits as planned, and fixed the Discord bot, so now
everything works as it did before I made all the ↹lature changes.</p>
<p>I updated to the latest version of <code>serenity</code> (a Rust crate interface
to the Discord API), but I think what I should've done is use <code>poise</code>,
which is a higher-level framework that makes things like context menu
bot commands easier.
I'm not gonna worry about that for now, and go back to ↹lature work
tomorrow.</p>
<p>This weekend is also the start to my time off work, so I'm hoping to
spend the time doing some particularly fun stuff with ↹lature, and
maybe get another demo out.
I don't want to put too much pressure on myself to get stuff done, but
it's the final stretch of December Adventure, and I want to continue
hiking along as I've been doing up until now.
I think the theme of "do a little bit every day, with an exploratory
mindset" has been very beneficial so far, not only for the project
itself, but also my attitude towards it.</p>
<h3 id="day-22">day 22</h3>
<p>Made some plans for how the UI layers are going to be implemented.
And that's not a euphemism for not having done anything, I did actually
write some code today!
Comments count as code, right?</p>
<p>Anyway, I'm chalking today off as a sort of wind-up to a future
follow-through.
I deserve it.
In fact, I always deserve to take things slow.</p>
<h3 id="day-23">day 23</h3>
<p>I added a bunch of stuff today, and pretty much all of it is stuff that
I discussed on <a href="https://wiki.jaxter184.net/log/dec-adv-2023/#day-09">day 09</a>:</p>
<ul>
<li>Refactored to a layer/mode hybrid UI system</li>
<li>Refactored the <code>CommandTemplate</code> struct to be (hopefully) a bit easier
to work with in the future when I add documentation for individual
arguments</li>
<li>Added a command popup interface, which is the main use case for the
string input dialog that I mentioned on day 09</li>
<li>Added commands for adding, inserting, and removing a processor</li>
</ul>
<p>But maybe it's easier to describe with a demo, showing the newly added
processor commands:</p>
<script async id="asciicast-628662" src="https://asciinema.org/a/628662.js"></script>
<p>(<a href="https://asciinema.org/a/628662">link to demo</a>)</p>
<p><del>TODO:</del>
I forgot to also demo the new command popup (which is probably the more
fun and interesting addition), but it's late, so I'll do it tomorrow.</p>
<h3 id="day-24">day 24</h3>
<ul>
<li>Added a structure to communicate information about the currently
selected item, so that you can use commands like <code>:insert processor delay</code> without specifying the block that the processor is going
to be added to (before, you had to use <code>:add processor --processor 0:this-block/3 delay</code>)</li>
<li>Improved the command popup.
Hints are now filtered based on whether they start with the current
typed value, and you can use <kbd>Tab</kbd> to navigate hints.</li>
<li><del>TODO:</del>
Ran into a bug where processors are added even if they don't exist,
which has proven to be a bit tough to fix because of the way that
↹lature is structured</li>
<li>TODO:
There seems to be a hitch in the audio thread, so I'll probably do some
profiling after December Adventure ends, and maybe look more into the
real-time safety</li>
</ul>
<p>I'll be honest, these log entries are much more for myself than they
are for other people.
I think something that would help with that is drawing a clearer
narrative.
For example, starting with how I want the user flow to work, some bad
approaches to accomplishing that, and then my final approach.</p>
<p>But until then, another demo! (The one I said I'd make yesterday):</p>
<script async id="asciicast-628751" src="https://asciinema.org/a/628751.js"></script>
<p>(unfortunately, the box drawing characters are still a bit broken)</p>
<h3 id="day-25">day 25</h3>
<ul>
<li>Fixed the bug where adding a plugin that doesn't exist crashes
the program</li>
</ul>
<p>I started some work on refactoring the structure I made yesterday.
As it is now, it just passes in arguments (like <code>--block</code>)
automatically, but really, I want the argument parsers to understand
the value, so I can use relative paths.</p>
<h3 id="day-26">day 26</h3>
<p>Finished refactoring the environment information passing.
I'm really happy with how usable commands like <code>rm processor</code> are now.
You can now use a relative path to add and remove processors and
blocks, which also lets me use <code>.</code> as a default value for the <code>--block</code>
and <code>--processor</code> variables, which gets interpreted as the currently
selected item.</p>
<h3 id="day-27">day 27</h3>
<p>Started some work on refactoring the OSC channels to more general CLAP
event channels.
Right now, I'm creating OSC events by leaking memory which is
definitely not sustainable, and I'm approaching the limit to the hacks
I can get away with by just leaking, so my plan for tomorrow is to
get a better idea of what CLAP needs from a lifetime perspective, and
adjust my implementation to better align with that.</p>
<h3 id="day-28">day 28</h3>
<p>Finished changing OSC channels to CLAP event channels.
Did a lot of strange stuff that is definitely not real-time safe (I
will leave that for future me to figure out), but it passes the events
as needed, and didn't really require too much thinking.
I just changed one of the types from <code>Vec<(u64, OscMessage)></code> to
<code>clack::EventBuffer</code>, and followed the Rust compiler's instructions
from there.
The most involved part was writing a function to filter the events
in the buffer and produce a <code>Vec<(u64, OscMessage)></code> for the OSC
file writer.</p>
<p>Hooray for Rust and its ability to facilitate
fearless<sup>[citation needed]</sup> refactoring!</p>
<p><img src="/images/memes/no-fear-one-fear-realtime.svg" alt="A remix of
the 'no fear - one fear' meme by Branson Reese. First pane: Someone
standing in a T-pose with a shirt emblazoned with the words 'NO FEAR'.
A second person approaches from out of frame. Second panel: A close up
of the second person's shirt, which reads 'what about real-time safety'.
Third pane: The first person, still in T-pose, but their shirt now says
'ONE FEAR'." width="100%"></p>
<p>Just yesterday, ↹lature received <a href="https://todo.sr.ht/~jaxter184/tlature/54">the first ticket submitted by
someone other than me</a>!
There are a few requests that folks have pointed out to me over private
channels because I asked them, but this is the first one from someone
I've had no prior contact with, and I didn't have to solicit them for
feedback.
It might seem pretty insignificant from an outside perspective, but
personally, it's really cool to see that someone cares enough about
this project to let me know that something is broken, even if it's just
because they want to check it out as research for their own project.
In fact, I'm pretty flattered that they looked at it and thought it
would be a good reference in the first place.</p>
<h3 id="day-29">day 29</h3>
<p>Fixing windows support, as requested.
I actually should have been working on it for a while because someone
else had also requested it (via private channels, and after I solicited
them to try it out), but the high of receiving a ticket really
motivated me to actually work on this.</p>
<p>There are three main things that are *nix-specific:</p>
<ul>
<li>The conversion OS paths to C strings in the resource directory
extension</li>
<li>The stdio redirect shenanigans to print plugin stdout to a file while
still printing the TUI to the terminal</li>
<li>JACK (technically cross-platform, but I dare you to find me a Windows
or MacOS user who regularly uses JACK)</li>
</ul>
<p>Today, I worked on the first two.
The resource directory extension was pretty easy, I just had to follow
the compiler's instructions.
With the stdio stuff, my solution was to ignore it, which will result
in issues when plugins print errors, but that also generally shouldn't
happen, and if it does, it means there's probably something in either
↹lature or the plugin that needs to be fixed.</p>
<p>There may be other things that I'm forgetting, but I suppose I will find
out when I actually try to use ↹lature on Windows.</p>
<h3 id="day-30">day 30</h3>
<p>I had forgotten how awful of an experience Windows is.
I had to install a bunch of stuff (Rust/cargo, git, ASIO4ALL, JACK),
and for the most part, it went fine, other than the part where I had
to agree to a license (that I didn't read) to use the MSVC compiler
(🤢).
It was a bit annoying to have to go to each website and download the
installer, but that's easy peasy compared to the weird display/USB
issues I was having the whole time.
For some reason, my USB hub kept disconnecting every couple seconds,
and Windows really didn't like my second monitor.
This last one is probably on me, but the peak disk speed was around
50MB/s, probably because I'm connecting my NVME SSD to my computer via
USB type C.</p>
<p>Anyway, after I got everything installed and got past the hiccups of
running JACK on a Windows machine, the TUI showed up and I was able to
navigate around the UI!
While I will almost certainly never use this, it's nice to see that it
at least works, and didn't take too much extra effort to adjust things
for Windows compatibility.</p>
<p>After checking that, I added some help windows to make it easier to
know what the available hotkeys are in any given mode.
I'd like to eventually do this in a more structured way such as
implementing default keybinds in a KDL file rather than manually
written out in Rust, but to implement it how I'm imagining would likely
require me to add a scripting language (and not roll it back this
time), which would also let me implement the keybinds as a file.</p>
<p>I think I'll explore <a href="https://www.lua.org/">Lua</a> support eventually (since <a href="https://rhai.rs/">rhai</a> was a
<a href="https://git.sr.ht/~jaxter184/tlature/log/rhai">bust</a>), but since tomorrow is the last day of December Adventure,
I think I'll try to top it off neatly by finishing the event offset
plugin, bringing this adventure full-circle to what I started the month
with.</p>
<h3 id="day-31">day 31</h3>
<p>It's the last day of December Adventure!
Today, I fixed event buffers so that CLAP events are properly passed
down.
I assume there are still a bunch of problems with it (especially with
regards to non-serial signal paths and nested groups), but for the most
part, it seems to work as expected.
Most importantly, the <code>event-offset</code> plugin works as intended!
If I put it between the <code>tracker</code> processor and the <code>physical/marimba</code>
plugin, it shifts the pitch up by the designated amount.
Right now, that amount is just a default value of 20 because parameters
are kind of a hassle that I don't want to deal with at the moment.
But it works!
The first task (at least, the first "actual" task) that I set out to
complete for December Adventure is now finished!</p>
<h3 id="retrospective">retrospective</h3>
<p>The main thing I was surprised about when it came to December Adventure
is how consistent I was.
There were a few days where I did less than 10 minutes of work, and one
day that I didn't do anything at all (though in my defence, I had a lot
going on that day, and knew a few days in advance that I wouldn't be
doing anything), but other than that, I made meaningful contributions
towards my project every day this month!
Looking back at my original plan, it's laughable that I ever thought I
would be capable of "a brand new plugin each day", or the "meaningful
feature every day" that I fell back on, but even with "just touch the
code at least once every day", I'm really happy with all the progress I
made on this project.</p>
<p>As for why I think I was able to do it, I think there are a few key
benefits to December Adventure:</p>
<ul>
<li>Choose your own adventure - Whatever you decide to work on is
something that you get to pick, so you're naturally more inclined to do
it compared to, for example, the coding interview-esque challenges of
Advent of Code.</li>
<li>Relaxed - There isn't any competitive pressure involved, and you're
just working on stuff because you want to.</li>
<li>Minimal - There's a sort of sentiment that I picked up on that any
amount of progress is valid progress.
For me, the first minute of working on something every day is the
hardest, usually because of decision paralysis, but by weighing
10 minutes of work against failing the task in the first week, or
breaking a 2 week streak, I found it much easier to just get started on
literally anything, which usually led to multiple hours spent happily
chugging away.</li>
</ul>
<p>There are a few things that I think could have been better:</p>
<ul>
<li>Community - While I did keep up with a few other peoples' December
Adventures, mostly through the #DecemberAdventure Fediverse hashtag,
there wasn't much of a dialogue.
I would read other people's logs, other people would read mine
(hopefully, though I don't blame them if they didn't; my log is over
6000 words at this point), but I didn't feel like I had anything
meaningful to add to their progress, and therefore didn't find myself
commenting on their progress.</li>
<li>Refactoring - I personally found myself doing a lot of refactoring
and bug fixing rather than exploring new things.
I think to some extent, this was inevitable, but it would have been
nice to somehow have been aware of this in advance so I could have more
realistic expectations about how I was going to spend the month.
Which isn't to say that I regret it!
I'm really happy with everything I did during December Adventure.</li>
</ul>
<p>Anyway, overall, it was a great month and I'm really glad I did it.
Thanks to <a href="https://eli.li/">eli</a> and all the other folks who did December Adventure!
Here's hoping 2024 is the year of the tracker on the Linux desktop!</p>
PapercraftThu, 30 Nov 2023 00:00:00 +0000Unknown
https://wiki.jaxter184.net/3d/papercraft/
https://wiki.jaxter184.net/3d/papercraft/<p>I bought a nice printer recently, and what better way to test it out than to make some papercrafts?
I loved papercraft as a kid, and the tools have come a long way since when I was last involved in the hobby.
Back in my day, all we had was the free demo of <a href="https://tamasoft.co.jp/pepakura_designer/">Pepakura</a>, or <a href="https://www.papertoys.com/">papertoys.com</a> (Sydney
Opera House was my favorite, and <a href="https://www.papertoys.com/statue.htm">Statue of Liberty</a> was my least favorite).
But now, there's a <a href="https://docs.blender.org/manual/en/latest/addons/import_export/paper_model.html">Blender add-on</a>!
And <a href="https://folduptoys.com/">Fold Up Toys</a> (Which has a much better <a href="https://folduptoys.com/product/lady-liberty/">Statue of
Liberty</a>)!</p>
<h2 id="first-attempt">First attempt</h2>
<p>The model I chose was <a href="https://sketchfab.com/3d-models/pixel-house-fb19698f540649d58d840e7e3459c9a6">"Pixel House"</a>, by <a href="https://tacocrepe.carrd.co/">tacocrepe</a>,
licensed under CC-BY 4.0:</p>
<iframe title="Pixel House" frameborder="0" allowfullscreen
mozallowfullscreen="true" webkitallowfullscreen="true"
allow="autoplay; fullscreen; xr-spatial-tracking" xr-spatial-tracking
execution-while-out-of-viewport execution-while-not-rendered web-share
src="https://sketchfab.com/models/fb19698f540649d58d840e7e3459c9a6/embed?ui_theme=dark"
width="100%" height="540px">
</iframe>
<p>It's a really fun design! The stone foundation and white top walls
give off a Tudor Revival vibe, but the white windows look a bit like
paper, and combined with the wooden posts, I came away with this with
an impression of early Japanese architecture. And the kicker for me is
that I could probably get away with simplifying the shape to roughly a
cube with a triangular prism on top.</p>
<p>I tried to pick a model that would be quick and easy to make, but I ended up wanting to simplify the geometry even
more.
After learning the shortcut for adding an edge through a face across two of its vertices (<a href="https://blender.stackexchange.com/questions/238505/how-to-divide-a-face-in-two">J</a>), I was able to make
the changes that I wanted to.</p>
<p><figure class="half-width">
<img
class="fig"
src="/images/papercraft-house-side-edge.jpg"
alt="photo of a little papercraft house in a sort of Tudor Revival style, with a stone first floor, wooden door, and
stucco second floor with wooden beams.
very simple shapes, basically a cube with a triangular prism roof.
first floor is very slightly inset from the rest of the house.
It is textured in a pixel-art style"
>
</figure>
<figure class="half-width">
<img
class="fig"
src="/images/papercraft-house-top-corner.jpg"
alt="photo of the same house as previous, but from a different angle"
>
</figure>
</p>
<p>First things first, the simplifications were absolutely not a good
tradeoff.
It didn't make assembly much easier, and in my opinion, it lost a lot
of the magic that made the model work in the first place. The colors
turned out nice though!
The toner rubbed off a bit in some places, but I believe that has to do
with the printer settings, and not the actual quality of the printer or
toner or paper, so it's probably an easy fix.</p>
<p>Being as creatively insatiable as I am, I naturally started on a second
attempt pretty much as soon as I finished it.</p>
<h2 id="second-attempt">Second attempt</h2>
<p>The main changes I made were that I omitted the simplifications (the
actual shape is practically identical to the original model, just
meshed slightly differently for better unfolding), and replacing the
windows with sanded clear plastic so that I could put a little light
inside and illuminate it.
About half of the time and effort spent cutting was probably spent on
the windows.</p>
<p>I also totally forgot to take pictures of the process, but it's more
or less what you would expect: I cut out all the pieces and glued
the tabs in. <a href="/files/pixel-house-2.pdf">But I do have the printout if you want to try it out
yourself</a> (yay for Creative Commons)!</p>
<iframe src="/files/pixel-house-2.pdf" width="100%" height="480"></iframe>
<h3 id="things-i-learned">Things I learned</h3>
<p>The measurement marks on the rotary cutter that I have are nice for
when you know the measurements for what you are cutting, but I found
that they're too difficult to use accurately when cutting along a
printed line.</p>
<p>Scoring a fold with the blunt side of a craft knife blade makes for an
easier, crisper fold.
I had been doing a similar thing with a "bend" wheel for my rotary
paper cutter, but not only is using a craft knife more accurate, but it
is also more effective in making a sharp bend.</p>
<p>Omitting small tabs can make assembly much easier, without sacrificing
too much stability.</p>
<p>Glue sticks are very annoying to use. Use liquid glue instead. I use
PVA glue, mostly out of convenience; I had a very old bottle of glue
laying around from when I was in like kindergarten.</p>
<figure >
<img
class="fig"
src="/images/papercraft-house-compare.jpg"
alt="photo of the same
papercraft house as above, with another house beside it with the same
general design as the one above, but with more complex geometry, like
additional rims and columns. the old house (from above) is to the right
with the new one to the left"
>
<figcaption>Comparison shot. New house is on the left.</figcaption>
</figure>
<br>
<figure style="width:60%">
<img
class="fig"
src="/images/papercraft-house-new-illuminated.jpg"
alt="photo of the new house with a light inside it, which makes the windows kinda glow"
>
<figcaption>Lights!</figcaption>
</figure>
<p>The colors came out a bit less vibrant, and I'm not sure exactly why,
but I would venture a guess that it has to do with the fact that I
changed the print preset to one for heavier paper.
But the craft itself turned out great, and it was a lot of fun!
I'm glad I tried it again with a more faithful geometry.</p>
<h2 id="links">Links</h2>
<ul>
<li><a href="https://mypapercraft.net/what-kind-of-paper-to-use-for-papercraft/">https://mypapercraft.net/what-kind-of-paper-to-use-for-papercraft/</a></li>
<li><a href="https://mypapercraft.net/best-transparent-glue-for-paper-crafts/">https://mypapercraft.net/best-transparent-glue-for-paper-crafts/</a></li>
<li><a href="https://mypapercraft.net/how-to-make-papercraft-hard-like-plastic/">https://mypapercraft.net/how-to-make-papercraft-hard-like-plastic/</a></li>
</ul>
Rust Audio Chat ArchiveTue, 07 Nov 2023 00:00:00 +0000Unknown
https://wiki.jaxter184.net/rust-audio/
https://wiki.jaxter184.net/rust-audio/<p>A curated chat archive of the various Rust Audio chat rooms</p>
<ul>
<li><a href="https://discord.com/invite/8qW6q2k">Discord</a></li>
<li><a href="https://matrix.to/#/#rust-audio:matrix.org">Matrix</a></li>
<li><a href="https://t.me/joinchat/BfEhnw0l4386Uzi5elmGrQ">Telegram</a></li>
<li>https://rust.audio/ (Discourse is discontinued)</li>
</ul>
<p>Rules:</p>
<ul>
<li>Follow the rules of the source chat room(s)</li>
<li>Only document publicly available information</li>
<li>Do not explicitly quote anyone without their permission</li>
</ul>
<p>Guidelines:</p>
<ul>
<li>Only post Q&A and technical discussions (if someone answers their own question, that's fine too): Things like project updates, organizational drama, and other news don't really make sense to document on this wiki.</li>
<li>Avoid opinions: "I think JACK is all you need to support" should instead be "JACK is cross-platform, so an application that uses it as a backend could hypothetically run on most modern computers"</li>
<li>Change/add a timestamp if you edit a section: it's nice to know how out-of-date a piece of information could be. Use <code><nowiki>~~~~~</nowiki></code>, which will turn into a datestamp when the edit is published.</li>
<li>If someone mentions details regarding their project, try to omit them unless the information is important, '''and''' they give permission</li>
</ul>
<h2 id="faq">FAQ</h2>
<h3 id="what-are-some-resources-for-learning-dsp">What are some resources for learning DSP?</h3>
<p><a href="https://github.com/BillyDM/Awesome-Audio-DSP">https://github.com/BillyDM/Awesome-Audio-DSP</a></p>
<h3 id="why-should-i-use-rust-over-c">Why should I use Rust over C++?</h3>
<p>(<a href="https://discord.com/channels/590254806208217089/590254806208217097/1183882123162169424">paraphrased from this
comment</a>)</p>
<ul>
<li>DSP: About the same, since it's just performing calculations, the
performance of which is roughly the same across almost all compiled
languages.</li>
<li>Metaprogramming: C++ templates are a little more flexible than Rust
macros, and Rust's generics require a lot of explicitness since
they're more closely tied to the type system.</li>
<li>Plumbing: Thread and type safety are really nice to have, and
eliminate whole classes of bugs. Something something "fearless
concurrency".</li>
<li>Abstraction level: Rust's memory model is simpler, and doesn't really
give up much in terms of higher-level abstractions. You can easily and
ergonomically use Rust for
anything from a bare-metal embedded program (as long as you're on ARM
or RISC-V) to a browser application (via WASM) to a web server, and
anything in between.</li>
</ul>
<p>(a couple additions that were not in the above comment)</p>
<ul>
<li>Cargo is lovely</li>
<li>Refactoring is really nice</li>
<li>Async is fun, though not particularly useful for audio applications
yet</li>
<li>A lot of nice development tooling:
<ul>
<li>integrated unit tests,</li>
<li>benchmarking</li>
<li>docstrings</li>
<li>helpful error messages</li>
<li>the <code>log</code> crate</li>
<li>panic traces</li>
<li>error handling in general</li>
</ul>
</li>
<li>nih-plug is really nice</li>
</ul>
<h3 id="how-do-i-create-virtual-audio-devices-that-i-can-route-signals-to-from">How do I create virtual audio devices that I can route signals to/from?</h3>
<p>At time of writing (2023-06-15) there aren't any robust cross-platform
methods for this, but on Linux, using Pipewire or JACK as your audio
server will let you route audio freely. Pipewire works with both
PulseAudio and JACK clients, while a JACK server will only be able to
communicate with JACK clients.</p>
<h2 id="linkdump">linkdump</h2>
<ul>
<li><a href="https://github.com/RustAudio/dasp">https://github.com/RustAudio/dasp</a> for some DSP building blocks</li>
<li><a href="https://github.com/MeadowlarkDAW/creek">https://github.com/MeadowlarkDAW/creek</a> for audio streaming from disk</li>
<li><a href="https://github.com/wrl/baseplug/blob/trunk/examples/svf/svf_simper.rs">https://github.com/wrl/baseplug/blob/trunk/examples/svf/svf_simper.rs</a> svf code example</li>
<li><a href="https://github.com/WeirdConstructor/HexoDSP">https://github.com/WeirdConstructor/HexoDSP</a></li>
<li><a href="https://github.com/WeirdConstructor/synfx-dsp">https://github.com/WeirdConstructor/synfx-dsp</a></li>
<li><a href="https://github.com/tesselode/kira">https://github.com/tesselode/kira</a> Library for expressive game audio</li>
</ul>
<p>Realtime programming:</p>
<ul>
<li><a href="http://www.rossbencina.com/code/real-time-audio-programming-101-time-waits-for-nothing">http://www.rossbencina.com/code/real-time-audio-programming-101-time-waits-for-nothing</a></li>
</ul>
<p>Graph processing:</p>
<ul>
<li><a href="https://fgiesen.wordpress.com/2018/03/05/a-whirlwind-introduction-to-dataflow-graphs/">https://fgiesen.wordpress.com/2018/03/05/a-whirlwind-introduction-to-dataflow-graphs/</a></li>
<li><a href="https://github.com/m-hilgendorf/audio-graph">https://github.com/m-hilgendorf/audio-graph</a></li>
</ul>
<p>Crates for handling soundfonts:</p>
<ul>
<li><a href="https://docs.rs/soundfont/latest/soundfont/">https://docs.rs/soundfont/latest/soundfont/</a> only parses <code>.sf*</code>
files, and doesn't actually do any audio decoding</li>
<li><a href="https://docs.rs/fluidlite/latest/fluidlite/">https://docs.rs/fluidlite/latest/fluidlite/</a></li>
</ul>
<h2 id="discussion">Discussion</h2>
<h3 id="issues-with-wav-file-decoding">Issues with WAV file decoding</h3>
<h4 id="pitch-frequency-of-the-resulting-output-is-too-high">Pitch/frequency of the resulting output is too high</h4>
<ul>
<li>Check to verify that the sample rate of your file matches the sample
rate of the output</li>
<li>Make sure that the channel count is being taken into
consideration. If you are reading a mono WAV file as if it were stereo,
the resulting output will be an octave higher (double the frequency)
and twice as fast (half as long)</li>
</ul>
<h4 id="output-amplitude-is-too-small-large">Output amplitude is too small/large</h4>
<ul>
<li>Make sure the numeric type is correct (i.e. not an f32 when you
should be using an i16)</li>
</ul>
<h3 id="trig-function-ranges">Trig function ranges</h3>
<p>Traditionally, sine functions will have a period of 2π, but
(allegedly) in many implementations of sine, the input is scaled to
make the range from 0.0 to 1.0. If possible, it would make more sense
to just make your initial phase range from 0.0 to 1.0 and use a sine
function that doesn't do the rescaling to save two multiplications. The
performance benefit of this is pretty minimal.</p>
PoemsMon, 06 Nov 2023 00:00:00 +0000Unknown
https://wiki.jaxter184.net/poems/
https://wiki.jaxter184.net/poems/<h2 id="zero">zero</h2>
<pre class="z-code"><code><span class="z-text z-plain">gotta start somewhere
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">this is hard
</span></code></pre>
<!--
Started as a 30 line poem about how writing poetry is difficult, but my
favorite thing about poetry is in how much it can communicate in such a
small, efficient set of words.
Not only that, but what it communicates is so much more than just
meaning: it's a mood or a vibe, like the discontent and frustration
that I (try) to express here.
If I wanted to talk about how writing poetry is difficult, I would just
write prose, like I'm doing now.
But with this initial poem, my goal is twofold: say more with less, and
bypass any quality filter that I have that prevents me from expressing
my poems to other people.
Perhaps due to my utilitarian nature, it seemed like the best way to do
it was to take the fewest thoughts that I could to express those goals
in a way that would accomplish them, and refine them to the best of my
ability, and just be done with it when I gave up.
And, again, due to my nature, any perceived shortcomings of the poem
itself (that is, any way that it could fail at these stated goals)
could simply be patched up by writing some prose about the poem itself,
as I have done here.
-->
<h2 id="oat-of-sight-oat-of-mind">oat of sight, oat of mind</h2>
<pre class="z-code"><code><span class="z-text z-plain">What could the humble oat teach us if we were not alienated from them?
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">Would they suggest growing and thriving to secure my future, as their ancestors did?
</span><span class="z-text z-plain">Would they take root without hesitation, as if it was the only option?
</span><span class="z-text z-plain">Would they bend with the wind, as the combine harvester reaps with unstoppable efficiency?
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">Tragically, I can't find the answers in my oat milk.
</span></code></pre>
<!--
Seems a bit of a shame that all the wholesome stuff that happens to the
things I eat (growing up surrounded by community, feeling the breeze
and the sun and the dirt), all happens outside my perception, and only
the un-wholesome stuff (using an unnaturally high-speed metal blade to
macerate ice with an oat slurry and matcha powder and sugar to make my
favorite matcha beverage) is all that's left for me.
Is this on purpose?
Why?
How would I find out if it was?
How would I fix it if it was?
Could I be a more complete person if things were different?
Thinking about the plants that I can't see leaves me with more
questions than I know what to do with, and while answers are overrated,
having more questions doesn't exactly feel great.
But that also brings along the more hopeful question that opens the
poem: what could I learn if this were not the case?
I feel like I have so many problems in my life that I'm powerless to
solve.
If they could think and feel as I do, would the oats that compose my
oat milk feel the same way?
Or would they be harvested and make their way into the carton anyway?
-->
<h2 id=""></h2>
<pre class="z-code"><code><span class="z-text z-plain">
</span></code></pre>
Tone TramMon, 02 Oct 2023 00:00:00 +0000Unknown
https://wiki.jaxter184.net/mus-tec/tone-tram/
https://wiki.jaxter184.net/mus-tec/tone-tram/<blockquote>
<p>Tangentially related to <a href="/mus-tec/aliqot">aliqot</a>, but not necessarily a sequel or anything.</p>
</blockquote>
<div style="height: 240px; overflow: hidden">
<img style="margin: -120px 0 0 0"
src="https://upload.wikimedia.org/wikipedia/commons/9/9f/Ondes-ruban.jpg"
alt="Image of an ondes Martenot, a keyboard with a wire over the keybed, and a small"
>
</div>
<p>The <a href="https://en.wikipedia.org/wiki/Ondes_Martenot">ondes Martenot</a> is an instrument made by some French guy.
I like it because it is essentially a more tactile version of the theremin, where you can still make large sweeping
slides in pitch and volume, but you get to feel how you're doing it.
Some versions of the instrument even come with registrations bumps that let you feel where the pitches are.</p>
<p>I want to make a digital one.</p>
<h2 id="the-pitch-wire">The pitch wire</h2>
<p>The main feature of the ondes Martenot is that the pitch is controlled by pulling a wire back and forth using a ring.
I am far from the first to make a digital version, and as far as I can tell, the go-to approach is to use the wire to spin a
wheel attached to an encoder or potentiometer:</p>
<ul>
<li><a href="https://youtu.be/fRdTDTK3QTQ?t=112">https://youtu.be/fRdTDTK3QTQ?t=112</a></li>
<li><a href="https://youtu.be/8hE9SBlTvto">https://youtu.be/8hE9SBlTvto</a></li>
<li><a href="https://youtu.be/BzYTdDpLfAU?t=15">https://youtu.be/BzYTdDpLfAU?t=15</a></li>
</ul>
<p>I don't like this approach because it requires either a multi-turn potentiometer or a calibration sequence whenever the
instrument is powered on to know exactly where the ring is.</p>
<p>My solution is to construct an absolute linear encoder using magnets and hall effect sensors.</p>
<h3 id="de-bruijn-sequence">de Bruijn sequence</h3>
<p>The original idea for this project actually came from me, on a complete whim, wanting to know whether there was a shift
equivalent to the <a href="https://en.wikipedia.org/wiki/Gray_code">Gray code</a>.
That is, in the same way that the Gray code is a series of unique values in a sequence that differ from their neighbors
by one bit, was there an equivalent code where the values were shifted versions of their neighbors?
The answer, of course, is yes, and it's called <a href="https://en.wikipedia.org/wiki/De_Bruijn_sequence">the de Bruijn sequence</a>.
The main benefit of such an encoding is that where I need N*M magnets in Gray Code, where N is the number of bits and M
is the number of positions, I could just use one channel of M magnets.
This saves a lot of money in magnets and a lot of labor in putting things together, plus its much easier to design and
produce around one row than N rows.</p>
<p>It turns out that the 2 bit case is the same for both Gray code and de Bruijn sequence, so as an example, lets consider
the three bit case:</p>
<table>
<colgroup>
<col span="1" style="width: 50%;">
<col span="1" style="width: 50%;">
</colgroup>
<tr>
<td>
Gray code:
<pre class="z-code"><code><span class="z-text z-plain" aria-label="a 0 0 0">a ⬜ ⬜ ⬜</span>
<span class="z-text z-plain" aria-label="b 0 0 1">b ⬜ ⬜ 🟫</span>
<span class="z-text z-plain" aria-label="c 0 1 1">c ⬜ 🟫 🟫</span>
<span class="z-text z-plain" aria-label="d 0 1 0">d ⬜ 🟫 ⬜</span>
<span class="z-text z-plain" aria-label="e 1 1 0">e 🟫 🟫 ⬜</span>
<span class="z-text z-plain" aria-label="f 1 1 1">f 🟫 🟫 🟫</span>
<span class="z-text z-plain" aria-label="g 1 0 1">g 🟫 ⬜ 🟫</span>
<span class="z-text z-plain" aria-label="h 1 0 0">h 🟫 ⬜ ⬜</span>
<span class="z-text z-plain" aria-label="a 0 0 0">a ⬜ ⬜ ⬜</span>
</code></pre>
Each row differs by the next row by one bit
</td>
<td>
de Bruijn sequence:
<pre class="z-code"><code><span class="z-text z-plain" aria-label="a 0 0 0">a ⬜ ⬜ ⬜</span>
<span class="z-text z-plain" aria-label="b 0 0 1">b ⬜ ⬜ 🟫</span>
<span class="z-text z-plain" aria-label="c 0 1 1">c ⬜ 🟫 🟫</span>
<span class="z-text z-plain" aria-label="d 1 1 1">d 🟫 🟫 🟫</span>
<span class="z-text z-plain" aria-label="e 1 1 0">e 🟫 🟫 ⬜</span>
<span class="z-text z-plain" aria-label="f 1 0 1">f 🟫 ⬜ 🟫</span>
<span class="z-text z-plain" aria-label="g 0 1 0">g ⬜ 🟫 ⬜</span>
<span class="z-text z-plain" aria-label="h 1 0 0">h 🟫 ⬜ ⬜</span>
<span class="z-text z-plain" aria-label="a 0 0 0">a ⬜ ⬜ ⬜</span>
</code></pre>
The last two bits of each row is the same as the first two bits of the next
</td>
</tr>
</table>
<p>One way to visualize the de Bruijn sequence to make the shifting relationship clearer is to visualize the value in the
context of the rest of the sequence (similar to what is done on the Wikipedia page):</p>
<pre class="z-code"><code><span class="z-source" aria-label="a [0 0 0]1 1 1 0 1">a [⬜ ⬜ ⬜]🟫 🟫 🟫 ⬜ 🟫</span>
<span class="z-source" aria-label="b [0 0 1]1 1 0 1 0">b [⬜ ⬜ 🟫]🟫 🟫 ⬜ 🟫 ⬜</span>
<span class="z-source" aria-label="c [0 1 1]1 0 1 0 0">c [⬜ 🟫 🟫]🟫 ⬜ 🟫 ⬜ ⬜</span>
<span class="z-source" aria-label="d [1 1 1]0 1 0 0 0">d [🟫 🟫 🟫]⬜ 🟫 ⬜ ⬜ ⬜</span>
<span class="z-source" aria-label="e [1 1 0]1 0 0 0 1">e [🟫 🟫 ⬜]🟫 ⬜ ⬜ ⬜ 🟫</span>
<span class="z-source" aria-label="f [1 0 1]0 0 0 1 1">f [🟫 ⬜ 🟫]⬜ ⬜ ⬜ 🟫 🟫</span>
<span class="z-source" aria-label="g [0 1 0]0 0 1 1 1">g [⬜ 🟫 ⬜]⬜ ⬜ 🟫 🟫 🟫</span>
<span class="z-source" aria-label="h [1 0 0]0 1 1 1 0">h [🟫 ⬜ ⬜]⬜ 🟫 🟫 🟫 ⬜</span>
<span class="z-source" aria-label="a [0 0 0]1 1 1 0 1 (repeats)">a [⬜ ⬜ ⬜]🟫 🟫 🟫 ⬜ 🟫 (repeats)</span>
</code></pre>
<p>Hopefully this makes it clearer that each row is a (cyclically) left-shifted version of the previous row.
Given the sequence <code aria-label="0 0 0 1 1 1 0 1">⬜ ⬜ ⬜ 🟫 🟫 🟫 ⬜ 🟫</code>
, we could use a row of magnets arranged with one pole representing zero and the other
representing one, and read any three adjacent magnets to know exactly where along the row we are, and which three
magnets we are reading.</p>
<p>If the hall effect sensors are analog, then we could <em>potentially</em> get an additional 12 bits of precision between
magnets (depending on the ADC, minus noise, and assuming magnets have exactly equal magnetic flux).</p>
<details class="blurb">
<summary>
Optional technical caveat:
</summary>
I say "potentially" because I am assuming that the in-between values are unique.
For example, an analog read halfway between the first two values <code aria-label="0 0 0">⬜ ⬜ ⬜</code>
and <code aria-label="0 0 1">⬜ ⬜ 🟫</code>
would read
<code aria-label="0 0 X">⬜ ⬜ 🔴</code>
, where <code aria-label="X">🔴</code>
is the analog value halfway between <code aria-label="0">⬜</code>
and
<code aria-label="1">🟫</code>
.
If we turn on the instrument in that position and it knows nothing about the previous context, we can only know exactly
where we are if <code aria-label="0 0 X">⬜ ⬜ 🔴</code>
is a unique value.
One way this could not be true is for the value <code aria-label="0 X X">⬜ 🔴 🔴</code>
,
if there existed adjacent values <code aria-label="0 0 1">⬜ ⬜ 🟫</code>
and
<code aria-label="0 1 0">⬜ 🟫 ⬜</code>
as well as <code aria-label="0 1 1">⬜ 🟫 🟫</code>
and <code aria-label="0 0 0">⬜ ⬜ ⬜</code>
.
In this case, we know this isn't possible since the shifting property does not allow
<code aria-label="0 1 1">⬜ 🟫 🟫</code>
to be next to <code aria-label="0 0 0">⬜ ⬜ ⬜</code>
.
I believe it is the case for every situation like this that there is only one valid pair of adjacent values, but I'm no
mathematician.
</details>
<p>Even with all the inaccuracies, I'm not aware of any linear position sensing mechanisms with that level of precision.
In addition, because none of the sensing components contact each other, the wear over time is greatly lower than, for
example, a linear potentiometer, where a wiper has to slide back and forth across a thin carbon strip.</p>
<p>According to my limited research, applying this concept to sensing is not an uncommon practice, but much of the
literature discussing it is paywalled.</p>
<h2 id="construction">construction</h2>
<p>TODO</p>
<ul>
<li>hall effect sensors</li>
<li>gluing magnets is a pain</li>
<li>modular connector that works with my cowbell pcb</li>
<li>de bruijn sequences
<ul>
<li>aren't unique</li>
<li>how to decode position?</li>
<li>if there's one thing I've learned in the last thousand revisions of this section, it's that these pattern
explanations are in dire need of interactive visualization</li>
<li>implement footnotes so I can mention that you can extend the sequence to: <code aria-label="0 0 0 1 1 1 0 1 0 0">⬜ ⬜ ⬜ 🟫 🟫 🟫 ⬜ 🟫 ⬜ ⬜</code>
</li>
</ul>
</li>
</ul>
<h2 id="further-reading">Further reading</h2>
<ul>
<li><a href="https://therevox.com/">https://therevox.com/</a></li>
</ul>
Monthly Logs 2023Sat, 30 Sep 2023 00:00:00 +0000Unknown
https://wiki.jaxter184.net/log/2023/
https://wiki.jaxter184.net/log/2023/<h2 id="09-september">09 - September</h2>
<h3 id="updated-contact-page">Updated <a href="https://contact.jaxter184.net">contact page</a></h3>
<p>It's been almost exactly one year since I made a contact page for
collecting all the different public online spaces where I exist.
I had a weird experience the other day where I joined a Discord
channel, and someone there used <code>contact.jaxter184.net</code> to find my
SoundCloud account, and then posted my music in the Discord channel.
At least, I assume they did, because I have it linked in my Discord
bio, though it's possible they just searched "jaxter184".
But even if they didn't use my contact page in this particular case,
I like the idea of making it easier for these disparate parts of my
online life to be more connected, especially in this age where social
media platforms seem to be fragile and temporary.</p>
<p>As part of this refresh, I decided to change my profile picture.
I've been using my old one for several years, but I've recently changed
the way I'm doing my hair (and it will probably change again in the
near future), so the old one felt outdated.
Previously, I had used Blender to render all the fancy shapes hanging
around my head, but I wanted to be a little more reserved in the new
one, so I shrunk the hexagon and used Inkscape.
This has the side-benefit of being more web-friendly in the few places
that I can use an SVG file.
I also had an SVG version of the old profile picture that I made for a
much older website, but I seem to have lost it.</p>
<table>
<tr> <th> Old </th> <th> New </th> </tr>
<tr>
<th>
<img src="/images/pfp-render.jpg" alt="old profile picture, titled
*Render*. A digitally painted, traced image of jaxter184 with short
hair. A wide bright green (#20ff80) curved irregular, but symmetrical
hexagon covers the contour of his face. A glossy cyan tube loops twice
around his head, and a collection of small matte orange icosahedrons
float." width="360">
</th>
<th>
<img src="/images/pfp-trunc.svg" alt="
new profile picture, titled *Gradient*. A vector depiction
of jaxter184 at a roughly 3/4 angle. a beige-skinned figure,
head tilted to the side, with hair tied up in a messy bun. A
bright green (#20ff80) hexagon barely covers his facial
features." width="360">
</th>
</tr>
</table>
<p>Frankly, I think I prefer the old one.
The sheen on the face covering is neat, and there's a bit more whimsy
in the loops around my head and the little orange icosahedrons.
That being said, I'm pretty happy with how the gradients look in the
new one, and it's certainly a more accurate depiction of what I look
like now.
Hopefully I'm a bit quicker to replace this new one than I was to
replace the old one.</p>
<h3 id="attempted-to-debug-probe-rs">Attempted to debug probe.rs</h3>
<p>A friend reached out to me to try and do some Matter IoT stuff
recently, and this is the chip he wanted to use.
So far, I've generated a peripheral access crate from the SVD files,
and verified that it works by using Silicon Labs' Simplicity Commander
tool to flash the ELF file.</p>
<p>However, the tool I use for most of my embedded development is
<code>probe-rs</code>, and I thought it would be nice to try and add support for
the particular chip I'm using.
They have support for some other Silicon Labs chips already, so how
hard could it be to add support for this one?</p>
<p>[pause for dramatic effect]</p>
<p>I've had <a href="https://github.com/probe-rs/probe-rs/issues/1700">an issue simmering in their tracker</a> for a while
now, and they've basically held my hand through the debugging process.
To be clear, the reason this is so difficult and taking so long is due
to my incompetence (if it wasn't clear from the issue discussion).
At this point, I'm considering just buying a dev board for Yatekii and
asking them to figure it out.
Would probably be faster and easier on their side rather than trying
to remotely debug through a lossy, flawed communication channel
(i.e. yours truly).</p>
<!--
[^1]: peripheral access crate: a sort of low-level, typed,
memory-unsafe API for modifying bare metal registers
-->
<h3 id="started-tone-tram">Started Tone Tram</h3>
<p>I have ideated yet another digital musical instrument interface to
add to my pile of half-complete digital musical instrument interface
pile.
I am nothing if not consistent.</p>
<p>This one is basically an <a href="https://en.wikipedia.org/wiki/Ondes_Martenot">ondes Martenot</a>, but digital.
Pretty funny coincidence that "Martenot" backwards is "tone tram",
since this thing is basically a little train that goes back and forth
to control the pitch of a tone.</p>
<div>
<a href="/mus-tec/tone-tram">
<div class="list-entry outer">
<span class="link">Tone tram</span>
- Reconstituting the ondes Martenot
</div>
</a>
</div>
<h3 id="started-crofu">Started crofu</h3>
<p>I've procrastinated on implementing <a href="/prj/icc">ICC</a> for quite a while
now.
I think at this point, with the help of the people who have been kind
enough to talk about it with me, I've developed the idea enough that
I'll learn the most from simply executing it.</p>
<p>And so, behold:</p>
<div>
<a href="/icc/crofu">
<div class="list-entry outer">
<span class="link">crofu</span>
- Incremental converse crowdfunding platform
</div>
</a>
</div>
<!--
### Upgraded desk
TODO: images
added mic stand
added ring light
-->
<h3 id="wrote-my-first-monthly-log">Wrote my first monthly log</h3>
<p>It remains to be seen how consistent I will be with these, but at the
very least, I think it helps with the blog-writing process.
It's very rare that I'll "finish" a project, so focusing on the little
things that I've done this month might be a better way to exercise
my blogging muscles, and make it easier to write the more interesting
articles on a given project.
I'm also planning on making project posts more "live", so that they change
as I progress through the project.</p>
<p>There are a couple different goals of this blog, but one big one
is to share the progress I've made.
Developing on an idea is a slow process riddled with obstructions, and
maybe by sharing my experiences, someone doing something similar in the
future will be able to move more effectively through the problem space.
Of course, no matter how much you learn about a subject, there will
always be issues, so I'm not too worried about depriving someone of the
joy of figuring things out.</p>
<h2 id="10-october">10 - October</h2>
<h3 id="card-game">Card game?</h3>
<p>Two of the folks on labyrinth.social <a href="https://social.linux.pizza/@jaxter184/111055809417501143">came up with an idea for Mastodon
trading cards</a>.
The idea immediately roused my interest, perhaps because I've been
very loosely dabbling in game design on and off for the last few months
(running a DnD campaign, trying out <a href="https://bevyengine.org/">Bevy</a>).
While there are plenty of services online that will print playing
cards for you with arbitrary designs on both the front and back, if
there's one thing I hate, it's spending a reasonable amount of money
for something convenient and high-quality when I could instead spend
even more money and time and get a far inferior result.
So after spending about $30 on paper, glue, and varnish, and $150 on a
used laser printer, I've come up with a vague process for creating some
cards that look and feel slightly better than if you simply printed
them out on regular paper with a regular printer.</p>
<p>This is in a very very early state, but hopefully it develops into
something meaningful!
Next step (honestly, this step should have come before the huge money
sink) is to do a few mockup designs and develop some game rules.
I had a few small ideas when I initially came across the concept:</p>
<h4 id="notes">notes</h4>
<p>the goal is not necessarily to win, its to:</p>
<ul>
<li>have fun</li>
<li>interact</li>
<li>commune</li>
<li>self-express</li>
</ul>
<p>minor goals:</p>
<ul>
<li>communicate</li>
<li>learn</li>
<li>connect</li>
</ul>
<p>though formalizing some sense of "victory" would help with the "have fun" part</p>
<ul>
<li>there should be a "game mechanic". perhaps dnd-esque? in that it is
collaborative rather than competitive
<ul>
<li>ooh, maybe like a multiplayer solitaire? or
<a href="https://en.wikipedia.org/wiki/Set_(card_game)">Set</a></li>
<li>even if there are no winners and losers, there should still be a
meaningful result.</li>
</ul>
</li>
<li>in my imagined world, people carry these cards around like business
cards and exchange them</li>
<li>there should be no reason to counterfeit them. they should only have
meaning because someone you know gave them to you.</li>
<li>put nfc chips in them?</li>
<li>each instance should have a different back, and maybe a front frame style</li>
</ul>
<h3 id="re-entering-the-world-of-papercraft">Re-entering the world of papercraft</h3>
<p>I also made a little papercraft to test the printer, and I think I'm
gonna do more:</p>
<figure style="width:60%">
<img
class="fig"
src="/images/papercraft-house-side-edge.jpg"
alt="photo of a little papercraft house in a sort of Tudor Revival style, with a stone first floor, wooden door, and
stucco second floor with wooden beams.
very simple shapes, basically a cube with a triangular prism roof.
first floor is very slightly inset from the rest of the house.
It is textured in a pixel-art style"
>
</figure>
<div>
<a href="/papercraft#first-attempt">
<div class="list-entry outer">
<span class="link">✂️ Papercraft</span>
- First attempt
</div>
</a>
</div>
<h3 id="researched-wikis">Researched wikis</h3>
<p>I stumbled upon <a href="https://maggieappleton.com/garden-history#a-brief-history-of-digital-gardens">Maggie Appleton's history of Digital Gardens</a>
on Mastodon, and while it has some insightful observations on the
nature of personal wikis and how they interact with the rest of the
internet, honestly, all it took for me to get on board with the idea
was simply the word "garden".
It feels so much more in line with the kind of thing that I want to
make than "blog".
So what are the steps I need to take to convert this blog to a garden?</p>
<p>Initially, I thought the move would be disruptive.
I was looking up wiki engines, and considering maybe even maintaining a
separate wiki and blog.
<a href="https://ikiwiki.info">ikiwiki</a> stood out as a particularly interesting engine, and prided
itself on its configurability, and the fact that you could also use it
as a static blog generator.
Tragically, it is written in Perl.</p>
<p>But running into ikiwiki made me realize that maybe a blog and a wiki
are two different shapes of the same material?
If I could just add some extra navigation elements and link more
aggressively between pages, then I'd be 90% of the way to a wiki.</p>
<p>That being said, there are some features
-- like automatically styling external links and streamlined editing
without logging in, or limiting edits to people who are (perhaps using
IndieAuth) --
that I would like to have, but would require significantly more work
than simply making a few minor changes to my Zola theme.</p>
<p>Like with the previous project, I haven't made any meaningful steps
toward actually doing anything. But I'm excited!</p>
<p>Also, recently, <a href="http://wiki.xxiivv.com/site/devine_lu_linvega.html">one of my favorite 'Lu's</a> got in contact with
<a href="https://www.todepond.com/wikiblogarden/my-wikiblogarden/">my other favorite 'Lu'</a> and recommended that they cultivate a
personal wiki.
The <a href="https://100r.co/site/log.html">Hundred Rabbits log</a> is what inspired me to start recording these
monthly log entries in the first place, so it's cool to see someone
who I really respect directly influencing someone else who I really
respect.
Especially when I'm being influenced in the same way!</p>
<h4 id="additional-reading">Additional reading</h4>
<ul>
<li><a href="https://j3s.sh/thought/my-website-is-one-binary.html">https://j3s.sh/thought/my-website-is-one-binary.html</a></li>
<li><a href="https://grimgrains.com/site/home.html">https://grimgrains.com/site/home.html</a></li>
<li><a href="http://notebook.zoeblade.com/">http://notebook.zoeblade.com/</a></li>
</ul>
<h3 id="refactored-tlature">Refactored <a href="https://git.sr.ht/~jaxter184/tlature">tlature</a></h3>
<p>While it may seem like I'm starting tons of projects and not actually
working on my old ones, it's actually the case that the single
non-sleep activity I spent the most time doing the last 32 days (16.1%
according to my logs) was working on tlature.
I set a bit of an artificial deadline for myself as I approached the
start date of my new job to get as much work done on it as possible
before I started.
While I had hoped to get to a point where it was usable by someone
other than myself to make music, I don't think it's quite there yet.</p>
<p>The current plan is to do a more detailed writeup of what I actually
did once I get Zola set up for a more wiki-like workflow, but in a few
words, I:</p>
<ul>
<li>Refactored routing
<ul>
<li>Block is now a chain of processors rather than just one</li>
<li>User-defined routing is now its own processor rather than
something built into the Block</li>
</ul>
</li>
<li>Revamp how the view modes behave; everything is now nested</li>
<li>Refactor <code>Tracker</code> as a processor <!--so you can have them anywhere
you can have any other processor, rather than operating on one global
`Tracker`. I will likely regret this in the near future, but the main
motivation was to make it easier to turn the `Tracker` into a plugin in
the future.--></li>
<li>Refactor how commands work <!-- why? --></li>
</ul>
<p>As you can see, it was mostly refactoring, and there were very few new
features.
In fact, I'd say it's probably less capable now than it was at the
beginning of the month.
Even so, I think overall, this is a good result, and has primed the
codebase for future features.
Unless my whims shift once again and I do another huge refactor.</p>
<h3 id="wrote-my-second-monthly-log">Wrote my second monthly log</h3>
<p>I was about 23 hours late to this month's post, but frankly, the fact
that I did it at all is something that I'm proud of.
It could have very easily been the case that I simply forgot to make
one.
In fact, it wasn't until about 2 hours ago that I even remebered this
was a thing that I was doing.
Maybe I should set an alarm or a calendar event to remind me.</p>
<p>In terms of the actual log content, I feel like I'm still having
trouble with the actual writing part;
a lot of my sentences are a bit clunky, and it's tough to communicate
what I want to, in the way that I want to, but also make sentences flow
naturally into each other.
But such problems are exactly the kind of thing that I'm hoping to fix
by making these entries, so I'm glad that I'm at least noticing the
shortcomings.</p>
<p>I think the core to this is going to be to better understand why people
read blog posts (and wiki articles), and what I can be doing to make my
writing more appealing for those people.
So if you've made it this far, <a href="https://contact.jaxter184.net">please reach out</a> and let me know what you
think!
Or even just let me know that you've made it here.</p>
<h2 id="11-november">11 - November</h2>
<h3 id="more-papercraft">More papercraft</h3>
<p>I made a second version of the papercraft house I made last month.</p>
<figure style="width:60%">
<img
class="fig"
src="/images/papercraft-house-new-illuminated.jpg"
alt="photo of a little papercraft house in a sort of Tudor Revival style, with a stone first floor, wooden door, and stucco
second floor with wooden beams.
It is textured in a pixel-art style.
There are some columns at each vertical edge, and a rim around the house at the boundary between each floor.
Very blocky."
>
<figcaption>It glows!</figcaption>
</figure>
<div>
<a href="/papercraft#second-attempt">
<div class="list-entry outer">
<span class="link">✂️ Papercraft</span>
- Second attempt
</div>
</a>
</div>
<h3 id="more-wiki-development">More wiki development</h3>
<p>Addad a bunch of features to my wiki template, including the site map
and table of contents that you see on the left sidebar (on desktop), and
little icons after every link, based on what site it links to (notably,
<a href="https://en.wikipedia.org">Wikipedia</a>).</p>
<p>Also moved to <a href="https://codeberg.page/">Codeberg pages</a> (and will probably move again
because I really don't like the upload workflow).
The issue that I had with sourcehut pages is that it <a href="https://srht.site/limitations">won't let you use
third party Javascript</a>.
While I totally understand, and still use sourcehut pages for <a href="https://contact.jaxter184.net">my other
site</a>, I really wanted <a href="https://help.sketchfab.com/hc/en-us/articles/203509907-Embedding-your-3D-models">Sketchfab</a> and
<a href="https://kicanvas.org/">KiCanvas</a> support, and while I could get away with including <a href="https://katex.org/">KaTeX</a>
source in my webpage, this was not true of Sketchfab's embeds, and
would likely require me to learn npm and TypeScript (eww) to do it for
KiCanvas.</p>
<h3 id="rust-on-riscv-esp32s">Rust on RISCV ESP32s</h3>
<p>Did some cursory exploration of flashing Rust code to RISCV ESP32s.
Seems to work OK for both the ESP32-C2 and ESP32-C3, but I couldn't
find or modify a Wi-Fi example to work with the C2. I'll keep you
updated though.</p>
<h3 id="game-engines">Game engines</h3>
<p>Wrote about every game I've made in recent history, and the engines (or lack thereof) that I used:</p>
<div>
<a href="/game-dev">
<div class="list-entry outer">
<span class="link">🎮 Game development</span>
</div>
</a>
</div>
<h2 id="12-december">12 - December</h2>
<p>For December Adventure, I worked on some ↹lature features laying the groundwork for better
plugin integration:</p>
<div>
<a href="/log/dec-adv-2023">
<div class="list-entry outer">
<span class="link">December Adventure 2023</span>
</div>
</a>
</div>
ICC ConcernsMon, 25 Sep 2023 00:00:00 +0000Unknown
https://wiki.jaxter184.net/icc/concerns/
https://wiki.jaxter184.net/icc/concerns/<p>This was originally part of the main ICC blog post, but I split it off because it got too long.</p>
<p>Read this first: <a href="../icc">ICC</a></p>
<h2 id="funding-comes-after-publication">funding comes after publication</h2>
<p>For many products,
there is a large up-front cost required to even begin work on completing it.
This is true for almost all physical products,
especially tech hardware.
ICC has no solution for these cases.
It instead focuses on information-based products,
like art and music and software,
as they can usually be broken down into sub-tasks,
their distribution costs are almost zero,
and the cost to the author to get started is generally very low.</p>
<p>The risk has to go somewhere, whether it is the funder risking the loss of their investment or the producer risking the
loss of the work they put in.
The reason ICC decides the money has to come after is because fluidity is important to the scoring process, and money
is more fluid than the deliverable <!--TODO: should I use the word "liquidity" here?-->
(i.e. you can't release 90% of an artwork, but you can provide 90% of the funding).
Also, since there are generally far more funders than producers in any given transaction, it makes more sense to treat
the funders as a more consistent collective decision-making body than the producers.</p>
<p>This is all to say that ICC isn't a viable means of funding for someone
with very little starting funds, so this will likely be used as either
as a side gig to complement a separate, more stable income, or for
artists who already have a significant fanbase.
However, no other method of funding really allows artists to start from
zero financial or social resources without putting all the risk on the
funder.</p>
<h2 id="ziglang-bounty-post">ziglang bounty post</h2>
<p>(added 23-268)</p>
<p><a href="https://ziglang.org/news/bounties-damage-open-source-projects/">https://ziglang.org/news/bounties-damage-open-source-projects/</a></p>
<p>This blog post highlights many issues with bounties that might also
apply to ICC:</p>
<h3 id="bounties-foster-competition-at-the-expense-of-cooperation">Bounties foster competition at the expense of cooperation.</h3>
<p>Possibly also applies to ICC. It depends on how an organization decides
to distribute a pledge that multiple people worked on, and ICC makes
few moves, if any, to help with this. The case that I see being the
most common is that donation payouts are shared, and new contributors
get a reduced share (the rest goes to maintainer) until they reach some
threshold. This has many downsides:</p>
<ul>
<li>for maximum payout, a contributor is encouraged work on a task alone</li>
<li>new contributors are considered outsiders with reduced entitlements</li>
<li>without good management, there might be conflicts when multiple
members work on the same task</li>
</ul>
<p>In the case of an organization with multiple members, perhaps it makes
more sense for all donations to a project to go to one big pool, and
for there to be some other decision-making process for deciding how the
donations are distributed.</p>
<h3 id="battle-royale-dynamics">battle royale dynamics</h3>
<blockquote>
<p>Instead of scouting for a suitable candidate, you’re letting battle
royale dynamics pick a winner for you, at the expense of everybody
who’s going to lose the competition.</p>
</blockquote>
<p>This observation highlights the importance of tying the pledge not just to a work, but also to the creator.
If a different creator produces interchangably similar work, it is hopefully obvious that they should not be entitled
to all of the pledges that were made towards this creator's work.</p>
<p>Likewise, by reducing or eliminating the payout in the case that an
outsider completes a task, these battle royale dynamics do not form, and
a suitable candidate can still be carefully selected.</p>
<h3 id="risk-allocation">risk allocation</h3>
<blockquote>
<p>Instead of creating a clear contract where you take on some of the
risk, you implicitly put the entirety of the risk on the contestants
(eg partial solutions don’t get any payout).</p>
</blockquote>
<p>A later point that I believe is also related:</p>
<blockquote>
<p>Instead of spreading unease to all the people involved, it would be
preferable you instead learned how to do business properly.</p>
</blockquote>
<p>The allocation of risk to contestants only forms when the criteria for payout is simply completing the task.
As long as the payout is tied to both the work <em>and</em> the creator, ICC does not allocate risk any more poorly than it
would be in a donation-based scheme.
That is, because payouts don't go to drive-by contributors, the risk of two people working on the same task is the
same as it is in any other FOSS project since there is no financial incentive for people not associated with the
organization (assuming the organization does a good job allocating work, which, to be fair, is not a given).</p>
<h3 id="reckless-action-to-pass-a-test-suite">reckless action to pass a test suite</h3>
<blockquote>
<p>Instead of allocating time and resources to proper due diligence, you
instead penalize any form of thoughtfulness in favor of reckless action
(eg a solution just needs to pass a test suite).</p>
</blockquote>
<p>As far as I can tell, this is less relevant due to converse
crowdfunding. Thoughtfulness is still required because the final arbiter
for quality is the donor, who can use whatever subjective mechanisms are
necessary to assess quality.</p>
<h3 id="software-lifecycle-considerations">software lifecycle considerations</h3>
<blockquote>
<p>Instead of planning for the full lifecycle of software, which also
includes maintenance, you end up with a quickly bitrotting artifact
that is of no practical use to anybody.</p>
</blockquote>
<p>Funding maintenance is definitely a concern, especially for larger projects.
ICC does not work well in cases where a few very wealthy organizations
all want the same thing,
and most open source libraries fall under this case.
Maintenance is incredibly important (I think most developers would say
that it's even more important than new features), but very boring (both
for maintainer and user) and hard to fund.</p>
<p>The Zig post goes one step further and highlights that if a contributor
cares more about the payout than the long-term sustainability of the
project, then their priorities discourage them from submitting
maintainable code.</p>
<p>For individual artists, this seems like an easy, though not trivial,
obstacle to overcome. My worries are greater when it comes to larger
organizations and more complex projects (like Zig). I've been simmering
on an organizational structure that can work in tandem with ICC, but I'm
skeptical as to whether it will actually work:</p>
<p>All donations go to the organization, and is distributed based on
"shares", which are received when one completes a task. As a result, the
contributor gets much less up front, but over time, they get a portion
of all future donations. This encourages contributors to make their
contributions more sustainable over the long term.</p>
<p>While this seems better than simply giving all the donations to
the contributor that completes the task, I am a little hesitant to
prescribe such a bureaucracy, and I think this is something that should
be figured out by the organization in question.</p>
<h3 id="social-pressure">social pressure</h3>
<blockquote>
<p>On projects less radical than Zig, you might also put pressure on the
development team to accept the winning submission, which, given the
above, will probably not be the most well-thought-out and maintainable
solution.</p>
</blockquote>
<p>I have no solution, except crossing my fingers and hoping people are
reasonable and understanding about this.</p>
<h3 id="conclusion">conclusion</h3>
<p>As I understand it, the conflict in the WASIX issue linked in the Zig
blog post mainly stemmed from the fact that implementing WASIX would
not have aligned with the goals of Zig.
Even though the bounty payout is not contingent on the implementation
being accepted into the mainline branch, it is understandable how this
bounty could be used as a tool to subvert the goals of the leadership
and community to push the progress of the project in a direction
that its participants don't want it to go in (<a href="https://wiki.jaxter184.net/icc/concerns/#more-money-means-more-influence">another relevant
section</a>).
While this concern is not explicitly mentioned in the Zig blog post, it
highlights yet another important aspect of ICC: the artist is the sole
entity that is allowed to create new proposals.
Before, I had made this decision because I thought it would be annoying
and unwieldy for the donors to be able to create new proposals (not to
mention the logistical headache of determining when two proposals are
similar enough to combine into one).
However, as this conflict shows, it is absolutely imperative for
proposal creation and modification to be restricted to the artist in
order to preserve artistic freedom, and it is perhaps worth looking
into additional protections.</p>
<p>Most of these points seem extremely pertinent to ICC (especially the
last two), and are worth thorough analysis during and after the initial
test run. It should not be understated the degree to which these issues
could result in harming FOSS more than it helps. The worst outcome for
this is that it leads to the destruction of a FOSS project that would
have otherwise survived, without preserving a project that would have
otherwise died.</p>
<h2 id="does-not-solve-the-foss-maintenance-crisis">does not solve the FOSS maintenance crisis</h2>
<p>The <a href="https://explore.tidelift.com/2024-survey/2024-tidelift-state-of-the-open-source-maintainer-report">Tidelift open source maintainer survey report</a> contains a lot of disheartening results:</p>
<ul>
<li>60% of maintainers are unpaid</li>
<li>Maintainers are spending 3x more time on security than they did a few years ago</li>
<li>In the wake of the xz utils hack, two-thirds of maintainers are less trusting of contributors</li>
<li>The open source maintainer community is getting grayer (i.e. older)</li>
</ul>
<!-- https://social.coop/@luis_in_brief/113154519777731026 -->
<p>Unlike a developer, a maintainer's role is less to implement new desired features, and more to ensure that a software
project continues to work in the future as the world around it changes.
This entails bug fixes, security improvements, using updated dependencies, and adding support for newer hardware, to
name a few.
Most of these things aren't really things people would be willing to pay for in an ICC scheme, at least not in the
numbers required to justify the work.</p>
<p>Bullshit Jobs is my least favorite David Graeber book, but one of the takeaways I got from reading it is that one
of the reasons many important jobs (nursing, teaching, waste management, etc) pay poorly is because extortion is an
important part of wage negotiations (if you don't pay me more, I will leave this job), and people are less willing to
withold their labor when they think what they're doing is an important, or even critical thing that needs to be done.</p>
<p>In a way, every quid pro quo financial transaction is extortion (for example, buying food at a grocery store), and since ICC
may imply this sort of transaction, a maintainer may feel uncomfortable signaling that the only thing standing between
them and fixing this important piece of infrastructure is getting paid.
I would argue that the implied contract of "if you produce it, I will fund this" is <em>not</em> a contract in the case of ICC
since the donor is allowed to refuse with no repercussion other than the system knowing that they pledged some amount
and didn't contribute it.</p>
<h2 id="artistic-freedom">artistic freedom</h2>
<p>I personally believe that this method allows for more artistic freedom
than copyright-based compensation schemes (like commissions). I have
no real basis for believing this, and will be on the lookout for the
effects of this funding method on artistic freedom when I see more
data.</p>
<p>In the end, the producer still gets final say in what they choose to do.
Even if one option has significantly more fund offers than the others,
the artist is still justified in deciding to do one of the others,
and those who offered to pay for the most popular one
don't lose any money from this.</p>
<!--
## lack of consistency
One of the proposed benefits of ICC is that since publication is much
more incremental and permissive licensing is (theoretically) encouraged,
it is much easier for an unaffiliated group to continue from where an
abandoned project was left off.
However, with this scenario,
there is potential for an inconsistent final product.
For example, a project that seeks to adapt a comic
into an animated cartoon
may only end up creating 15 episodes
before the team decides to halt production.
In the case, another team could continue production,
perhaps with a different approach that makes it more financially efficient.
Whether this continuation is higher or lower in quality,
the collected works as a whole are slightly lower in value due to the inconsitency.
It becomes noticable after episode 15 that something is different,
and that breaks immersion for the viewer.
However, the alternative is for the cartoon to be completely cancelled,
so I don't see this as being any worse that the current status quo.
-->
<h2 id="more-money-means-more-influence">more money means more influence</h2>
<p>In general, people with more money have more power, and if implemented
without careful intent, this tool will likely cause the same power
consolidation effects of many other market systems.
One way to counteract this is some sort of voting system where each
person gets a single vote, but that has its own complications in
verifying that all of the voters are actually distinct people (and not
bots or malicious actors), or the fact that some people are more
reliant on a project than others.
That being said, the goal with ICC is to make money flow to people
making cool things, with no regard for power, and the currently
targeted demographic is one that is very conscious of how money and
power flow.
I've seen very few people who pursue art or FOSS software out of a
desire for money and power.
This is also why my platform (as is currently planned) requires FOSS
and/or Creative Commons licensing, as it filters out the people for
whom this is exclusively a means of making more money (which isnt to
say that making money can't be someone's primary goal).</p>
<p>Another potential concern is that
an individual with a large amount of money
could basically have monopoly-level artistic influence
over a large part of digital art production.
However, if this platform led to
a small amount of people paying artists large amounts of money
to produce what they (both the funder and the producer) want,
I'd say thats an outcome I'd be willing to work with.</p>
<p>That being said, the hope is that most of the income comes from a large number of individuals instead of a couple rich
people or big companies.
If you're paying for something, it's because you're getting something out of it in return, and ideally, a large number
of people should be getting something from these projects.
Plus, if you're not paying, you have no say in what gets made.</p>
<h2 id="low-variance-and-regular-output-producers">low variance and regular output producers</h2>
<p>For a producer who only produces one type of content
at a fixed interval,
there isn't much economic benefit to incremental converse crowdfunding
over methods like crowdfunded patronage or donation.</p>
<h3 id="case-study-99-invisible">case study: 99% Invisible</h3>
<p>The main work that 99% Invisible produces is their weekly podcast
discussing design.
There isn't really any reason for a listener to pledge towards a
"Release a podcast" product offering, as the quality, quantity, and
timing of that deliverable would be practically the same regardless of
funding amount.
Moreover, part of the joy of the podcast is revealing a part of the
built world that is often underappreciated, so holding a poll to decide
on the topic of the next podcast would be antithetical to this premise.
For this type of creator, it would make more sense to run funding
drives (which, last time I checked, is what 99% Invisible does), use a
platform like Ko-fi or Patreon, or seek public funding.</p>
<h2 id="research">research</h2>
<p>(added 23-104)</p>
<p>It goes without saying that
research is an important part of the development process.
I believe ICC would not be ideal for directly funding research
for a few reasons:</p>
<ul>
<li>The value of research is hard to know before it is done.
Using exploration of a forest as an analogy, the biggest and most
important breakthroughs aren't those that cover the largest area,
but rather those in the path that connect the starting point to the
destination.</li>
<li>It is hard to break down into smaller increments, as the value is
really only realized in aggregate.
A deliverable like "produce a reliable method to sense the position
of a shuttle along a rail" would have so many factors involved in its
assessment that even with conditional offerings, it would be difficult
to express the suitability across the various use cases, and how the
final solution navigates the many tradeoffs involved.</li>
</ul>
<h2 id="accessibility">accessibility</h2>
<p>(added 23-268)</p>
<p>Accessibility improvements only really get done via central planning measures (at a small scale, Apple, at larger
scales, the American Disability Act).
I don't forsee ICC hurting current accessibility efforts, but it is important to recognize that this is not really a
"rising tide that lifts all boats" in FOSS and free culture, and that other measures are necessary.</p>
<p>See also: <a href="https://hachyderm.io/@ekuber/112718152844250234">https://hachyderm.io/@ekuber/112718152844250234</a></p>
<blockquote>
<p>#OpenSource and #FreeSoftware is predicated on people "scratching their own itch", with the expectation that if a
solution is useful to you, it will likely be useful to others.
But that breaks down when it comes to hard, niche problems that require specialized knowledge and skills.
Things like good #accessibility in desktop environments doesn't happen without lots of effort, knowledge and intent.</p>
</blockquote>
<h2 id="getting-scooped">getting scooped</h2>
<p>If you're a video essayist, you might want to create proposals that
describe the topic of the proposed video in detail.
However, doing so could result in a lower-effort essayist "scooping"
your topic before you.
While it would be interesting to see how the essayist would combat that
(perhaps by using the "scoop" as another reference to fact check their
own video, or to gauge the audience reaction and use that to expand on
some parts and remove others),
in general, the best move is probably to use more vague descriptions of
the topics in their proposals.</p>
<h2 id="a-bit-complicated">a bit complicated</h2>
<p>One of the claimed benefits
of Ko-fi over other platforms is its simplicity.
ICC at its base level is a non-negligible amount of stuff to be aware of, and once you incorporate details like
sub-deliverables and expected donation score and conditional offerings, it becomes an economic labyrinth.
In addition, the onus is on the donor
to determine to what extent the creator has satisfied their claims,
which is additional work
that not a lot of supporters are willing to put the time and energy into.</p>
<p>Notably, the game engine <a href="https://bevyengine.org/news/bevys-third-birthday/#funding-bevy-is-confusing">Bevy
observed</a>
that when it moved from a simple system (send all money to the
creator/maintainer) to a more complex system (choose which contributer
to send money to), <strong>new donors went way down</strong>.</p>
<h2 id="money-corrupts-the-joy-of-creation">money corrupts the joy of creation</h2>
<p>In some cases where there is no financial incentive, there's a lot
more passion, in part out of necessity.
But such a scenario is almost never financially sustainable.</p>
<!-- dead link
See also: <https://queer.party/@Lyude/110861481994039644>
-->
<h2 id="money-is-not-an-ideal-driver-for-an-organization">money is not an ideal driver for an organization</h2>
<p>Big multi-person projects will fall apart if the main thing holding them together is money.
These things need vision and a group of people who collectively hold it, especially early on.
For money to be the sole motivator is inviting failure, and in the case that it makes it past the early stages, leads
to an organization that is driven by a perverse incentive.</p>
<h2 id="cc-by-nc">CC-BY-NC</h2>
<p>Not really an issue with the actual mechanism behind ICC, but it's
probably not legal for an artist to use this as a platform for a work
that triggers the non-commercial restrictions of a Creative Commons
license. While ICC is a framework for donation (it does not transfer
any rights or meaningful property in exchange for the donation), I
belive the non-commercial restriction still triggers even in the case
of donation-funded media (like Blender open movies). Just something
that it's probably worth clarifying to the artist.</p>
<h2 id="this-seems-pretty-consumerist">this seems pretty consumerist</h2>
<!--
(this is mostly irrelevant, and does not have enough nuance to accurately reflect my beliefs)
Frankly, I believe markets, when used correctly, are a very useful
economic tool.
In particular, I appreciate how effectively they distribute the most
resources to causes that can provide the most direct utility with such
a simple system.
It does have its flaws, and is often misused (looking at you, Chicago school of economics), but I think that the
general idea of designing a market-like space that encourages commence is an easy and effective way to get things made.
-->
<p>Sure, this does "encourage the acquisition of goods and services in
ever-increasing amounts" (which is how Wikipedia defines consumerism),
but in moderation, isn't that a good thing?</p>
<p>It's really easy to look at the world and find one of the myriad ways that the profit motive results in a sub-optimal
outcome.
For example, the immense resources and investment in self-driving cars, and the relative dearth invested in
electrifying and automating public mass transit, simply because creating a world where people are forced to buy,
insure, and maintain heavy machinery to participate in society is much more lucrative than getting them where they need
to be cheaply and efficiently.
While ICC as an endeavor seeks to better align the profit motive with actual good societal outcome, there is an
argument that could be made that relying so heavily on the profit motive is fundamentally flawed, and the only solution
is to stop depending on it.
I have no idea how to accomplish that, and I feel like I will get a lot more actual good done by pushing for my
incremental changes that I will trying to implement an unspecified ideal solution.</p>
<p>That being said, if you're looking for a less commerce-driven solution, here are some options:</p>
<ul>
<li>lobby politicians to increase funding for public services like radio
and television</li>
<li>publicly voice support for your country's endowments and fellowships
(like the National Endowment for the Arts)</li>
<li>support the artists in your local and online communities</li>
<li>go commission a profile picture or something. artists are really good
at that kind of thing, and its really satisfying to show off something
that was made just for you. also maybe like a ringtone.
<ul>
<li><del><a href="https://banchan.art/">https://banchan.art/</a></del> RIP</li>
<li>(arguably also commerce-driven)</li>
</ul>
</li>
<li><a href="https://comradery.co/">https://comradery.co/</a>: Democratic cooperative Patreon alternative</li>
<li><a href="https://resonate.coop/pricing/">https://resonate.coop/pricing/</a>: Alternative funding model for music streaming</li>
<li>Benn Jordan's proposal of <a href="https://youtu.be/PJSTFzhs1O4?t=509">socialized copyright</a></li>
</ul>
<!--* [Francis Muguet's Mécénat Global](https://www.itu.int/net/wsis/implementation/2009/forum/geneva/Pdfs/WSISForum09-patronage-V0%202.pdf)-->
<h2 id="donor-entitlement">donor entitlement</h2>
<p>In order to preserve artistic autonomy, it must be abundantly clear that even if a proposal has the highest expected
payout, it is not guaranteed to be the top priority.
The creator should always be the one making the final decision, and they should ideally be completely unencumbered in
their decision.
Of course, reality is a poor fit for ideals, and even if someone was totally free to do whatever they wanted in their
project, we live in a society with laws and costs of living that will influence what they are willing or capable of
doing.</p>
<h2 id="closing-statement">closing statement</h2>
<p>For the foreseeable future, any attempt to fund a cool public project
is a balance of doing the right thing for the future of the project and
finding a sustainable source of income.
If improvements came without costs and sacrifices, then things would be
much easier.
Hopefully, this paradigm is an improvement on the options currently
available, but that remains to be seen.</p>
MIDI Sucks: A RantSat, 01 Apr 2023 00:00:00 +0000Unknown
https://wiki.jaxter184.net/mus-tec/midi-rant/
https://wiki.jaxter184.net/mus-tec/midi-rant/<p>The following is an archive of a Twitter thread I made on January 12,
2019.</p>
<p>MIDI is a garbage standard that needs to be replaced in order for
the music production and performance world to progress. It's modern
development basically consists of workaround after workaround to try to
make something that vaguely resembles a usable format.</p>
<p>The only thing its good at is boiling down keyboard instrument
data. Literally any instrument that doesn't consist of a
black-and-white set of clavier keys cannot be fully expressed using
MIDI. For example, drums make different sounds depending on where and
when you hit them.</p>
<p>MIDI also doesn't have enough data precision to represent a lot of
human expression. Most notably, they had to split pitch bend control
into two MIDI messages. If that's not kludging it, I don't know what
is.</p>
<p>Furthermore, General MIDI is weak. It did well up until the early 2000s
because pop music had a very limited use of synthesis. Everything
was basically either a saw/squ lead/pad. However, with the surge in
electronic influence on pop music, this is no longer the case.</p>
<p>127 is a very limited number. There are more than 127 types of musical
sounds in the world. Heck, the number of percussion sounds alone
exceeds the 127 limit of MIDI. Also, 7 bits? Really? Are we gonna live
in 2019 and still not use a power of 2?</p>
<p>OSC is the obvious alternative, but it lacks a crucial feature that
made MIDI ubiquitous: standardization. Because OSC doesn't have a
rigorous standard, it can't really compete, which, in its defense,
was intentional. MIDI's rigidity is what will (hopefully) lead to its
demise.</p>
<p>Another crucial downside of OSC that I forgot to mention is that it
doesn't have a file format. It kinda makes sense due to the fact that
the messages aren't standardized, but if it's gonna overtake MIDI,
having a way to save streams of data to read later or edit is a must.</p>
<p>Not to beat a dead horse, but another thing MIDI lacks that I forgot
to mention is that it can't send multiple messages at once, making
it pretty annoying and unpredictable when using it to stream live
sequenced music data.</p>
<h2 id="replies">Replies</h2>
<p>@[REDACTED] said: I think it works just fine...</p>
<figure>
<img
class="fig"
src="https://pbs.twimg.com/media/Dwv2Ye0UwAEIB00?format=jpg"
alt="photograph of someone's studio, with a Roland TR-808, Akai MPC
3000, Prophet V0, a mixer board, and lots of rack-mount synthesizers"
>
</figure>
<p>My reply:</p>
<p>I think this picture is actually a very good argument against MIDI. In
the center of your image is the Roland TR-808, which is quite possibly
the most iconic electronic instrument ever, despite not using MIDI at
all in any component of its functionality.</p>
<p>In addition, most of the stuff on 19' racks don't use MIDI as
far as I can tell, and it would also be physically impossible for
your mixer board to be MIDI compatible because it has more than
16 channels. Ultimately, MIDI was meant for a very specific thing:
keyboard instruments.</p>
<p>I think of all the gear in that picture, the only thing that requires
MIDI to function at a basic level is that red synth in the top
right. Sure, some of the things in that picture can send and recieve
MIDI, but I assuming you don't use the MIDI functionality of those
keyboards.</p>
<p>Addendum (2023-091): The 'M' in "MPC" literally stands for MIDI, so
that product could also probably be classified as one that "requires
MIDI to function at a basic level".</p>
A Plea for PoEThu, 30 Mar 2023 00:00:00 +0000Unknown
https://wiki.jaxter184.net/mus-tec/poe/
https://wiki.jaxter184.net/mus-tec/poe/<!--
## First, a bit of speculative imagination
In my ideal future, I imagine an eletronic musician on a stage with
about 4 other bandmates, each with various instruments, almost all of
which are not invented at time of writing. The lead musician takes the
stage, with an instrument made of aluminum and plastic, that looks a
bit like a hybrid between a bass guitar and a saxophone. At first,
it may seem like a bit of a gimmick, intended to distinguish this
particular musician from the many others, but after they start playing,
you realize: it's not the instrument that distinguishes the musician,
but the musician's ability that instead makes the instrument shine
far brighter than the many other instruments that exist. As you see
them shred their solo to bits, you can't help but grin, appreciating
everything that had to happen to put this person in front of you,
conveying the whole of their creative spirit through their musical
perfomance.
-->
<p>Ever since music festivals became the primary medium for electronic music, live performance has become more about the
giant spectacle of screens, lights, and fire.
The musician becomes a mere speck on the stage, only there to make the audience feel like they're not just paying for a
pre-programmed lightshow and a playlist<sup class="footnote-reference"><a href="#1">1</a></sup>.
CDJs and laptops lock the performer to one position, and limits the dynamism and physicality of their performances.
Heck, it's called Electronic *Dance* Music, but the vast majority of DJs don't do much more than bob their head, pump
their arms, and clap their hands (TODO: write an article about how throwing a cake is the most innovative dance move
ever performed by a DJ).
It's great that electronic music has given lighting and effects operators the freedom to unleash their craft on the
world, but I can't help but compare these shows to more musically performative events like rock concerts and drum
circles, and wonder: what could it look like if today's instruments were better at expressing today's music?</p>
<!--
<figure >
<img
class="fig"
src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLVBwLqNKHMCuGPx2MQNb8aROgG4hUVbXe3mU_vbum0l6g5ORInmnNiBw9Usv0t7CuA1U2icxTk32JL77st6NH2iMOkXZfWa81aQt29eokkukIYQUGoukRYFXBJXNG5wzdt_mhGRuT-LgP/s1600/Aoki-92.jpg"
alt="a starkly lit crowd with their hands up and eyes closed, likely due to the blurry sheet cake flying towards them, inches from their faces"
>
</figure>
-->
<p>I do want to emphasize: it is not the music, the musician, or the stage that is in need of reform.
There is plenty of musicianship and creativity in electronic music today; arguably more than there was for rock and
classical.
Rather, it's the limitations and hassle of the hardware that restrict how that music can be performed.
Perhaps a laptop is effective and convenient enough for the <em>production</em> of music, but I don't see a future in
laptops as a tool for <em>performance</em>.</p>
<h2 id="anecdote-of-a-musical-stage-perforance">Anecdote of a musical stage perforance</h2>
<p>I recently helped out with a few showcases for a local multimedia festival.
One such showcase brought in a bunch of musicians of various genres one after another.
One band had 5 members and a drum kit that had to be moved on and off, while one had just a laptop and a
microphone<sup class="footnote-reference"><a href="#2">2</a></sup>.
Now, between the two, you would think that the laptop would be a lot easier, right?
Contrary to what I had expected, technical difficulties with the laptop put the whole schedule back almost an hour.
A dongle was lost at some point, cables had to be switched out, devices had to be restarted; a textbook example of
<a href="https://en.wikipedia.org/wiki/Murphy's_law">Murphy's Law</a>.
And this was on top of the already cumbersome setup process of taking each device out of a suitcase and plugging in the
various power and USB cables.
Once it got started, though, the performance was really cool!</p>
<p>The rest of this blog post presents a case for Power over Ethernet, a technological standard that I think is underused
in music hardware today.
I can't guarantee that it will solve all of these problems, but if there's anything I've learned from those last few
days of stage work, it's that electronic music performance setup processes are in dire need of simplification.
In its current state, using a laptop on stage with its tangle of cables going to MIDI controllers, audio interfaces,
and power supplies is a complicated problem, solutions for which are very fragile (compared to setups for other types
of music performance).
My goal with this manifesto is to convince music hardware manufacturers to simplify the experience for electronic musicians
performing their music, and provide them with the resources to make it easy to do so.</p>
<!--
## Communication channels
My argument is that the primary limiting factor is the lack of a good
communication channel.
Most non-stationary instruments, like handheld
lutes ([321.3](hs321)), non-free aerophones
([42[wl]](hs42)), and electric guitars, require only
one cable between the instrument and the sound system (usually XLR
or TRS/TS).
A particularly complicated signal path for an electric
guitar might be:
```
[guitar] -TS-> [pedals] -TS-> [DI box] -XLR-> ...
```
while for a MIDI controller (not a standalone synthesizer) at its
simplest would be:
```
[controller] -USB-> [laptop] -USB-> [audio interface] -XLR-> ...
```
(I am not aware of any laptops with XLR outputs)
-->
<h2 id="crash-course-on-poe">Crash course on PoE</h2>
<p><a href="https://en.wikipedia.org/wiki/Power_over_Ethernet">Power over Ethernet (PoE)</a> is a set of standards for, as you may have guessed, sending power over an
Ethernet connection.
Similar to <a href="https://en.wikipedia.org/wiki/USB_hardware#USB_Power_Delivery">USB-PD</a>, the devices negotiate power, and both sides have to be compatible with the standard.
If one side does not support it, the communication channel falls back to the default, which is 5 volts 0.5 amps (2.5
watts) for USB and no power for Ethernet.</p>
<p>As a disclaimer, PoE is competing against plain USB for most music hardware, but if you want something like a built-in
speaker or motorized faders or LEDs out the wazoo, you'll want more than the 2.5 watts that regular USB is
rated for, which will require some sort of external DC power supply, whether that's a barrel jack wall wart, USB-PD
or PoE (or just crossing your fingers and hoping that your USB 2.0 host can provide more than 0.5 amps).</p>
<p>In all my reading, I was only able to find three "canonical" applications for PoE: security cameras, IP phones (phones
that communicate over the internet rather than cell networks), and commercial wireless access points.
Hopefully we can change that.</p>
<h2 id="poe-vs-usb-pd">PoE vs USB-PD</h2>
<table><thead><tr><th></th><th>Ethernet</th><th>USB 3.0</th></tr></thead><tbody>
<tr><td><strong>Use cases</strong></td><td>Limited to internet networking, and a small handful of other niches (one of which we will come back to later)</td><td>Very flexible, and near-universal (which seems to have been the goal, based on the name).</td></tr>
<tr><td><strong>Speed</strong></td><td>Cat 5e only allows for speeds up to 1 gigabit per second (but Cat 8 allows for up to 80 gigabits!)</td><td>At time of writing, can go up to 20 gigabits per second with compatible devices and cables.</td></tr>
</tbody></table>
<table><thead><tr><th></th><th>PoE</th><th>USB</th><th>USB-PD</th></tr></thead><tbody>
<tr><td><strong>Availability</strong></td><td>Power usually comes from a switch, and most network switches (outside of commercial IT) don't support PoE (though PoE switches seem to be more common than USB-PD hubs)</td><td>Nearly every modern electronic device supports this. Computers, phones, keyboards, portable speakers, books, cigarettes, screwdrivers, etc.</td><td>Most laptops come with USB-PD chargers at time of writing, and if a battery bank has a USB type C output, chances are, it supports USB-PD.</td></tr>
<tr><td><strong>Power</strong></td><td>802.3af (Base-level PoE) nominally supports 15.4 watts, while 812.3bt (PoE++) supports up to 90 watts<sup style="opacity:0.5"><a href="#1">[1]</a></sup><sup style="opacity:0.5"><a href="#2">[2]</a></sup>. Usually runs at 48 volts<sup style="opacity:0.5"><a href="#3">[3]</a></sup>.</td><td>A measly 5 volts at 0.5 amps, but often more than enough for digital musical instruments.</td><td>Boasts over 180 watts at 36 volts.</td></tr>
<tr><td><strong>Impl</strong></td><td>Requires dedicated circuitry. See the section on <a href="https://wiki.jaxter184.net/mus-tec/poe/#implementing-poe">implementing PoE</a>.</td><td>Just a plain 5 volt DC output.</td><td>Doesn't necessarily require a dedicated chip, only that your microcontroller has peripherals for USB-PD negotiation.</td></tr>
<tr><td><strong>Negotiation</strong></td><td>"Passive" process using current draw</td><td>Automatic, no negotiation</td><td>i have no clue tbh</td></tr>
</tbody></table>
<h2 id="so-why-poe">So why PoE?</h2>
<p>Despite USB being a better standard for 90% of applications, there is still a reason that Ethernet dominates the
networking industry.
The two main benefits that I can see are: cables are extremely cheap (and easy to crimp yourself), and can get
extremely long.
With USB 3.0 (using 26 AWG wire), you get approximately 3 meters maximum<sup class="footnote-reference"><a href="#3">3</a></sup>.
Even with the lower-bandwith USB 2.0, you get only get 5 meters<sup style="opacity:0.5"><a href="#4">[4]</a></sup><sup style="opacity:0.5"><a href="#5">[5]</a></sup>.
On the other hand, Ethernet cables (of any Category) are generally rated for 100 meters.</p>
<p>I believe this extra length is <em>absolutely vital</em> for making digital music controllers more performable.
With USB, the musician is tethered to a circular area with a radius of 5 meters, but with Ethernet, the laptop doesn't
even have to be on the stage.
It could be setup somewhere out of the way and left alone between performances, which would have almost certainly
prevented the issue with the laptop in the anecdote from earlier, since you could have all the equipment set up
before the show starts, or at the very least, start setting up before the previous band is done.
Or you could go as far as to have a rack-mount desktop, connect it to a PoE switch, and run a single ethernet cable to
each musician and move around with the same freedom that an electric guitarist might have.
Just one cable between the musician and the sound system.</p>
<p>While I don't know of any hardware that uses PoE specifically,
there are plenty of open standards for <a href="https://en.wikipedia.org/wiki/Audio_over_Ethernet">audio over an Ethernet
connection</a>, often used to send many channels of audio over a
"digital snake" from a stage to its sound booth and back.
It isn't strange to see a roll or two of Cat 5(e) cable in a live
venue's arsenal.</p>
<p>Some Pioneer CDJs and mixers are equipped with Ethernet for a feature that they call <a href="https://support.pioneerdj.com/hc/en-us/articles/4405902925593-What-is-the-PRO-DJ-LINK-function-">Pro DJ Link</a>.
Among other things, it connects any attached filesystems to other devices on the network, and can send the audio to a
laptop (also on the network) with the appropriate software.
I only know about this because during the aforementioned showcase, anytime there was a DJ accompanying the vocalist,
the CDJs and mixer would be brought on stage in their flight case, (along with a Wi-Fi router and power strip), and
setup was as easy as just plugging in the power and letting the booth take it from there.
It was quick, easy, and went without a hitch every time.</p>
<p>The current state of DJ hardware connectivity is the future that I see
for all music hardware: controllers, audio interfaces, rack-mount
synthesizers, effects processors, eurorack, and anything other
electronic device that can be used to make or perform music, all
connected to one PoE switch offstage and nothing else.</p>
<p>One of the other use cases I've discovered recently is [Personal Monitoring Mixers].
These are apparently pretty common in studios and orchestra pits, and often (but not always) have PoE support.
Unfortunately, they also tend to use proprietary audio over IP protocols, which I'm not too jazzed about.</p>
<h2 id="enter-open-sound-control">Enter: Open Sound Control</h2>
<p>By reading this far, you have just activated my trap card. For the past
3 years or so, I have slowly been coming to the conclusion that <a href="https://en.wikipedia.org/wiki/Open_Sound_Control">Open
Sound Control (OSC)</a> is the solution to
almost all of my musical problems.
Voice synthesizer plugin needs some way to receive phoneme info? OSC
has character and string types.
Want more semantically useful numeric types in a tracker-based DAW?
OSC has single and double precision floats, 32 and 64 bit integers,
booleans, infinity, timetags, and nil (if you count that as a numerical
value).
Need a file format that can express sample-accurate timing and
namespacing?
It's not standardized, but <code>oscdump</code> has a pretty reasonable output,
and if that's not to your liking, you can just store the raw bytes,
since time information is encoded in the packet (along with the message
and its namespace, of course).
Of course, OSC has its own drawbacks, so if you can do something
entirely with MIDI, it's probably best to do it with MIDI.
But if you need the length of an Ethernet connection anyway, you might
as well use OSC as the transport layer for your MIDI data.</p>
<p>One of the notable things about OSC is that its designed to run on top of a network.
Most OSC-compatible software asks for things like IP addresses and ports, and can communicate with OSC endpoints over
the internet.
OSC was made for Ethernet, and with the locking clip feature of RJ45 connectors (or optionally Ethercon), longer
maximum cable length, and industry adoption of audio over Ethernet, PoE music hardware seems like it could be the
<a href="https://en.wikipedia.org/wiki/Killer_application">"killer app"</a> of OSC.</p>
<h2 id="implementing-poe">Implementing PoE</h2>
<p>It's a bit tough to say authoritatively which is easier to implement between PoE and USB-PD from a hardware development
perspective (focusing on the receiving end), as I've never implemented either.</p>
<p>That being said, I've spent a lot of my last few months looking at PoE, and it seems like the most simple way to go
about it is using an RJ45 jack with built-in magnetics and a bridge rectifier (HY931147C or LPJ4112CNL), and a module
like <a href="https://silvertel.com/images/shortforms/Selector_GuidePoE.pdf">those offered by Silvertel</a>, which total to about $15, which doesn't even include any of the parts you
would need for the data part of ethernet.
Certainly much more expensive than a USB-B jack.</p>
<p>If you're willing to get more DIY with it, you can break both the RJ45 jack and the PoE module up into their discrete
components, for example with the following parts:</p>
<table><thead><tr><th style="text-align: right">price</th><th>part no.</th><th>description</th></tr></thead><tbody>
<tr><td style="text-align: right">$0.88</td><td>MTJ-88ARX1-FSM-LG</td><td>RJ45 jack*</td></tr>
<tr><td style="text-align: right">~$0.50</td><td>S16503G</td><td>magnetics (isolation filter, transformer)</td></tr>
<tr><td style="text-align: right">$0.02</td><td>MB10S</td><td>bridge rectifier</td></tr>
<tr><td style="text-align: right">$1.18</td><td>TPS2376D</td><td>PoE negotiator</td></tr>
<tr><td style="text-align: right">$0.76</td><td>TX4138</td><td>step-down regulator</td></tr>
</tbody></table>
<p>*: The RJ45 jack can be cheaper if you don't care about status LEDs or shielding</p>
<p>Approximating the generic parts (resistors, diodes, capacitors, inductors, etc) to less than $1 total, this comes out
to under $4.34, which is still much more than the USB-B jack, but still managable for a high-end digital instrument.</p>
<p>TODO: finish</p>
<ul>
<li><strong>espoir</strong>
<ul>
<li><a href="https://docs.connaxio.com/doc/espoir_schematics_latest.pdf">https://docs.connaxio.com/doc/espoir_schematics_latest.pdf</a></li>
<li><a href="https://github.com/Connaxio/espoir">https://github.com/Connaxio/espoir</a></li>
</ul>
</li>
<li><a href="https://www.olimex.com/">https://www.olimex.com/</a>
<ul>
<li>esp32-poe
<ul>
<li><a href="https://github.com/OLIMEX/ESP32-POE/blob/master/HARDWARE/ESP32-PoE-hardware-revision-L/ESP32-PoE_Rev_L.pdf">https://github.com/OLIMEX/ESP32-POE/blob/master/HARDWARE/ESP32-PoE-hardware-revision-L/ESP32-PoE_Rev_L.pdf</a></li>
</ul>
</li>
<li><a href="https://github.com/OLIMEX/ESP32-POE-ISO">https://github.com/OLIMEX/ESP32-POE-ISO</a></li>
<li><a href="https://github.com/OLIMEX/S3-OLinuXino">https://github.com/OLIMEX/S3-OLinuXino</a></li>
</ul>
</li>
<li>wiznet pico
<ul>
<li><a href="https://docs.wiznet.io/Product/Open-Source-Hardware/wiznet-pico-poe">https://docs.wiznet.io/Product/Open-Source-Hardware/wiznet-pico-poe</a></li>
<li><a href="https://docs.wiznet.io/img/osh/WIZnet_Pico_PoE/Sch_WIZnet_Pico_PoE_FIN.pdf">https://docs.wiznet.io/img/osh/WIZnet_Pico_PoE/Sch_WIZnet_Pico_PoE_FIN.pdf</a></li>
<li><a href="https://github.com/Wiznet/Hardware-Files-of-WIZnet/tree/master/08_OSHW/WIZnet%20Pico%20PoE">https://github.com/Wiznet/Hardware-Files-of-WIZnet/tree/master/08_OSHW/WIZnet%20Pico%20PoE</a></li>
</ul>
</li>
<li>chips
<ul>
<li><a href="https://www.ti.com/power-management/power-over-ethernet-poe/powered-devices/products.html">https://www.ti.com/power-management/power-over-ethernet-poe/powered-devices/products.html</a></li>
<li><a href="https://www.st.com/en/power-management/power-over-ethernet-ics/products.html">https://www.st.com/en/power-management/power-over-ethernet-ics/products.html</a></li>
<li><a href="https://www.ti.com/lit/ds/symlink/tps2375-1.pdf">https://www.ti.com/lit/ds/symlink/tps2375-1.pdf</a></li>
<li><a href="https://www.analog.com/en/parametricsearch/11407#/">https://www.analog.com/en/parametricsearch/11407#/</a></li>
<li>transformers
<ul>
<li>find the replacement for SM13117EL</li>
</ul>
</li>
</ul>
</li>
<li><a href="https://www.st.com/en/evaluation-tools/steval-poe001v1.html">https://www.st.com/en/evaluation-tools/steval-poe001v1.html</a></li>
</ul>
<h2 id="closing-thoughts">Closing thoughts</h2>
<p>Please let me connect the music half of my 19" rack to the networking
half.</p>
<h2 id="footnotes">Footnotes</h2>
<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup>
<p>Don't get me wrong, a good light operator makes the show worth watching; <a href="https://ghostdad.world/">ghostdad</a> is at least 20% responsible
(a conservative estimate!) for the success of Worlds and its public perception as a <a href="https://en.wikipedia.org/wiki/Gesamtkunstwerk">Gesamtkunstwerk</a>
rather than just an album.
Also, CDJs can totally be the right instrument for certain situations!
Small sets at showcases and conventions, and places like Boiler Room come to mind.
If you want music at your party, then a DJ is the perfect musician, and CDJs are the perfect instrument.
Plus, some musicians don't <em>want</em> to learn to play an instrument, but still want to be involved in the presentation of
their music, and for that, CDJs are perfect.</p>
</div>
<div class="footnote-definition" id="2"><sup class="footnote-definition-label">2</sup>
<p>One performer even brought his own hardware rack-mount vocal
processing device, complete with a dedicated technician/operator,
who stood at the side, just offstage and spent most of the performance
turning a single knob (presumably in a very particular or technically
demanding way, otherwise he wouldn't have been flown out to do it)</p>
</div>
<div class="footnote-definition" id="3"><sup class="footnote-definition-label">3</sup>
<p>I've definitely used USB (both 2.0 and 3.0) cables that are longer than the recommended maximum length, but even
so, the maximum recommended length of an Ethernet cable is <strong>orders of magnitude</strong> longer.
There are also active USB cables, and some that transmit over optical fibers, but at great cost.</p>
</div>
<h2 id="references">References</h2>
<p><a id="1">[1]</a>
https://www.netgear.com/business/solutions/poe/overview/</p>
<p><a id="2">[2]</a>
https://www.tp-link.com/us/solution/poe/</p>
<p><a id="3">[3]</a>
https://superuser.com/questions/1105242/how-many-volts-there-are-in-poe</p>
<p><a id="4">[4]</a>
http://janaxelson.com/usb3faq.htm#ca_maximum</p>
<p><a id="5">[5]</a>
https://blog.tripplite.com/usb-cable-max-length</p>
ICC Prior ArtWed, 28 Dec 2022 00:00:00 +0000Unknown
https://wiki.jaxter184.net/icc/prior-art/
https://wiki.jaxter184.net/icc/prior-art/<p>Continued from: <a href="/icc">ICC</a></p>
<!--
## Problems with traditional crowdfunding
To inspect the flaws of traditional crowdfunding and how it differs from ICC, let's consider another example:
This is Winterton.
Winterton is a fairly popular TikTok influencer known for his intellectual close-readings of saturday-morning cartoons.
Despite having no industry experience and never having written any narrative works before, he wants to produce a shonen
anime series.
To do this, he needs a boatload of money.
His choices are thus:
* Find investors or an established company to fund production
* Save up money or take a loan
* Traditional crowdfunding (Indiegogo, Kickstarter)
The first option can be dismissed because he wants complete control over intellectual property, and the second is bad
because he doesn't want to risk his own money/credit.
Traditional crowdfunding is unique in that it is practically a donation drive.
Sure, the donors might get early access or some nice merchandise, but the actual value of those perks is significantly
lower than the cost; it must be, otherwise it wouldn't make sense from the creator's perspective to run the campaign.
So Winterton goes to KickGogo and starts a campaign for a new production company that will be making this anime series.
* fans are excited
* he claims to start production
* goes quiet
* fans are confused
* there are various reasons that Winterton could have done this
<https://nebula.tv/videos/strangeparts-i-wanted-to-love-this-revolutionary-3d-printer-micronics-sls-review> @ 33:30 "a
lot of people see kickstarter these days as a presale platform"
## ICC vs traditional crowdfunding
I believe the biggest problem with traditional crowdfunding is that the final result is often far less than what is
initially pitched.
Searching for "failed kickstarters" provides plenty of listicles outlining Kickstarter campaigns and why they failed,
and the various reasons why:
* the people running the campaigns were scammers
* the people making the product severely overestimated their abilities
* unforseen legal or logistical obstacles
* all of the above
In addition, once the funding is provided, there is little reason for
producers to be expedient in their delivery
(other than their usually nonexistent reputation),
and it is difficult, if not impossible, for funders to get their money
back if the producer takes too long or disappears.
### Prelaunch
https://www.reddit.com/r/writerDeck/comments/1c9zbm5/the_byok_bring_your_own_keyboard/l0r3453/ they claim to not like
preorders, but
https://prelaunch.com/projects/mech-wallet-simple-joy-for-men-mech-wallet-push-to-reveal-fidget-for-fun
for many products, the aim is to funnel people into kickstarter at a discount, which is honestly worse because it gives
them less money, making it even less likely that theyll succeed.
https://www.reddit.com/r/writerDeck/comments/1c9zbm5/the_byok_bring_your_own_keyboard/l11ubwh/ kinda addressed here, but
in an unsatisfying and kinda weasel-y way
-->
<h2 id="bounties">bounties</h2>
<p>Bountysource is a platform that allows bounties to be assigned
to feature requests in an open-source code repository
like GitHub
(it currently does not support sr.ht, GitLab, Codeberg, or Bitbucket).
Bountysource differs from incremental converse crowdfunding by:</p>
<ul>
<li>allowing both users and developers to make bounties for a project,
rather than just the project owners</li>
<li>acting as an escrow agent between feature requesters and devlopers
(ICC explicitly does not hold any money,
and allows funders to withhold funding after the deliverable is published)</li>
</ul>
<p>Other examples:</p>
<ul>
<li><a href="https://github.com/obsproject/obs-studio/wiki/OBS-Project-Bounty-Program">https://github.com/obsproject/obs-studio/wiki/OBS-Project-Bounty-Program</a></li>
<li><a href="https://contest.com/">https://contest.com/</a></li>
<li><a href="https://docs.libretro.com/development/bounties/">https://docs.libretro.com/development/bounties/</a> (not a separate example, but discusses Bountysource)</li>
<li><a href="https://www.cadsketcher.com/cad-sketcher-bounty-board">https://www.cadsketcher.com/cad-sketcher-bounty-board</a></li>
</ul>
<p>There have been quite a few arguments over the years against using bounties:</p>
<ul>
<li><a href="https://twitter.com/mitsuhiko/status/1703452194429690346">https://twitter.com/mitsuhiko/status/1703452194429690346</a></li>
<li><a href="https://ziglang.org/news/bounties-damage-open-source-projects/">https://ziglang.org/news/bounties-damage-open-source-projects/</a>
<ul>
<li>A point-by-point analysis of the ziglang blog post is available <a href="/icc/concerns#ziglang-bounty-post">in this supplementary
reading</a></li>
</ul>
</li>
<li><a href="https://wiki.gnome.org/Attic/BountiesDiscussion">https://wiki.gnome.org/Attic/BountiesDiscussion</a></li>
<li><a href="http://dneary.free.fr/gimp_bounties.html">http://dneary.free.fr/gimp_bounties.html</a></li>
<li><a href="https://discourse.gnome.org/t/bounty-development/5395/6">https://discourse.gnome.org/t/bounty-development/5395/6</a></li>
</ul>
<h2 id="patronage">patronage</h2>
<p>I am personally a fan of the feudal patron system where individuals with large amounts of money (like the Medicis) fund
artists (like Mozart or DaVinci) to produce art.
These days, it doesn't really make sense in the modern world where we have the labor specialization to fill important
jobs like food production efficiently with just a few people, leaving pretty much everyone else to do whatever they
want, and wealth consolidation is generally frowned upon.
But given the situation at the time, I think it's cool that rich people (and churches) had an appreciation for the
value and utility of the fine arts.
Rich people these days have no sense of noblesse oblige.</p>
<p>That being said, it is neither democratic nor sustainable for the power of patronage to be so densely concentrated to
so few people.</p>
<p>There are some small-scale examples of classical patronage, such as art commissions and feature sponsorships.
<a href="http://ikiwiki.info/consultants/">http://ikiwiki.info/consultants/</a> describes their model as a way to "fund the development of a specific feature",
which is frankly very similar to the proposal of ICC, but without being incremental, converse, or crowdfunded.</p>
<h2 id="crowdfunded-patronage">crowdfunded patronage</h2>
<p>To address the issues with feudal patronage, platforms like <a href="https://comradery.co/">Comradery</a>, <a href="https://ko-fi.com/">Ko-fi</a>, and <a href="https://www.patreon.com/">Patreon</a> take this model and
combine it with crowdfunding.
These platforms are a pretty good way for creators with a decent following to get money from the their audiences.
In general, I am in support of these platforms (Comradery and Ko-fi in particular),
and I believe these types of crowdfunding will still have a place in the future,
but a qualm I have with them is the issue of cross-subsidization.</p>
<!-- TODO: use koan sound instead, because their output is completely detached from their music, while
insaneintherain's rewards are necessary byproducts of producing videos https://www.patreon.com/koansound
on the other hand, insaneintherain uses per-video payments instead of koan sounds monthly (though insaneintherain now
uses monthly as well, and patreon as a whole is sunsetting per-release) -->
<p>To focus on a particular example,
let's look at the Patreon of insaneintherainmusic,
who makes Jazz (and Jazz-adjacent) covers of video game music.
Funders pay a set price per cover video,
which suggests that that is the product they are paying for.
However, the patron tiers tell a different story;
depending on how much a funder pays per video,
they receive perks like
access to special patron-only communication channels,
early previews,
and free lossless music downloads.
In terms of incentive,
it is clear to me that these perks,
and not the actual videos,
are what these patrons are paying for.
After all, the videos will come out regardless of whether
I, as an individual, choose to give $5, $1, or $0.
For producers who don't have this kind of tier,
Patreon is essentially a donation platform.</p>
<blockquote>
<p>UPDATE: Patreon is phasing out per-creation billing, and will discontinue it by November 2025:
<a href="https://news.patreon.com/articles/understanding-apple-requirements-for-patreon">https://news.patreon.com/articles/understanding-apple-requirements-for-patreon</a></p>
</blockquote>
<p>Ko-fi is similar, but only allows monthly videos
(rather than per-deliverable),
and also has infrastructure for
shops,
commissions,
and one-time donations.
While their language is more clear about the fact that you're paying for the perks,
some users don't have perks,
or the perks are very low in value
compared to the value of their creative or technical output,
which, again, means that this is essentially a donation platform
(albeit a very successful one).</p>
<h2 id="case-study-philosophy-tube-reading-list">case study: Philosophy Tube reading list</h2>
<p>Philosophy Tube is a YouTube channel that aims to "give out a philosophy degree for free" (paraphrased).
There used to be a book wishlist where people could buy books for the channel, and it is explicitly advertised that
buying books from this list could have a big influence on the topic of future videos.
There are some similarities to ICC: the artist proposes topics for future works, and people can use
donations to steer the direction of the channel.
There are, however, a few critical differences:</p>
<ul>
<li>not converse: payment comes before the deliverable</li>
<li>there is no guarantee that an episode will be published that uses
the book</li>
<li>funding is one-to-one; rather than being selected by a crowd, it is
selected by an individual</li>
<li>the money goes to Amazon</li>
<li>it does not scale very well (limited by reading speed)</li>
</ul>
<p>I believe that this wishlist has been abandoned, and nowadays, the channel is mainly funded through Nebula and Patreon,
which is understandable given the last bullet point and the size of the channel's audience.</p>
<h2 id="commissions">commissions</h2>
<p>It's great that technology has made it so easy for me to commission a work of art from an artist I like.
However, commissions are pretty expensive, and are generally heavily restricted by copyright.
This leads to a less extreme version of the issue with patronage, where only people who can afford high prices for a
luxury product can influence the creative output of artists.
In my opinion, the critical downside of this is that commissions are a pretty inefficient use of artist time; since a
digital product can be copied infinitely, there's a lot of missed revenue from only being able to sell the product to
one person.</p>
<p>Commissions can be done fairly easily even without a dedicated platform, but Fiverr is a site that specializes in
commissions.
I've used Fiverr to sell work in the past, and it's fine, but it's not really creator-focused, and the quality is a
bit shoddy.</p>
<p>As a sidenote, it may be useful going forward to think of ICC as a
combination of commissions and crowdfunded patronage.</p>
<h2 id="donation">donation</h2>
<p>Donation is a great way for a funder to show their appreciation for
the creator in a tangible and beneficial way. However, the thing that
makes this method of funding so meaningful, namely the lack of direct
incentive for the funder, is exactly what makes it an unreliable
and disproportionate form of funding. Unreliable because whether a
person does or doesn't donate is difficult to predict and influence,
and disproportionate because better quality or quantity doesn't have
a direct influence on the donation total.</p>
<p>That being said, soliciting donations is often sufficient and sustainable for maintaining a project, even large
projects like Wikipedia.
Or rather, <em>especially</em> large projects like Wikipedia.</p>
<p>Quick sidenote: donations to the Wikimedia Foundation do not go to the volunteer editors of Wikipedia, many of whom
believe that it is important that Wikipedia editors don't get compensated in order to prevent conflicts of interest and
perverse incentives.
A small fraction of the donations go to server infrastructure, but most of the money goes to employees of the
foundation, whose job is, as far as I can tell, to solicit more donations for the Wikimedia Foundation.
A "Bullshit Job" if I've ever heard one.</p>
<!--
Additionally, donation makes kind and generous people pay for something that everyone, including those too stingy to
donate, has access to.
would it be too libertarian-coded to cite the free-rider problem?
how do libertarians feel about donation?
-->
<p>Examples:</p>
<ul>
<li><a href="https://opencollective.com/">OpenCollective</a> - a donation platform with a focus on transparent spending</li>
<li><a href="https://liberapay.com/">LiberaPay</a> - a donation platform where funders can provide periodic donations to creators
who contribute to the commons, whether they "make free art, spread free knowledge, [or] write free software"</li>
</ul>
<h2 id="advertising-sponsorships">advertising/sponsorships</h2>
<p>I'm not a fan of advertising.</p>
<p>I don't like that I, as a producer, have to choose between ethics
and money. Sure, I have the option to just ignore sponsors if I value
ethics over money, but say I were a 3D printing video producer, and
other people in my space are getting paid to shill some 3D printer
filament while I have to pay for my own, plus they can use that extra
funding to make their product better. Similarly to the previous case,
I'm at a disadvantage as a result of my stubbornness.</p>
<p>I don't like that I, as a consumer, can be so easily influenced by a
third party to buy their product. This ability to influence me also
gives them a financial incentive to gather data on my behavior, ideally
without me knowing. Knowledge is power, and through advertising, that
power can be leveraged to move money from my hands to theirs. Also I
think advertisements are annoying and I have yet to hear of a single
person who believes otherwise.</p>
<p>I don't like that I, as a third party, can get a producer to leverage
the trust they have with their audience to make me money. If I don't
advertise and my competitors do, and my product is not good enough to
compensate for the difference in brand recognition, then I lose sales.
I'm not sure what the ideal method of informing consumers is, but I
think the current state of advertising is definitely not it.</p>
<p>To be clear, "advertising sponsorships" refers to a third party giving
a producer money in exchange for promotion of the third-party's good
or service to the producer's audience. Affiliate links are often used
to track the effectiveness of these sponsorships. Systems like GitHub
Sponsors are more akin to crowdfunded patronage or donation.</p>
<p>There are some benefits to advertising though, and these benefits make
it a very enticing (and difficult to replace) system for the people that
use it.
The main benefit (for the advertiser) is that participation is
extremely passive.
Advertising requires no login, no payment information, no calls
to action.
Sometimes can even happen without the viewer noticing with techniques
like product placement (and that lack of awareness is, in my opinion,
a big part of the problem).</p>
<h2 id="microtransactions">microtransactions</h2>
<p>In a sense, it is the opposite of advertising, in that participation is much more deliberate, and rather than targeting
a wide range of people, it targets a very small subset of people who pay large amounts (known as <a href="https://en.wikipedia.org/wiki/Free-to-play#Comparison_with_traditional_model">whales</a>), and
constantly re-affirm their committment.
Whales often make up the large majority of income for free-to-play games, and subsidize development, allowing even huge
online games like Fortnite to be widely accessible at no cost or burden to the player.
"From each according to ability, to each according to need", if you will.</p>
<p>I also put things like <a href="https://web.archive.org/web/20240202005633/https://www.tiktok.com/creators/creator-portal/en-us/getting-paid-to-create/live-gifting/">TikTok's live gifts</a> and Twitch bits in this category, but they are a notably
distinct subset that encourages its own behaviors.</p>
<h2 id="collectibles">collectibles</h2>
<p>There is a class of item which has near-zero utility and cost to manufacture despite having a comparatively high price.
Items in this category include signed portraits, art books, and vinyl records (way more people buy them than people who
actually listen to them; myself included).</p>
<p>The primary issue with this class of product is the fact that they only provide funds by cross-subsidizing other
offerings.
With this useless token whose value is practically only influenced by how much other people are willing to pay for it,
you can take a token like a vinyl record that costs $6 to produce, and sell it for $100 (or more).
Its value comes primarily from its rarity, which limits the amount of these that you can produce before you start
losing money, but for most people, it's worth making a few of these tokens to cross-subsidize their actual product,
even if the collectors make far more money than the producers.</p>
<h3 id="nfts">NFTs</h3>
<p>Though they were a passing fad that pretty much disappeared as soon as they started being regulated, NFTs were
advertised as a solution for this type of problem, and I think they're at least worth acknowledging.
Non-fungible tokens were a means of abstracting a concept or non-tangible item into a tradeable digital token, allowing
people to effectively trade it with each other as they would a unique physical item.
There was a period of time when people brought these up whenever I mentioned ICC, but it seems those days have passed.</p>
<h2 id="open-source-pledge">Open source pledge</h2>
<p>Aside from the fact that it primarily targets maintainers of infrastructural FOSS projects (which <a href="/icc/concerns#does-not-solve-the-foss-maintenance-crisis">ICC is not very
well-equipped to serve</a>), the <a href="https://wiki.jaxter184.net/icc/prior-art/osp">Open Source Pledge</a> (OSP)
seems in some ways similar to ICC. Notably, it follows the same converse principle of "if you produce it, I will fund
it", and is also nominally "pledge"-based.</p>
<p>However, because of the perspective that the leadership is approaching it from, and the shame-based nature of the
campaign, I am skeptical in the OSP's ability to effectively solicit donations.
My criticisms of the OSP can be summed up with two observations:</p>
<ul>
<li>the only difference between the OSP and a voluntary periodic donation is the branding</li>
<li>the money is coming from large companies</li>
</ul>
<h3 id="difference-to-voluntary-periodic-donation-or-rather-the-lack-thereof">Difference to voluntary periodic donation (or rather, the lack thereof)</h3>
<p>The progenitor of the Open Source Pledge (OSP) is Chad Whitacre's article <a href="https://wiki.jaxter184.net/icc/prior-art/osiar">"Open Source Is a Restaurant"</a>, where
they spend most of the article giving a long-winded and, in my opinion, ultimately irrelevant analogy about different
models for charging for food.</p>
<p>They criticize grants because there are "hoops" that companies have to jump through to get them, but running a whole
marketing campaign to shame corporations seems to me like a lot more work than navigating the grantwriting bureaucracy.
Plus, I imagine this campaign will have to be repeated indefinitely, with much more momentum and support behind it than
there is now, because, as the OSP campaign's own advertisements point out, tech companies are extremely stingy, and
they never seem to feel shame in that fact.
Heck, there are tech companies have been directly targeted for their active role in genocide, and I haven't seen any
meaningfully change their behavior on that front.</p>
<p>Not only that, but I don't see any recommended mechanism for choosing which projects to donate to in the OSP
literature, which indicates to me that it's up to the companies to decide?
In which case what you've essentially done is replace a grant writing team with an entire marketing departement that is
trying to convince the companies depnding on your project to donate to you instead of one of their other dependencies.
Not only is this a more expensive hoop, but it is arguably more annoying than simple grant writing because the target
audience is every dependent company and their developers, rather than just the entity distributing the grant.
In contrast, ICC, rather than pitting different projects against each other, its more that different tasks under the
same project are competing with each other, and assuming all tasks share a marketing budget, it doesn't really make
sense for them to advertise against each other.</p>
<pre class="z-code"><code><span class="z-text z-plain">TODO: clarify why that's the case, stick in the anecdote about how
</span><span class="z-text z-plain">tobacco companies agreed to an advertising ban because something
</span><span class="z-text z-plain">something tragedy of the commons, and more profit when they didnt have
</span><span class="z-text z-plain">to compete with each other in advertising
</span></code></pre>
<p>Perhaps I'm approaching this with too much naïvité, but for the type of FOSS project that this targets, why not just
threaten a strike?
The maintainers could just say "this is how much money we need to sustain the project, and if we don't get it, then we
will stop maintaining it until we do".
Essentially, that would put the burden on the software companies to figure out a good way to fund it.</p>
<p>As it stands, the only companies that will choose to accept the OSP are those that feel honor, or ethical obligation,
creating an artificial financial disadvantage for those companies (compared to those with no such honor or ethics).
One could argue that this downside is worth being able to fund FOSS projects, but I'm skeptical whether that's the
case.</p>
<h3 id="the-money-comes-from-large-companies">The money comes from large companies</h3>
<p>This might be unintuitive, but I think money should come from individual users more than large companies.
Or at the very least, the amount that large companies give to a project should be a small proportion of the total.
Taking the US government as an example, sources of money have a huge influence on the decisionmaking of an
organization, even without an explicit quid pro quo, so collecting money from many smaller donors is, from my
observation, important for making sure a software project remains beholden and accountable to its actual users instead
of just one giant user.</p>
<h2 id="thanks-dev">thanks.dev</h2>
<p>Sentry, one of the main participants of the OSP, is also one of the main donors on the platform <a href="https://thanks.dev">https://thanks.dev</a>,
another donation platform.
However, unlike the OSP, thanks.dev at least tries to implement a mechanism for determining how money should go to
dependencies.
The main flaw in this is that this creates a metric that can be gamed.
While I'm hopeful that this will generally not be the case, consider this imaginary example:</p>
<p>Assuming the metric is based on number of calls into a API.
A developer has a choice to implement something as either one call or two, and by making it two calls, they can
increase the calls from each of their dependent libraries by an average of 3.
Assuming each dependent has roughly 1000 API calls to various libraries before the new changes, then using two calls
represents about a 0.3% increase in the metric.
If they have 60 dependents with an average donation pool of $100,000 per year:
$${60 * 100,000 * 0.3% = 18,000}$$
That's a roughly $18,000 per year increase in that one choice.
Of course, these numbers are complete spitballed fabrications, but hopefully this shows that just one small change
could potentially lead to huge changes in income, making it very enticing to make your library subtly more cumbersome
and verbose to use than it otherwise would be.</p>
<p>With careful decisions on how the metric is calculated, I think it's possible for this to be mitigated enough that it's
a net positive, but it does end up being a bit of an arms race.</p>
<p>However, unlike many other algorithms, the ICC algorithm works under the assumption that all involved understand its inner
workings, and in fact, only works if people understand how it works.
This hopefully makes it more stable and robust, and less prone to change.</p>
<p>Some other attempts at similar systems:</p>
<ul>
<li>https://docs.drips.network/
<ul>
<li>https://www.drips.network/blog/posts/radworks-gives-1m-to-foss-dependencies-with-drips</li>
</ul>
</li>
<li>https://tea.xyz/
<ul>
<li>had a scandal a while back where people tried to claim project income by adding <code>.yml</code> files to FOSS repositories.</li>
</ul>
</li>
</ul>
<p>Notably, both of these are cryptocurrency-centric projects, and for the same reason that I think large companies should
not be the primary funding source for FOSS projects, I also think that speculative cryptocurrency bubble money is also
not healthy, with the added downside of being even less financially sustainable.</p>
<h2 id="todo">TODO:</h2>
<ul>
<li>TikTok yum yum ice cream girl
<ul>
<li>she's not doing it because she loves doing it and people want to see it.</li>
<li>she's doing it because tiktok has created a system and she's figured out a way to game it.</li>
<li>my tinfoil hat hypothesis is that the thing people like about it is the sense of control and connection (the instant
gratification helps).</li>
<li>how that concept be reframed into something that actually works for the common good?</li>
</ul>
</li>
<li><a href="https://artfight.net/donate">https://artfight.net/donate</a>
<ul>
<li>not sure how to categorize this is because its a donation, but there are goal tiers that collectively paywall
features (i.e.</li>
</ul>
</li>
<li>bitwig's update model
<ul>
<li>explicitly not a subscription</li>
<li>i havent bought a bitwig update in like 5 years because i dont use it as much, but im glad i still can</li>
<li>how common is it for people to just chain updates back to back instead of just waiting?</li>
<li>how do the users feel about it? how do they perceive it?
<ul>
<li>do they consider it "free updates that come with the update they bought"?</li>
<li>or just "buying a year of updates"? (probably this one)</li>
</ul>
</li>
</ul>
</li>
<li>group buys / presale
<ul>
<li>massdrop</li>
</ul>
</li>
</ul>
ICC ScoringWed, 28 Dec 2022 00:00:00 +0000Unknown
https://wiki.jaxter184.net/icc/scoring/
https://wiki.jaxter184.net/icc/scoring/<p>Read this first: <a href="..">ICC</a></p>
<!--
## Motivating theory
The base concept motivating the creation of ICC is that data alone has
almost no value. It can be trivially copied and distributed at almost
no cost. Where the value comes from is the fact that someone organized
it, whether by writing, debugging, maintaining, sketching, inking,
coloring, modeling, posing, animating, designing, etc. The work comes
not in generating the bits that define the file on your computer, but
rather by making the decisions that refine those bits into something
meaningful.
-->
<!--
https://viralinstruction.com/posts/defense/
Software is not unlimited
As an artifact, software is quite different from the physical creations
of other crafts: Producing it consumes no raw materials. It requires no
specialized tools to manufacture even the highest quality code. The
product has no weight, and its physical distribution is almost
effortless. There is almost no cost to producing millions of copies and
shipping them all around the world.
So, without these constrains, is software unbounded, infinite? No, it
is held back by other constrains. Sometimes software is constrained by
the physical capability of the machines it runs on, disk space, memory
usage or speed of computation. I don't want to discount these physical
constrains: After all, much of what I've written on this blog is about
performance. But mostly, software is bounded by its creation process:
Programmers have limited time to create, and especially limited time to
maintain, code.
-->
<h2 id="expected-donation-score">Expected Donation Score</h2>
<!-- casual version:
One potential formula for this metric is $$I_0 = P/O$$, where
$$I_0$$ is the index, $$P$$ is the total amount a user has paid,
and $$O$$ is the total amount a user has offered. But alas! If the
fraudulent funder has previously made a full payment of an offer of $1,
then they have a maximum reliability index, and we're back to square
one.
To fix this, let's scale the index by the amount paid: $$I_1 =
P*P/O = P^2/O$$. Now, while the fraudulent funder still
has an index of 1, a genuine funder who has paid $120 of their proposed
$200 over the past 2 years has an index of 72, greatly outshining the
fraudulent funder.
-->
<p>In my opinion, the most obvious concern is: if a donor can withhold
their donation, what's preventing them from making a very large pledge
and not paying after the work is published?</p>
<p>My proposed solution to this is to assign each donor a score that would roughly predict the amount that a given donor
will give based on their pledge amount and past donations.</p>
<p>Lets consider an example:
A fraudulent donor made a large pledge for proposal <code>A</code>
that they don't intend to pay.
I, as an artist, am choosing what to work on, and I see that
proposal <code>A</code> has a much higher payout estimate than proposal
<code>B</code>, and work on <code>A</code> as a result.
Once I release my work, I end up not getting paid nearly as much as I
would have for <code>B</code>, which, in this example, has mostly genuine pledges
(though I cannot know that at this point in time).</p>
<p>This is where the "Expected Donor Score" (EDS) comes in.</p>
<p>One potential formula for this metric is
$${S_0 = \frac{D}{P}}$$
where ${S_0}$ is the coefficient,
${D}$ is the sum of a donor's past donations,
and ${P}$ is the sum of a donor's past pledges.
But alas!
If the fraudulent funder has previously fulfilled their pledge with a
donation of $0.01,
then they have a maximum EDS,
and we're back to square one.</p>
<p>To fix this, let's scale the EDS by the amount paid:
$${S_1 = D\frac{D}{P} = \frac{D^2}{P}}$$
Now, while the fraudulent funder still has an EDS of 1,
a genuine funder who has paid $120 of their proposed $200 over the past 2 years
has an EDS of 72,
greatly outshining the fraudulent funder.
72 is a really big number though,
and over the course of many years,
it could be reasonable for a third donor to spend thousands of dollars total,
making their "influence" many orders of magnitude higher
than even the donor who has spent $120.</p>
<p>Let's try adding a time scaling factor.
$${S_2 = \sum_i e^{-t_i} \frac{D_i^2}{P_i}}$$
where each payment/offering pair is scaled by how recent it was,
${t_i}$ being the amount of time that has passed
since payment ${i}$ in months (an arbitrary time unit).
Doing some napkin math,
it seems like this helps with balancing
the influence of long-term donors vs medium-term donors.
We could add a few scaling and offset factors here and there,
but for the most part,
this seems to solve the problem at hand.</p>
<p>It's worth noting that this is definitely not the only solution to the stated problems, and almost certainly not the
best!
Perhaps it would be worth allowing artists to mess with this formula in order to let them use whatever they believe
best predicts payouts.</p>
<p>One last potential issue I want to address is how fund offering amounts
scale the EDS.
Someone who has paid $120 over the course of a year
suddenly declaring a $2000 fund offering is a bit suspicious,
so we should incorporate that into our final calculation somehow.
However, I think it's fine to leave the EDS as it is for now,
and figure that out later if it actually becomes a problem.</p>
<p>As it stands now, the EDS roughly maps to the amount of money a donor is likely to provide based on all of their past
donations.</p>
<p>The calulation of this score is critical to the functionality of converse crowdfunding, as it is what allows ICC to
still work even though donors are allowed to retract their entire pledge after the work is published.</p>
<h3 id="todo">TODO</h3>
<p>Consider the following:
a fraudulent artist makes a proposal
that they do not plan on fulfilling,
and some donors declare pledges.
In this example,
the artist fraudulently "publishes"
a trivial or low-quality work
(say, a blank white image file rather than a page of a comic)
just to claim the pledges.
How can the system be built to minimally punish these donors?
It's easy to say that the donors should have been smarter,
and should either pay the amount they offered
or retract their entire pledge
and just take the hit to their EDS,
but ideally,
the system should try its best to nullify any incentive
that would make the donors want to pay the fraudulent artist.
In the long run,
this type of fraudulent artist wouldn't last long,
but one could easily imagine a washed-up one-hit-wonder artist
making one last grand proposal,
to which many donors offer pledges,
just to run into the above situation
where the work is low-quality or nonexistent,
intended only to trigger the donation invoice.</p>
<p>Could the EDS be adjusted to mitigate these
scenarios? Would it require a totally separate mechanism?</p>
<p>In this case,
perhaps we could normalize the offerings?
If almost every funder withholds
part or all of their compensation,
we should be able to use that
to quantitatively measure
the extent to which the producer "failed" to deliver.</p>
<p>This might be solved by making the EDS unique to each funder-creator relationship, since it's unlikely that the funder
cares about their EDS when the creator has tanked trust.
However, what about the case where there's plausible deniability, and there might be hope for the creator to make
something legitimate in the future?</p>
aliqotFri, 13 Dec 2019 00:00:00 +0000Unknown
https://wiki.jaxter184.net/mus-tec/aliqot/
https://wiki.jaxter184.net/mus-tec/aliqot/<p>This was a project I did in my undergraduate studies to build a digital music controller abstractly based on the bass
guitar.
The main sensing mechanism was a long row of capacitive touch sensors, intended to sense where along the neck the
player was touching, and how much of their finger was touching the surface.
In the end, this barely worked, and was a bit clunky to use, and I therefore didn't end up building a full-scale
version of the device.</p>
<p>Prior art:</p>
<ul>
<li>Someone who made something cooler and more useful and more concretely
similar to a bass: <a href="https://youtu.be/4m1YorzgxkY">https://youtu.be/4m1YorzgxkY</a></li>
<li>A commercially available device that (I think) uses the same sensing
technology as the previous example: <a href="https://maywadenki-os.stores.jp/items/5eef5b521829cd47b90668be">https://maywadenki-os.stores.jp/items/5eef5b521829cd47b90668be</a></li>
</ul>
<h2 id="capacitive-touch"><a href="https://gitlab.com/aliqot/cstrip">Capacitive touch</a></h2>
<p><figure class="half-width">
<img
class="fig"
src="/images/aliqot-cstrip-front.jpg"
alt="front of aliqot capacitive sensing pcb"
>
<figcaption>Front</figcaption>
</figure>
<figure class="half-width">
<img
class="fig"
src="/images/aliqot-cstrip-back.jpg"
alt="back of aliqot capacitive sensing pcb"
>
<figcaption>Back</figcaption>
</figure>
</p>
<p>Capacitive touch is one of those things that I had heard a lot about,
but never really tried myself. I've had a little capacitive sensing
test PCB sitting around for a while, but I never got around to using
it. The general idea is that a conductive pad and a finger of varying
distance constitutes a capacitor, and by measuring the capacitance
at the pad, you can find out how far the finger is from the pad, or
how much of the finger is covering it. In retrospect, it was pretty
straightforward, but I definitely feel much more comfortable now that I
did in the earlier stages of this project.</p>
<h3 id="555">555</h3>
<p><kicanvas-embed
src="/kicad/aliqot-555.kicad_sch"
controls="basic"
theme="kicad"
>
</kicanvas-embed></p>
<p>My first approach to measuring the capacitance was an analog circuit. I
am most definitely not an analog person. The only analog signals I feel
comfortable with are those that either come out of a digital-to-analog
converter or one coming into an analog-to-digital converter. However,
since the goal of this project was to learn things, getting out of my
comfort zone seemed like a good idea.</p>
<p>The idea behind this approach was to set up a 555 timer with a diode
and some resistors such that it would pull up the voltage with some RC
constant and pull it down with a different RC time constant. I thought
that if my clock speed was high enough, I could get a pretty smooth
continuous analog signal that I could measure with an ADC. While this
did end up working, the range of voltages was too low to be useful, and
led to a lot of extra circuitry that could have been avoided by using a
different method.</p>
<h3 id="pulse-and-read">Pulse and read</h3>
<p>The more traditional approach to capacitive touch sensing is to connect
the pad to an ADC, a GPIO output, and a pull-down resistor. By turning
the GPIO on, then setting it to an input (floating), the voltage of
the pad will decay at different speeds depending on the capacitance. I
thought I was being clever by leaving the diode in from the previous
approach, but it turned out to make my measurements a little less
accurate, so I probably should have removed it. This is the approach
I ended up using, and I think it's accurate enough for decent analog
measurements.</p>
<h3 id="next-steps">Next steps</h3>
<p>Something that I'm less than satisfied with is the response curve of
the pad. It seems to basically max out as soon as the pad is touched,
and I was hoping that it would be more of a gradual change depeding
on how much of the pad was covered. To solve this, I could either
change the shape and size of the pad or decrease the resistance of the
pull-down resistor.</p>
<h2 id="sound-generation">Sound generation</h2>
<p><figure class="half-width">
<img
class="fig"
src="/images/aliqot-sound-front.jpg"
alt="front of aliqot sound generation perfboard breakout"
>
<figcaption>Front</figcaption>
</figure>
<figure class="half-width">
<img
class="fig"
src="/images/aliqot-sound-back.jpg"
alt="back of aliqot sound generation perfboard breakout"
>
<figcaption>Back</figcaption>
</figure>
</p>
<p>The rough acoustic equivalent of the digital instrument that I
hope to design is a bass guitar. In my experience, the easiest
way to acoustically simulate a bass guitar sound is to send a PWM
waveform through a low-pass filter into a saturator. For now,
the plan is to start with the PWM wave. A stretch goal was to
design the sound processing in <a href="https://faust.grame.fr/">a music programming language called
Faust</a>, but like many of my stretch goals,
I never really got to that point. I ended up learning Faust anyway
for fun, and I think it's really cool, especially since it compiles to
C++. Getting a variable pitch PWM output from the TM4C microcontroller
was pretty straightforward, and I didn't really run into any huge
problems setting that up.</p>
<p>A secondary benefit of using PWM rather than an ADC is that
to amplify it, all I need is a single transistor.
I eventually want to have more flexible audio outputs, so I'll need
a real amplifier in the future, but for now, I've just arranged 2
<a href="https://www.onsemi.com/pub/Collateral/2N3903-D.PDF">2N2304</a> transistors in a <a href="https://en.wikipedia.org/wiki/Darlington_transistor">Darlington pair</a> configuration.</p>
<h2 id="switches"><a href="https://gitlab.com/aliqot/column">Switches</a></h2>
<p><figure class="half-width">
<img
class="fig"
src="/images/aliqot-column-front.jpg"
alt="front of aliqot keyswitch pcb"
>
<figcaption>Front</figcaption>
</figure>
<figure class="half-width">
<img
class="fig"
src="/images/aliqot-column-back.jpg"
alt="back of aliqot keyswitch pcb"
>
<figcaption>Back</figcaption>
</figure>
</p>
<p>In the time I spent thinking about instrument interfaces and which ones
work and which ones don't, one of the conclusions I came to was that
while pitch control is generally easier to implement as a non-tactile
experience, rhythm, or more generally, volume, is almost necessarily
tactile. For example, a violin can be split into the fingerboard,
which controls pitch, and the bow, which mostly controls volume. The
fingerboard feels about the same at low pitches as it does at higher
pitches. On the other hand (pun intended), the bow transmits the
resistance of the hairs against the string, increasing as you apply
more pressure or move the bow faster. The piano is similar in that
different pitches mostly feel the same to play, but different volumes
feel different (assuming you're only playing with one finger). This
distinction is true to some extent for most instruments, though it is
unclear if that is due to how instruments have evolved over time or if
it is a physical restriction of acoustic instruments. Either way, what
is most intuitive at this point in time is for rhythmic elements to be
tactile, which is why I've included switches as the sound triggering
element of the instrument.</p>
<h3 id="shift-registers">Shift registers</h3>
<p>I'm usually pretty good about reading datasheets. I'm not sure exactly
what happened, but when I was making my PCB, I thought that the pin
labelled DS (for Data Serial) was the serialized output of the shift
register. It turns out that it's the input, which I figured out after
not getting any output until accidentally probing the adjacent pin. I
think if the serial in and serial out pins weren't next to each other,
I would have spent much longer trying to figure out what was wrong.</p>
<h3 id="qfn">QFN</h3>
<p>The more keen-eyed readers will notice that the above PCB
is unpopulated except for the switches and one integrated
circuit. This is because it is a more recent revision
that I haven't yet gotten to work.
The main difference between this revision and the last one is that I
swapped out the <a href="https://en.wikipedia.org/wiki/Small_outline_integrated_circuit">SOIC</a> chip for a <a href="https://en.wikipedia.org/wiki/Quad_Flat_No-leads_package">QFN</a> one.
This allowed me to make the board much smaller, but also much more
difficult to debug. Because there is no way to probe whether the pad on
the PCB is conducting to the pad on the IC, I have been having trouble
figuring out why the boards are having issues being programmed. For
now, I've decided to ignore it because I have a working board with
essentially the same circuit.</p>
<h2 id="mechanical-design">Mechanical Design</h2>
<p><figure class="half-width">
<img
class="fig"
src="/images/aliqot-mch-front.jpg"
alt="front of aliqot capacitive sensing bracket"
>
<figcaption>Front</figcaption>
</figure>
<figure class="half-width">
<img
class="fig"
src="/images/aliqot-mch-back.jpg"
alt="back of aliqot capacitive sensing bracket"
>
<figcaption>Back</figcaption>
</figure>
</p>
<p>Pictured above are two revisions of the 3D printed bracket part
that I made to hold the capacitive sensing PCB, both made in
<a href="https://www.freecadweb.org/">FreeCAD</a>.
The part was built to fit onto a piece of 2020 aluminum extrusion.
I'm a big fan of aluminum extrusion because it's very easy to use.
In this case, all I have to do is cut it to an arbitrary length and
attach stuff to it.
I don't even need a second piece.
I've had issues in the past where the structures I've build were too
weak to hold their own weight, but because this is lighter and simpler,
I didn't run into that problem.
The main feature of the newer revision is that
it has registration holes to align the loose end of the bracket with
the bracket below.
While the 3D printed part is very rigid (material is <a href="https://en.wikipedia.org/wiki/Polyethylene_terephthalate">PETG</a>), even a
small change in alignment could tear the pads off one of the boards
since they are soldered together on the back.</p>
<h2 id="the-framework">The framework</h2>
<p>The goal was to create a framework that would allow hot-swappable
modules to send continuous streams of low latency analog-to-digital
converted data. The details of the communication framework are probably
enough to fill an entire other blog post. In fact, I probably will
write another one at some point that goes into why I made all the
design decisions I did. For now, though, I'll just outline how it
works.</p>
<p>When the modules are initially connected, they are detected by their
parent module and registered with the main module (in this case,
the TM4C that is generating the sound). This registration value is
prepended to all of the communication from this module to tell the main
module where each set of data is coming from.</p>
<p>The first byte of each communication from a child module to its parent
is the number of modules that are sending data in a given set. Each
module's data then follows one after another. The first byte of a
module's data is its address, and the second byte is how many values
are being sent as data. The switch modules send 1 byte and the
capacitive sensing modules send 8 bytes.</p>
<p>The example below is what is sent to the TM4C from a capacitive
sensing module, which is connected to a switch module such that TM4C <-
capacitive_sensing <- switches.</p>
<pre class="z-code"><code><span class="z-text z-plain">byte #: 1 2 3 4 5 6 7 8 9 10 11 12 13 14
</span><span class="z-text z-plain">hexadecimal value: 0x02 0x01 0x08 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x02 0x01 0x0A
</span></code></pre>
<p>Byte descriptions:</p>
<ul>
<li>1st byte: # of modules</li>
<li>2nd byte: Address of the first module</li>
<li>3rd byte: # of bytes of data in the first module</li>
<li>[4:11]th byte: Data</li>
<li>12th byte: Address of the second module</li>
<li>13th byte: # of bytes of data in the second module</li>
<li>14th byte: Data</li>
</ul>
<p>Byte addresses are one-indexed (as opposed to <a href="https://en.wikipedia.org/wiki/Zero-based_numbering">zero-indexed</a>)and all data has 1 added to it because a 0
byte indicates that data is not yet ready to be send. I wouldn't need
to do this if I was using a multi-leader bus, but I'm not using one, so
I do need to. Either that or I would have to somehow indicate whether
or not a byte is ready to be sent.</p>
<h3 id="circular-buffers"><a href="https://en.wikipedia.org/wiki/Circular_buffer">Circular buffers!</a></h3>
<p>The way the framework is set up, I need two buffers
for input and output data. Because these buffers are
continuously filled and emptied as a FIFO queue, it made sense
to make it a circular buffer. In the repository, this is implemented in
<a href="https://gitlab.com/aliqot/cstrip/blob/master/src/modport.c"><code>modport.c</code></a>.
One of the goofs that I ran into was that I was incrementing the
position of the queue when I added elements to it rather than its
length. Oops.</p>
<h4 id="an-addendum">An addendum</h4>
<p>While circular buffers are great, and worked fine for this application,
I think it actually would have made more sense to make it a <a href="https://en.wikipedia.org/wiki/Multiple_buffering#Page_flipping">ping-pong
buffer</a>,
as one way to implement the communication framework is to make the
output buffer a concatenation of the input buffer and the additional
data for the module, with one modification at the beginning of the
input queue. I think I would also have to change the communication
protocol to send the number of bytes rather than the number of modules,
which limits throughput more than I would like. Also, I like the idea
that the data packet can be indefinite, so I'll probably leave it as is
until I hit a performance obstacle.</p>
<h3 id="obstacles">Obstacles</h3>
<p>I spent much longer than I should have trying to figure out why
the protocol was working with the switches, but not the capacitive
sensing. A third of the time was because the initialization was
commented out, and the remainder is because the initializations have to
be in a particular order for some reason.</p>
<p>The only thing that I haven't figured out yet is why the TM4C (the
main module that produces the sound) won't communicate over SPI
with its child modules. Serial communications are definitely being
initiated, but there is no data being transferred between the two
microcontrollers. I suspect it has something to do with the fact that
TI uses SSI, which has Rx and Tx, while the ATtiny uses SPI, which has
LOMI and LIMO. Either that or it's a 3.3v vs 5v issue. Now that I think
about it, it's almost certainly the latter.</p>
<h2 id="an-aside-regarding-the-silly-name">an aside regarding the silly name</h2>
<p>The goal of this project was to create a modular instrument
framework that I could use in the future to more quickly
produce digital instruments. I spent roughly 20 minutes
jumping around on Wikipedia until I ended up on the entry for <a href="https://en.wikipedia.org/wiki/Aliquot_stringing">aliquot
stringing</a>,
and after seeing the keywords "several" and "sympathetically", I
figured it would be a good name for the project given the goals. I also
took out a letter for better search engine optimization.</p>
<h2 id="closing-thoughts">Closing thoughts</h2>
<p>Though I didn't end up with a fully functional instrument, I achieved
all but one of my goals. I have a thing that takes an input and a thing
that makes sound, and now all I need to do is put them together. Based
on my initial tests, the latency in communication between modules is
pretty much negligible. I haven't tested it with more than 4 modules,
but the results seemed negligible at a small scale, and I'm fairly
certain that most of the observed latency was caused by communication
with the LEDs rather than the communication between the modules
themselves. One thing to test in the future is using a bus protocol
like CAN or I2C (preferably CAN due to bidirectional multi-leader
capabilities) rather than a serial protocol, as it could help with
scalability.</p>
HXCSTRSun, 01 Dec 2019 00:00:00 +0000Unknown
https://wiki.jaxter184.net/mus-tec/hxcstr/
https://wiki.jaxter184.net/mus-tec/hxcstr/<!--
## The name
The name is complete nonsense, but really, as long as it's search
engine optimized, I don't think the name is too important. Regardless,
the main reason I generally name things is so that I care about them
more, so the less time and energy I spend thinking about the name, the
more useful it usually ends up being.
-->
<h2 id="the-problem">The problem</h2>
<p>This project came out of an interest in learning how to play isomorphic
keyboard layouts and a personal wish to perform live electronic
music. The primary product this is based on, the Novation Launchpad
Mk II, is a square grid of 64 red/green LED backlit silicone dome
keys. While this was a suitable setup for some cases, I often found
myself needing more buttons, and didn't like using it as a pitched
instrument. In addition, this time in my life was a pivotal point in
my love for hexagons. In my research, I had discovered the sonome,
a large (and expensive) isomorphic keyboard, and the C-thru Axis 49, a
smaller (but still pretty expensive) isomorphic keyboard. I liked both
of these, but my favorite thing about the Launchpad was the colored
lights, as they not only provided visual feedback to the player, but
also easily turned an audio-based performance into an audiovisual
experience. Also, there's an entire industry built around putting
lights with music, and I feel like that has to exist for some reason.</p>
<h2 id="the-solution">The solution</h2>
<p>After seeing the viability of a hexagonally arranged set of keys, I
sought to make a keyboard that was minimally expensive (below $500
per unit, optimally below $150 at large scales), RBG LED backlit,
and user-reprogrammable. Other than these necessary elements, other
design priorities included satisfying physical and visual response,
ergonomics, and ease of manufacturing.</p>
<figure style="width:50%">
<img
class="fig"
src="/images/hxcstr-potato.jpg"
alt="picture of 5 black HXCSTRs and one white one, tesselated together, with a single potato placed on the white one"
>
<figcaption>Potato for scale</figcaption>
</figure>
<h2 id="teensy">Teensy</h2>
<p>PJRC's Teensy seems ubiquitous in the hobbyist music technology
scene nowadays, and I would attribute that to 2 factors. The first is
that it's a very wholesome company that seems to only be able to do
good things. The second is that the Teensy has supported MIDI over
USB since the Teensy 2.0. My original prototype of the HXCSTR used
the Arduino, which is a great teaching tool, but a pretty lackluster
microcontroller. I think Arduino boards nowadays support MIDI, but it
took them a loooong time to do it.</p>
<h2 id="keys">Keys</h2>
<figure style="width:50%">
<img
class="fig"
src="/images/hxcstr-inside.jpg"
alt="an extremely blurry and glare-ridden image of a PCB with
hexagonally arranged Cherry MX Black keyswitches.
There is a Teensy, a barrel jack, and a USB Type-B connector at the
edge"
>
<figcaption>Inside the HXCSTR</figcaption>
</figure>
<h3 id="switches">Switches</h3>
<p>I don't remember how exactly I came across the Cherry MX line of
switches, but I had a friend around that time of my life who was really
into mechanical keyboards, and I think I learned about them from him. I
liked the keys because they had a pretty long travel distance, which
most MIDI controllers didn't (other than piano-style keyboards). I also
felt like building the HXCSTR using them made them feel more premium,
though I'm not sure how accurate that is. For anyone who knows about
mechanical keyboard switches, my choice of the black variety of Cherry
MXs may seem like a terrible idea, but there was a very very good
reason for it: they're much cheaper than other Cherry switches. For
anyone not familiar, the Cherry MX line of keyswitches come in many
colors, and each color indicates a different style of switch. Some
have a tactile bump, others have an audible click, and there are many
different actuation forces for each. Black Cherry MX switches have the
highest actuation force and no tactile or audible enhancements. While a
loud MIDI keyboard would probably be distracting, I think the tactile
bump would have been helpful. It didn't really matter which style I
chose anyway, since they all have the same PCB footprint and I could
replace them if I ever felt the need to.</p>
<h3 id="shift-registers">Shift registers</h3>
<p>Most mechanical keyboards and MIDI controllers read their switches
by using a diode matrix. The basic idea behind this is that you
can index by rows and columns rather than attach a pin to each
individual switch. This means for a 12x12 grid of switches, you
only need 24 pins from a microcontroller to read all the switches
rather than 144. A better solution (as far as I know), however,
is to use shift registers. Though the way it works is very very
different, a shift register is practically a digital multiplexer.
You can send a signal to it to tell it to read all of its inputs (8
inputs per shift register in my case), then use a simplified verison of
<a href="https://en.wikipedia.org/wiki/Serial_Peripheral_Interface">SPI</a> to send that data to the microcontroller.</p>
<h3 id="velocity">Velocity?</h3>
<p>Unfortunately not. I saw <a href="http://www.lvl1.org/2011/03/22/diy-isomorphic-keyboard/">a similar
project</a>
at one point that also used Cherry MX switches, but they cut off the
bottom of each switch to allow the stem to hit a second switch. I
(foolishly) thought that velocity sensitivity wasn't worth cutting
up 91 Cherry MX switches, designing and populating a whole extra
PCB, and doubling the read time of each switch, so I went without
velocity sensitivity. This project was less about being able to play
expressively and more about learning the layout (and, to be honest,
showing off).</p>
<h3 id="keycaps">Keycaps</h3>
<p>The keycaps in my first prototype were laser cut, and I also
experimented with resin casting a 3D printed part (a very smelly
and sticky mistake), but once I got to the point where I could make
these at larger volumes, I switched to injection molding. It was a
huge hassle, but in the end, I think it was the right decision. The
keycaps are made from polypropylene, and the diffusion is achieved by
introducing a little bit of white pigment. I'm very satisfied with the
appearance of the keycaps, but they add a lot of unnecessary weight,
and the stems should have been round instead of rectangular.</p>
<h4 id="spacing">Spacing</h4>
<p>The key offset spacing was selected to be similar to that of a piano
key. Additional spacing between keys is not something that existed in
the Sonome or Axis-49, but I thought that it would be necessary in
order to reduce friction between keys. It was not, and I definitely
should not have included spacing between the keys. Hindsight is 20/20.</p>
<h2 id="leds">LEDs</h2>
<p>In the first prototype, I hot-glued the LEDs to the keys.
While that was all fine and dandy, it was definitely not the optimal
way to add lights.
In the final version, I ended up with two PCBs-- <a href="https://gitlab.com/jaxter184/hxcstr/-/tree/master/pcb/mxblack">one for the
keys</a> and <a href="https://gitlab.com/jaxter184/hxcstr/-/tree/master/pcb/led">one for the lights</a>.
The lights PCB was essentially a glorified LED strip that snaked
through the board.
I ended up giving myself a few headaches by not indexing the lights
in the same direction in every row (instead, the rows snaked back and
forth, alternating direction), but nothing too terrible.
I actually had to reorder the LED boards because I didn't leave enough
clearance for the stems to pass through the holes in the board.</p>
<h3 id="ws2812b">WS2812B</h3>
<p>If you've ever worked with large sets of RGB LEDs, you've probably
heard the part number 'WS2812B' before. This LED is individually
addressable, meaning each LED can be a different color, and it only
requires one data pin, as opposed to the usual 2. The way it achieves
this is by using a synchronous communication protocol. This protocol
uses a varying pulse-width signal to send digital data serially
through the chain of LEDs. What this essentially amounts to is an
LED strip that's super cheap and very easy to send data to, but
also limited in its data transmission rate. A synchronous protocol
is (almost?) always limited to a single clock rate, and to ensure
compatibility with a wide range of microcontrollers, that clock rate
is generally not the highest it can be. As a comparison, the WS2812B's
data rate is one LED per 30 microseconds, while the APA102, which
has a clock signal, only takes a little over 2 microseconds per LED
at its fastest. While a 28 microsecond difference is small by itself,
these LEDs are generally used in large numbers, usually hundreds in a
chain (in this project, there were 91 in a single serial chain). The
more important difference in timing, however, is the internal PWM
oscillator rate. To allow a range of colors, each component color
of the LED is controlled by a PWM signal, which can be modified to
make the color seem brighter or dimmer. This brightness is more or
less controlled by turning the LED on and off very very quickly,
and if the speed that it's turning on and off is too slow, it can
lead to weird artifacts in videos and pictures, or worse, eye strain
(not a confirmed medical effect, just me guessing). <a href="https://cpldcpu.wordpress.com/2014/08/27/apa102/">According to
Tim</a>, the WS2812B
is about 430 Hz while the APA102 runs at 19200 Hz. I've never had
any issue with image artefacts with APA102s. If you leave this blog
post with one takeway, it should be that APA102s are much better than
WS2812Bs.</p>
<h3 id="power">Power</h3>
<p>One of the most annoying hardware problems with the initial prototype was dealing with the 3.3v and 5v conversion.
I also ended up switching my power jack component when I didn't need to, which was a bummer.
The fact that it was a different height than the USB jack was a little annoying.
Plus, for the most part, the current is low enough that it can be powered by just USB, so if I designed it better, I
could've avoided the barrel jack altogether, or at least made it optional.</p>
<h2 id="layouts">Layouts</h2>
<p>TODO</p>
<ul>
<li>Tonnetz/Harmonic Table</li>
<li>Wicki-Hayden</li>
<li>Janko</li>
<li>Drum Flowers</li>
</ul>
<h2 id="other-hexagonal-isomorphic-keyboards">Other hexagonal isomorphic keyboards</h2>
<p>Roughly in chronological order</p>
<h3 id="starr-labs-microzone-u648">Starr Labs MicroZone U648</h3>
<ul>
<li><a href="https://youtu.be/Ffa-XiMMeVo">https://youtu.be/Ffa-XiMMeVo</a></li>
<li><a href="https://www.starrlabs.com/product/microzone-u648/">https://www.starrlabs.com/product/microzone-u648/</a></li>
</ul>
<p>I know next to nothing abouth this one, and only came across it as I was writing this article.
Seems fine.</p>
<h3 id="c-thru-axis-49">C-Thru Axis-49</h3>
<ul>
<li><a href="https://youtu.be/rJcChYiMzg4?t=8">https://youtu.be/rJcChYiMzg4?t=8</a></li>
<li><a href="https://www.c-thru-music.com/cgi/?page=prod_axis-49">https://www.c-thru-music.com/cgi/?page=prod_axis-49</a></li>
</ul>
<p>Mentioned earlier in the article, and probably the first commercial electronic isomorphic keyboard.
They are now out of business, but their controllers are still floating around on resellers these days.
In my opinion, the keys were way too close together, and felt terrible.
At least they were velocity sensitive though.
These were the primary inspiration for the HXCSTR, in the sense that I thought I could do a lot better.</p>
<h3 id="rainboard">Rainboard</h3>
<p><a href="https://youtu.be/blNM4tX_0gE">https://youtu.be/blNM4tX_0gE</a></p>
<p>Very pretty, and I appreciate the transparent enclosure.
It seems like the velocity is global though, and at that point, I would've opted to omit velocity sensitivity
altogether.
I also personally don't like how arcade buttons feel, but there are definitely people out there who really like them.
The accompanying app also seems like a pretty convenient way to mess with the configuration.
The pitchbend is also a nice feature that I wish I had on the HXCSTR.</p>
<p>I think shiverware fizzled out a long time ago, but <a href="https://youtu.be/OwO7W3zLots">someone else made their own rainboard</a> in 2020,
and they do not seem to be affiliated.</p>
<h3 id="unnamed-keyboard">Unnamed keyboard</h3>
<ul>
<li><a href="https://youtu.be/ItuyHjraXlg">https://youtu.be/ItuyHjraXlg</a></li>
</ul>
<p>Another one with mechanical switches. Reminds me of this rat maze: <a href="https://youtu.be/skcJc2KV4_Q?t=1722">https://youtu.be/skcJc2KV4_Q?t=1722</a></p>
<h3 id="beeboard">BeeBoard</h3>
<ul>
<li><a href="https://youtu.be/YEPOQTkfUn8">https://youtu.be/YEPOQTkfUn8</a></li>
</ul>
<p>Also don't know much about this one.</p>
<h3 id="lumatone">Lumatone</h3>
<ul>
<li><a href="https://youtu.be/IlZv13YZzSM">https://youtu.be/IlZv13YZzSM</a></li>
<li><a href="https://orders.lumatone.io/products/lumatone-keyboard">https://orders.lumatone.io/products/lumatone-keyboard</a></li>
</ul>
<p>One of the more recent iterations, and probably the biggest so far.
And most expensive.
They seem to be aiming pretty firmly at the microtonal crowd, which I think is clever because I think they tend to be
more open to buying more esoteric and gimmicky gear than most other types of musicians.</p>
<h3 id="dualo-equis">dualo/equis</h3>
<ul>
<li><a href="https://dualo.com/en/">https://dualo.com/en/</a></li>
</ul>
<p>The polyphonic expression is nice.</p>
<h3 id="s-ol-0x-pads">s-ol 0x pads</h3>
<ul>
<li><a href="https://hw.s-ol.nu/0xC.pad/">https://hw.s-ol.nu/0xC.pad/</a></li>
<li><a href="https://hw.s-ol.nu/0x33.board/">https://hw.s-ol.nu/0x33.board/</a></li>
<li><a href="https://youtu.be/GDOJF7oPh4s">https://youtu.be/GDOJF7oPh4s</a></li>
</ul>
<p>Designed to work as both a computer keyboard as well as a musical keyboard.
Also the only one on this list that is OSHW certified.
I'm personally really interested in this one because I saw a picture of an alternate case that lets 4 of them fit
together to make a mega keyboard, and I think that's fun.</p>
<p>There's also <a href="https://www.digikey.com/en/maker/projects/hexpad/03c5c09ff86c4d19819565a82413abcf">a guide</a> written by someone who works at Adafruit on making your own board with the same keycaps (<a href="https://hw.s-ol.nu/HEX-keycaps/">which
are available seperately</a>).</p>
<h3 id="hexboard">Hexboard</h3>
<ul>
<li><a href="https://shapingthesilence.substack.com/p/hexboard-hardware-and-firmware-updates">https://shapingthesilence.substack.com/p/hexboard-hardware-and-firmware-updates</a></li>
<li><a href="https://youtu.be/BvEU9YZ2l9o">https://youtu.be/BvEU9YZ2l9o</a></li>
<li><a href="https://shapingthesilence.com/tech/hexboard-midi-controller/">https://shapingthesilence.com/tech/hexboard-midi-controller/</a></li>
</ul>
<p>3D-printed keys.
Not sure how I feel about them.
The firmware seems to have a lot of features though, and they are fellow <a href="https://git.sr.ht">sr.ht</a> enjoyers.</p>
This websiteSun, 01 Dec 2019 00:00:00 +0000Unknown
https://wiki.jaxter184.net/website/
https://wiki.jaxter184.net/website/<h2 id="so-you-want-to-make-a-website">So you want to make a website?</h2>
<p>Make a text file, put whatever words you want in it ("Hello World!" are
some popular first words), and put <code>.html</code> at the end of the filename.
Congratulations! You've made a website!</p>
<p>Of course, this lacks many of the features widely considered important
for most websites, such as being connected to the world via the
internet.</p>
<p>But there are two pieces of advice that I have for any form of
self-expression:</p>
<ol>
<li>Put more things out into the world before they're done.
One of the great things about digital media is that it's practically
free to "release" something, so do it as often as you can, throughout
the process.</li>
<li>You can put as much into a medium as you would like.
<a href="https://en.wikipedia.org/wiki/4%E2%80%B233%E2%80%B3">Even if that amount is practically
nothing</a>.</li>
</ol>
<h2 id="tools-i-ve-used-to-make-websites">Tools I've used to make websites</h2>
<h3 id="plain-html-and-css">Plain HTML and CSS</h3>
<p>In my opinion, very underrated.
My [contact page] is made using just HTML and CSS, and I think it does
an excellent job at conveying the information it needs to.
It was easy to set up and easy to update and adjust.
It doesn't scale up very well, but it scales down excellently.</p>
<h3 id="static-site-generators">Static site generators</h3>
<p>The first iteration of this website was made using Jekyll, and I now
use [Zola], but all static site generators work pretty much the same
way:</p>
<ul>
<li>pick a theme</li>
<li>write some markdown files</li>
<li>run the generator</li>
<li>upload the resulting files to a hosting service (or self-host!)</li>
</ul>
<p>I like this a little more because it separates the actual content of
the site from the minute details of how everything is going to look.</p>
<h3 id="hosting-services">Hosting services</h3>
<p>Any web service should be hosted.
While you could host it yourself (I don't know exactly how, but I hear
NGINX is involved), there are so many free (in both cost and source
code) options out there that, in my opinion, it really doesn't make
much sense to self host a <em>static</em> site.</p>
<ul>
<li>GitLab - I had some issues getting the GitLab pages pipeline to work
properly, and some redirection authentication shenanigans.
I don't remember a lot about the process, but at the time, I found it
to be smooth and simple.
I'm not sure what my frame of reference was.</li>
<li>Codeberg - I had a few hiccups (that I've unfortunately forgotten the
details of), but other than the fact that the compiled website has to
be stored in a repository, I'm pretty happy with it.</li>
<li>pages.sr.ht - Very nice upload workflow that lets you just <code>curl</code> a
<code>.tar.gz</code> file.
The upload process is as easy as running a shell script, and when it
comes to statically generated sites, I greatly prefer running a script
over the Codeberg workflow, which requires two seperate repositories:
one for the source files and one for the generated site output.
The only caveat with sr.ht is that <a href="https://srht.site/limitations">it doesn't allow third-party
scripts</a>.
Which, frankly, makes sense, but I really want asciinema and KiCanvas
and Sketchfab embeds, so I'm currently useing Codeberg for this site.</li>
</ul>
<p>There are also some options that I haven't evaluated yet:</p>
<ul>
<li><a href="https://surge.sh/">surge</a> - The CLI-centric workflow is nice, but I
don't want to install some random node.js program.</li>
<li><a href="https://glitch.com/">glitch</a> - You have to use their editor; doesn't
let you upload your own website (except through a GitHub import, after
which you once again have to use their editor)</li>
<li><a href="https://bunny.net">bunny</a> - It seems like there's an FTP upload
option, but I can't figure out how to use it. Also, it's not free.</li>
</ul>
<h2 id="markdown-shortcodes-in-zola">Markdown shortcodes in Zola</h2>
<p>Made a <a href="https://git.sr.ht/~jaxter184/after-dark/tree/3ac49304af69f3ac9ae02a13a0a3eacee496b14e/item/templates/shortcodes/card.html">shortcode file</a>, and I use it by adding the following snippet to a post:</p>
<pre data-lang="md" class="language-md z-code"><code class="language-md" data-lang="md"><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">{{card(
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown"> title="↹lature",
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown"> link="/mus-tec/tlature#chain-view",
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown"> desc="devlog - Implementing a niri-like signal chain viewer"
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">)} }
</span></span></code></pre>
<p>It creates a little card for visiting the article.</p>
<p>Unfortunately, Zola doesn't provide the information to automatically generate the title and description from the
markdown <a href="https://www.getzola.org/documentation/content/section/#front-matter">front matter</a> (all available variables can be shown with <code>{{ __tera_context | json_encode(pretty=true) }}</code>),
so next step is to see if that's a thing that can be forked in.</p>
<p>This information is available in the context of page templates, which is how the auto-generation of the "All pages"
sections on group pages like the <a href="https://wiki.jaxter184.net">homepage</a> or <a href="https://wiki.jaxter184.net/mus-tec/">Music Technology</a> page, but those variables store the output of the
markdown -> html rendering process, which of course won't be available during the process itself.
That being said, assuming the front matter is independent of the html output, it should be possible to have that
information available during markdown rendering.</p>
<h2 id="wiki-garden">wiki/garden?</h2>
<p>TODO: write a review of the following blog post:
https://maggieappleton.com/garden-history#a-brief-history-of-digital-gardens</p>
TrackballsFri, 18 Oct 2019 00:00:00 +0000Unknown
https://wiki.jaxter184.net/on-trackballs/
https://wiki.jaxter184.net/on-trackballs/<h2 id="pros">Pros:</h2>
<ul>
<li>Very very space efficient</li>
<li>Related to the previous, but it can be used on any surface. I often
place the trackball on my leg or on the windowsill of the bus. Even my
massive trackball that's not intended for travel is easier to travel
with than the most portable mice I've seen due to the freedom of
placement.</li>
<li>Less arm movement. I use Dvorak for the same reason.</li>
<li>More thumb usage. It's the strongest finger, why don't we use it
more?</li>
</ul>
<h2 id="cons">Cons:</h2>
<ul>
<li>The muscles that you have to use to move the cursor a given amount
in a given direction change depending on the current hand and finger
position due to the spherical nature of the ball. Mice do not have this
problem because they are usually used on a flat surface. Imagine using
a mouse on a spherical surface.</li>
<li>Less selection</li>
</ul>
<h2 id="recommendations">Recommendations:</h2>
<h3 id="elecom-huge"><a href="https://www.elecom.co.jp.e.gj.hp.transer.com/products/M-HT1DRBK.html">Elecom HUGE</a></h3>
<p>Best large trackball I have used. Better than the Kensington Expert in
ergonomics, function buttons, and scroll mechanics. As much as I love
scroll rings, they really aren't easy to use. Comes in both wired and
wireless USB dongle varieties.</p>
<h3 id="elecom-deft-pro"><a href="https://www.elecom.co.jp.e.gj.hp.transer.com/products/M-DPT1MRXBK.html">Elecom DEFT PRO</a></h3>
<p>This is the trackball I use with my laptop. The Bluetooth option is a
huge workflow improvement, but my favorite thing about it is that it
allows for wired USB, wireless USB or Bluetooth in the same model, as
opposed to the Elecom HUGE, which has two separate models for wired and
wireless, and no Bluetooth on either. Also has one fewer function key
than the HUGE.</p>
<h3 id="logitech-m570"><a href="https://www.logitech.com/en-us/product/wireless-trackball-m570">Logitech M570</a></h3>
<p>A good entry level trackball. I personally don't like thumb balls,
but all of my friends seem to prefer them over finger balls, possibly
due to the positioning of the left and right click. Finding left click
seems to be the first issue that people have when they try to use my
trackballs. I personally think it only takes about 30 seconds to get
used to the positioning on a finger ball, but mileage may vary.</p>
<h3 id="logitech-mx-ergo"><a href="https://www.logitech.com/en-us/product/mx-ergo-wireless-trackball-mouse">Logitech MX Ergo</a></h3>
<p>I have never owned or used one of these, so I can't officially endorse
it, but it seems to be the thumb ball equivalent of the Elecom DEFT
PRO. Very pricey though.</p>