jaxter184 https://wiki.jaxter184.net Zola en Wed, 26 Feb 2025 00:00:00 +0000 Monthly Log 2025 Wed, 26 Feb 2025 00:00:00 +0000 Unknown 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 crofu Wed, 25 Dec 2024 00:00:00 +0000 Unknown 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 Regulator Sat, 14 Dec 2024 00:00:00 +0000 Unknown https://wiki.jaxter184.net/3d/ps06/ https://wiki.jaxter184.net/3d/ps06/ <figure > <img class="fig" src="&#x2F;images&#x2F;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 -&gt; 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 2024 Sun, 01 Dec 2024 00:00:00 +0000 Unknown 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="&#x2F;icc&#x2F;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">=&gt;</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">&lt;</span><span class="z-storage z-modifier z-lifetime z-rust">&#39;a</span><span class="z-punctuation z-definition z-generic z-end z-rust">&gt;</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">&amp;</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">&amp;</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">&lt;</span><span class="z-storage z-modifier z-lifetime z-rust">&#39;a</span><span class="z-punctuation z-definition z-generic z-end z-rust">&gt;</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">-&gt;</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">&lt;</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">&gt;</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">&quot;</span><span class="z-constant z-other z-placeholder z-rust">{}</span><span class="z-punctuation z-definition z-string z-end z-rust">&quot;</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">&lt;</span>Uuid<span class="z-punctuation z-definition z-generic z-end z-rust">&gt;</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">-&gt;</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">&lt;</span><span class="z-variable z-other z-rust">$name</span><span class="z-punctuation z-definition z-generic z-end z-rust">&gt;</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">-&gt;</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">&lt;</span>Uuid<span class="z-punctuation z-definition z-generic z-end z-rust">&gt;</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">&amp;</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">&amp;</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">-&gt;</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">&lt;</span><span class="z-variable z-other z-rust">$name</span><span class="z-punctuation z-definition z-generic z-end z-rust">&gt;</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">&amp;</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">&amp;</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">-&gt;</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="&#x2F;images&#x2F;crofu&#x2F;pledge-v0.png" alt="A webpage titled &#x27;add cool feature XYZ to frontend&#x27;. In the center is a dark box listing the current pledge amount, an input box with the current pledge amount again, a checkbox labelled &#x27;Pledge anonymously&#x27;, and two buttons: a blue one labelled &#x27;make pledge&#x27; and a red one labelled &#x27;retract pledge&#x27;. 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="&#x2F;images&#x2F;crofu&#x2F;my-pledges-v0.png" alt="A webpage titled &#x27;All pledges&#x27;, featuring a table listing tasks, their projects, and the amounts pledged to each task. Above the table is a total listing &#x27;$36&#x27;." > </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>&lt;br&gt;&lt;/br&gt;</code> instead of just <code>&lt;br&gt;</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">&quot;</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">&quot;</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">&quot;</span>=================<span class="z-punctuation z-definition z-string z-end z-shell">&quot;</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">&quot;</span>=================<span class="z-punctuation z-definition z-string z-end z-shell">&quot;</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">&gt;</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">&gt;</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>&lt;nav&gt;</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">&quot;</span>profiles<span class="z-punctuation z-definition z-string z-end z-sql">&quot;</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=&lt;insert secret code here&gt;</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> -&gt; <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">&#39;</span>/html/body/div/form/fieldset/table/tr/td/label[@for=&quot;username&quot;]/../../following-sibling::tr[1]/td[1]/text()<span class="z-punctuation z-definition z-string z-end z-shell">&#39;</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">&lt;</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">&gt;</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">&lt;</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">&gt;</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">&lt;</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">&gt;</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">&lt;</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">&gt;</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">&lt;</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">&gt;</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">&lt;!--</span> etc <span class="z-punctuation z-definition z-comment z-end z-html">--&gt;</span></span> </span></code></pre> <p><code>[@for="username"]</code>: This part checks the attributes of the <code>&lt;label&gt;</code> tags to find one that looks like <code>&lt;label for="username"&gt;</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>&lt;td&gt;</code> child node</p> <p><code>text()</code>: Prints the text contents, i.e. <code>&lt;tag&gt;this part of the xml data, excluding the tags&lt;/tag&gt;</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="&#x2F;images&#x2F;crofu&#x2F;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">&lt;</span><span class="z-support z-type z-rust">String</span><span class="z-punctuation z-definition z-generic z-end z-rust">&gt;</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">&lt;</span>Database<span class="z-punctuation z-definition z-generic z-end z-rust">&gt;</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">-&gt;</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">&lt;</span><span class="z-meta z-generic z-rust">Html<span class="z-punctuation z-definition z-generic z-begin z-rust">&lt;</span><span class="z-support z-type z-rust">String</span><span class="z-punctuation z-definition z-generic z-end z-rust">&gt;</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">&gt;</span></span></span></span><span class="z-punctuation z-terminator z-rust">;</span> </span></code></pre> <ul> <li><code>Path(profile): Path&lt;String&gt;</code> - populated according to the route (<code>.route(&amp;url::new_project(":profile"), get(get_new_project))</code>), which specifies one matchable path parameter: the profile name.</li> <li><code>db: Extension&lt;Database&gt;</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&lt;NewProjectErrorState&gt;</code>, then I get a compilation error complaining that <code>the trait `Handler&lt;_, _&gt;` is not implemented for fn item &lt;function signature of get_new_project&gt;</code>, since <code>Option&lt;NewProjectErrorState&gt;</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">&lt;</span>S<span class="z-punctuation z-definition z-generic z-end z-rust">&gt;</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">&lt;</span>S<span class="z-punctuation z-definition z-generic z-end z-rust">&gt;</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">&amp;</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">&amp;</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">-&gt;</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">&lt;</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">&gt;</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">&lt;</span>EditProjectForm<span class="z-punctuation z-definition z-generic z-end z-rust">&gt;</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">-&gt;</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">&lt;</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">&gt;</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">&amp;</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">&amp;</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">&amp;</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">&amp;</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 Algorithm Thu, 05 Sep 2024 00:00:00 +0000 Unknown https://wiki.jaxter184.net/graph-layout/ https://wiki.jaxter184.net/graph-layout/ <figure > <img class="fig" src="&#x2F;images&#x2F;graph-layout&#x2F;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="&#x2F;images&#x2F;graph-layout&#x2F;build&#x2F;00.kdl.svg" alt="node labelled &#x27;kouign amann&#x27; 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="&#x2F;images&#x2F;graph-layout&#x2F;build&#x2F;01.kdl.svg" alt="same as previous image, but with two more nodes under labelled &#x27;laminated dough&#x27; and &#x27;syrup&#x27;" > </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="&#x2F;images&#x2F;graph-layout&#x2F;build&#x2F;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="&#x2F;images&#x2F;graph-layout&#x2F;build&#x2F;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="&#x2F;images&#x2F;graph-layout&#x2F;build&#x2F;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="&#x2F;images&#x2F;graph-layout&#x2F;build&#x2F;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="&#x2F;images&#x2F;graph-layout&#x2F;build&#x2F;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="&#x2F;images&#x2F;graph-layout&#x2F;build&#x2F;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="&#x2F;images&#x2F;graph-layout&#x2F;build&#x2F;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="&#x2F;images&#x2F;graph-layout&#x2F;build&#x2F;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="&#x2F;images&#x2F;graph-layout&#x2F;build&#x2F;10.kdl.svg" alt="" > </figure> <figure > <img class="fig" src="&#x2F;images&#x2F;graph-layout&#x2F;build&#x2F;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 Chart Thu, 08 Aug 2024 00:00:00 +0000 Unknown 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:&#x2F;&#x2F;www.batteries4pro.com&#x2F;22082-pos_thickbox&#x2F;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 swatch Mon, 05 Aug 2024 00:00:00 +0000 Unknown 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="&#x2F;images&#x2F;pcb-swatch&#x2F;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="&#x2F;images&#x2F;pcb-swatch&#x2F;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="&#x2F;images&#x2F;pcb-swatch&#x2F;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 Counter Sun, 30 Jun 2024 00:00:00 +0000 Unknown 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&amp;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="&#x2F;images&#x2F;oko&#x2F;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="&#x2F;images&#x2F;oko&#x2F;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="&#x2F;images&#x2F;oko&#x2F;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="&#x2F;images&#x2F;oko&#x2F;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="&#x2F;images&#x2F;oko&#x2F;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="&#x2F;images&#x2F;oko&#x2F;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="&#x2F;images&#x2F;oko&#x2F;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="&#x2F;images&#x2F;oko&#x2F;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="&#x2F;images&#x2F;oko&#x2F;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="&#x2F;images&#x2F;oko&#x2F;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 Summit Wed, 12 Jun 2024 00:00:00 +0000 Unknown 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 machines Sat, 25 May 2024 00:00:00 +0000 Unknown 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="&#x2F;images&#x2F;fsm&#x2F;build&#x2F;sm-00.dot.svg" alt="A state machine with two unconnected states &#x27;A&#x27; and &#x27;B&#x27;" > <figcaption>A state machine has states (duh?)</figcaption> </figure> <figure > <img class="fig" src="&#x2F;images&#x2F;fsm&#x2F;build&#x2F;sm-01.dot.svg" alt="A state machine with starting state &#x27;A&#x27; and ending state &#x27;B&#x27;, where &#x27;A&#x27; transitions into &#x27;B&#x27;." > <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="&#x2F;images&#x2F;fsm&#x2F;build&#x2F;sm-02.dot.svg" alt="A state machine with state &#x27;A&#x27; transitioning to ending state &#x27;B&#x27; on condition `var`, and to &#x27;C&#x27; on condition `!var`. &#x27;C&#x27; transtitions to &#x27;B&#x27; 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="&#x2F;images&#x2F;fsm&#x2F;build&#x2F;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 &amp;&amp; w</code> (to <code>B</code>)</th><th style="text-align: center"><code>u &amp;&amp; !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">&quot;</span>dash<span class="z-punctuation z-definition z-string z-end z-ron">&quot;</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">&quot;</span>do<span class="z-punctuation z-definition z-string z-end z-ron">&quot;</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">&quot;</span>dash::cooldown<span class="z-punctuation z-definition z-string z-end z-ron">&quot;</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">&quot;</span>dash::cooldown<span class="z-punctuation z-definition z-string z-end z-ron">&quot;</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">&quot;</span>dash::duration<span class="z-punctuation z-definition z-string z-end z-ron">&quot;</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> &quot;Stats&quot; 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">&quot;</span>dash::<span class="z-punctuation z-definition z-string z-end z-ron">&quot;</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">&quot;</span>dash::<span class="z-punctuation z-definition z-string z-end z-ron">&quot;</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">&quot;</span>end<span class="z-punctuation z-definition z-string z-end z-ron">&quot;</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">&quot;</span>dash::duration<span class="z-punctuation z-definition z-string z-end z-ron">&quot;</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 &quot;end&quot; 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 &quot;end&quot; 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">&quot;</span>do<span class="z-punctuation z-definition z-string z-end z-ron">&quot;</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 (&quot;end&quot;) 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">&quot;</span>dash::<span class="z-punctuation z-definition z-string z-end z-ron">&quot;</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">&quot;</span>dash::<span class="z-punctuation z-definition z-string z-end z-ron">&quot;</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 Physics Thu, 09 May 2024 00:00:00 +0000 Unknown 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) &lt; 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">-&gt;</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">&gt;</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">-&gt;</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">&lt;</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">-&gt;</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">&lt;</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">-&gt;</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">&lt;</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) &lt; 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">-&gt;</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">&lt;</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">-&gt;</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">&lt;</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">-&gt;</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">&gt;</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 &amp;&gt; p_{2y} - \text{slope} * p_{2x} &amp;&amp; \text{our original equation} \\ d_y - \frac{p_{2y} - p_{1y}}{p_{2x} - p_{1x}}d_x &amp;&gt; p_{2y} - \frac{p_{2y} - p_{1y}}{p_{2x} - p_{1x}}p_{2x} &amp;&amp; \text{slope} = \frac{p_{2y} - p_{1y}}{p_{2x} - p_{1x}}\\ d_y - p_{2y} &amp;&gt; \frac{p_{2y} - p_{1y}}{p_{2x} - p_{1x}}d_x - \frac{p_{2y} - p_{1y}}{p_{2x} - p_{1x}}p_{2x} &amp;&amp; \text{add terms to both sides}\\ d_y - p_{2y} &amp;&gt; \frac{p_{2y} - p_{1y}}{p_{2x} - p_{1x}}(d_x - p_{2x}) &amp;&amp; \text{factor out slope}\\ (p_{2x} - p_{1x})(d_y - p_{2y}) &amp;&gt; (p_{2y} - p_{1y})(d_x - p_{2x}) &amp;&amp; \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">-&gt;</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">&gt;</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">&gt;</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="&#x2F;game-dev&#x2F;spell-scripting&#x2F;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> ↹lature Tue, 26 Mar 2024 00:00:00 +0000 Unknown 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">&lt;</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">&gt;</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">&quot;</span>8 bit raw midi<span class="z-punctuation z-definition z-string z-end z-rust">&quot;</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">&quot;</span>HXCSTR<span class="z-punctuation z-definition z-string z-end z-rust">&quot;</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">&quot;</span>found port {ea_port:?}<span class="z-punctuation z-definition z-string z-end z-rust">&quot;</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">&amp;</span>ea_port<span class="z-punctuation z-separator z-rust">,</span> <span class="z-keyword z-operator z-bitwise z-rust">&amp;</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">&quot;</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">&quot;</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">&amp;</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>&quot;Could not connect ↹lature midi input to &quot;<span class="z-constant z-other z-placeholder z-rust">{:?}</span>&quot;.<span class="z-punctuation z-definition z-string z-end z-rust">&quot;#</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">&quot;</span>/HXCSTR<span class="z-punctuation z-definition z-string z-end z-rust">&quot;</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">&lt;</span><span class="z-storage z-type z-rust">u16</span><span class="z-punctuation z-definition z-generic z-end z-rust">&gt;</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">&quot;</span>bounds check should clamp cursor to a valid index of constraints<span class="z-punctuation z-definition z-string z-end z-rust">&quot;</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">=&gt;</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">&gt;</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">=&gt;</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">&quot;</span>offset should not go past end of chain<span class="z-punctuation z-definition z-string z-end z-rust">&quot;</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> Replicon Thu, 01 Feb 2024 00:00:00 +0000 Unknown 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">&lt;</span>WinitPlugin<span class="z-punctuation z-definition z-generic z-end z-rust">&gt;</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">&lt;</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">&gt;</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">&lt;</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">&gt;</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">&lt;</span>IS_SERVER<span class="z-punctuation z-definition z-generic z-end z-rust">&gt;</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">&amp;</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">&amp;</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">&lt;</span><span class="z-meta z-generic z-rust">FromClient<span class="z-punctuation z-definition z-generic z-begin z-rust">&lt;</span>PlayerEvent<span class="z-punctuation z-definition z-generic z-end z-rust">&gt;</span></span><span class="z-punctuation z-definition z-generic z-end z-rust">&gt;</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">&lt;</span><span class="z-punctuation z-section z-group z-begin z-rust">(</span>Entity, <span class="z-keyword z-operator z-rust">&amp;</span>UnitId, <span class="z-keyword z-operator z-rust">&amp;</span><span class="z-storage z-modifier z-rust">mut</span> PlayerState, <span class="z-keyword z-operator z-rust">&amp;</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">&gt;</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">&amp;</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">&amp;</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">&lt;</span>PlayerEvent<span class="z-punctuation z-definition z-generic z-end z-rust">&gt;</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">&lt;</span><span class="z-punctuation z-section z-group z-begin z-rust">(</span>Entity, <span class="z-keyword z-operator z-rust">&amp;</span>UnitId, <span class="z-keyword z-operator z-rust">&amp;</span><span class="z-storage z-modifier z-rust">mut</span> PlayerState, <span class="z-keyword z-operator z-rust">&amp;</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">&gt;</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">&amp;</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">&amp;</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">&amp;</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">&amp;</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">&amp;</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">&lt;</span><span class="z-punctuation z-section z-group z-begin z-rust">(</span>Entity, <span class="z-keyword z-operator z-rust">&amp;</span>UnitId, <span class="z-keyword z-operator z-rust">&amp;</span><span class="z-storage z-modifier z-rust">mut</span> PlayerState, <span class="z-keyword z-operator z-rust">&amp;</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">&gt;</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">=&gt;</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">=&gt;</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">&gt;</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">=&gt;</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">&gt;</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">=&gt;</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">=&gt;</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&lt;YourEvent&gt;</code> parameter in your client system, but to read these from the server system, you use <code>EventReader&lt;FromClient&lt;YourEvent&gt;&gt;</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 2024 Thu, 01 Feb 2024 00:00:00 +0000 Unknown 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="&#x2F;game-dev&#x2F;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="&#x2F;game-dev&#x2F;bevy-netcode&#x2F;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="&#x2F;log&#x2F;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>&lt;input&gt;</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>&lt;div&gt;</code> tags.</p> <h4 id="problem-2">Problem 2</h4> <p>There's no way to resize the <code>&lt;textarea&gt;</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>&lt;div&gt;</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="&#x2F;game-dev&#x2F;spell-scripting&#x2F;"> <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="&#x2F;game-dev&#x2F;spell-scripting&#x2F;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="&#x2F;mus-tec&#x2F;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="&#x2F;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="&#x2F;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="&#x2F;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="&#x2F;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="&#x2F;icc&#x2F;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="&#x2F;icc&#x2F;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 2023 Fri, 01 Dec 2023 00:00:00 +0000 Unknown 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">&amp;</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">&lt;</span>&#39;<span class="z-keyword z-operator z-rust">_</span><span class="z-punctuation z-definition z-generic z-end z-rust">&gt;</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">&lt;</span>&#39;<span class="z-keyword z-operator z-rust">_</span><span class="z-punctuation z-definition z-generic z-end z-rust">&gt;</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">&lt;</span>&#39;<span class="z-keyword z-operator z-rust">_</span><span class="z-punctuation z-definition z-generic z-end z-rust">&gt;</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">-&gt;</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">&lt;</span>ProcessStatus, PluginError<span class="z-punctuation z-definition z-generic z-end z-rust">&gt;</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">&quot;</span>test<span class="z-punctuation z-definition z-string z-end z-rust">&quot;</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">&amp;</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">&lt;</span>OscEvent<span class="z-punctuation z-definition z-generic z-end z-rust">&gt;</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">=&gt;</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">=&gt;</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">&quot;</span>handle OSC bundles<span class="z-punctuation z-definition z-string z-end z-rust">&quot;</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">&lt;</span>Location, AudioBuffer<span class="z-punctuation z-definition z-generic z-end z-rust">&gt;</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">&lt;</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">&lt;</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">&gt;</span></span><span class="z-punctuation z-definition z-generic z-end z-rust">&gt;</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">&quot;</span><span class="z-punctuation z-definition z-string z-end z-kdl">&quot;</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">&quot;</span><span class="z-punctuation z-definition z-string z-end z-kdl">&quot;</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>&quot; </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"> &quot; </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>&quot;,T ....... ,T ,T ..... ,T ,T . ,T ..... ,T ..... ,T . *6;20 ,T . *E;20 ,T . *8;20&quot; </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>&quot;.... ,T ... *38;8&quot; </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>&quot;*4. ,C0 ,50 *3A;2&quot; </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>&quot;,T *3F.&quot; </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">&quot;</span><span class="z-punctuation z-definition z-string z-end z-kdl">&quot;</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">&quot;</span><span class="z-punctuation z-definition z-string z-end z-kdl">&quot;</span></span></span> &quot;mixer&quot; <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 &quot;../bass/&quot; </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 &quot;../drums/&quot; </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 &quot;sisqsa&quot; 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>&quot;q&quot; </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 &quot;sisqsa&quot; 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>&quot;q&quot; </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 &quot;beep-kit&quot; </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">&amp;&gt;</span> jaxrec.log</span> <span class="z-keyword z-operator z-logical z-job z-shell">&amp;</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>&amp;&gt; jaxrec.log</code>: pipe all stdout and stderr to the file <code>jaxrec.log</code>.</li> <li><code>&amp;</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&lt;(u64, OscMessage)&gt;</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&lt;(u64, OscMessage)&gt;</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> Papercraft Thu, 30 Nov 2023 00:00:00 +0000 Unknown 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="&#x2F;images&#x2F;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="&#x2F;images&#x2F;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="&#x2F;images&#x2F;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="&#x2F;images&#x2F;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 Archive Tue, 07 Nov 2023 00:00:00 +0000 Unknown 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&amp;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> Poems Mon, 06 Nov 2023 00:00:00 +0000 Unknown 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&#39;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 Tram Mon, 02 Oct 2023 00:00:00 +0000 Unknown 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 2023 Sat, 30 Sep 2023 00:00:00 +0000 Unknown 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="&#x2F;mus-tec&#x2F;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="&#x2F;icc&#x2F;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="&#x2F;images&#x2F;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="&#x2F;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="&#x2F;images&#x2F;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="&#x2F;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="&#x2F;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="&#x2F;log&#x2F;dec-adv-2023"> <div class="list-entry outer"> <span class="link">December Adventure 2023</span> </div> </a> </div> ICC Concerns Mon, 25 Sep 2023 00:00:00 +0000 Unknown 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 Rant Sat, 01 Apr 2023 00:00:00 +0000 Unknown 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 PoE Thu, 30 Mar 2023 00:00:00 +0000 Unknown 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:&#x2F;&#x2F;blogger.googleusercontent.com&#x2F;img&#x2F;b&#x2F;R29vZ2xl&#x2F;AVvXsEhLVBwLqNKHMCuGPx2MQNb8aROgG4hUVbXe3mU_vbum0l6g5ORInmnNiBw9Usv0t7CuA1U2icxTk32JL77st6NH2iMOkXZfWa81aQt29eokkukIYQUGoukRYFXBJXNG5wzdt_mhGRuT-LgP&#x2F;s1600&#x2F;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&#x27;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 Art Wed, 28 Dec 2022 00:00:00 +0000 Unknown 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&#39;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 Scoring Wed, 28 Dec 2022 00:00:00 +0000 Unknown 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> aliqot Fri, 13 Dec 2019 00:00:00 +0000 Unknown 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="&#x2F;images&#x2F;aliqot-cstrip-front.jpg" alt="front of aliqot capacitive sensing pcb" > <figcaption>Front</figcaption> </figure> <figure class="half-width"> <img class="fig" src="&#x2F;images&#x2F;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="&#x2F;images&#x2F;aliqot-sound-front.jpg" alt="front of aliqot sound generation perfboard breakout" > <figcaption>Front</figcaption> </figure> <figure class="half-width"> <img class="fig" src="&#x2F;images&#x2F;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="&#x2F;images&#x2F;aliqot-column-front.jpg" alt="front of aliqot keyswitch pcb" > <figcaption>Front</figcaption> </figure> <figure class="half-width"> <img class="fig" src="&#x2F;images&#x2F;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="&#x2F;images&#x2F;aliqot-mch-front.jpg" alt="front of aliqot capacitive sensing bracket" > <figcaption>Front</figcaption> </figure> <figure class="half-width"> <img class="fig" src="&#x2F;images&#x2F;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 &lt;- capacitive_sensing &lt;- 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> HXCSTR Sun, 01 Dec 2019 00:00:00 +0000 Unknown 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 website Sun, 01 Dec 2019 00:00:00 +0000 Unknown 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=&quot;↹lature&quot;, </span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown"> link=&quot;/mus-tec/tlature#chain-view&quot;, </span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown"> desc=&quot;devlog - Implementing a niri-like signal chain viewer&quot; </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 -&gt; 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> Trackballs Fri, 18 Oct 2019 00:00:00 +0000 Unknown 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>