Feature request: "Note" value display for the Generic button (show a MIDI value as C4)

Summary

Could the Generic button’s value display get a new ”Note” option, so that a MIDI value (0–127) is shown as its note name using the standard MIDI convention — number → note + octave, e.g. 60C4?

What I’d like

Today the Generic button can display the raw MIDI value (a number). I’d love an extra display format that reads that value as a MIDI note number and shows the note name. For example: 36 → C2, 48 → C3, 60 → C4, 61 → C#4, 72 → C5.

Ideally the octave numbering follows the common C4 = middle C = note 60 convention (Scientific Pitch Notation, what most DAWs use). A small toggle for C4=60 vs C3=60 would be a nice bonus, but the C4 default alone would already be perfect.

Why — concrete use case

I use the plugin in my live keyboard rig. A very common thing to configure is a keyboard split point — the note that separates, say, a bass sound (left hand) from a lead/pad (right hand). A split point is a MIDI note number, but “split at 60” is meaningless at a glance on stage — “split at C4” is instantly readable. A note-name display makes the button self-documenting and removes the note-number → note-name math during a gig. Same benefit for any pitch-valued button (transpose base, lowest note, trigger note…).

I’m happy to test

If you put together a build with this, I can test it and report back whether it works — correct note names, correct octaves across the full 0–127 range, edge values (0 = C-1, 127 = G9). Just point me to a test build.

Thanks a lot for the plugin — it’s central to my live setup!

I would suggest using a translation file for this.

Brilliant, thank you – the translation file is exactly the right tool for this. I hadn’t realised a Cycle button could carry arbitrary display text; that solves it perfectly. Posting a ready-made file here in case it helps anyone else who wants note names on a key.

What it does: maps every MIDI value 0-127 to its note name using C4 = middle C = 60 (Scientific Pitch Notation, the DAW default), so a Generic Midi button in Cycle mode shows C4, F#2, A0, C-1 … instead of a bare number. Pressing steps chromatically; an incoming value snaps to the matching note thanks to useclosestvalueonreceive. Great for a split-point / transpose / lowest-note button – “C4” reads instantly on stage where “60” doesn’t.

How to use it

  1. Save the XML below as MIDI Note Names (C4).xml in your Documents/Trevliga Spel folder.
  2. Add a Generic Midi button and set it to Cycle.
  3. In the Translation file field, click Select file… and pick the XML.
  4. Done – the key now displays note names. (The plugin hot-reloads the file, so any edit shows instantly.)
