Displaying MIDI 2.0 High Resolution Values to Users

MIDI 2.0 Protocol provides many improvements to musicians to help in the creation of music. One of the stand out features is the increase of resolution of MIDI messages from 128 steps to 4.2 billion steps (and 65 thousand for velocity).
This increase ensures that changes in parameters are far smoother and more analogue in their responsiveness.
Note: In MIDI specifications we use hex representations because it can be easier to understand for development. For 32 bit values we use 0x00000000 to represent the smallest value, and 0xFFFFFFFF represents the largest value.
However this huge range presents problems to developers on how to display these values to users. A lot of MIDI 1.0 Devices use 0-127 as the value displayed to musicians, and those musicians are used to seeing and understanding those numbers.
When numbers are much larger it is more difficult to display these numbers to users. For example, is 0x8000000 the halfway point, or is it just 3.1% of the total range of values?*
For more information on converting between different bit sizes and how to handle different resolutions, please see the document MIDI 2.0 Bit Scaling and Resolution.
How to Display Such Large Numbers to a User?
Where possible, the MIDI Association recommends that developers undertake these options in this order:
1. Provide the Interpreted Value to the Musician.
The MIDI Association is working hard to provide ways for MIDI Devices to declare meaningful units and values. For example Profiles and Property Exchange contain methods that declare how a MIDI message value could be represented as a human readable value.
For example, Using the Default Control Change Mapping Profile to understand that Control Change #7 defines the representation of the volume in decibels from -infinity to +0db is more informative than 0x00000000 to 0xFFFFFFFF.
2. Display 0% – 100%
When a unit and value is unknown it is often acceptable to use percent values [0% .. 100%], or to use [-100% .. 100%] for bipolar values if applicable.
As a developer you may decide how many decimal places are best suited for your display.
3. Display 0.00 – 1.00
Similarly to the previous option, when a unit and value is unknown it is often acceptable to use floating point [0.0 .. 1.0], or to use [-1.0 to 1.0] for bipolar values, if applicable. This is more inline with the internal values used in plugins.
As a developer you may decide how many decimal places are best suited for your display.
4. Display 0 – 127 with decimal places
If the previous options do not suit and you must display MIDI 1.0 like values, or if you need to display values where the MIDI device is working in both MIDI 1.0 and MIDI 2.0 Protocol environments; then the 32 bit value should be treated as 7.25 fixed point values.

This means the value being visualized starts at 0 and approaches (but not reaches) 128.

In doing this the precise center value (0x80000000) is maintained and translation between MIDI 2.0 and MIDI 1.0 Protocol results in the expected value being sent to MIDI 1.0 devices.
In order to visualize the absolute minimum value (0x00000000), the absolute maximum value (0xFFFFFFFF) and the precise center value (0x80000000) one should use symbolic values “MIN”, “MAX” and “MID”.
This ensures that these special values can be differentiated from values being very close to them (0.00000001 rounded to e.g. 0.000, 63.9999999 rounded to e.g. 64.0, 127.9999998 rounded to e.g. 127.999).
C++ Example of a function to display these values:
template <typename T>
std::string hirezRepresentationString(T value, uint32_t decimalPlaces)
{
constexpr int numBits = sizeof(T) * 8;
constexpr int fractionalBits = numBits - 7;
constexpr T maxVal = std::numeric_limits<T>::max();
constexpr T midVal = (1 << (numBits - 1));
if (value == 0) {
return "MIN";
}
if (value == midVal) {
return "MID";
}
if (value == maxVal) {
return "MAX";
}
const auto hiRezValue = static_cast<double>(value) / static_cast<double>(1 << fractionalBits);
const auto formatString = std::format("{{:.{}f}}", decimalPlaces);
return std::vformat(formatString, std::make_format_args(hiRezValue));
}
Note that this code works up to 6 decimal places. However, this is more than enough for most applications.
* It’s 3.1% for those playing at home 🙂