Something went wrong. Try again.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576// panel.cpp — ports of the Main firmware parameter codec and display formatter.// Codec: param_encode 0x0800e4fc / param_decode 0x0800e644.// Display formatter: param_value_convert_scale 0x0804e404.
#include "control/panel.hpp"
#include <algorithm>#include <array>#include <cmath>#include <cstdint>#include <limits>#include <string>#include <string_view>
// ARM register-shift and conversion semantics used by the firmware.namespace {
constexpr std::uint32_t arm_lsl(std::uint32_t x, std::uint32_t c) { return c >= 32U ? 0U : x << c;}constexpr std::int32_t arm_asr(std::int32_t x, std::uint32_t c) { if (c >= 32U) { return x < 0 ? -1 : 0; } return x >> c;}
// String/table data copied from the firmware's display tables.constexpr std::array<std::string_view, 23> kOscillatorNames{{ "Idle", "BasicWaves", "SuperWave", "Wavetable", "Harmo", "KarplusStr", "V.Analog", "Waveshaper", "Two Op. FM", "Formant", "Chords", "Speech", "Modal", "Noise", "Vocoder", "Bass", "SawX", "Harm", "WaveUser", "Sample", "Scan Grains", "Cloud Grains", "Hit Grains",}};constexpr std::array<std::string_view, 4> kVcoType2Param1{{ "Saw", "Square", "Triangle", "Sinus",}};constexpr std::array<std::string_view, 11> kVcoType10Param1{{ "Oct", "5", "sus4", "m", "m7", "m9", "m11", "69", "M9", "M7", "M",}};constexpr std::array<std::string_view, 11> kGlideSyncDivisions{{ "1/32T", "1/32", "1/16T", "1/16", "1/8T", "1/8", "1/4T", "1/4", "1/2T", "1/2", "1/1",}};// Millisecond curves at 0x08075438 (Hold), 0x080754cc (Attack), and// 0x080754e0 (Decay/Release); param_value_convert_scale interpolates them.constexpr std::array<std::uint16_t, 10> kCyclingHoldMs{{ 0, 100, 500, 1200, 2000, 3000, 4300, 5900, 7800, 10000,}};constexpr std::array<std::uint16_t, 10> kAmpAttackMs{{ 0, 25, 75, 200, 470, 940, 1830, 3100, 5700, 10000,}};constexpr std::array<std::uint16_t, 10> kAmpDecayReleaseMs{{ 0, 900, 1400, 2200, 3200, 4900, 6800, 10200, 15400, 25000,}};// Runtime pointer 0x2001c7d8 is initialized to this flash table at 0x080619c8.constexpr std::array<std::int16_t, 3> kVocoderParam1Thresholds{{ 3276, 29491, 32767,}};
// Firmware table at 0x0806205c has 129 entries for nonnegative LFO-rate raw// values. param_apply arithmetic-shifts the signed raw value before indexing;// therefore negative synthetic values read the 128 preceding flash words.// Include those adjacent words as well so this host port matches the firmware// on the signed-endpoint values in the formatter regression sweep.constexpr std::array<std::uint32_t, 257> kLfoRateCurve{{ 0x6EC9E5DEU, 0x6FE7DFDEU, 0x70F4965EU, 0x71F0E85EU, 0x72DDAFDEU, 0x73BBC15EU, 0x748BE8DEU, 0x754EED5EU, 0x76058CDEU, 0x76B07DDEU, 0x77506EDEU, 0x77E606DEU, 0x7871E45EU, 0x78F49EDEU, 0x796EC4DEU, 0x79E0DEDEU, 0x7A4B6D5EU, 0x7AAEE95EU, 0x7B0BC5DEU, 0x7B626F5EU, 0x7BB34B5EU, 0x7BFEB95EU, 0x7C45145EU, 0x7C86B0DEU, 0x7CC3DDDEU, 0x7CFCE6DEU, 0x7D3211DEU, 0x7D639FDEU, 0x7D91CEDEU, 0x7DBCD7DEU, 0x7DE4F1DEU, 0x7E0A4E5EU, 0x7E2D1C5EU, 0x7E4D88DEU, 0x7E6BBC5EU, 0x7E87DDDEU, 0x7EA210DEU, 0x7EBA76DEU, 0x7ED12F5EU, 0x7EE657DEU, 0x7EFA0BDEU, 0x7F0C63DEU, 0x7F1D785EU, 0x7F2D5F5EU, 0x7F3C2DDEU, 0x7F49F65EU, 0x7F56CB5EU, 0x7F62BD5EU, 0x7F6DDBDEU, 0x7F7835DEU, 0x7F81D8DEU, 0x7F8AD0DEU, 0x7F932A5EU, 0x7F9AEF5EU, 0x7FA22B5EU, 0x7FA8E6DEU, 0x7FAF2ADEU, 0x7FB4FFDEU, 0x7FBA6D5EU, 0x7FBF7ADEU, 0x7FC42EDEU, 0x7FC88EDEU, 0x7FCCA1DEU, 0x7FD06C5EU, 0x7FD3F35EU, 0x7FD73BDEU, 0x7FDA49DEU, 0x7FDD21DEU, 0x7FDFC75EU, 0x7FE23DDEU, 0x7FE488DEU, 0x7FE6AADEU, 0x7FE8A75EU, 0x7FEA805EU, 0x7FEC385EU, 0x7FEDD1DEU, 0x7FEF4EDEU, 0x7FF0B1DEU, 0x7FF1FBDEU, 0x7FF32F5EU, 0x7FF44CDEU, 0x7FF556DEU, 0x7FF64EDEU, 0x7FF734DEU, 0x7FF80B5EU, 0x7FF8D2DEU, 0x7FF98CDEU, 0x7FFA395EU, 0x7FFADA5EU, 0x7FFB6FDEU, 0x7FFBFB5EU, 0x7FFC7CDEU, 0x7FFCF55EU, 0x7FFD655EU, 0x7FFDCDDEU, 0x81648000U, 0x843A82CDU, 0x871F85AAU, 0x8A148898U, 0x8D1A8B95U, 0x90318EA4U, 0x935A91C3U, 0x969494F4U, 0x99E09837U, 0x9D3E9B8DU, 0xA0B09EF5U, 0xA435A270U, 0xA7CDA5FEU, 0xAB7AA9A1U, 0xAF3BAD58U, 0xB311B123U, 0xB6FDB504U, 0xBAFFB8FBU, 0xBF17BD08U, 0xC346C12CU, 0xC78DC567U, 0xCBECC9B9U, 0xD063CE24U, 0xD4F3D2A8U, 0xD99DD744U, 0xDE60DBFBU, 0xE33FE0CCU, 0xE839E5B9U, 0xED4FEAC0U, 0xF281EFE4U, 0xF7D0F525U, 0xFD3EFA83U, 0x0000FFFFU, 0x00015D86U, 0x00016EA1U, 0x000180C8U, 0x0001940BU, 0x0001A87CU, 0x0001BE2DU, 0x0001D531U, 0x0001ED9FU, 0x0002078AU, 0x0002230CU, 0x0002403DU, 0x00025F37U, 0x00028015U, 0x0002A2F7U, 0x0002C7FBU, 0x0002EF43U, 0x000318F2U, 0x0003452EU, 0x0003741FU, 0x0003A5EFU, 0x0003DACBU, 0x000412E3U, 0x00044E69U, 0x00048D94U, 0x0004D09CU, 0x000517BEU, 0x0005633BU, 0x0005B355U, 0x00060856U, 0x0006628AU, 0x0006C243U, 0x000727D8U, 0x000793A3U, 0x00080607U, 0x00087F6AU, 0x0009003BU, 0x000988EDU, 0x000A19FCU, 0x000AB3EAU, 0x000B5744U, 0x000C049CU, 0x000CBC90U, 0x000D7FC4U, 0x000E4EEAU, 0x000F2ABCU, 0x00101400U, 0x00110B8BU, 0x0012123AU, 0x001328FCU, 0x001450CBU, 0x00158AB4U, 0x0016D7D1U, 0x00183950U, 0x0019B06FU, 0x001B3E81U, 0x001CE4EEU, 0x001EA533U, 0x002080E5U, 0x002279B1U, 0x00249160U, 0x0026C9D4U, 0x00292510U, 0x002BA534U, 0x002E4C82U, 0x00311D5FU, 0x00341A57U, 0x0037461CU, 0x003AA38BU, 0x003E35AEU, 0x0041FFBEU, 0x00460527U, 0x004A498CU, 0x004ED0C6U, 0x00539EECU, 0x0058B854U, 0x005E219BU, 0x0063DFA2U, 0x0069F798U, 0x00706F00U, 0x00774BAEU, 0x007E93D6U, 0x00864E0BU, 0x008E8147U, 0x009734F2U, 0x00A070E7U, 0x00AA3D7DU, 0x00B4A38DU, 0x00BFAC7BU, 0x00CB623DU, 0x00D7CF67U, 0x00E4FF31U, 0x00F2FD84U, 0x0101D702U, 0x01119913U, 0x012251F3U, 0x013410BCU, 0x0146E572U, 0x015AE117U, 0x017015B7U, 0x01869677U, 0x019E77A9U, 0x01B7CEDEU, 0x01D2B2F7U, 0x01EF3C3CU, 0x020D8472U, 0x022DA6F0U, 0x024FC0BCU, 0x0273F0A0U, 0x029A574AU, 0x02C31765U, 0x02EE55BFU, 0x031C3962U, 0x034CEBC0U, 0x038098D2U, 0x03B76F40U, 0x03F1A08FU, 0x042F614BU, 0x0470E937U, 0x04B6737FU, 0x05003EEFU, 0x054E8E29U, 0x05A1A7E3U, 0x05F9D728U, 0x06576B97U, 0x06BAB9B2U, 0x07241B23U, 0x0793EF13U, 0x080A9A7DU, 0x08888888U,}};
std::uint32_t truncate_unsigned(const float value) { if (!(value > 0.0F)) return 0; if (value >= static_cast<float>(std::numeric_limits<std::uint32_t>::max())) { return std::numeric_limits<std::uint32_t>::max(); } return static_cast<std::uint32_t>(value);}
std::string format_seconds(const float milliseconds, const std::string_view suffix = "s") { const auto seconds = static_cast<std::uint32_t>(milliseconds / 1000.0F); const auto tenths = static_cast<std::uint32_t>(milliseconds / 100.0F) % 10U; return std::to_string(seconds) + "." + std::to_string(tenths) + std::string(suffix);}
std::string format_glide_seconds(const float milliseconds, const std::string_view suffix) { const auto seconds = static_cast<std::uint32_t>(milliseconds / 1000.0F); const auto hundredths = static_cast<std::uint32_t>(milliseconds / 10.0F) % 100U; const std::string fraction = hundredths < 10U ? "0" + std::to_string(hundredths) : std::to_string(hundredths); return std::to_string(seconds) + "." + fraction + std::string(suffix);}
std::string format_signed_percent(const std::int16_t raw) { float percent = static_cast<float>(raw) / 327.67F; if (raw != 0) percent += percent > 0.0F ? 0.05F : -0.05F; if (percent >= 100.0F) return "100"; if (percent <= -100.0F) return "-100";
const auto whole = static_cast<std::int32_t>(percent); auto tenth = static_cast<std::int32_t>(percent * 10.0F) % 10; if (tenth < 0) tenth = -tenth; if (tenth == 0) return std::to_string(whole); if (percent < 0.0F && whole == 0) return "-0." + std::to_string(tenth); return std::to_string(whole) + "." + std::to_string(tenth);}
std::string format_percent_from_high_byte(const std::uint16_t raw) { const auto high = static_cast<std::int8_t>(static_cast<std::uint8_t>(raw >> 8U)); const auto scaled = (static_cast<std::uint32_t>(static_cast<std::uint16_t>(high + 1)) * 25U) & 0x3fffU; return std::to_string(scaled >> 5U) + "%";}
std::string format_curve_time(const std::int16_t raw, const std::array<std::uint16_t, 10> &curve, const std::uint16_t upper_seconds, const bool round_milliseconds) { if (raw < 0) return "0ms"; const float position = static_cast<float>(raw) / 3640.0F; const auto index = static_cast<std::uint32_t>(position); if (index >= 9U) return std::to_string(upper_seconds) + ".0s";
const auto fraction = static_cast<std::uint32_t>(position * 100.0F) % 100U; const std::uint32_t blended = (100U - fraction) * curve[index] + fraction * curve[index + 1U]; const float milliseconds = static_cast<float>(blended) * 0.01F; if (milliseconds < 1000.0F) { const float rounded = milliseconds + (round_milliseconds ? 0.5F : 0.0F); return std::to_string(truncate_unsigned(rounded)) + "ms"; } return format_seconds(milliseconds);}
std::string format_cubic_time(const std::int16_t raw) { const float normalized = static_cast<float>(raw) / 32767.0F; const float milliseconds = normalized * normalized * normalized * 10000.0F; if (milliseconds < 1000.0F) { return std::to_string(truncate_unsigned(milliseconds + 0.5F)) + "ms"; } return format_seconds(milliseconds);}
std::string_view enum_display(const std::uint16_t id, const std::int16_t decoded) { const auto lookup = [decoded](const auto &table) -> std::string_view { const std::size_t index = decoded <= 0 ? 0U : std::min<std::size_t>(static_cast<std::size_t>(decoded), table.size() - 1U); return table[index]; }; static constexpr std::array<std::string_view, 3> kFilterType{{"LP", "BP", "HP"}}; static constexpr std::array<std::string_view, 3> kCyclingEnvMode{{"Env", "Run", "Loop"}}; static constexpr std::array<std::string_view, 7> kOctaves{{ "-3", "-2", "-1", "0", "+1", "+2", "+3", }}; static constexpr std::array<std::string_view, 2> kVelocityMode{{"Aftertouch", "Velocity"}}; static constexpr std::array<std::string_view, 3> kGlideMode{{"Time", "Sync", "Rate"}}; static constexpr std::array<std::string_view, 9> kScales{{ "Global", "OFF", "Major", "Minor", "HarmoMinor", "Dorian", "Mixolydian", "Blues", "Penta", }}; static constexpr std::array<std::string_view, 12> kRootNotes{{ "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", }}; static constexpr std::array<std::string_view, 11> kArpDivision{{ "1/1", "1/2", "1/2T", "1/4", "1/4T", "1/8", "1/8T", "1/16", "1/16T", "1/32", "1/32T", }}; static constexpr std::array<std::string_view, 4> kArpRange{{"1", "2", "3", "4"}}; static constexpr std::array<std::string_view, 4> kArpDirection{{ "Up", "Order", "Random", "Pattern", }}; static constexpr std::array<std::string_view, 6> kLfoShape{{ "Sin", "Tri", "Saw", "Sqr", "SnH", "SnHF", }}; static constexpr std::array<std::string_view, 13> kLfoDivision{{ "8/1", "4/1", "2/1", "1/1", "1/2", "1/2T", "1/4", "1/4T", "1/8", "1/8T", "1/16", "1/16T", "1/32", }}; static constexpr std::array<std::string_view, 3> kRetrig{{"OFF", "ON", "Legato"}}; static constexpr std::array<std::string_view, 2> kKeyModeSource{{"Linear", "Random"}}; static constexpr std::array<std::string_view, 3> kHissMode{{"Off", "Switched", "Pass"}}; static constexpr std::array<std::string_view, 2> kBoolean{{"OFF", "ON"}};
if (id == 0x0000U) return lookup(kOscillatorNames); if (id == 0x0100U) return lookup(kFilterType); if (id == 0x0200U) return lookup(kCyclingEnvMode); if (id == 0x0301U) return lookup(kOctaves); if (id == 0x0302U) return lookup(kVelocityMode); if (id == 0x0303U || id == 0x0400U || id == 0x0405U || id == 0x0409U || id == 0x0503U || id == 0x0700U || id == 0x0701U || id == 0x0600U || id == 0x0604U || id == 0x0606U || id == 0x1201U || (id >= 0x1203U && id <= 0x1206U)) { return lookup(kBoolean); } if (id == 0x0304U) return lookup(kGlideMode); if (id == 0x0306U) return lookup(kScales); if (id == 0x0307U) return lookup(kRootNotes); if (id == 0x0401U) return lookup(kArpRange); if (id == 0x0402U) return lookup(kArpDivision); if (id == 0x0408U) return lookup(kArpDirection); if (id == 0x0500U) return lookup(kLfoShape); if (id == 0x0501U) return lookup(kLfoDivision); if (id == 0x0504U) return lookup(kRetrig); if (id == 0x0905U) return lookup(kKeyModeSource); if (id == 0x1300U) return lookup(kHissMode); return {};}
std::string format_lfo_rate(const std::uint16_t raw) { const auto signed_raw = static_cast<std::int16_t>(raw); const auto index = arm_asr(static_cast<std::int32_t>(signed_raw), 8U); const auto table_index = static_cast<std::size_t>(index + 128); const auto current = kLfoRateCurve[table_index]; const auto next = kLfoRateCurve[table_index + 1U]; const auto difference = static_cast<std::uint32_t>(next - current); const auto product = static_cast<std::uint32_t>(raw & 0xffU) * difference; const auto step = product / 255U; const auto interpolated = static_cast<std::uint32_t>(current + step); const float denominator = 134912640.0F / static_cast<float>(interpolated); const float hz = 100.0F / denominator;
const auto whole_byte = static_cast<std::uint8_t>(truncate_unsigned(hz)); if (whole_byte >= 100U) return "100.00Hz"; const auto fractional_hundredths = static_cast<std::uint16_t>(truncate_unsigned(hz * 100.0F)) % 100U; const auto whole = std::to_string(whole_byte); if (fractional_hundredths <= 9U) { return whole + ".0" + std::to_string(fractional_hundredths) + "Hz"; } return whole + "." + std::to_string(fractional_hundredths) + "Hz";}
} // namespace
std::uint16_t mf::panel::Param::encode(const std::uint8_t mode, const std::uint16_t value) { const std::uint32_t v = value; if (mode < 0xd8U) { if (mode == 0U) { return value; // passthrough } if (mode < 0x80U) { // smlabb (signed 16-bit × signed 16-bit) + sdiv + sxth const auto m = static_cast<std::int32_t>(mode); const auto num = (static_cast<std::int32_t>(static_cast<std::int16_t>(value)) * 0x7fff) + (m >> 1); return static_cast<std::uint16_t>(static_cast<std::int16_t>(num / m)); } return 0U; // 0x80..0xd7 } switch (mode) { // 0xe1..0xef: bit-replicated enum fields case 0xe1: { const std::uint32_t w = v & 3U; return static_cast<std::uint16_t>(w << 1U | w << 3U | w << 5U | w << 7U | w << 9U | w << 11U | w << 13U); } case 0xe2: { const std::uint32_t w = v & 7U; return static_cast<std::uint16_t>(w | w << 3U | w << 6U | w << 9U | w << 12U); } case 0xe3: { const std::uint32_t w = v & 0xfU; return static_cast<std::uint16_t>(w << 3U | w << 7U | w << 11U); } case 0xe4: { const std::uint32_t w = v & 0x1fU; return static_cast<std::uint16_t>(w | w << 5U | w << 10U); } case 0xe5: { const std::uint32_t w = v & 0x3fU; return static_cast<std::uint16_t>(w << 3U | w << 9U); } case 0xe6: { const std::uint32_t w = v & 0x7fU; return static_cast<std::uint16_t>(w << 1U | w << 8U); } case 0xe7: { const std::uint32_t w = v & 0xffU; return static_cast<std::uint16_t>(w >> 1U | w << 7U); } case 0xe8: { const std::uint32_t w = v & 0x1ffU; return static_cast<std::uint16_t>(w >> 3U | w << 6U); } case 0xe9: { const std::uint32_t w = v & 0x3ffU; return static_cast<std::uint16_t>(w >> 5U | w << 5U); } case 0xea: { const std::uint32_t w = v & 0x7ffU; return static_cast<std::uint16_t>(w >> 7U | w << 4U); } case 0xeb: { const std::uint32_t w = v & 0xfffU; return static_cast<std::uint16_t>(w >> 9U | w << 3U); } case 0xec: { const std::uint32_t w = v & 0x1fffU; return static_cast<std::uint16_t>(w >> 11U | w << 2U); } case 0xed: { const std::uint32_t w = v & 0x3fffU; return static_cast<std::uint16_t>(w >> 13U | w << 1U); } case 0xee: return static_cast<std::uint16_t>(v & 0x7fffU); case 0xef: return static_cast<std::uint16_t>(static_cast<std::int32_t>(v) >> 1); case 0xf1: return 0U; case 0xf0: { // rsb/sub pair: t = v*0x7fff - 0xeff5b, then the smull-based // reciprocal division (magic 0xf2b9d649 at 0x0800e640). const auto t = (static_cast<std::int32_t>(v) * 0x7fff) - 0xeff5b; const auto prod = static_cast<std::int64_t>(static_cast<std::int32_t>(0xF2B9D649U)) * t; const auto acc = static_cast<std::int32_t>(prod >> 32) + t; // `adds` may wrap const std::int32_t r = (acc >> 8) - (t >> 31); return static_cast<std::uint16_t>(static_cast<std::int16_t>(r)); } default: // 0xd8..0xe0 and 0xf2..0xff: bipolar ±0x4000 break; } const std::uint32_t shift = (0xfeU - mode) & 0xffU; return static_cast<std::uint16_t>(arm_lsl(v, shift) + 0x4000U);}
std::int16_t mf::panel::Param::decode(const std::uint8_t mode, const std::uint16_t raw) { const auto r = static_cast<std::int32_t>(static_cast<std::int16_t>(raw)); // ldrsh if (mode < 0xd8U) { if (mode == 0U) { return static_cast<std::int16_t>(r); } if (mode < 0x80U) { const auto m = static_cast<std::int32_t>(mode); const std::int32_t s = ((m * r) + r) >> 15; // mla + asr #15 return static_cast<std::int16_t>(s > m ? m : s); // clamp high to mode } return 0; // 0x80..0xd7 } if (mode >= 0xf0U) { if (mode == 0xf0U) { // r3 = r + r*16 = 17r; r0 = (17r << 4) - r = 271r; asr 15; + 30 const std::int32_t t = r + (r << 4); return static_cast<std::int16_t>(arm_asr((t << 4) - r, 15U) + 0x1e); } if (mode == 0xf1U) { return 0; } } else { switch (mode) { case 0xe1: return static_cast<std::int16_t>(r >> 13); case 0xe2: return static_cast<std::int16_t>(r >> 12); case 0xe3: return static_cast<std::int16_t>(r >> 11); case 0xe4: return static_cast<std::int16_t>(r >> 10); case 0xe5: return static_cast<std::int16_t>(r >> 9); case 0xe6: return static_cast<std::int16_t>(r >> 8); case 0xe7: return static_cast<std::int16_t>(r >> 7); case 0xe8: return static_cast<std::int16_t>(r >> 6); case 0xe9: return static_cast<std::int16_t>(r >> 5); case 0xea: return static_cast<std::int16_t>(r >> 4); case 0xeb: return static_cast<std::int16_t>(r >> 3); case 0xec: return static_cast<std::int16_t>(r >> 2); case 0xed: return static_cast<std::int16_t>(r >> 1); case 0xee: return static_cast<std::int16_t>(r); case 0xef: { const auto u = static_cast<std::uint32_t>(r); return static_cast<std::int16_t>((u >> 15U) | (u << 1U)); } default: // 0xd8..0xe0: bipolar break; } } const std::uint32_t shift = (0xfeU - mode) & 0xffU; return static_cast<std::int16_t>(arm_asr(r - 0x4000, shift));}
std::string mf::panel::Param::format_display(const std::uint8_t mode, const std::uint16_t raw, const DisplayContext &context) const { const std::int16_t signed_raw = static_cast<std::int16_t>(raw); const std::int16_t decoded = Param::decode(mode, raw); if (id == 0x0206U || id == 0x0603U || id == 0x0605U || id == 0x0102U || id == 0x0202U || id == 0x0205U) { return format_percent_from_high_byte(raw); } if (id >= 0x0902U && id <= 0x0904U) return format_signed_percent(signed_raw); if ((id >> 8U) >= 0x0aU && (id >> 8U) <= 0x10U && (id & 0xffU) <= 5U) { return format_signed_percent(signed_raw); } if (id == 0x0204U) { if (context.cycling_env_mode_raw > 0) { return format_curve_time(signed_raw, kCyclingHoldMs, 10, true); } return format_percent_from_high_byte(raw); } if (id == 0x0404U) return std::to_string(static_cast<int>(decoded) + 50) + "%"; if (id == 0x0403U) { if (context.arp_clock_bpm_x100.has_value()) { const auto beat = *context.arp_clock_bpm_x100; return std::to_string(beat / 100U) + "." + std::to_string((beat / 10U) % 10U) + "bpm"; } if (decoded < 0 || decoded >= 0x7b0c) return "240.0bpm"; const auto bpm_x100 = static_cast<std::uint32_t>( (static_cast<std::int32_t>(decoded) * 2) / 3 + 3000); return std::to_string(bpm_x100 / 100U) + "." + std::to_string((bpm_x100 / 10U) % 10U) + "bpm"; } if (id == 0x0300U) { if (signed_raw == 0) return "Off"; if (context.glide_mode == 1) { const auto index = std::min<std::uint32_t>((raw >> 2U) / 0x333U, 10U); return std::string(kGlideSyncDivisions[index]); } if (context.glide_mode != 0 && context.glide_mode != 2) return {}; const float normalized = static_cast<float>(signed_raw) / 32767.0F; const float milliseconds = normalized * normalized * normalized * 10000.0F; const std::string_view suffix = context.glide_mode == 2 ? "s/octave" : "s"; const std::string_view ms_suffix = context.glide_mode == 2 ? "ms/octave" : "ms"; if (milliseconds >= 1000.0F) return format_glide_seconds(milliseconds, suffix); return std::to_string(truncate_unsigned(milliseconds + 0.5F)) + std::string(ms_suffix); } if (id == 0x0502U) return format_lfo_rate(raw); if (id == 0x0601U) return format_curve_time(signed_raw, kAmpAttackMs, 10, true); if (id == 0x0602U) return format_curve_time(signed_raw, kAmpDecayReleaseMs, 25, false); if (id == 0x0101U) { const float exponent = static_cast<float>(signed_raw) / 3072.0F; const float frequency = std::exp2(exponent) * 16.351598739624023F; if (frequency <= 1000.0F) return std::to_string(truncate_unsigned(frequency)) + "Hz"; const auto whole = truncate_unsigned(frequency / 1000.0F); const auto tenth = static_cast<std::uint8_t>(truncate_unsigned(frequency / 100.0F)) % 10U; return std::to_string(whole) + "." + std::to_string(tenth) + "KHz"; } if (id == 0x0201U || id == 0x0203U) return format_cubic_time(signed_raw);
if (id == 0x0001U || id == 0x0003U || id == 0x0005U) { switch (context.oscillator_type) { case 1: if (id == 0x0003U) { const auto sum = static_cast<std::uint32_t>( static_cast<std::int32_t>(signed_raw) * 0x2d + 0x4000); return " " + std::to_string((sum >> 15U) + 0x32U); } break; case 2: if (id == 0x0001U) { if (decoded < 0 || decoded >= static_cast<std::int16_t>(kVcoType2Param1.size())) { return {}; } return std::string(kVcoType2Param1[static_cast<std::size_t>(decoded)]); } break; case 3: case 0x12: if (id == 0x0001U) return " " + std::to_string(arm_asr(signed_raw, 11U) + 1); break; case 10: if (id == 0x0001U) { if (decoded < 0 || decoded >= static_cast<std::int16_t>(kVcoType10Param1.size())) { return {}; } return std::string(kVcoType10Param1[static_cast<std::size_t>(decoded)]); } break; case 14: if (id == 0x0001U) { if (signed_raw <= kVocoderParam1Thresholds[0]) return "Saw"; if (signed_raw <= kVocoderParam1Thresholds[1]) { const auto delta = static_cast<std::int32_t>(signed_raw - kVocoderParam1Thresholds[0]); const auto numerator = delta * 0x7fff; const auto fraction = numerator / (kVocoderParam1Thresholds[1] - kVocoderParam1Thresholds[0]); return "PW" + std::to_string((fraction * 3 >> 11) + 0x32) + "%"; } if (signed_raw <= kVocoderParam1Thresholds[2]) return "Noise"; return {}; } break; case 19: if (id == 0x0003U) { const auto centered = static_cast<std::int16_t>( (static_cast<std::int32_t>(signed_raw) - 0x4000) * 2); return format_signed_percent(centered); } if (id == 0x0005U) { if (signed_raw < 0x80) return "Off"; const auto scaled = (static_cast<std::int32_t>(signed_raw - 0x80) * 0x807f) >> 15; return format_signed_percent(static_cast<std::int16_t>(scaled)); } break; case 20: if (id == 0x0001U) { float ratio = 0.0F; if (signed_raw < 0x4000) { ratio = static_cast<float>(signed_raw) / 32768.0F; } else if (signed_raw < 0x6000) { ratio = static_cast<float>(signed_raw - 0x4000) * 0.5F * 0.00012207031F + 0.5F; } else if (signed_raw < 0x6080) { ratio = 1.0F; } else { ratio = static_cast<float>(signed_raw - 0x6080) * 4.0F / 8064.0F + 1.01F; } const auto whole = static_cast<std::int32_t>(ratio); const float first_fraction = (ratio - static_cast<float>(whole)) * 10.0F; const auto first = static_cast<std::int32_t>(first_fraction); const auto second = static_cast<std::int32_t>( (first_fraction - static_cast<float>(first)) * 10.0F); return "x" + std::to_string(whole) + "." + std::to_string(first) + std::to_string(second); } break; default: break; } return format_signed_percent(signed_raw); }
const std::string_view label = enum_display(id, decoded); if (!label.empty()) return std::string(label); const auto high = static_cast<std::int8_t>(static_cast<std::uint8_t>(raw >> 8U)); return std::to_string(static_cast<int>(high));}