<?xml version="1.0" encoding="utf-8" ?>
<MidiSteps version="1.1">
  <Default send="yes" receive="yes" display="" image="" displayonsend="yes" useclosestvalueonreceive="yes"/>
  <StepValues>
    <Step value="0" display="C-1"/>
    <Step value="1" display="C#-1"/>
    <Step value="2" display="D-1"/>
    <Step value="3" display="D#-1"/>
    <Step value="4" display="E-1"/>
    <Step value="5" display="F-1"/>
    <Step value="6" display="F#-1"/>
    <Step value="7" display="G-1"/>
    <Step value="8" display="G#-1"/>
    <Step value="9" display="A-1"/>
    <Step value="10" display="A#-1"/>
    <Step value="11" display="B-1"/>
    <Step value="12" display="C0"/>
    <Step value="13" display="C#0"/>
    <Step value="14" display="D0"/>
    <Step value="15" display="D#0"/>
    <Step value="16" display="E0"/>
    <Step value="17" display="F0"/>
    <Step value="18" display="F#0"/>
    <Step value="19" display="G0"/>
    <Step value="20" display="G#0"/>
    <Step value="21" display="A0"/>
    <Step value="22" display="A#0"/>
    <Step value="23" display="B0"/>
    <Step value="24" display="C1"/>
    <Step value="25" display="C#1"/>
    <Step value="26" display="D1"/>
    <Step value="27" display="D#1"/>
    <Step value="28" display="E1"/>
    <Step value="29" display="F1"/>
    <Step value="30" display="F#1"/>
    <Step value="31" display="G1"/>
    <Step value="32" display="G#1"/>
    <Step value="33" display="A1"/>
    <Step value="34" display="A#1"/>
    <Step value="35" display="B1"/>
    <Step value="36" display="C2"/>
    <Step value="37" display="C#2"/>
    <Step value="38" display="D2"/>
    <Step value="39" display="D#2"/>
    <Step value="40" display="E2"/>
    <Step value="41" display="F2"/>
    <Step value="42" display="F#2"/>
    <Step value="43" display="G2"/>
    <Step value="44" display="G#2"/>
    <Step value="45" display="A2"/>
    <Step value="46" display="A#2"/>
    <Step value="47" display="B2"/>
    <Step value="48" display="C3"/>
    <Step value="49" display="C#3"/>
    <Step value="50" display="D3"/>
    <Step value="51" display="D#3"/>
    <Step value="52" display="E3"/>
    <Step value="53" display="F3"/>
    <Step value="54" display="F#3"/>
    <Step value="55" display="G3"/>
    <Step value="56" display="G#3"/>
    <Step value="57" display="A3"/>
    <Step value="58" display="A#3"/>
    <Step value="59" display="B3"/>
    <Step value="60" display="C4"/>
    <Step value="61" display="C#4"/>
    <Step value="62" display="D4"/>
    <Step value="63" display="D#4"/>
    <Step value="64" display="E4"/>
    <Step value="65" display="F4"/>
    <Step value="66" display="F#4"/>
    <Step value="67" display="G4"/>
    <Step value="68" display="G#4"/>
    <Step value="69" display="A4"/>
    <Step value="70" display="A#4"/>
    <Step value="71" display="B4"/>
    <Step value="72" display="C5"/>
    <Step value="73" display="C#5"/>
    <Step value="74" display="D5"/>
    <Step value="75" display="D#5"/>
    <Step value="76" display="E5"/>
    <Step value="77" display="F5"/>
    <Step value="78" display="F#5"/>
    <Step value="79" display="G5"/>
    <Step value="80" display="G#5"/>
    <Step value="81" display="A5"/>
    <Step value="82" display="A#5"/>
    <Step value="83" display="B5"/>
    <Step value="84" display="C6"/>
    <Step value="85" display="C#6"/>
    <Step value="86" display="D6"/>
    <Step value="87" display="D#6"/>
    <Step value="88" display="E6"/>
    <Step value="89" display="F6"/>
    <Step value="90" display="F#6"/>
    <Step value="91" display="G6"/>
    <Step value="92" display="G#6"/>
    <Step value="93" display="A6"/>
    <Step value="94" display="A#6"/>
    <Step value="95" display="B6"/>
    <Step value="96" display="C7"/>
    <Step value="97" display="C#7"/>
    <Step value="98" display="D7"/>
    <Step value="99" display="D#7"/>
    <Step value="100" display="E7"/>
    <Step value="101" display="F7"/>
    <Step value="102" display="F#7"/>
    <Step value="103" display="G7"/>
    <Step value="104" display="G#7"/>
    <Step value="105" display="A7"/>
    <Step value="106" display="A#7"/>
    <Step value="107" display="B7"/>
    <Step value="108" display="C8"/>
    <Step value="109" display="C#8"/>
    <Step value="110" display="D8"/>
    <Step value="111" display="D#8"/>
    <Step value="112" display="E8"/>
    <Step value="113" display="F8"/>
    <Step value="114" display="F#8"/>
    <Step value="115" display="G8"/>
    <Step value="116" display="G#8"/>
    <Step value="117" display="A8"/>
    <Step value="118" display="A#8"/>
    <Step value="119" display="B8"/>
    <Step value="120" display="C9"/>
    <Step value="121" display="C#9"/>
    <Step value="122" display="D9"/>
    <Step value="123" display="D#9"/>
    <Step value="124" display="E9"/>
    <Step value="125" display="F9"/>
    <Step value="126" display="F#9"/>
    <Step value="127" display="G9"/>
  </StepValues>
</MidiSteps>

If you prefer the C3 = 60 convention (Yamaha / some older gear), just shift every octave number down by one – happy to post that variant too.

Thanks again for the pointer, and for the plugin – it’s the backbone of my live rig! :pray:

Sharing the result here for anyone it might interest – I got the split point shown as a note name (plus a little piano) on a Scripted Dial, and there is a nice bit of logic to it.

The formula. Rather than a lookup file, I compute the note name straight from the raw MIDI value: note letter from value % 12, octave from INT(value / 12) - 1 (C4 = middle C = 60, Scientific Pitch Notation). In MidiScript:

{title:#IF(value%12=0,"C",IF(value%12=1,"C#",IF(value%12=2,"D",IF(value%12=3,"D#",IF(value%12=4,"E",IF(value%12=5,"F",IF(value%12=6,"F#",IF(value%12=7,"G",IF(value%12=8,"G#",IF(value%12=9,"A",IF(value%12=10,"A#","B")))))))))))#}
{text:#INT(value/12)-1#}

(use @e_value on rotate and @e_ccvalue on receive in place of value.)

Important gotcha: the sharp in each note name must be the Unicode MUSIC SHARP SIGN, code point U+266F, NOT the ASCII # shown here for readability. A literal # terminates the surrounding #...# expression and breaks the parser, so replace each one with U+266F before use.

Two display tricks that made it work nicely:

  1. A little piano on the right, animated by the value itself ({iconright}). I drew 12 small images, one per chromatic key – kbd_0 = C, kbd_1 = C#, … kbd_11 = B (a tiny Python script in the repo generates them). The trick is that I put the value straight into the icon path:

    {iconright:/Users/me/Documents/Trevliga Spel/PianoIcons/kbd_#value%12#.png}
    

    Because #value%12# is evaluated live, the path is recomputed on every rotation and the plugin loads the matching picture – so the lit key moves along the keyboard and follows the split point as you turn the dial. No animation feature needed; it is just the same value % 12 selecting the image file the way it selects the letter. (“Show large icons” makes it bigger.)

  2. Splitting the note across {title} and {text} (letter in one, octave in the other), so the width of the sharp never shifts the octave – it stays perfectly aligned as you turn the dial.

A small limitation, maybe a feature request: with the image on I could not get the title and the value text to both sit on the left together, next to the keyboard. Being able to freely place title + text would be a great addition.

Everything – the complete MidiScript, the 12 per-note piano images + the Python generator, high-res mockups of the display, and a full write-up of the gotchas – is in a public repo:

Thanks again for the translation-file pointer – the formula ended up fitting my live keyboard-split use case best.

This is really cool, thanks for sharing it. :+1:

I can’t guarantee it works everywhere, but generally, you can use a double hash ## where you want the plugin to treat it as a single # rather than an expression terminator.

You should be able to do that by changing the layout “alignment” json property for the text.

Got it exactly the way I wanted — thanks again @Gunnar. Here’s the full recipe in case it helps anyone else who wants a note-name readout on a Stream Deck+ dial.

The note letter and the octave read as one tight F♯0 token (letter red, octave green, same size), with a one-octave piano on the right showing the current key. Turning the dial moves the split point.

1) Custom layout (ts_note_split.json) — three objects, keys prefixed l_ so the script variables stay local to the dial:

{
  "id": "ts_note_split_v7",
  "items": [
    { "key": "l_keyboard",    "type": "pixmap", "rect": [122,18,74,64], "enabled": false, "zOrder": 10 },
    { "key": "l_note_letter", "type": "text",   "rect": [0,4,60,92],  "alignment": "right", "color": "#FF5A5A", "enabled": false, "zOrder": 20 },
    { "key": "l_note_octave", "type": "text",   "rect": [62,4,34,92], "alignment": "left",  "color": "#33D17A", "enabled": false, "zOrder": 30 }
  ]
}

Enable it in the Property Inspector: Custom layout → tick “Use a custom layout” → pick the JSON.

2) Script (Scripted Dial, CC 103 / ch 2 here). The objects use custom keys, so {title}/{text} won’t fill them — you drive each object by name, and you must set the font size in the script (.fontsize), because the plugin auto-fits text to the box and ignores the font in the layout file:

[(init+)(cc:2,103,*){@l_note_letter.enabled:true}{@l_note_octave.enabled:true}{@l_keyboard.enabled:true}{@l_note_letter.fontsize:44}{@l_note_octave.fontsize:44}{@l_note_letter:#IF(@e_ccvalue%12=0,"C",IF(@e_ccvalue%12=1,"C♯", … ,"B") … #}{@l_note_octave:#INT(@e_ccvalue/12)-1#}{@l_keyboard:/path/to/kbd_#@e_ccvalue%12#.png}]
[(rotate){cc:2,103,#@e_value#}{@l_note_letter:#…same formula on @e_value…#}{@l_note_octave:#INT(@e_value/12)-1#}{@l_keyboard:/path/to/kbd_#@e_value%12#.png}]

(The letter is a nested IF on value % 12; the octave is INT(value/12) - 1 for C4 = 60. Sharp is the U+266F glyph — your ## tip works too.)

Three things that tripped me up:

  1. Font size lives in the script, not the JSON. The plugin auto-fits the size to the box, so the layout file’s font is ignored — and forcing a size bigger than fits hides the text entirely. {@l_note_letter.fontsize:44} is the fix.
  2. Tight “F♯0” for every note: right-align the letter to a fixed seam (x=60) and left-align the octave right after it (x=62). A wide “F♯” and a narrow “C” then both end at the same x, so the octave sits flush — no per-note gap.
  3. Rejection traps: keep every object inside the 200×100 canvas and give each a distinct zOrder, or the whole layout is silently ignored.

Full repo (layout + script + the 12 kbd_*.png + generators): GitHub - Beennnn/streamdeck-midi-note-display: Stream Deck+ — show a MIDI value (e.g. a keyboard split point) as a note name + mini piano, via the Trevliga Spel MIDI plugin · GitHub

Thanks @Gunnar for the plugin and the custom-layout pointer! :folded_hands: