<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: knobmonster</title>
    <description>The latest articles on DEV Community by knobmonster (@knobmonster).</description>
    <link>https://dev.to/knobmonster</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3193451%2Fc668ff3f-fc2e-49c7-ae5d-478ada130e36.png</url>
      <title>DEV Community: knobmonster</title>
      <link>https://dev.to/knobmonster</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/knobmonster"/>
    <language>en</language>
    <item>
      <title>You Don't Need MIDI-OX Anymore: Building a SysEx Librarian with the Web MIDI API</title>
      <dc:creator>knobmonster</dc:creator>
      <pubDate>Thu, 02 Jul 2026 22:49:43 +0000</pubDate>
      <link>https://dev.to/knobmonster/you-dont-need-midi-ox-anymore-building-a-sysex-librarian-with-the-web-midi-api-3pii</link>
      <guid>https://dev.to/knobmonster/you-dont-need-midi-ox-anymore-building-a-sysex-librarian-with-the-web-midi-api-3pii</guid>
      <description>&lt;h2&gt;
  
  
  You Don't Need MIDI-OX Anymore: Building a SysEx Librarian with the Web MIDI API
&lt;/h2&gt;

&lt;p&gt;If you own a Yamaha DX7 or a Roland Juno-106, you already know the ritual.&lt;/p&gt;

&lt;p&gt;Install a utility from 2003. Fight USB-MIDI drivers on Windows 11. Open MIDI-OX. Configure ports. Pray the SysEx buffer doesn't choke mid-dump. Save a &lt;code&gt;.syx&lt;/code&gt; file with a name like &lt;code&gt;backup_final_v3_REAL.syx&lt;/code&gt;. Lose it anyway.&lt;/p&gt;

&lt;p&gt;I built &lt;a href="https://knob.monster" rel="noopener noreferrer"&gt;knob.monster&lt;/a&gt; to kill that ritual. It's a browser-native cloud librarian: plug in your synth, capture a bank, see patch names, restore with one click. No Electron. No installers. No registry edits.&lt;/p&gt;

&lt;p&gt;This post is the technical story: &lt;strong&gt;Web MIDI in, SysEx bytes through the wire, names out of the payload, bytes back to the synth.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem is not MIDI. It's desktop software.
&lt;/h2&gt;

&lt;p&gt;Vintage synths speak &lt;strong&gt;System Exclusive (SysEx)&lt;/strong&gt;. Bulk dumps are just long binary messages wrapped in &lt;code&gt;F0 ... F7&lt;/code&gt;. The hardware side hasn't changed since the 80s.&lt;/p&gt;

&lt;p&gt;What &lt;em&gt;has&lt;/em&gt; changed is everything around it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Modern OSes don't ship MIDI utilities&lt;/li&gt;
&lt;li&gt;Abandonware editors break on new machines&lt;/li&gt;
&lt;li&gt;Driver stacks for cheap USB-MIDI cables are flaky&lt;/li&gt;
&lt;li&gt;Cloud backup means "a folder I hope still exists"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_MIDI_API" rel="noopener noreferrer"&gt;Web MIDI API&lt;/a&gt; has been in Chromium browsers for years. Almost nobody used it for &lt;strong&gt;real librarian workflows&lt;/strong&gt; until recently. That's the gap.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture in one sentence
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Browser ↔ Web MIDI ↔ USB interface ↔ synth.&lt;/strong&gt; Server stores raw hex + parsed names. Restore streams hex back out the same port.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────┐    requestMIDIAccess({ sysex: true })    ┌──────────────┐
│   Chrome    │ ◄──────────────────────────────────────► │  USB-MIDI    │
│  knob.monster│    Uint8Array SysEx streams             │  interface   │
└──────┬──────┘                                          └──────┬───────┘
       │ POST sysex_hex + synth_model                           │
       ▼                                                        ▼
┌─────────────┐                                          ┌──────────────┐
│   Server    │  parse_dx7_sysex() / parse_juno106()     │  DX7 / Juno │
│  + Postgres │  → ["BASS 1", "BRASS 2", ...]            │  / M1 / CZ   │
└─────────────┘                                          └──────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 1: Ask for SysEx permission (not optional)
&lt;/h2&gt;

