<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>fosstog - fluidsynth</title>
    <subtitle>Free &amp; Open Source Photography</subtitle>
    <link rel="self" type="application/atom+xml" href="https://fosstog.com/tags/fluidsynth/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://fosstog.com"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-05-31T00:00:00+00:00</updated>
    <id>https://fosstog.com/tags/fluidsynth/atom.xml</id>
    <entry xml:lang="en">
        <title>QMidiGen 0.1.2: Fifty-Five Styles and Several Hard Lessons</title>
        <published>2026-05-31T00:00:00+00:00</published>
        <updated>2026-05-31T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              ganthore
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://fosstog.com/blog/qmidigen-012/"/>
        <id>https://fosstog.com/blog/qmidigen-012/</id>
        
        <content type="html" xml:base="https://fosstog.com/blog/qmidigen-012/">&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&#x2F;-&#x2F;releases&quot;&gt;QMidiGen 0.1.2&lt;&#x2F;a&gt; is out today. The JRPG preset grew from roughly 30 styles to &lt;strong&gt;55&lt;&#x2F;strong&gt;, calibrated against a serious study of classic and modern JRPG soundtracks. The generator got a motif system, a call-and-response voice, and an instrument coherence filter. Three &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.fluidsynth.org&#x2F;&quot;&gt;FluidSynth&lt;&#x2F;a&gt; bugs — one making seeks silently land at tick 0, one occasionally corrupting the C heap, one quietly breaking loop sections — turned out to have been there since 0.1.0. And the soundfont profile system learned to disqualify programs that sound categorically wrong, not just loud. The wins are real; a few of the pain points were genuinely educational.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Section&lt;&#x2F;th&gt;&lt;th&gt;Summary&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fosstog.com&#x2F;blog&#x2F;qmidigen-012&#x2F;#the-measurement-project&quot;&gt;The Measurement Project&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td&gt;Why I studied actual JRPG soundtracks instead of guessing&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fosstog.com&#x2F;blog&#x2F;qmidigen-012&#x2F;#fifty-five-styles&quot;&gt;Fifty-Five Styles&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td&gt;What the preset expansion actually looks like&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fosstog.com&#x2F;blog&#x2F;qmidigen-012&#x2F;#generator-quality-pass&quot;&gt;Generator Quality Pass&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td&gt;Motifs, call-and-response, coherence filters&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fosstog.com&#x2F;blog&#x2F;qmidigen-012&#x2F;#soundfont-profiles-got-teeth&quot;&gt;Soundfont Profiles Got Teeth&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;avoid_programs&lt;&#x2F;code&gt; and the Timbres of Heaven overhaul&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fosstog.com&#x2F;blog&#x2F;qmidigen-012&#x2F;#three-fluidsynth-bugs&quot;&gt;Three FluidSynth Bugs&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td&gt;Seek ordering, heap corruption, and what the C source says&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fosstog.com&#x2F;blog&#x2F;qmidigen-012&#x2F;#the-cxx-qt-signal-problem&quot;&gt;The cxx-qt Signal Problem&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td&gt;Property setters from Rust that refuse to wake QML&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fosstog.com&#x2F;blog&#x2F;qmidigen-012&#x2F;#the-ui-additions-nobody-will-notice&quot;&gt;The UI Additions Nobody Will Notice&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td&gt;File → Save, Import MIDI, soundfont persistence&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https:&#x2F;&#x2F;fosstog.com&#x2F;blog&#x2F;qmidigen-012&#x2F;#lessons&quot;&gt;Lessons&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td&gt;What I’d do differently&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;the-measurement-project&quot;&gt;The Measurement Project&lt;a class=&quot;post-anchor&quot; href=&quot;#the-measurement-project&quot; aria-label=&quot;Anchor link for: the-measurement-project&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 JRPG preset in 0.1.0 and 0.1.1 had reasonable-sounding values — tempos around where you’d expect them, density factors that produced something recognizable. “Reasonable-sounding” and “actually calibrated” are different things, though, and expanding the preset to cover 30+ more styles made that difference impossible to ignore.&lt;&#x2F;p&gt;
