Parameter Display Text
Let a node say what its numbers mean
Parameter Display Text
A tracker parameter is a byte. The parameter window draws that byte, so a
filter cutoff reads 176 and an oscillator model reads 3.
A processor can do better. Override one method, return a short string, and the window draws the string instead.
| Column | Without | With |
|---|---|---|
| Model | 1 |
Supersaw |
| Cutoff | 176 |
2.4 kHz |
| Osc 2 Level | 0 |
off |
| Osc 2 Tune | 128 |
+0.0 st |
The method
QString parameterDisplayText(int parameterIndex, Value value) const override;
Return an empty string to keep the number. That is the default, so a processor that does not override this behaves exactly as it does today.
The three rules
Return text, and only text. Not a colour, not a widget, not a layout. The view draws. The processor describes. That boundary is what lets the method be tested with no window, no engine and no audio device.
Keep it pure. Take the index and the value, return a string. Do not read engine state and do not write anything. The window calls this while the audio block drive runs on the same thread, so a method that read live state would be reading a value in the middle of a change.
Never call it from process(). Building a string allocates. The block
drive must not allocate, because an unpredictable block time is heard later as
a dropped buffer. The parameter window is the only caller, and it calls only
when the value has changed.
A worked example
Take the oscillator level. Zero means the slot is disabled, which is a different statement from "quiet", so zero gets its own word:
case ColOsc2Level:
return (raw == 0) ? QStringLiteral("off")
: QStringLiteral("%1%").arg(u() * 100.0, 0, 'f', 0);
This is the case authors get wrong. 0% and off look alike and mean
different things to a person reading the instrument.
Show the map the instrument actually uses
A cutoff knob is exponential. A time knob is squared. If the text is computed from a different formula than the sound, the two drift apart and the text becomes a lie that is hard to catch.
Call the same helper the audio path calls:
case ColCutoff: {
const double hz = 40.0 * std::pow(400.0, u());
return (hz >= 1000.0)
? QStringLiteral("%1 kHz").arg(hz / 1000.0, 0, 'f', 1)
: QStringLiteral("%1 Hz").arg(hz, 0, 'f', 0);
}
Say nothing about a no-value
Every byte column in the tracker uses 0xFF to mean "this row says nothing".
That is not a value, so do not describe it:
if (parameterIndex != ColNote && raw == BYTE_NO_VALUE) {
return QString();
}
The same collision has a second consequence worth knowing: because the maximum
byte and the no-value sentinel are the same number, a slider can never reach
0xFF. The parameter window caps its sliders one step below it.
Where to look
MonosynthProcessor::parameterDisplayText is the reference implementation. It
covers every hard case in one processor: an enumeration, a unit whose prefix
changes, a value squared across its range, a signed value around a centre, and
a zero with its own word.