&lt;p&gt;Default Web MIDI access is useless for librarians. You need &lt;strong&gt;&lt;code&gt;sysex: true&lt;/code&gt;&lt;/strong&gt; or the browser strips exclusive messages.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;midiAccess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requestMIDIAccess&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;sysex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;midiAccess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmidimessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;ingestSysExBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Reality check:&lt;/strong&gt; Works in Chrome, Edge, and Opera. Safari and Firefox users are out of luck for now. We say that upfront on the landing page. Hiding it creates angry forum posts.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Request a dump (every synth is a special snowflake)
&lt;/h2&gt;

&lt;p&gt;There is no universal "please send all patches" command. Each manufacturer has its own handshake.&lt;/p&gt;

&lt;p&gt;For a &lt;strong&gt;Yamaha DX7&lt;/strong&gt;, we send a bulk dump request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dumpRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mh"&gt;0xF0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x43&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x09&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xF7&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="nx"&gt;outputPort&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dumpRequest&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Korg M1&lt;/strong&gt;, &lt;strong&gt;Roland Jupiter-6&lt;/strong&gt;, and &lt;strong&gt;Casio CZ-101&lt;/strong&gt; each get different byte sequences. The &lt;strong&gt;Juno-106&lt;/strong&gt; doesn't respond to an outbound request at all: the user must press &lt;strong&gt;WRITE&lt;/strong&gt; on the panel while we're listening. The UI has to say that out loud or support mail explodes.&lt;/p&gt;

&lt;p&gt;We concatenate incoming messages into one buffer, watch for idle timeout, then POST the hex string to the server.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Reassemble the stream
&lt;/h2&gt;

&lt;p&gt;SysEx often arrives as multiple &lt;code&gt;MIDIMessage&lt;/code&gt; events. A single bulk dump can be thousands of bytes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;ingestedBytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ingestSysExBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;byte&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ingestedBytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;finalizeDump&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hexString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ingestedBytes&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;padStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/banks&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;formDataWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hexString&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We log the stream in a monospace "MIDI monitor" panel so power users can sanity-check the handshake. Transparency builds trust with synth people who've been burned by black-box tools before.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Index extraction (where the magic is)
&lt;/h2&gt;

&lt;p&gt;Raw SysEx is opaque. Users want &lt;strong&gt;names&lt;/strong&gt;: &lt;code&gt;EP BASS&lt;/code&gt;, &lt;code&gt;UNI VERS&lt;/code&gt;, &lt;code&gt;A11 ACID BASS&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  DX7: names live at a fixed offset
&lt;/h3&gt;

&lt;p&gt;A DX7 32-voice bulk dump is ~4104 bytes. Each voice is 128 bytes. The patch name is &lt;strong&gt;the last 10 bytes&lt;/strong&gt; of each voice structure.&lt;/p&gt;

