<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
    <channel>
      <title>fosstog - qmidigen</title>
      <link>https://fosstog.com</link>
      <description>Free &amp; Open Source Photography</description>
      <generator>Zola</generator>
      <language>en</language>
      <atom:link href="https://fosstog.com/tags/qmidigen/rss.xml" rel="self" type="application/rss+xml"/>
      <lastBuildDate>Fri, 29 May 2026 00:00:00 +0000</lastBuildDate>
      <item>
          <title>Building QMidiGen: A Procedural MIDI Composer in Rust</title>
          <pubDate>Fri, 29 May 2026 00:00:00 +0000</pubDate>
          <author>ganthore</author>
          <link>https://fosstog.com/blog/qmidigen/</link>
          <guid>https://fosstog.com/blog/qmidigen/</guid>
          <description xml:base="https://fosstog.com/blog/qmidigen/">&lt;p&gt;&lt;strong&gt;TL;DR&lt;&#x2F;strong&gt;: &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;ganthore&#x2F;qmidigen&quot;&gt;QMidiGen&lt;&#x2F;a&gt; is a procedural MIDI music generator I’ve been building, spiritually descended from a 1999 Windows shareware program called Melody Raiser. It runs natively on Linux, macOS, and Windows, generates complete multi-section songs from musical rules rather than samples, and has an optional &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;modelcontextprotocol.io&#x2F;&quot;&gt;MCP&lt;&#x2F;a&gt; server so AI agents can ask it to compose things. The stack is Rust talking to Qt&#x2F;QML through &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;KDAB&#x2F;cxx-qt&quot;&gt;cxx-qt&lt;&#x2F;a&gt;, which is genuinely interesting and occasionally a nightmare.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;the-1999-inspiration&quot;&gt;The 1999 Inspiration&lt;a class=&quot;post-anchor&quot; href=&quot;#the-1999-inspiration&quot; aria-label=&quot;Anchor link for: the-1999-inspiration&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Melody Raiser was a piece of Windows 9x shareware by a Japanese developer named Yoji Ojima. It did one thing: generate MIDI songs procedurally from a handful of parameters. No samples, no loops, no pre-recorded anything — just math turning into music. You’d set a key, a tempo, pick a genre, and it would produce a completely original piece that you could export or keep generating until something clicked.&lt;&#x2F;p&gt;
