Skip to content

Add a keyboard row in OUTPUT - #113

Merged
benjamindehli merged 1 commit into
release/v1.17.0from
feature/106-a-keyboard-row-in-output-so-the-key-number-can-scale-a-partials-level
Nov 3, 2026
Merged

benjamindehli merged 1 commit into
release/v1.17.0from
feature/106-a-keyboard-row-in-output-so-the-key-number-can-scale-a-partials-level

Conversation

@benjamindehli

Copy link
Copy Markdown
Owner

I wanted each channel's level to follow where on the keyboard a note is played, so a patch can be voiced across the keyboard the way an organ builder voices a stop: the fundamental weaker on the low keys, bell partials only on the top keys, the noise only in one range, or a partial that only sounds on one side of a near split. TRACK makes high notes duller, but it is the same for every partial and only works one way. This row is set per channel and can go either way.

The row

KEYBOARD sits in OUTPUT between VELOCITY and AFTERTOUCH, on every channel and on noise. It runs from -200% to 200% and only ever turns keys down, so a partial is never louder than its fader. Zero is the default and does nothing.

  • Up to 100% either way it tilts around middle C (key 60), which never changes. Positive turns down the keys below it, negative the keys above it.
  • At 100% the slope is 24 dB per octave. The slope grows as the square of the amount, so 50% is 6 dB per octave and 25% is 1.5, and the gentle slopes used for voicing get most of the knob.
  • Past 100% the slope stays at 24 and the turn moves away from middle C, an octave for every 20%, to key 120 at 200% and key 0 at -200%. Every step away from zero turns more of the keyboard down, so a macro, LINK or automation moving the knob always does more of the same.
  • The readout says the slope and the key the fade starts from, such as "6.0 dB/oct below C4" or "24 dB/oct above F#1". Typing a reading or a percentage back into a host gives the same amount.

It reads the key number, not the pitch, so bend, TUNE, the temperament and drift leave the level alone. Under a glide the key slides with the pitch from the old key to the new one, so a legato line does not jump in level. The noise channel takes its key from the first partial, as the resonator does. The row scales AFTERTOUCH's part of the level too, so pressure cannot bring a partial back on keys the row has faded it out of. The gain is worked out again only when the amount or the key changes, so a held note costs nothing extra.

The DSP side is Source/dsp/Keyboard.h: keyboardTilt, keyboardDecibels, keyboardGain, and KeyboardCache, which keeps the last gain per partial.

The lamp

The OUTPUT rule now has one long lamp in three parts instead of two round lamps: velocity, keyboard and aftertouch, in the order of the rows. I rendered three round lamps at 7 and 6 px first. On a 38 px strip each gets about 12 px, and since each lamp is its own opaque component, its glow was cut off in a straight line at its edge. One slot, like the PITCH MOD needle's, keeps all three glows inside one component. The seams between the parts mix the colours on either side and darken them, so it reads as one lamp. The keyboard part shows what the row leaves on the key being played, full at rest like the velocity part, and it follows a glide.

Older patches and automation

  • The 33 parameters, "H1 Keyboard" to "H32 Keyboard" and "Noise Keyboard", are on the end of the list, so automation already written keeps its place. There are now 884 parameters.
  • A saved session that does not name them loads them at zero, which is their default. A preset file that does not name them would keep the last patch's values, so valueInOlderFiles sets them to zero there too. The factory presets set zero in their shared base.
  • Keyboard is the last row in the macro list, row 20, because a macro stores its row as a position in that list. A new row anywhere else would move the saved macros.

A correction to how older files load

While testing this I found that the comments from the velocity curve were wrong about JUCE. A session that does not name a parameter puts it at its default. It does not leave it where it was. Only a preset file keeps the last patch's value. The velocity curve behaves correctly either way, since its default is dB and an old session has to load on Classic. I corrected the comments in PluginParameters.h and PluginProcessor.cpp and the paragraph in DESIGN.md.

What changes for users

  • A KEYBOARD row on every channel and on noise, with a tooltip on the row name and on the noise channel's knob.
  • A three-part lamp on the OUTPUT rule.
  • One more row, so the mixer is 30 px taller.
  • The controls page has a new section on the row, the strip table and the lamp text are updated, and the counts are updated in the docs, the README, DESIGN.md, ARCHITECTURE.md and ACCESSIBILITY.md.
  • The window and strip screenshots show the row and the new lamp.

How it was checked

DSP suite, a new test:

  • middle C keeps its level at every amount up to 100% either way
  • at 100% the keys below middle C fall 24 dB an octave and the keys above are untouched, and -100% is the mirror
  • 50% is 6 dB an octave and 25% is 1.5
  • past 100% the centre is where the readout says, an octave per 20%, and the two halves meet at 100% without a jump
  • across all 128 keys, every step away from zero turns each key down further or leaves it, and no amount raises a key
  • through the engine, C3 at 100% plays 24 dB down and the lamp shows it, bend and TUNE do not change it, and the noise channel has its own row
  • a glide from C2 to C4 starts 48 dB down, arrives at the fader, and rises in steps under a dB

Runtime suite, a new test:

  • 33 parameters on the end of the list, from -200% to 200%, starting at zero, named after their channels
  • the readout for eight amounts, and typed readings and percentages read back to the same amount
  • a session and a preset file from before the row load it at zero, and one that names it loads what it names
  • a macro on Keyboard moves the row
  • the output lamp lights the part for each row and only that part, stays dark with nothing sounding, and runs the width of the strip

Breaking it on purpose:

  • With the key read without the glide, the level jumped 48 dB at once and the glide check failed.
  • With the keyboard rows taken out of the older-files rule, the preset file check failed. The session check still passed, which is how I found that JUCE resets a parameter a session does not name.

The runtime suite passes with 1548 checks and the DSP suite with 1418, both with no failures. The DSP core builds on its own with -Werror. clang-format, prettier, build_site.py --check, the counts and structured data checks and the lambda scan pass too.

Closes #106

So the key number can scale a partial's level
@benjamindehli benjamindehli self-assigned this Nov 3, 2026
@benjamindehli benjamindehli added this to the v1.17.0 milestone Nov 3, 2026
@benjamindehli benjamindehli added the enhancement New feature or request label Nov 3, 2026
@benjamindehli
benjamindehli merged commit 537c61e into release/v1.17.0 Nov 3, 2026
9 checks passed
@benjamindehli
benjamindehli deleted the feature/106-a-keyboard-row-in-output-so-the-key-number-can-scale-a-partials-level branch November 3, 2026 23:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A keyboard row in OUTPUT, so the key number can scale a partial's level

1 participant