&lt;p&gt;Python on the server (same logic you'd use in JS):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;clean_ascii&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name_bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;chars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;name_bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;126&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;chars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;chr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;chars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chars&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_dx7_sysex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;voices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="c1"&gt;# Find F0 43 .. 09 header or accept raw 4096-byte voice block
&lt;/span&gt;    &lt;span class="n"&gt;start_offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\xF0\x43\x00\x09&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;voice_offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;start_offset&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;name_offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;voice_offset&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;118&lt;/span&gt;
        &lt;span class="n"&gt;name_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;name_offset&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;name_offset&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;patch_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;clean_ascii&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name_bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DX7 Voice &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;02&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;voices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;patch_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;voices&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;+ 118&lt;/code&gt; line is the entire product demo. Hex editor on the left, readable names on the right.&lt;/p&gt;

&lt;h3&gt;
  
  
  Juno-106: no stored names, infer from sliders
&lt;/h3&gt;

&lt;p&gt;Juno patches don't ship ASCII labels in the dump. We parse 18 parameter bytes per message and generate descriptors like &lt;code&gt;A11 ACID BASS&lt;/code&gt; from cutoff, resonance, and envelope shape. Different problem, same goal: &lt;strong&gt;human-readable rows in a table.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Generic fallback: scan for printable ASCII runs
&lt;/h3&gt;

&lt;p&gt;Unknown synth? Walk the byte stream, collect 6–16 character printable sequences, filter junk. Surprisingly effective on 80s ROMs that embed names inline.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Single-click recall (flash the payload back)
&lt;/h2&gt;

&lt;p&gt;Backup is half the job. Restore means &lt;strong&gt;sending the same bytes back&lt;/strong&gt; through the output port, often with pacing so 1980s UART buffers don't overflow.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;flashBank&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sysexHex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;hexToUint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sysexHex&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;outputPort&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// chunk + delay in production&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We chunk large transfers and show progress. Users named this "Sync to Synth" in usability tests. Engineers call it "write SysEx." Same thing.&lt;/p&gt;




&lt;h2&gt;
  
  
  What we deliberately did NOT build
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A patch editor.&lt;/strong&gt; No virtual sliders. We're a librarian, not a programmer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A DAW plugin.&lt;/strong&gt; Browser tab, not VST.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Universal synth support on day one.&lt;/strong&gt; DX7, Juno-106, M1, Jupiter-6, CZ-101, then generic scan. Depth beats breadth.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Saying "no" on the landing page filtered out the wrong customers and reduced support load.&lt;/p&gt;




&lt;h2&gt;
  
  
  Security and privacy (brief, because people ask)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Libraries are &lt;strong&gt;private by default&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;We store &lt;strong&gt;raw &lt;code&gt;.syx&lt;/code&gt; equivalent hex&lt;/strong&gt; for restore fidelity&lt;/li&gt;
&lt;li&gt;Parsing extracts &lt;strong&gt;names&lt;/strong&gt; for the UI; we don't need to expose your dumps publicly&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;requestMIDIAccess&lt;/code&gt; is a powerful permission. We only ask when you start a capture&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Business model (for the curious)
&lt;/h2&gt;

&lt;p&gt;Lifetime pricing, not subscription. Personal tier for bedroom studios, Studio tier for commercial use. Regional pricing (CAD, GBP, EUR) because &lt;code&gt;$39&lt;/code&gt; on a Canadian screen reads American, not "fair."&lt;/p&gt;

&lt;p&gt;Organic discovery beat ads at this LTV. HN, Synthtopia, Gearspace, SEO guides for "DX7 backup" and "MIDI-OX alternative Mac." The Web MIDI angle is what developers share. The Juno-106 angle is what musicians search.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lessons if you're building hardware-facing web apps
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Lead with the demo moment.&lt;/strong&gt; HN taught me: one live counter beats three paragraphs of copy. For us, it's patch names appearing from hex.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Document manufacturer quirks in UI&lt;/strong&gt;, not a PDF manual from 1987.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sysex: true&lt;/strong&gt; or go home.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ship parsers incrementally.&lt;/strong&gt; One great DX7 path &amp;gt; ten broken generic paths.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Export guarantee.&lt;/strong&gt; Let people download &lt;code&gt;.syx&lt;/code&gt; anytime. Trust compounds.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  If this is you
&lt;/h2&gt;

&lt;p&gt;You're the person with a DX7 in the bedroom and a drawer full of cables that almost work.&lt;/p&gt;

&lt;p&gt;You're the Juno owner who finally got SysEx working once, then forgot the switch combo for six months.&lt;/p&gt;

&lt;p&gt;You're the repair tech who just needs the bank &lt;strong&gt;out&lt;/strong&gt; before the customer leaves, and &lt;strong&gt;back in&lt;/strong&gt; when the battery's swapped.&lt;/p&gt;

&lt;p&gt;You're the developer who looked at MIDI-OX and thought: &lt;em&gt;this should be a URL, not an installer.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You don't need another rabbit hole. You need the dump saved, the names readable, the restore one click away.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://knob.monster" rel="noopener noreferrer"&gt;knob.monster&lt;/a&gt; is free to start. No card. Chrome, a USB-MIDI cable, memory protect off, and the synth you already love.&lt;/p&gt;

&lt;p&gt;If you're building something else entirely, steal the Web MIDI patterns anyway. Pedals, controllers, lab gear. The browser is the installer your users were never asking for.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Links&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://knob.monster" rel="noopener noreferrer"&gt;knob.monster&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://knob.monster/sysex-librarian-alternatives" rel="noopener noreferrer"&gt;SysEx librarian alternatives guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_MIDI_API" rel="noopener noreferrer"&gt;Web MIDI on MDN&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>showdev</category>
      <category>browser</category>
    </item>
  </channel>
</rss>