&lt;p&gt;I found it because I was deep into &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.rpgmakerweb.com&#x2F;&quot;&gt;RPG Maker 95&lt;&#x2F;a&gt; and &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.rpgmakerweb.com&#x2F;&quot;&gt;RPG Maker 2000&lt;&#x2F;a&gt; at the time, which meant I needed music. It was delightful — for a 12-year-old.&lt;&#x2F;p&gt;
&lt;p&gt;It was also last updated around the time the first Matrix movie came out and has not been updated since. The program runs in a compatibility shim on modern Windows, doesn’t run at all on anything else, and the source code is not available. So the options were: keep running a 25-year-old binary through Wine, or build an evolution of the idea that actually runs in the current decade — with a piano roll editor, multi-section song structures, real-time synthesis, native file dialogs, cross-platform packaging, and an MCP server for good measure.&lt;&#x2F;p&gt;
&lt;p&gt;I went with the second option, which has taken considerably longer than I originally estimated. Shocking.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;why-rust&quot;&gt;Why Rust&lt;a class=&quot;post-anchor&quot; href=&quot;#why-rust&quot; aria-label=&quot;Anchor link for: why-rust&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The natural choice for a Qt app in 2026 is C++. Qt is written in C++. QML interoperates with C++ directly. The ecosystem, the documentation, the examples — all C++. Picking Rust and then bridging it back to C++ is the kind of decision you have to be able to explain.&lt;&#x2F;p&gt;
&lt;p&gt;The honest answer is that I write Rust for fun and C++ for penance. The more defensible answer is that the music generation logic is pure computation — no UI state, no event loops, no Qt objects, just data going in and MIDI tracks coming out. That kind of code is exactly where Rust earns its keep: deterministic ownership means the generation functions are straightforward to test in isolation, the type system catches whole categories of bugs that C++ would happily let through, and &lt;code&gt;cargo test&lt;&#x2F;code&gt; works without standing up a Qt application. The generator has hundreds of rules and edge cases; being able to run them headlessly matters.&lt;&#x2F;p&gt;
&lt;p&gt;The tradeoff is that anything touching Qt still has to cross the C++ boundary, which brings us to cxx-qt and most of the technical headaches in this project.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;why-midi&quot;&gt;Why MIDI&lt;a class=&quot;post-anchor&quot; href=&quot;#why-midi&quot; aria-label=&quot;Anchor link for: why-midi&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;MIDI is a 1983 file format. It is 43 years old. It has survived every “this will replace it” moment in digital music production and is more widely supported today than it’s ever been.&lt;&#x2F;p&gt;
&lt;p&gt;The reason is that MIDI is not audio — it’s instructions. A MIDI file doesn’t record sound; it records “at this timestamp, play this note on this instrument at this velocity.” The audio you hear depends entirely on what interprets those instructions. Change the soundfont, change the instrument assignments, change the synthesis engine, and the same MIDI file sounds completely different. You can take a MIDI composition from 1987, load it into a modern synthesizer, and it will sound the way the composer intended it to sound — except better, because the synthesis has improved in the intervening 37 years.&lt;&#x2F;p&gt;
&lt;p&gt;For a procedural generator, this is exactly right. The generator produces structure — chord progressions, melody lines, rhythm patterns, harmonic relationships between parts. It doesn’t care what those things sound like. That concern belongs to FluidSynth and whichever soundfont you’re using, and those are completely swappable at runtime.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;midly&quot;&gt;midly&lt;&#x2F;a&gt; crate handles all the MIDI serialization. It’s well-designed and the API maps cleanly onto what the generator produces. The main complication is that MIDI tracks store delta times (ticks since the previous event) rather than absolute timestamps, so export involves sorting all note-on and note-off events by absolute tick and then walking the list to convert. Straightforward, but easy to get wrong if you’re not thinking about it.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;pub fn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; export_midi&lt;&#x2F;span&gt;&lt;span&gt;(song&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;text-decoration: underline;&quot;&gt;Song&lt;&#x2F;span&gt;&lt;span&gt;, path&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;text-decoration: underline;&quot;&gt;Path&lt;&#x2F;span&gt;&lt;span&gt;, meta&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;text-decoration: underline;&quot;&gt;ExportMeta&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;text-decoration: underline;&quot;&gt;_&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #000000;background-color: #FFFFFF;&quot;&gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;text-decoration: underline;&quot;&gt;Result&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;()&amp;gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; mut&lt;&#x2F;span&gt;&lt;span&gt; smf_tracks&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #000000;background-color: #FFFFFF;&quot;&gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;text-decoration: underline;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;text-decoration: underline;&quot;&gt;Track&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;text-decoration: underline;&quot;&gt;static&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #000000;background-color: #FFFFFF;&quot;&gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;text-decoration: underline;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;new&lt;&#x2F;span&gt;&lt;span&gt;();&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span&gt; first_stanza&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; song&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;stanzas&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;first&lt;&#x2F;span&gt;&lt;span&gt;();&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span&gt; tempo_bpm&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; first_stanza&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt;s&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; s&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;tempo)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;unwrap_or&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;120.0&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span&gt; ts&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; first_stanza&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt;s&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; s&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;time_signature)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;unwrap_or&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;text-decoration: underline;&quot;&gt;TimeSignature&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;FOUR_FOUR&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    smf_tracks&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;build_tempo_track&lt;&#x2F;span&gt;&lt;span&gt;(tempo_bpm, ts, meta));&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;    &#x2F;&#x2F; Flatten stanzas sequentially; one track per instrument slot across all sections.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span&gt; max_tracks&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; song&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;stanzas&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;iter&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt;s&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; s&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;tracks&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;len&lt;&#x2F;span&gt;&lt;span&gt;())&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;max&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;unwrap_or&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    for&lt;&#x2F;span&gt;&lt;span&gt; track_idx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; in&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span&gt;max_tracks {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        smf_tracks&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;build_instrument_track&lt;&#x2F;span&gt;&lt;span&gt;(song, track_idx));&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;    write_smf&lt;&#x2F;span&gt;&lt;span&gt;(smf_tracks, path)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The TICKS_PER_BEAT constant is 480. Higher values give finer timing resolution; 480 is the standard sweet spot that most DAWs expect and that gives enough precision for the generator’s rhythm cells without inflating file sizes.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;procedural-generation&quot;&gt;Procedural Generation&lt;a class=&quot;post-anchor&quot; href=&quot;#procedural-generation&quot; aria-label=&quot;Anchor link for: procedural-generation&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The generator works from the outside in: song structure first, then harmonic skeleton, then individual voices.&lt;&#x2F;p&gt;
&lt;p&gt;Every generation run starts by picking a key, a tempo from a per-style range, and a set of section types from the preset. A “section” here is what the code calls a stanza — a distinct musical segment with its own tempo, time signature, instrumentation, and rules. An Overworld song might have an intro, two main sections, a bridge, and a return. A Boss Fight has different instrumentation, a denser rhythm grid, and a semitone lift going into the final section.&lt;&#x2F;p&gt;
&lt;p&gt;The harmonic skeleton is a chord progression generated from a scale palette appropriate to the style. The scale isn’t just major&#x2F;minor — the JRPG subtypes draw from things like &lt;code&gt;PhrygianDominant&lt;&#x2F;code&gt; ([0,1,4,5,7,8,10]) for dungeon-appropriate tension, &lt;code&gt;MelodicMinor&lt;&#x2F;code&gt; for emotional sections, and &lt;code&gt;Blues&lt;&#x2F;code&gt; where the vibe calls for it.&lt;&#x2F;p&gt;
&lt;p&gt;Once the chord progression exists, it drives everything else. The melody generates note choices from the chord tones plus scale passing notes, subject to a rhythmic “cell” that defines the accent pattern for that section. The bass line derives from the same root motion. The percussion pattern is templated to the style but has per-bar density variation built in, including occasional drop bars where the drums step aside. Counter-melody voices fill in between the primary melody’s phrase gaps.&lt;&#x2F;p&gt;
&lt;p&gt;The result is that everything in a section shares a structural skeleton — the tracks don’t generate independently and then get stacked, which is how you get music that feels like several programs running at the same time. Whether it always sounds &lt;em&gt;good&lt;&#x2F;em&gt; is a different conversation, but it sounds intentional.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;# A slice of what a JRPG Combat preset looks like in YAML&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; JRPG&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;styles&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; id&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; combat&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    label&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; Combat&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    tempo_range&lt;&#x2F;span&gt;&lt;span&gt;: [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;148&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 180&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    scale&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; PhrygianDominant&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    time_signature&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;4&#x2F;4&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    sections&lt;&#x2F;span&gt;&lt;span&gt;: [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;intro&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; a&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; b&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; a&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; climax&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    instruments&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;      melody&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; TrumpetSection&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;      harmony&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; StringEnsemble&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;      bass&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; ElectricBassPick&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;      percussion&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; StandardKit&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;One implementation detail worth mentioning: the primary melody instrument is locked once per song and held consistent across sections. This sounds obvious in retrospect, but the first version picked independently per section and the result was disorienting in a way that took a while to diagnose. Music has continuity expectations that aren’t explicitly written in any rule.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;cxx-qt-the-bridge-that-works-until-it-doesn-t&quot;&gt;cxx-qt: The Bridge That Works Until It Doesn’t&lt;a class=&quot;post-anchor&quot; href=&quot;#cxx-qt-the-bridge-that-works-until-it-doesn-t&quot; aria-label=&quot;Anchor link for: cxx-qt-the-bridge-that-works-until-it-doesn-t&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;KDAB&#x2F;cxx-qt&quot;&gt;cxx-qt&lt;&#x2F;a&gt; is a KDAB project that generates C++&#x2F;QML bindings from Rust. The basic idea: you annotate a Rust struct with &lt;code&gt;#[cxx_qt::bridge]&lt;&#x2F;code&gt;, declare which fields get exposed to QML as properties, and the build system generates the C++ glue code that makes the struct a proper &lt;code&gt;QObject&lt;&#x2F;code&gt; that QML can data-bind against.&lt;&#x2F;p&gt;
&lt;p&gt;In theory this means the music generation logic stays in pure Rust and QML binds directly to the results. In practice this works well for the generation side — the &lt;code&gt;AppController&lt;&#x2F;code&gt; QObject exposes the song data and generation controls, QML observes property changes and updates the UI, and the generator never touches Qt types.&lt;&#x2F;p&gt;
&lt;p&gt;The friction appears at the edges. The &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;kdab.github.io&#x2F;cxx-qt&#x2F;book&#x2F;&quot;&gt;cxx-qt bridge&lt;&#x2F;a&gt; has opinions about what types can cross the boundary — &lt;code&gt;QString&lt;&#x2F;code&gt;, &lt;code&gt;QList&amp;lt;T&amp;gt;&lt;&#x2F;code&gt;, the cxx-qt-lib types. If you need to pass a nested data structure from Rust to QML you have options: JSON-serialize it and parse on the QML side (genuinely what I ended up doing for the piano roll note data), define a &lt;code&gt;QObject&lt;&#x2F;code&gt; wrapper, or fight the type system until one of you gives up. The piano roll has thousands of notes that need to be inspectable and editable from QML. Serializing to JSON and deserializing on access was the path of least resistance and has been fine in practice.&lt;&#x2F;p&gt;
&lt;p&gt;The build script (&lt;code&gt;build.rs&lt;&#x2F;code&gt;) is where things get interesting. &lt;code&gt;cxx-qt-build&lt;&#x2F;code&gt; compiles the Qt C++ and generates the QML module registration, but it invokes cmake and ninja under the hood, which means the Rust build invokes a C++ build system that invokes a Rust build. Getting the dependency tracking right — so that any relevant change triggers a rebuild — required explicit &lt;code&gt;rerun-if-changed&lt;&#x2F;code&gt; declarations for every QML file, every C++ source, and the Rust bridge file:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;&#x2F;&#x2F; build.rs: explicit rerun-if-changed for QML, C++, and the Rust bridge&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;println!&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;cargo:rerun-if-changed=src&#x2F;bridge&#x2F;app_controller.rs&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;println!&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;cargo:rerun-if-changed=src&#x2F;file_dialog.cpp&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;println!&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;cargo:rerun-if-changed=qml&#x2F;components&#x2F;PianoRoll.qml&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;println!&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;cargo:rerun-if-changed=qml&#x2F;components&#x2F;TransportBar.qml&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;&#x2F;&#x2F; ... etc&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Running qmllint as part of the build step turned out to be worth the setup cost. QML syntax errors produce cryptic failures from the C++ compilation step unless you catch them first with the linter, and by the time the error surfaces through cxx-qt-build’s output you’ve lost the file and line number. Putting qmllint first in &lt;code&gt;build.rs&lt;&#x2F;code&gt; means you get a clean error immediately.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;the-c-that-couldn-t-be-avoided&quot;&gt;The C++ That Couldn’t Be Avoided&lt;a class=&quot;post-anchor&quot; href=&quot;#the-c-that-couldn-t-be-avoided&quot; aria-label=&quot;Anchor link for: the-c-that-couldn-t-be-avoided&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Three pieces of the project are C++ by necessity.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;File dialogs.&lt;&#x2F;strong&gt; Nobody wants a Qt-themed file picker on a KDE desktop. The native dialog backends are platform-specific C APIs: &lt;code&gt;kdialog&lt;&#x2F;code&gt; on KDE, &lt;code&gt;xdg-desktop-portal&lt;&#x2F;code&gt; elsewhere on Linux, &lt;code&gt;osascript&lt;&#x2F;code&gt; on macOS, &lt;code&gt;GetOpenFileNameW&lt;&#x2F;code&gt; &#x2F; &lt;code&gt;IFileOpenDialog&lt;&#x2F;code&gt; on Windows. These are all C or C++ APIs. The file dialog code lives in &lt;code&gt;src&#x2F;file_dialog.cpp&lt;&#x2F;code&gt; with a C-compatible header, and Rust calls it via &lt;code&gt;extern &quot;C&quot;&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;extern&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;C&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    fn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; kdialog_pick_open&lt;&#x2F;span&gt;&lt;span&gt;(title&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;: *const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #000000;background-color: #FFFFFF;&quot;&gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;text-decoration: underline;&quot;&gt;i8&lt;&#x2F;span&gt;&lt;span&gt;, start_dir&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;: *const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #000000;background-color: #FFFFFF;&quot;&gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;text-decoration: underline;&quot;&gt;i8&lt;&#x2F;span&gt;&lt;span&gt;, filter&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;: *const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #000000;background-color: #FFFFFF;&quot;&gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;text-decoration: underline;&quot;&gt;i8&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; -&amp;gt; *mut&lt;&#x2F;span&gt;&lt;span style=&quot;color: #000000;background-color: #FFFFFF;&quot;&gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;text-decoration: underline;&quot;&gt;i8&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    fn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; kdialog_free&lt;&#x2F;span&gt;&lt;span&gt;(s&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;: *mut&lt;&#x2F;span&gt;&lt;span style=&quot;color: #000000;background-color: #FFFFFF;&quot;&gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;text-decoration: underline;&quot;&gt;i8&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;App icon.&lt;&#x2F;strong&gt; Setting the application icon requires a Qt call that has to happen before &lt;code&gt;QGuiApplication&lt;&#x2F;code&gt; is fully initialized. That’s a &lt;code&gt;qmidigen_set_app_icon()&lt;&#x2F;code&gt; C++ function called from &lt;code&gt;main.rs&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;QML engine guard.&lt;&#x2F;strong&gt; The &lt;code&gt;QQmlApplicationEngine::load()&lt;&#x2F;code&gt; call prints errors to stderr but does not panic or return an error code if QML fails to load. You get a blank window and no indication of what went wrong. &lt;code&gt;qml_guard.cpp&lt;&#x2F;code&gt; provides a C function to check whether root objects were created, which lets &lt;code&gt;main.rs&lt;&#x2F;code&gt; detect the failure and exit with a useful message rather than silently showing nothing.&lt;&#x2F;p&gt;
&lt;p&gt;The plasma-integration race condition deserves its own paragraph. KDE’s &lt;code&gt;plasma-integration&lt;&#x2F;code&gt; plugin, loaded when Qt detects a Plasma session, calls &lt;code&gt;QQuickStyle::setStyle(&quot;org.kde.breeze&quot;)&lt;&#x2F;code&gt; during application construction. That call initializes an internal static singleton to &lt;code&gt;&quot;org.kde.breeze&quot;&lt;&#x2F;code&gt; and locks it — any subsequent &lt;code&gt;setStyle()&lt;&#x2F;code&gt; call is silently ignored. If your app needs the Basic style (which QMidiGen does, because its QML is built for Basic and Breeze touches things it shouldn’t), you have to win the race by calling &lt;code&gt;setStyle(&quot;Basic&quot;)&lt;&#x2F;code&gt; before &lt;code&gt;QGuiApplication::new()&lt;&#x2F;code&gt;. Miss the race and you get white borders, null crashes under Kvantum, and a debugging session that starts with “why does this only happen on KDE.”&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;&#x2F;&#x2F; Must be called before QGuiApplication::new() — wins the plasma-integration&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;&#x2F;&#x2F; static-init race before it can lock the style to org.kde.breeze.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;unsafe&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; qmidigen_reset_quick_style&lt;&#x2F;span&gt;&lt;span&gt;(); }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;mcp-the-optional-part&quot;&gt;MCP: The Optional Part&lt;a class=&quot;post-anchor&quot; href=&quot;#mcp-the-optional-part&quot; aria-label=&quot;Anchor link for: mcp-the-optional-part&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The &lt;code&gt;qmidigen-mcp&lt;&#x2F;code&gt; crate is a standalone binary with zero Qt dependency. It implements the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;modelcontextprotocol.io&#x2F;&quot;&gt;Model Context Protocol&lt;&#x2F;a&gt; — the open standard for giving AI agents structured access to tools — over stdin&#x2F;stdout JSON-RPC. Wire it into any MCP-compatible client and the agent can ask the generator to compose music.&lt;&#x2F;p&gt;
&lt;p&gt;The key design decision was keeping it completely separate from the GUI application. The MCP server doesn’t need Qt and shouldn’t need Qt. It directly includes the generation source:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;&#x2F;&#x2F; mcp&#x2F;src&#x2F;main.rs — directly path-includes the shared Rust modules&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#[path &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;..&#x2F;..&#x2F;src&#x2F;music&#x2F;mod.rs&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt; mod&lt;&#x2F;span&gt;&lt;span&gt; music;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#[path &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;..&#x2F;..&#x2F;src&#x2F;midi&#x2F;mod.rs&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  mod&lt;&#x2F;span&gt;&lt;span&gt; midi;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#[path &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;..&#x2F;..&#x2F;src&#x2F;audio&#x2F;mod.rs&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt; mod&lt;&#x2F;span&gt;&lt;span&gt; audio;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is unorthodox and somewhat frowned upon in polite cargo society, but the alternative — pulling the generation logic into a separate crate that both the GUI and MCP server depend on — would require restructuring the whole project to accommodate a feature that most users will never use. The current approach keeps the GUI crate simple and builds the MCP server in isolation when needed.&lt;&#x2F;p&gt;
&lt;p&gt;The MCP server exposes tools for listing presets, generating full songs, generating individual sections, playing audio via FluidSynth, and exporting to audio formats. An agent can ask for a dungeon theme in F# minor, get back a MIDI file path, and either play it or export it to MP3. The generation is seeded, so specifying a seed gets you the same piece every time — useful for iterating on something you like without having to describe it all over again.&lt;&#x2F;p&gt;
&lt;p&gt;This is entirely optional. The main application doesn’t know the MCP server exists. You build it separately (&lt;code&gt;ansible-playbook playbooks&#x2F;qmidigen.yml -t mcp&lt;&#x2F;code&gt;), configure it in your MCP client, and it’s there. Skip it entirely and nothing changes.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;the-parts-that-were-unexpectedly-painful&quot;&gt;The Parts That Were Unexpectedly Painful&lt;a class=&quot;post-anchor&quot; href=&quot;#the-parts-that-were-unexpectedly-painful&quot; aria-label=&quot;Anchor link for: the-parts-that-were-unexpectedly-painful&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;AppImage packaging.&lt;&#x2F;strong&gt; An AppImage needs to bundle everything it uses. Qt’s Wayland stack includes a GPU buffer sharing layer (&lt;code&gt;wayland-graphics-integration-client&lt;&#x2F;code&gt; plugins) that Qt needs to use GPU rendering on Wayland. Without them, Qt prints “Available client buffer integrations: QList()” and crashes with “Failed to create RHI” — a crash message that gives you no indication that the missing thing is a Wayland plugin you’ve never heard of. The fix is bundling those plugins explicitly; finding out that’s the fix required reading Qt source.&lt;&#x2F;p&gt;
&lt;p&gt;Similarly, AppImages built against a newer Qt will pull in QML plugins that link against the bundled Qt version. If you forget to bundle those plugins’ &lt;code&gt;libQt6*.so&lt;&#x2F;code&gt; dependencies and the user has an older system Qt in their path, the plugins find the system library at runtime and crash on the first symbol mismatch. The fix is scanning every QML plugin &lt;code&gt;.so&lt;&#x2F;code&gt; with &lt;code&gt;ldd&lt;&#x2F;code&gt; at package time and bundling anything it links that isn’t already present.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Windows emoji rendering.&lt;&#x2F;strong&gt; Qt on Windows under the Basic style cannot reliably render SMP Unicode characters (anything above U+FFFF) in &lt;code&gt;Text&lt;&#x2F;code&gt; elements. The mute icon, the track lock icon — these used emoji from the U+1F5xx range. On Windows they rendered as boxes. The fix was replacing them with BMP-range Unicode symbols (&lt;code&gt;⊗&lt;&#x2F;code&gt;, &lt;code&gt;♩&lt;&#x2F;code&gt;, &lt;code&gt;♪&lt;&#x2F;code&gt;, &lt;code&gt;■&lt;&#x2F;code&gt;, &lt;code&gt;□&lt;&#x2F;code&gt;) that Qt’s text rendering can actually handle. It works, but discovering that Qt silently ignores SMP characters instead of falling back gracefully required more time staring at a mostly-blank Windows VM than I’d like.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Wayland vs. X11 menu grabs.&lt;&#x2F;strong&gt; Qt defaults to the &lt;code&gt;xcb&lt;&#x2F;code&gt; (X11) platform plugin even inside a Wayland session, which runs the app through XWayland. On XWayland, X11 menu grabs are often silently rejected by KWin — popups render but clicks don’t register. The fix is forcing &lt;code&gt;QT_QPA_PLATFORM=wayland&lt;&#x2F;code&gt; when a Wayland session is detected. The detection requires checking three different environment variables because there’s no single authoritative indicator.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;current-state&quot;&gt;Current State&lt;a class=&quot;post-anchor&quot; href=&quot;#current-state&quot; aria-label=&quot;Anchor link for: current-state&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;ganthore&#x2F;qmidigen&#x2F;-&#x2F;releases&quot;&gt;0.1.1 release&lt;&#x2F;a&gt; is out, with AppImage for Linux, DMGs for both Apple Silicon and Intel macOS, and an NSIS installer for Windows. The JRPG preset is where most of the generation work has gone — 13 subtypes, each with distinct instrumentation, chord palettes, and section structures. The other genres (Pop, Jazz, Blues, Classical, Funk, Ambient) exist and work but are less opinionated.&lt;&#x2F;p&gt;
&lt;p&gt;The piano roll editor does what a piano roll editor should do: add, delete, move, resize notes; rubber-band selection; bulk operations; playhead scrubbing. It’s not competing with a DAW. It’s for making targeted edits after generation, not composing from scratch.&lt;&#x2F;p&gt;
&lt;p&gt;AUR packages are available (&lt;code&gt;qmidigen&lt;&#x2F;code&gt; stable, &lt;code&gt;qmidigen-git&lt;&#x2F;code&gt; VCS), and a FreeBSD port (&lt;code&gt;audio&#x2F;qmidigen&lt;&#x2F;code&gt;) is in the tree.&lt;&#x2F;p&gt;
&lt;p&gt;It’s been a more interesting project than most. The domain — procedural music with real-time synthesis — turns out to have a lot of surface area. And the stack, whatever its rough edges, is more comfortable to work in than a pure C++ Qt app would have been. Melody Raiser earned its place in the story. This is just where it goes next.&lt;&#x2F;p&gt;
</description>
      </item>
    </channel>
</rss>