&lt;p&gt;For 0.1.2 I stopped guessing and started actually studying source material. The process: pick a representative cross-section of classic and modern JRPG soundtracks, work through them carefully noting tempo, density, and structural choices, and compare those observations against what the generator was producing.&lt;&#x2F;p&gt;
&lt;p&gt;Some of what came back was not what I expected.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Last Dungeon was supposed to be slow and oppressive.&lt;&#x2F;strong&gt; That’s the genre convention: you’re in the final area, dread is mounting, everything is slower and heavier. Actual JRPG soundtracks disagreed — endgame areas trend toward some of the fastest, densest tracks in their respective games. The intuition “final area = slow and foreboding” is a feeling, not a musical fact. The generator had been producing Last Dungeon at 92 BPM with density 0.80. Corrected to 136 BPM and 1.10.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Victory Fanfare was the busiest thing I found.&lt;&#x2F;strong&gt; By note density, victory fanfares are consistently some of the most active cues in the genre — which makes sense if you think about it for five seconds, but I hadn’t. The generator was producing it at density 0.85, lower than a standard battle theme. Corrected to 1.35. Now it actually sounds like something happened.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Final Boss is not always minor.&lt;&#x2F;strong&gt; Some of the most iconic JRPG final boss themes are in major keys — the “vast and hopeful” finale rather than the “oppressive and dark” one. I had the Final Boss scale palette excluding major entirely. Intuition overridden.&lt;&#x2F;p&gt;
&lt;p&gt;The pattern is consistent: intuitions about what JRPG music sounds like are shaped by memory and genre convention, and memory is not calibrated. Studying the actual music and correcting against it produced a preset that generates things you’d recognize as belonging in a game, rather than a generator’s approximation of that.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;fifty-five-styles&quot;&gt;Fifty-Five Styles&lt;a class=&quot;post-anchor&quot; href=&quot;#fifty-five-styles&quot; aria-label=&quot;Anchor link for: fifty-five-styles&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 preset went from the original general-purpose styles (Pop, Jazz, Blues, Classical, Funk, Ambient) plus roughly 20 JRPG subtypes to &lt;strong&gt;55 JRPG subtypes&lt;&#x2F;strong&gt; across nine categories. The new subtypes fill in gaps the original set left obvious.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Emotional&lt;&#x2F;strong&gt; gained &lt;code&gt;Desolation&lt;&#x2F;code&gt; (near-static, drumless, grief-register) and &lt;code&gt;Theme of Love&lt;&#x2F;code&gt; (88 BPM, A minor — the slow, warm romantic ballad archetype). &lt;code&gt;Reminiscence&lt;&#x2F;code&gt; and &lt;code&gt;Drifter&lt;&#x2F;code&gt; were already there; the expansion completes the palette.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Uplifting&lt;&#x2F;strong&gt; is a new category. &lt;code&gt;AscendingDawn&lt;&#x2F;code&gt; (86 BPM, C major) is the slow-grand hopeful finale — the major-key “this is actually enormous and triumphant” archetype rather than the minor-key dread variant. &lt;code&gt;DragonCalling&lt;&#x2F;code&gt; covers mythic and destiny-fulfilled moments. The category got its own UI group because these styles don’t fit under any of the existing ones without feeling wrong.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Airship &amp;amp; Space&lt;&#x2F;strong&gt; split off from Overworld to give &lt;code&gt;CelestialVoyage&lt;&#x2F;code&gt; its own home. Density 0.18 — space &lt;em&gt;is&lt;&#x2F;em&gt; the texture. Sustained pads, near-zero attack instruments, silence as a compositional element. Getting this to sound like something rather than nothing was more work than the numbers suggest.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Tension &amp;amp; Escape&lt;&#x2F;strong&gt; arrived for &lt;code&gt;FleetingEscape&lt;&#x2F;code&gt; — the urgent-chase subtype, the classic “you are running and the game will not let you forget it” archetype. High tempo, dense rhythm, short sections, no resolution. The category also houses the existing &lt;code&gt;Tension&lt;&#x2F;code&gt; style, which had been floating without a parent.&lt;&#x2F;p&gt;
&lt;p&gt;Each new subtype required calibrating five core parameters: default tempo, tempo range, density factor, drum intensity, and scale palette. The new UI category groups keep the picker navigable as the list grows.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;generator-quality-pass&quot;&gt;Generator Quality Pass&lt;a class=&quot;post-anchor&quot; href=&quot;#generator-quality-pass&quot; aria-label=&quot;Anchor link for: generator-quality-pass&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;Adding styles without improving the underlying generation would just produce more variations of the same mediocrity. Several significant changes went into the generator this cycle.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;motif-system&quot;&gt;Motif system&lt;a class=&quot;post-anchor&quot; href=&quot;#motif-system&quot; aria-label=&quot;Anchor link for: motif-system&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;The generator now picks a seed motif at the start of each song — a short sequence of pitch intervals and durations. Across sections, it reappears in two variations: &lt;strong&gt;augmentation&lt;&#x2F;strong&gt; (durations doubled, the shape stretched out) and &lt;strong&gt;retrograde&lt;&#x2F;strong&gt; (intervals and durations reversed). This isn’t subtle compositional theory; it’s the basic move that ties a piece together. Without it, sections generate independently and stack — they share instrumentation and key, but they don’t share anything that a listener would recognize as a common thread. With the motif system, you hear the same shape recurring in different forms, which is what musical coherence actually is.&lt;&#x2F;p&gt;
&lt;p&gt;The motif is fixed for a given generation seed, so regenerating the full song gives you the same motif in a different arrangement. Regenerating a single section inherits the motif from the rest of the song rather than picking a new one and breaking the thread.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;call-and-response-voice-channel-7&quot;&gt;Call-and-response voice (channel 7)&lt;a class=&quot;post-anchor&quot; href=&quot;#call-and-response-voice-channel-7&quot; aria-label=&quot;Anchor link for: call-and-response-voice-channel-7&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Non-battle styles now generate a response voice on MIDI channel 7. While the primary melody (channel 0) is resting between phrases, the response voice plays an inverted version of the motif in a contrasting register. Six rotating response templates per phrase window prevent it from settling into a predictable pattern.&lt;&#x2F;p&gt;
&lt;p&gt;This solved a problem I’d been aware of since 0.1.0 but hadn’t named clearly: non-battle styles had a melody, a harmony, and a bass, but the space between melody phrases was just pad sustain and drums ticking away. The response voice fills that space without competing when the melody is active.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;instrument-coherence-filter&quot;&gt;Instrument coherence filter&lt;a class=&quot;post-anchor&quot; href=&quot;#instrument-coherence-filter&quot; aria-label=&quot;Anchor link for: instrument-coherence-filter&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;The &lt;code&gt;programs_compatible&lt;&#x2F;code&gt; function blocks jarring instrument pairings — synth leads over orchestral strings, rock guitar alongside a woodwind section, that kind of thing. Implemented as a set of GM program ranges that don’t belong in the same arrangement:&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;fn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; programs_compatible&lt;&#x2F;span&gt;&lt;span&gt;(melody&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;u8&lt;&#x2F;span&gt;&lt;span&gt;, harmony&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;u8&lt;&#x2F;span&gt;&lt;span&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;bool&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; synth_lead&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 80&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;..=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;95_&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;text-decoration: underline;&quot;&gt;u8&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; orchestral_strings&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 40&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;..=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;47_&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;text-decoration: underline;&quot;&gt;u8&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;    if&lt;&#x2F;span&gt;&lt;span&gt; synth_lead&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;contains&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;melody)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; &amp;amp;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt; orchestral_strings&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;contains&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;harmony) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;        return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; false&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;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;    &#x2F;&#x2F; additional known-bad pairings&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;    true&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 generator picks new harmony instruments until it finds one that passes, with a fallback to the original pick if the pool is exhausted. The blocklist is deliberately conservative — it blocks things that are actively bad, not things that are merely unusual.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;sustained-instrument-handling&quot;&gt;Sustained instrument handling&lt;a class=&quot;post-anchor&quot; href=&quot;#sustained-instrument-handling&quot; aria-label=&quot;Anchor link for: sustained-instrument-handling&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Strings, woodwinds, and horns now generate differently from attack instruments (piano, plucked strings, percussion-adjacent programs). Sustained instruments get notes held 1.4× longer at lower cell density. A string section playing thirty-second notes sounds like a malfunction, not orchestral writing. The distinction is made by GM program number range, not by category inference.&lt;&#x2F;p&gt;
&lt;p&gt;The cumulative effect of these changes is that songs sound meaningfully better than they did in 0.1.0. This happens every cycle and it’s still slightly surprising every cycle. I don’t know what I expected.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;soundfont-profiles-got-teeth&quot;&gt;Soundfont Profiles Got Teeth&lt;a class=&quot;post-anchor&quot; href=&quot;#soundfont-profiles-got-teeth&quot; aria-label=&quot;Anchor link for: soundfont-profiles-got-teeth&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 soundfont profile system launched with &lt;code&gt;velocity_scale&lt;&#x2F;code&gt; (a global volume multiplier) and &lt;code&gt;velocity_ceilings&lt;&#x2F;code&gt; (per-program max velocity). These are loudness controls — they fix instruments that are too hot relative to the rest of the font.&lt;&#x2F;p&gt;
&lt;p&gt;What they can’t fix is a timbre problem. If a soundfont’s violin samples sound shrill regardless of velocity, capping their velocity makes them quieter and still shrill. The right answer is not to use that program.&lt;&#x2F;p&gt;
&lt;p&gt;0.1.2 adds &lt;code&gt;avoid_programs&lt;&#x2F;code&gt; to the profile format:&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;# assets&#x2F;soundfonts&#x2F;profiles&#x2F;timbres_of_heaven.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;avoid_programs&lt;&#x2F;span&gt;&lt;span&gt;: [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;25&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 40&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 56&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 57&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 61&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 80&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 81&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;  # 25 = Steel Guitar — bizarre timbre in this font&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;  # 40 = Violin       — shrill at any velocity&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;  # 56 = Trumpet      — clips and distorts&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;  # 57 = Trombone     — same&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;  # 61 = Brass Sect.  — clips and distorts&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;  # 80 = Square Lead  — wrong sonic category for orchestral arrangements&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;  # 81 = Saw Wave     — same&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The generator checks &lt;code&gt;avoid_programs&lt;&#x2F;code&gt; when picking instruments and routes around the listed programs, picking an alternative from the same pool. If the entire pool is on the avoid list, it falls back to the full pool rather than produce silence.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.hammersound.net&#x2F;hs_soundfonts.html&quot;&gt;Timbres of Heaven (XGM) 4.00(G)&lt;&#x2F;a&gt; got the most thorough overhaul: &lt;code&gt;velocity_scale&lt;&#x2F;code&gt; down to 0.90, 18 per-program velocity ceilings, and seven programs added to &lt;code&gt;avoid_programs&lt;&#x2F;code&gt;. Before this, generating with Timbres of Heaven landed on something that made you want to immediately regenerate roughly 20% of the time. After: it’s a legitimately usable font with known problem areas automatically routed around.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;musical-artifacts.com&#x2F;artifacts&#x2F;3753&quot;&gt;DSoundFontV4&lt;&#x2F;a&gt; and Roland SC-55 Up got more targeted fixes. Brass Section 61 clips and distorts in DSoundFontV4 at any scale — sample defect, not a loudness issue — so it’s avoided entirely. Roland SC-55 Up’s Synth Brass 1 runs 3× louder than everything else in the font; a single velocity ceiling of 80 handles it.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;three-fluidsynth-bugs&quot;&gt;Three FluidSynth Bugs&lt;a class=&quot;post-anchor&quot; href=&quot;#three-fluidsynth-bugs&quot; aria-label=&quot;Anchor link for: three-fluidsynth-bugs&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;Two of these had been present since 0.1.0. One was intermittent enough to blame on the environment until it wasn’t.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;bug-1-seek-always-landed-at-tick-0&quot;&gt;Bug 1: seek always landed at tick 0&lt;a class=&quot;post-anchor&quot; href=&quot;#bug-1-seek-always-landed-at-tick-0&quot; aria-label=&quot;Anchor link for: bug-1-seek-always-landed-at-tick-0&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;The original seek implementation:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Start playback: &lt;code&gt;fluid_player_play(player)&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Seek to position: &lt;code&gt;fluid_player_seek(player, tick)&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;This is backwards. &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.fluidsynth.org&#x2F;&quot;&gt;FluidSynth’s&lt;&#x2F;a&gt; audio driver runs on a separate thread. The moment &lt;code&gt;fluid_player_play&lt;&#x2F;code&gt; is called, that thread is live and processing ticks. Calling &lt;code&gt;fluid_player_seek&lt;&#x2F;code&gt; after that creates a race: the audio thread may have already processed the first callback before the seek arrives, at which point FluidSynth drops the seek silently and plays from tick 0. No error. No warning. Audio just starts from the beginning.&lt;&#x2F;p&gt;
&lt;p&gt;The fix is calling &lt;code&gt;fluid_player_seek&lt;&#x2F;code&gt; while the player is still in &lt;code&gt;FLUID_PLAYER_READY&lt;&#x2F;code&gt; state — before &lt;code&gt;fluid_player_play&lt;&#x2F;code&gt;. In that state there’s no audio thread competing for the position:&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 unsafe fn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; play_midi_bytes&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FD971F;&quot;&gt; self&lt;&#x2F;span&gt;&lt;span&gt;, bytes&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;text-decoration: underline;&quot;&gt;u8&lt;&#x2F;span&gt;&lt;span&gt;], start_tick&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;i64&lt;&#x2F;span&gt;&lt;span&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: #88846F;&quot;&gt;    &#x2F;&#x2F; ... load bytes into player ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span&gt; start_tick&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; &amp;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: #A6E22E;&quot;&gt;        fluid_player_seek&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FD971F;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;player, start_tick&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; as&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;i32&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;  &#x2F;&#x2F; must be while READY&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;    fluid_player_play&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FD971F;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;player);&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;                          &#x2F;&#x2F; THEN start&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&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;Ok&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;Every “seek to position and play” action — section switching, scrubbing, jumping to a section in the list — had been silently rewinding to the beginning. The UI showed the right position. The audio played from the start. These two facts coexisted for the entire 0.1.0–0.1.1 cycle without producing an error.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;bug-2-stopping-caused-occasional-heap-corruption&quot;&gt;Bug 2: stopping caused occasional heap corruption&lt;a class=&quot;post-anchor&quot; href=&quot;#bug-2-stopping-caused-occasional-heap-corruption&quot; aria-label=&quot;Anchor link for: bug-2-stopping-caused-occasional-heap-corruption&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;The crash message, when it appeared:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;malloc(): unsorted double linked list corrupted&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That’s a C heap report with no meaningful pointer to the actual site. What was happening: &lt;code&gt;fluid_player_stop()&lt;&#x2F;code&gt; signals the player to stop, but the audio driver thread is still running. It keeps processing for a few milliseconds while the player’s state winds down. Deleting the player while the audio thread is still active means the audio thread continues writing into freed memory.&lt;&#x2F;p&gt;
&lt;p&gt;The fix is silencing the synth before stopping the player:&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 unsafe fn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; stop_player&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FD971F;&quot;&gt; self&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; chan&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 style=&quot;color: #AE81FF;&quot;&gt;16&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;        fluid_synth_all_sounds_off&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FD971F;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;synth, chan);&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;  &#x2F;&#x2F; kill voices on audio thread&amp;#39;s synth&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;    fluid_player_stop&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FD971F;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;player);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;    fluid_player_join&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FD971F;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;player);&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;   &#x2F;&#x2F; block until player thread confirms stopped&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;    delete_fluid_player&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FD971F;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;player);&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;code&gt;fluid_synth_all_sounds_off&lt;&#x2F;code&gt; kills active voices immediately on the synth side, so by the time the player is deleted the audio thread is writing silence and the synth state is stable. &lt;code&gt;fluid_player_join&lt;&#x2F;code&gt; makes the “stopped” part actually synchronous.&lt;&#x2F;p&gt;
&lt;p&gt;The corruption was intermittent and more common on faster machines, where the audio thread had more time to run between the stop signal and the delete. The kind of bug that’s easy to attribute to the wrong thing — a QML state issue, a build flag, memory elsewhere — before you read the FluidSynth source and understand what the threads are actually doing.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;bug-3-bpm-set-after-play-not-a-crash-just-quietly-wrong&quot;&gt;Bug 3: BPM set after play (not a crash, just quietly wrong)&lt;a class=&quot;post-anchor&quot; href=&quot;#bug-3-bpm-set-after-play-not-a-crash-just-quietly-wrong&quot; aria-label=&quot;Anchor link for: bug-3-bpm-set-after-play-not-a-crash-just-quietly-wrong&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;A smaller one. Calling &lt;code&gt;fluid_player_set_bpm&lt;&#x2F;code&gt; after &lt;code&gt;fluid_player_play&lt;&#x2F;code&gt; in FluidSynth 2.3+ can trigger an internal re-seek on the first processed tick, interfering with where loop sections restart. Same root cause: the audio thread is live, and the BPM change arrives mid-stream. Fix is the same pattern — call it before &lt;code&gt;fluid_player_play&lt;&#x2F;code&gt;, while the player is still in &lt;code&gt;FLUID_PLAYER_READY&lt;&#x2F;code&gt;. No crash, but section-loop behavior was quietly wrong in ways that looked like a position tracking bug until it wasn’t.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;the-cxx-qt-signal-problem&quot;&gt;The cxx-qt Signal Problem&lt;a class=&quot;post-anchor&quot; href=&quot;#the-cxx-qt-signal-problem&quot; aria-label=&quot;Anchor link for: the-cxx-qt-signal-problem&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;This is the one that cost the most time.&lt;&#x2F;p&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; generates C++ &lt;code&gt;QObject&lt;&#x2F;code&gt; bindings from annotated Rust structs. When a field is exposed as a Qt property, changes to it emit a &lt;code&gt;NOTIFY&lt;&#x2F;code&gt; signal, which QML &lt;code&gt;Connections&lt;&#x2F;code&gt; handlers listen for. The contract seems clear: Rust setter emits signal, QML handler fires.&lt;&#x2F;p&gt;
&lt;p&gt;In practice:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Setting a property from QML fires the Connections handler reliably.&lt;&#x2F;strong&gt; QML does &lt;code&gt;controller.someProperty = newValue&lt;&#x2F;code&gt;, handler fires.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Setting a property from Rust often does not fire the Connections handler.&lt;&#x2F;strong&gt; A Rust method — invoked from QML — calls &lt;code&gt;self.as_mut().set_some_property(value)&lt;&#x2F;code&gt;. The &lt;code&gt;NOTIFY&lt;&#x2F;code&gt; signal IS emitted. But the &lt;code&gt;Connections { function onSomePropertyChanged() { ... } }&lt;&#x2F;code&gt; handler may not fire. “May not” meaning “usually doesn’t, with no error, no warning, and no indication that anything went wrong.”&lt;&#x2F;p&gt;
&lt;p&gt;This turned up in three separate places during 0.1.2:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;onDefaultSoundfontPathChanged&lt;&#x2F;code&gt; set from Rust — fired correctly when set from QML, silently did nothing when set from Rust&lt;&#x2F;li&gt;
&lt;li&gt;Two other handlers in the soundfont and preset systems — neither fired when set from Rust&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The pattern, once you’ve seen it enough times, is consistent enough to be a rule: &lt;strong&gt;never rely on a QML &lt;code&gt;Connections&lt;&#x2F;code&gt; handler reacting to a property set from Rust.&lt;&#x2F;strong&gt; Alternatives that actually work:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Fire a different signal at the method boundary — signals emitted explicitly from the invoked method seem to propagate more reliably than property-change notifications from setters called inside that method&lt;&#x2F;li&gt;
&lt;li&gt;Call a QML function directly after the Rust method returns, rather than waiting for a signal&lt;&#x2F;li&gt;
&lt;li&gt;Read the property directly from QML at call time instead of caching via a signal handler&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Why does this happen? My best current understanding: the generated C++ emits the property-change signal through a queued connection when called from a Rust method executing on the Qt thread. The event loop needs to process the queued signal before the &lt;code&gt;Connections&lt;&#x2F;code&gt; handler fires. If there’s a phase mismatch between when the signal is enqueued and when the &lt;code&gt;Connections&lt;&#x2F;code&gt; binding is evaluated, the handler is skipped. I haven’t read enough of cxx-qt’s generated C++ to be fully confident in that explanation. The workarounds work regardless.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;the-ui-additions-nobody-will-notice&quot;&gt;The UI Additions Nobody Will Notice&lt;a class=&quot;post-anchor&quot; href=&quot;#the-ui-additions-nobody-will-notice&quot; aria-label=&quot;Anchor link for: the-ui-additions-nobody-will-notice&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;File → Save&lt;&#x2F;strong&gt; (&lt;code&gt;Ctrl+S&lt;&#x2F;code&gt;): if a file is already open, saves in place; otherwise opens Save As. This is the behavior everyone expects from every application that handles files, and it was missing in 0.1.1. That’s on me.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;File → Save As…&lt;&#x2F;strong&gt; (&lt;code&gt;Ctrl+Shift+S&lt;&#x2F;code&gt;): always prompts. Separately bound from Save so saving a copy doesn’t overwrite the working file.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;File → Import MIDI…&lt;&#x2F;strong&gt;: takes an external MIDI file and adds its tracks to the current song. Useful for importing reference tracks, combining separately-generated sections, or bringing in something you want to edit. It appends, it doesn’t replace.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Default soundfont persistence&lt;&#x2F;strong&gt;: the active soundfont is now written to settings on change and restored on next launch. Previously, every launch required re-selecting your soundfont. The app also probes a list of standard system soundfont paths on first launch so it has a reasonable chance of finding something useful automatically.&lt;&#x2F;p&gt;
&lt;p&gt;None of these are interesting to write about, which is roughly proportional to how much it matters that they work.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;lessons&quot;&gt;Lessons&lt;a class=&quot;post-anchor&quot; href=&quot;#lessons&quot; aria-label=&quot;Anchor link for: lessons&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;Study the source material.&lt;&#x2F;strong&gt; “I know what this should sound like” is not calibration. The Last Dungeon correction (92 → 136 BPM) came directly from listening to a lot of JRPG endgame tracks and noticing they’re consistently among the fastest in their respective games — the opposite of the assumption baked into the original preset. That’s not something intuition produces, and the intuition pointed in the wrong direction. If you’re modeling a genre, study actual examples and correct against what you find.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;FluidSynth’s threading model is not optional.&lt;&#x2F;strong&gt; It’s a C library with an audio thread that is live the moment you call &lt;code&gt;fluid_player_play&lt;&#x2F;code&gt;. Every operation that touches player or synth state after that point is a potential race. The documentation mentions this, but “mentions” is different from “makes you feel the consequence” — the seek bug was there for the full 0.1.0–0.1.1 cycle because it was consistent enough to look like a feature and not obviously broken until you measured the behavior. Read the source for any C audio library you’re wrapping in Rust. The safety boundary you build in &lt;code&gt;Drop&lt;&#x2F;code&gt; is only as good as your understanding of what the C side is doing on its threads.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;cxx-qt signals set from Rust are unreliable; work around them from the QML side.&lt;&#x2F;strong&gt; This may be a design constraint rather than a bug, but either way: if your UI depends on reacting to Rust-initiated property changes via &lt;code&gt;Connections&lt;&#x2F;code&gt; handlers, you will spend time debugging something that produces no error output. Push updates from QML; don’t wait for Rust to pull.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Sustained instruments are a different problem than attack instruments.&lt;&#x2F;strong&gt; A piano plays a note and the decay handles itself. A violin plays a note and holds it — for as long as the note lasts, and possibly longer if the release is slow. A generator tuned for piano-family instruments produces string writing that sounds like a pianist who got stuck holding the keys down. GM program number ranges are a good enough proxy for the distinction; they don’t need to be perfect to be useful.&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
