From 5e2a38802a0c03554ec255e8c5ab8237a9b444f7 Mon Sep 17 00:00:00 2001 From: Benjamin Klum Date: Tue, 13 Feb 2024 17:31:05 +0100 Subject: [PATCH] Add color support to Midi Fighter Twister controller presets --- main/lib/helgoboss-learn | 2 +- .../midi-fighter-twister-grid.preset.luau | 2 +- .../compartment-common.luau | 262 ++++++++++++++++++ ...preset-commons.luau => preset-common.luau} | 26 +- .../midi-fighter-twister-numbered.preset.luau | 2 +- resources/test-projects/mft-light-test.RPP | 239 ++++++++++++++++ 6 files changed, 528 insertions(+), 5 deletions(-) create mode 100644 resources/controller-presets/factory/djtechtools/midi-fighter-twister-lib/compartment-common.luau rename resources/controller-presets/factory/djtechtools/midi-fighter-twister-lib/{preset-commons.luau => preset-common.luau} (94%) create mode 100644 resources/test-projects/mft-light-test.RPP diff --git a/main/lib/helgoboss-learn b/main/lib/helgoboss-learn index 04e12a503..7f3cfd1da 160000 --- a/main/lib/helgoboss-learn +++ b/main/lib/helgoboss-learn @@ -1 +1 @@ -Subproject commit 04e12a50313d91755f0e148ff5d5d68d4d12b7eb +Subproject commit 7f3cfd1da6a22209604fff829ddf84b6adf63bfa diff --git a/resources/controller-presets/factory/djtechtools/midi-fighter-twister-grid.preset.luau b/resources/controller-presets/factory/djtechtools/midi-fighter-twister-grid.preset.luau index 97c44b6df..30394a3a3 100644 --- a/resources/controller-presets/factory/djtechtools/midi-fighter-twister-grid.preset.luau +++ b/resources/controller-presets/factory/djtechtools/midi-fighter-twister-grid.preset.luau @@ -17,7 +17,7 @@ --!strict local realearn = require("realearn") -local commons = require("djtechtools/midi-fighter-twister-lib/preset-commons") +local commons = require("djtechtools/midi-fighter-twister-lib/preset-common") return commons.create_compartment { create_push_target = function (col, row) diff --git a/resources/controller-presets/factory/djtechtools/midi-fighter-twister-lib/compartment-common.luau b/resources/controller-presets/factory/djtechtools/midi-fighter-twister-lib/compartment-common.luau new file mode 100644 index 000000000..047c04349 --- /dev/null +++ b/resources/controller-presets/factory/djtechtools/midi-fighter-twister-lib/compartment-common.luau @@ -0,0 +1,262 @@ +--!strict + +local midi_script = require("midi_script_source_runtime") + +local module = {} + +local color_palette: { midi_script.RgbColor } = { + { r = 0, g = 0, b = 0 }, + { r = 0, g = 0, b = 255 }, + { r = 0, g = 21, b = 255 }, + { r = 0, g = 34, b = 255 }, + { r = 0, g = 46, b = 255 }, + { r = 0, g = 59, b = 255 }, + { r = 0, g = 68, b = 255 }, + { r = 0, g = 80, b = 255 }, + { r = 0, g = 93, b = 255 }, + { r = 0, g = 106, b = 255 }, + { r = 0, g = 119, b = 255 }, + { r = 0, g = 127, b = 255 }, + { r = 0, g = 140, b = 255 }, + { r = 0, g = 153, b = 255 }, + { r = 0, g = 165, b = 255 }, + { r = 0, g = 178, b = 255 }, + { r = 0, g = 191, b = 255 }, + { r = 0, g = 199, b = 255 }, + { r = 0, g = 212, b = 255 }, + { r = 0, g = 225, b = 255 }, + { r = 0, g = 238, b = 255 }, + { r = 0, g = 250, b = 255 }, + { r = 0, g = 255, b = 250 }, + { r = 0, g = 255, b = 237 }, + { r = 0, g = 255, b = 225 }, + { r = 0, g = 255, b = 212 }, + { r = 0, g = 255, b = 199 }, + { r = 0, g = 255, b = 191 }, + { r = 0, g = 255, b = 178 }, + { r = 0, g = 255, b = 165 }, + { r = 0, g = 255, b = 153 }, + { r = 0, g = 255, b = 140 }, + { r = 0, g = 255, b = 127 }, + { r = 0, g = 255, b = 119 }, + { r = 0, g = 255, b = 106 }, + { r = 0, g = 255, b = 93 }, + { r = 0, g = 255, b = 80 }, + { r = 0, g = 255, b = 67 }, + { r = 0, g = 255, b = 59 }, + { r = 0, g = 255, b = 46 }, + { r = 0, g = 255, b = 33 }, + { r = 0, g = 255, b = 21 }, + { r = 0, g = 255, b = 8 }, + { r = 0, g = 255, b = 0 }, + { r = 12, g = 255, b = 0 }, + { r = 25, g = 255, b = 0 }, + { r = 38, g = 255, b = 0 }, + { r = 51, g = 255, b = 0 }, + { r = 63, g = 255, b = 0 }, + { r = 72, g = 255, b = 0 }, + { r = 84, g = 255, b = 0 }, + { r = 97, g = 255, b = 0 }, + { r = 110, g = 255, b = 0 }, + { r = 123, g = 255, b = 0 }, + { r = 131, g = 255, b = 0 }, + { r = 144, g = 255, b = 0 }, + { r = 157, g = 255, b = 0 }, + { r = 170, g = 255, b = 0 }, + { r = 182, g = 255, b = 0 }, + { r = 191, g = 255, b = 0 }, + { r = 203, g = 255, b = 0 }, + { r = 216, g = 255, b = 0 }, + { r = 229, g = 255, b = 0 }, + { r = 242, g = 255, b = 0 }, + { r = 255, g = 255, b = 0 }, + { r = 255, g = 246, b = 0 }, + { r = 255, g = 233, b = 0 }, + { r = 255, g = 220, b = 0 }, + { r = 255, g = 208, b = 0 }, + { r = 255, g = 195, b = 0 }, + { r = 255, g = 187, b = 0 }, + { r = 255, g = 174, b = 0 }, + { r = 255, g = 161, b = 0 }, + { r = 255, g = 148, b = 0 }, + { r = 255, g = 135, b = 0 }, + { r = 255, g = 127, b = 0 }, + { r = 255, g = 114, b = 0 }, + { r = 255, g = 102, b = 0 }, + { r = 255, g = 89, b = 0 }, + { r = 255, g = 76, b = 0 }, + { r = 255, g = 63, b = 0 }, + { r = 255, g = 55, b = 0 }, + { r = 255, g = 42, b = 0 }, + { r = 255, g = 29, b = 0 }, + { r = 255, g = 16, b = 0 }, + { r = 255, g = 4, b = 0 }, + { r = 255, g = 0, b = 4 }, + { r = 255, g = 0, b = 16 }, + { r = 255, g = 0, b = 29 }, + { r = 255, g = 0, b = 42 }, + { r = 255, g = 0, b = 55 }, + { r = 255, g = 0, b = 63 }, + { r = 255, g = 0, b = 76 }, + { r = 255, g = 0, b = 89 }, + { r = 255, g = 0, b = 102 }, + { r = 255, g = 0, b = 114 }, + { r = 255, g = 0, b = 127 }, + { r = 255, g = 0, b = 135 }, + { r = 255, g = 0, b = 148 }, + { r = 255, g = 0, b = 161 }, + { r = 255, g = 0, b = 174 }, + { r = 255, g = 0, b = 186 }, + { r = 255, g = 0, b = 195 }, + { r = 255, g = 0, b = 208 }, + { r = 255, g = 0, b = 221 }, + { r = 255, g = 0, b = 233 }, + { r = 255, g = 0, b = 246 }, + { r = 255, g = 0, b = 255 }, + { r = 242, g = 0, b = 255 }, + { r = 229, g = 0, b = 255 }, + { r = 216, g = 0, b = 255 }, + { r = 204, g = 0, b = 255 }, + { r = 191, g = 0, b = 255 }, + { r = 182, g = 0, b = 255 }, + { r = 169, g = 0, b = 255 }, + { r = 157, g = 0, b = 255 }, + { r = 144, g = 0, b = 255 }, + { r = 131, g = 0, b = 255 }, + { r = 123, g = 0, b = 255 }, + { r = 110, g = 0, b = 255 }, + { r = 97, g = 0, b = 255 }, + { r = 85, g = 0, b = 255 }, + { r = 72, g = 0, b = 255 }, + { r = 63, g = 0, b = 255 }, + { r = 50, g = 0, b = 255 }, + { r = 38, g = 0, b = 255 }, + { r = 25, g = 0, b = 255 }, + { r = 240, g = 240, b = 225 }, +} + +type FeedbackEntry = { + behavior: number, + color: number?, +} +local dark = 30 +local medium = 40 +local bright = 47 +local pulsing = 12 +local blinking = 8 +local red_color = 83 +local default_color = 21 +local feedback_table: { [string]: FeedbackEntry? } = { + empty = { + behavior = bright, + color = 0, + }, + armed = { + behavior = dark, + color = red_color, + }, + stopped = { + behavior = dark, + }, + scheduled_for_play_start = { + behavior = medium, + }, + playing = { + behavior = pulsing, + }, + paused = { + behavior = dark, + }, + scheduled_for_play_stop = { + behavior = blinking, + }, + scheduled_for_record_start = { + behavior = medium, + color = red_color, + }, + recording = { + behavior = pulsing, + color = red_color, + }, + scheduled_for_record_stop = { + behavior = blinking, + color = red_color, + }, +} + +--- Returns the 0-based index within the given palette array. +local function find_closest_color_in_palette(color: midi_script.RgbColor, palette: { midi_script.RgbColor }): number + local ifurthest = 0 + local furthest = 3 * math.pow(255, 2) + 1 + for i, c in palette do + if color.r == c.r and color.g == c.g and color.b == c.b then + return i - 1 + end + local distance = math.pow((color.r - c.r), 2) + math.pow((color.g - c.g), 2) + math.pow((color.b - c.b), 2) + if distance < furthest then + furthest = distance + ifurthest = i - 1 + end + end + return ifurthest +end + +local function create_output(pad_index: number, color_index: number, behavior: number): midi_script.Output + return { + address = pad_index, + messages = { + { 0xB1, pad_index, color_index }, + { 0xB2, pad_index, behavior }, + }, + } +end + +function module.pad_script( + pad_index: number, + y: midi_script.InputValue, + context: midi_script.Context +): midi_script.Output + local off_color = 0x00 + local off_output = create_output(pad_index, off_color, bright) + -- Handle "off" feedback + if y == nil then + return off_output + end + -- Handle numeric feedback + if type(y) == "number" then + -- Translate number to solid feedback color. + local color = math.floor(y * 127) + return create_output(pad_index, color, bright) + end + -- Handle text feedback + if type(y) == "string" then + -- This can be used as a grid controller for controlling a clip matrix, so we should be able to + -- handle text feedback that conforms to Playtime's clip state convention. + local entry = feedback_table[y] + if entry == nil then + -- Unknown texts switch the LED off + return off_output + end + local explicit_color = entry.color + if explicit_color ~= nil then + -- Switch LED to explicit color + return create_output(pad_index, explicit_color, entry.behavior) + end + -- No explicit color means we should use the event color (which is set in the "Glue" section) + local event_color = context.feedback_event.color + if event_color == nil then + -- Event has default color / no specific color + return create_output(pad_index, default_color, entry.behavior) + end + -- Find color available on the controller that's closest to the desired RGB color + local closest_color = find_closest_color_in_palette(event_color, color_palette) + return create_output(pad_index, closest_color, entry.behavior) + end + -- Complex feedback is highly individual. It doesn't make sense to handle this in a general-purpose controller preset. + return { + address = pad_index, + messages = {}, + } +end + +return module diff --git a/resources/controller-presets/factory/djtechtools/midi-fighter-twister-lib/preset-commons.luau b/resources/controller-presets/factory/djtechtools/midi-fighter-twister-lib/preset-common.luau similarity index 94% rename from resources/controller-presets/factory/djtechtools/midi-fighter-twister-lib/preset-commons.luau rename to resources/controller-presets/factory/djtechtools/midi-fighter-twister-lib/preset-common.luau index eb84d774f..a8e9b66d4 100644 --- a/resources/controller-presets/factory/djtechtools/midi-fighter-twister-lib/preset-commons.luau +++ b/resources/controller-presets/factory/djtechtools/midi-fighter-twister-lib/preset-common.luau @@ -1,5 +1,6 @@ --!strict +local preset_runtime = require("preset_runtime") local realearn = require("realearn") local module = {} @@ -9,11 +10,18 @@ export type MidiFighterTwisterPresetConfig = { create_push_target: (col: number, row: number) -> realearn.Target, } +local common_lua = preset_runtime.include_str("djtechtools/midi-fighter-twister-lib/compartment-common.luau") + +local function build_pad_script(pad_index: number): string + return `return require("compartment").pad_script({pad_index}, y, context)` +end + function module.create_compartment(config: MidiFighterTwisterPresetConfig): realearn.Compartment -- Preparation - local function pad(col: number, row: number, cc_number: number): realearn.Mapping + local function pad_control(col: number, row: number, cc_number: number): realearn.Mapping return realearn.Mapping { id = `col{col + 1}/row{row + 1}/pad`, + feedback_enabled = false, source = realearn.Source.MidiControlChangeValue { channel = 1, controller_number = cc_number, @@ -23,6 +31,18 @@ function module.create_compartment(config: MidiFighterTwisterPresetConfig): real } end + local function pad_feedback(col: number, row: number, cc_number: number): realearn.Mapping + return realearn.Mapping { + id = `col{col + 1}/row{row + 1}/pad/feedback`, + control_enabled = false, + source = realearn.Source.MidiScript { + script_kind = "Lua", + script = build_pad_script(cc_number), + }, + target = config.create_push_target(col, row) + } + end + local function knob(col: number, row: number, cc_number: number): realearn.Mapping return realearn.Mapping { id = `col{col + 1}/row{row + 1}/knob`, @@ -67,7 +87,8 @@ function module.create_compartment(config: MidiFighterTwisterPresetConfig): real for col = 0, column_count - 1 do for row = 0, row_count - 1 do local cc_number = row * row_count + col - table.insert(mappings, pad(col, row, cc_number)) + table.insert(mappings, pad_control(col, row, cc_number)) + table.insert(mappings, pad_feedback(col, row, cc_number)) table.insert(mappings, knob(col, row, cc_number)) end end @@ -562,6 +583,7 @@ function module.create_compartment(config: MidiFighterTwisterPresetConfig): real -- Result return realearn.Compartment { mappings = mappings, + common_lua = common_lua, custom_data = { companion = companion_data, numbered = { diff --git a/resources/controller-presets/factory/djtechtools/midi-fighter-twister-numbered.preset.luau b/resources/controller-presets/factory/djtechtools/midi-fighter-twister-numbered.preset.luau index 88358a69f..667aef16d 100644 --- a/resources/controller-presets/factory/djtechtools/midi-fighter-twister-numbered.preset.luau +++ b/resources/controller-presets/factory/djtechtools/midi-fighter-twister-numbered.preset.luau @@ -17,7 +17,7 @@ --!strict local realearn = require("realearn") -local commons = require("djtechtools/midi-fighter-twister-lib/preset-commons") +local commons = require("djtechtools/midi-fighter-twister-lib/preset-common") return commons.create_compartment { create_push_target = function (col, row) diff --git a/resources/test-projects/mft-light-test.RPP b/resources/test-projects/mft-light-test.RPP new file mode 100644 index 000000000..ccd24683d --- /dev/null +++ b/resources/test-projects/mft-light-test.RPP @@ -0,0 +1,239 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 1 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + ENVFADESZ10 40 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 0 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + PANLAWFLAGS 3 + CURSOR 0 + ZOOM 100 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 0 + LOOPGRAN 0 4 + RECORD_PATH "Media" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 120 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 0 0 0 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_PANLAWFLAGS 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + "" + bHJiaO5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAAAAABvJgAAAQAAAAAAEAA= + eyJtYWluVW5pdCI6eyJ2ZXJzaW9uIjoiMi4xNi4wLXByZS45IiwiaWQiOiJZQU5CLUxpbCIsInN0YXlBY3RpdmVXaGVuUHJvamVjdEluQmFja2dyb3VuZCI6Ik9ubHlJ + ZkJhY2tncm91bmRQcm9qZWN0SXNSdW5uaW5nIiwiY29udHJvbERldmljZUlkIjoiMTkiLCJmZWVkYmFja0RldmljZUlkIjoiNCIsImRlZmF1bHRHcm91cCI6e30sImdy + b3VwcyI6W3siaWQiOiJMZjE0a0hNRy1rOHdZOVFNZV9QVHYiLCJuYW1lIjoiQ29sb3JzIn0seyJpZCI6ImhBNzRyZGZaWDZrNXZyZUVIblJsQSIsIm5hbWUiOiJCcmln + aHRuZXNzIn0seyJpZCI6IjMtQkZYUTAwb00tRllxOHhDcUwxayIsIm5hbWUiOiJBbmltYXRpb24ifSx7ImlkIjoiZnY2bnAxa1pTampoeENJQ3ZLMVBpIiwibmFtZSI6 + InRlc3QifSx7ImlkIjoiRHV6Wm9laVNjeXJoV08tY2xEMHlyIiwibmFtZSI6IkluZGljYXRvciBhbmltYXRpb24ifV0sImRlZmF1bHRDb250cm9sbGVyR3JvdXAiOnt9 + LCJtYXBwaW5ncyI6W3siaWQiOiJISldXUnFwaHJycmVQMTZqWmZ0MDMiLCJuYW1lIjoiR3JlZW4iLCJncm91cElkIjoiTGYxNGtITUctazh3WTlRTWVfUFR2Iiwic291 + cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NDgsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29u + dHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDEvcm93MS9wYWQifSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWlu + U3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjoyOSwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNl + LCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5 + Ijp0cnVlLCJzZW5kTWlkaURlc3RpbmF0aW9uIjoiZmVlZGJhY2stb3V0cHV0IiwicmF3TWlkaVBhdHRlcm4iOiJCMSAwMCA3RiIsIm9zY0FyZ0luZGV4IjowLCJtb3Vz + ZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX0seyJpZCI6Ikt6OUdu + VjdrN0NMcTJ5WVFmZ0FjYyIsIm5hbWUiOiJPcmFuZ2UiLCJncm91cElkIjoiTGYxNGtITUctazh3WTlRTWVfUFR2Iiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwi + OjAsIm51bWJlciI6NTAsImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9u + IiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDEvcm93MS9wYWQifSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZh + Y3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjoyOSwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxz + ZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJzZW5kTWlkaURlc3RpbmF0 + aW9uIjoiZmVlZGJhY2stb3V0cHV0IiwicmF3TWlkaVBhdHRlcm4iOiJCMSAwMCAzRiIsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRv + IiwiYXhpcyI6IlgifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX0seyJpZCI6IlQtMWhlSElpa3FobzB2SS1QMHZLZCIsIm5hbWUi + OiJPZmYiLCJncm91cElkIjoiaEE3NHJkZlpYNms1dnJlRUhuUmxBIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NTUsImlzUmVnaXN0ZXJl + ZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJyYXdNaWRpUGF0dGVybiI6IkIyIDAwIDE5Iiwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRv + biIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wxL3JvdzEvcGFkIn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBG + YWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6MjksImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFs + c2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwic2VuZE1pZGlEZXN0aW5h + dGlvbiI6ImZlZWRiYWNrLW91dHB1dCIsInJhd01pZGlQYXR0ZXJuIjoiQjIgMDAgMTEiLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVU + byIsImF4aXMiOiJYIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19LHsiaWQiOiJFZUdORWxnRVF2bHNDSFg1YXNQbksiLCJuYW1l + IjoiRGFyayIsImdyb3VwSWQiOiJoQTc0cmRmWlg2azV2cmVFSG5SbEEiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo1NywiaXNSZWdpc3Rl + cmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsInJhd01pZGlQYXR0ZXJuIjoiQjIgMDAgMTkiLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0 + dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDEvcm93MS9wYWQifSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3Rl + cEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjoyOSwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpm + YWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJzZW5kTWlkaURlc3Rp + bmF0aW9uIjoiZmVlZGJhY2stb3V0cHV0IiwicmF3TWlkaVBhdHRlcm4iOiJCMiAwMCAyNyIsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92 + ZVRvIiwiYXhpcyI6IlgifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX0seyJpZCI6IkNZQ19Sd1hNbjFKWUNzaXpPYW5UcyIsIm5h + bWUiOiJCcmlnaHQiLCJncm91cElkIjoiaEE3NHJkZlpYNms1dnJlRUhuUmxBIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NTksImlzUmVn + aXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJyYXdNaWRpUGF0dGVybiI6IkIyIDAwIDE5Iiwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6 + ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wxL3JvdzEvcGFkIn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1h + eFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6MjksImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGlu + ZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwic2VuZE1pZGlE + ZXN0aW5hdGlvbiI6ImZlZWRiYWNrLW91dHB1dCIsInJhd01pZGlQYXR0ZXJuIjoiQjIgMDAgMkYiLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6 + Ik1vdmVUbyIsImF4aXMiOiJYIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19LHsiaWQiOiJfRWd5X182cXNCRFdqckswRldyTEMi + LCJuYW1lIjoiR2F0ZSBvZmYiLCJncm91cElkIjoiMy1CRlhRMDBvTS1GWXE4eENxTDFrIiwic291cmNlIjp7InR5cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NjAs + ImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJyYXdNaWRpUGF0dGVybiI6IkIyIDAwIDE5Iiwib3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50 + VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wxL3JvdzEvcGFkIn0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3Ii + OjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6MjksImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxlY3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tH + cm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVWaWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwic2Vu + ZE1pZGlEZXN0aW5hdGlvbiI6ImZlZWRiYWNrLW91dHB1dCIsInJhd01pZGlQYXR0ZXJuIjoiQjIgMDAgMDAiLCJvc2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsi + a2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19LHsiaWQiOiJvZGpwQzV1eHdRQ0k5X2Vo + SmJWUDUiLCJuYW1lIjoiR2F0ZSBzbG93IiwiZ3JvdXBJZCI6IjMtQkZYUTAwb00tRllxOHhDcUwxayIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1i + ZXIiOjYyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwicmF3TWlkaVBhdHRlcm4iOiJCMiAwMCAxOSIsIm9zY0FyZ0luZGV4IjowLCJjb250cm9s + RWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMS9yb3cxL3BhZCJ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVw + RmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjI5LCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVz + ZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRy + dWUsInNlbmRNaWRpRGVzdGluYXRpb24iOiJmZWVkYmFjay1vdXRwdXQiLCJyYXdNaWRpUGF0dGVybiI6IkIyIDAwIDAzIiwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0 + aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19fSx7ImlkIjoiYVIwZFJHcHgz + eF9UZFpYN2w3ZmV4IiwibmFtZSI6IkdhdGUgZmFzdCIsImdyb3VwSWQiOiIzLUJGWFEwMG9NLUZZcTh4Q3FMMWsiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6 + MCwibnVtYmVyIjo2NCwiaXNSZWdpc3RlcmVkIjpmYWxzZSwiaXMxNEJpdCI6ZmFsc2UsInJhd01pZGlQYXR0ZXJuIjoiQjIgMDAgMTkiLCJvc2NBcmdJbmRleCI6MCwi + Y29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29udHJvbEVsZW1lbnRJbmRleCI6ImNvbDEvcm93MS9wYWQifSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwi + bWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6NX0sInRhcmdldCI6eyJ0eXBlIjoyOSwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZh + bHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQ + bGF5Ijp0cnVlLCJzZW5kTWlkaURlc3RpbmF0aW9uIjoiZmVlZGJhY2stb3V0cHV0IiwicmF3TWlkaVBhdHRlcm4iOiJCMiAwMCAwOCIsIm9zY0FyZ0luZGV4IjowLCJt + b3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhpcyI6IlgifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX0seyJpZCI6IlhD + b2QzdkFxNGtIU1o5NEFYeC16NiIsIm5hbWUiOiJQdWxzZSBzbG93IiwiZ3JvdXBJZCI6IjMtQkZYUTAwb00tRllxOHhDcUwxayIsInNvdXJjZSI6eyJ0eXBlIjoxLCJj + aGFubmVsIjowLCJudW1iZXIiOjY3LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwicmF3TWlkaVBhdHRlcm4iOiJCMiAwMCAxOSIsIm9zY0FyZ0lu + ZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMS9yb3cxL3BhZCJ9LCJtb2RlIjp7Im1heFN0ZXBTaXpl + IjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjI5LCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2Fu + Z2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1 + ZSwic2Vla1BsYXkiOnRydWUsInNlbmRNaWRpRGVzdGluYXRpb24iOiJmZWVkYmFjay1vdXRwdXQiLCJyYXdNaWRpUGF0dGVybiI6IkIyIDAwIDBDIiwib3NjQXJnSW5k + ZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19fSx7 + ImlkIjoidno4ZDB6VVlBMUlaOG1Da2x2SHNCIiwibmFtZSI6IlB1bHNlIGZhc3QiLCJncm91cElkIjoiMy1CRlhRMDBvTS1GWXE4eENxTDFrIiwic291cmNlIjp7InR5 + cGUiOjEsImNoYW5uZWwiOjAsIm51bWJlciI6NjksImlzUmVnaXN0ZXJlZCI6ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJyYXdNaWRpUGF0dGVybiI6IkIyIDAwIDE5Iiwi + b3NjQXJnSW5kZXgiOjAsImNvbnRyb2xFbGVtZW50VHlwZSI6ImJ1dHRvbiIsImNvbnRyb2xFbGVtZW50SW5kZXgiOiJjb2wxL3JvdzEvcGFkIn0sIm1vZGUiOnsibWF4 + U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6MjksImZ4QW5jaG9yIjoiaWQiLCJ1c2VTZWxl + Y3Rpb25HYW5naW5nIjpmYWxzZSwidXNlVHJhY2tHcm91cGluZyI6ZmFsc2UsInNlZWtCZWhhdmlvciI6IkltbWVkaWF0ZSIsInVzZVByb2plY3QiOnRydWUsIm1vdmVW + aWV3Ijp0cnVlLCJzZWVrUGxheSI6dHJ1ZSwic2VuZE1pZGlEZXN0aW5hdGlvbiI6ImZlZWRiYWNrLW91dHB1dCIsInJhd01pZGlQYXR0ZXJuIjoiQjIgMDAgMEYiLCJv + c2NBcmdJbmRleCI6MCwibW91c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2Fk + ZWQifX19LHsiaWQiOiJaQkh6MFE2Wk5lSzhHRUg3Ny1Kc2YiLCJuYW1lIjoiSW5kaWNhdG9yIGdhdGUgb2ZmIiwiZ3JvdXBJZCI6IkR1elpvZWlTY3lyaFdPLWNsRDB5 + ciIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjcyLCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxzZSwicmF3TWlkaVBhdHRl + cm4iOiJCMiAwMCAxOSIsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4IjoiY29sMS9yb3cxL3Bh + ZCJ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5cGUiOjI5LCJmeEFuY2hv + ciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1lZGlhdGUiLCJ1c2VQcm9q + ZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsInNlbmRNaWRpRGVzdGluYXRpb24iOiJmZWVkYmFjay1vdXRwdXQiLCJyYXdNaWRpUGF0dGVy + biI6IkI1IDAwIDMwIiwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJ0YWtlTWFwcGluZ1NuYXBzaG90Ijp7 + ImtpbmQiOiJMYXN0TG9hZGVkIn19fSx7ImlkIjoiQVF3NUZpLUwyczRYV2pnc2M5OVhvIiwibmFtZSI6IkluZGljYXRvciBnYXRlIG9uIiwiZ3JvdXBJZCI6IkR1elpv + ZWlTY3lyaFdPLWNsRDB5ciIsInNvdXJjZSI6eyJ0eXBlIjoxLCJjaGFubmVsIjowLCJudW1iZXIiOjc0LCJpc1JlZ2lzdGVyZWQiOmZhbHNlLCJpczE0Qml0IjpmYWxz + ZSwicmF3TWlkaVBhdHRlcm4iOiJCMiAwMCAxOSIsIm9zY0FyZ0luZGV4IjowLCJjb250cm9sRWxlbWVudFR5cGUiOiJidXR0b24iLCJjb250cm9sRWxlbWVudEluZGV4 + IjoiY29sMS9yb3cxL3BhZCJ9LCJtb2RlIjp7Im1heFN0ZXBTaXplIjowLjA1LCJtaW5TdGVwRmFjdG9yIjoxLCJtYXhTdGVwRmFjdG9yIjo1fSwidGFyZ2V0Ijp7InR5 + cGUiOjI5LCJmeEFuY2hvciI6ImlkIiwidXNlU2VsZWN0aW9uR2FuZ2luZyI6ZmFsc2UsInVzZVRyYWNrR3JvdXBpbmciOmZhbHNlLCJzZWVrQmVoYXZpb3IiOiJJbW1l + ZGlhdGUiLCJ1c2VQcm9qZWN0Ijp0cnVlLCJtb3ZlVmlldyI6dHJ1ZSwic2Vla1BsYXkiOnRydWUsInNlbmRNaWRpRGVzdGluYXRpb24iOiJmZWVkYmFjay1vdXRwdXQi + LCJyYXdNaWRpUGF0dGVybiI6IkI1IDAwIDM0Iiwib3NjQXJnSW5kZXgiOjAsIm1vdXNlQWN0aW9uIjp7ImtpbmQiOiJNb3ZlVG8iLCJheGlzIjoiWCJ9LCJ0YWtlTWFw + cGluZ1NuYXBzaG90Ijp7ImtpbmQiOiJMYXN0TG9hZGVkIn19fSx7ImlkIjoiNElXV0lOTGJYY2JvVldZZG5DbnVwIiwibmFtZSI6IkluZGljYXRvciBwdWxzZSBvbiIs + Imdyb3VwSWQiOiJEdXpab2VpU2N5cmhXTy1jbEQweXIiLCJzb3VyY2UiOnsidHlwZSI6MSwiY2hhbm5lbCI6MCwibnVtYmVyIjo3NiwiaXNSZWdpc3RlcmVkIjpmYWxz + ZSwiaXMxNEJpdCI6ZmFsc2UsInJhd01pZGlQYXR0ZXJuIjoiQjIgMDAgMTkiLCJvc2NBcmdJbmRleCI6MCwiY29udHJvbEVsZW1lbnRUeXBlIjoiYnV0dG9uIiwiY29u + dHJvbEVsZW1lbnRJbmRleCI6ImNvbDEvcm93MS9wYWQifSwibW9kZSI6eyJtYXhTdGVwU2l6ZSI6MC4wNSwibWluU3RlcEZhY3RvciI6MSwibWF4U3RlcEZhY3RvciI6 + NX0sInRhcmdldCI6eyJ0eXBlIjoyOSwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwic2Vl + a0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJzZW5kTWlkaURlc3RpbmF0aW9uIjoi + ZmVlZGJhY2stb3V0cHV0IiwicmF3TWlkaVBhdHRlcm4iOiJCNSAwMCAzYSIsIm9zY0FyZ0luZGV4IjowLCJtb3VzZUFjdGlvbiI6eyJraW5kIjoiTW92ZVRvIiwiYXhp + cyI6IlgifSwidGFrZU1hcHBpbmdTbmFwc2hvdCI6eyJraW5kIjoiTGFzdExvYWRlZCJ9fX0seyJpZCI6IkRHbUZaUm0xTW5hR0ducEwwUFM3cSIsIm5hbWUiOiIxMiIs + Imdyb3VwSWQiOiJmdjZucDFrWlNqamh4Q0lDdksxUGkiLCJzb3VyY2UiOnsiY2hhbm5lbCI6MCwibnVtYmVyIjowLCJjaGFyYWN0ZXIiOjMsImlzUmVnaXN0ZXJlZCI6 + ZmFsc2UsImlzMTRCaXQiOmZhbHNlLCJvc2NBcmdJbmRleCI6MH0sIm1vZGUiOnsibWF4U3RlcFNpemUiOjAuMDUsIm1pblN0ZXBGYWN0b3IiOjEsIm1heFN0ZXBGYWN0 + b3IiOjV9LCJ0YXJnZXQiOnsidHlwZSI6MiwiZnhBbmNob3IiOiJpZCIsInVzZVNlbGVjdGlvbkdhbmdpbmciOmZhbHNlLCJ1c2VUcmFja0dyb3VwaW5nIjpmYWxzZSwi + c2Vla0JlaGF2aW9yIjoiSW1tZWRpYXRlIiwidXNlUHJvamVjdCI6dHJ1ZSwibW92ZVZpZXciOnRydWUsInNlZWtQbGF5Ijp0cnVlLCJvc2NBcmdJbmRleCI6MCwibW91 + c2VBY3Rpb24iOnsia2luZCI6Ik1vdmVUbyIsImF4aXMiOiJYIn0sInRha2VNYXBwaW5nU25hcHNob3QiOnsia2luZCI6Ikxhc3RMb2FkZWQifX19XSwiaW5zdGFuY2VG + eCI6eyJhZGRyZXNzIjoiRm9jdXNlZCJ9fSwiYWRkaXRpb25hbFVuaXRzIjpbXX0= + AFByb2dyYW0gMQAQAAAA + > + FLOAT 692 119 752 674 + FXID {1A4B459B-FC82-E442-8789-DEBD4FE90AD7} + WAK 0 0 + > + > +>