-- AudioScience Iyo Dante/AES67 QSC plugin -- November 2018 PluginInfo = { Name = "AudioScience~Iyo Dante", Version = "1.0.0", Id = "b76fdbd7-72d3-408b-9bb8-0d3e2e018580", ShowDebug = false, Description = "AudioScience Iyo Dante plugin" } function GetPrettyName(props) return "AudioScience Iyo Dante (AES67 mode) 1.0.0" end function GetColor(props) return { 190, 83, 28 } end function GetProperties() props = { { Name = "ModelType", Type = "enum", Choices = {"8.8M", "16.16M", "32.32M", "16.0M", "0.16L", "32.0M", "0.32L"}, Value = "32.32M" -- default }, } return props end -- Get the I/O count from the model type property function GetIOCount(model_type) local input_count, output_count = 0 if model_type == "8.8M" then input_count = 8 output_count = 8 elseif model_type == "16.16M" then input_count = 16 output_count = 16 elseif model_type == "32.32M" then input_count = 32 output_count = 32 elseif model_type == "16.0M" then input_count = 16 elseif model_type == "0.16L" then output_count = 16 elseif model_type == "32.0M" then input_count = 32 elseif model_type == "0.32L" then output_count = 32 end return input_count, output_count end function ModelTypeToNo(model_type) local model_no = 0 if model_type == "8.8M" then model_no = 2701 elseif model_type == "16.16M" then model_no = 2702 elseif model_type == "32.32M" then model_no = 2703 elseif model_type == "16.0M" then model_no = 2705 elseif model_type == "0.16L" then model_no = 2708 elseif model_type == "32.0M" then model_no = 2706 elseif model_type == "0.32L" then model_no = 2709 end return model_no end -- Create additional channel group pages as necessary for the selected model type function GetPages(props) local pages = {} local io_pagenames = {'Channels 1 - 8', 'Channels 9 - 16', 'Channels 17 - 24', 'Channels 25 - 32'} local io_only_pagenames = {'Channels 1 - 16', 'Channels 17 - 32'} local model_type = props["ModelType"].Value if model_type == "8.8M" then table.insert( pages, { name = io_pagenames[1] } ) elseif model_type == "16.16M" then for idx = 1, 2 do table.insert( pages, { name = io_pagenames[idx] } ) end elseif model_type == "32.32M" then for idx = 1, 4 do table.insert( pages, { name = io_pagenames[idx] } ) end elseif model_type == "16.0M" or model_type == "0.16L" then table.insert( pages, { name = io_only_pagenames[1] } ) elseif model_type == "32.0M" or model_type == "0.32L" then for idx = 1, 2 do table.insert( pages, { name = io_only_pagenames[idx] } ) end end return pages end -- Define Controls function GetControls(props) input_count, output_count = GetIOCount(props["ModelType"].Value) return { -- 'Select a Device' group -- Device selection combobox { Name = "SelectedDevice", ControlType = "Text", UserPin = false, Count = 1, }, -- Selected device connection status text { Name = "SelectedDeviceStatus", ControlType = "Text", UserPin = false, Count = 1, }, -- 'Device' group -- Device info parameters text { Name = "DeviceInfo", ControlType = "Text", UserPin = true, PinStyle = "Output", Count = 5, }, -- 'Firmware' group -- Firmware Info parameters text { Name = "FirmwareInfo", ControlType = "Text", UserPin = true, PinStyle = "Output", Count = 2, }, -- 'Status' group -- Status Info parameters text { Name = "StatusInfo", ControlType = "Text", UserPin = true, PinStyle = "Output", Count = 2, }, -- Status LEDs { Name = "StatusLEDs", ControlType = "Indicator", IndicatorType = "Led", UserPin = false, Count = 2, }, -- Identify button { Name = "Identify", ControlType = "Button", ButtonType = "Momentary", UserPin = true, PinStyle = "Both", Count = 1, }, -- 'Settings' group -- LED Brightness combobox { Name = "LED_Brightness", ControlType = "Text", UserPin = true, PinStyle = "Both", Count = 1, }, -- 'Input/Transmit AES67 Details' group -- AES67 TX details listbox { Name = "TXAES67Details", ControlType = "Text", UserPin = false, Count = 1, }, -- 'Receive/Output AES67 Details' group -- AES67 RX Details listbox { Name = "RXAES67Details", ControlType = "Text", UserPin = false, Count = 1, }, -- 'Channel Strip' controls -- TX channel name { Name = "TXChannelName", ControlType = "Text", UserPin = false, Count = input_count, }, -- RX channel name { Name = "RXChannelName", ControlType = "Text", UserPin = false, Count = output_count, }, -- TX meter peak text { Name = "TXChanneldBFS", ControlType = "Text", UserPin = true, PinStyle = "Output", Count = input_count, }, -- RX meter peak text { Name = "RXChanneldBFS", ControlType = "Text", UserPin = true, PinStyle = "Output", Count = output_count, }, -- TX meter horz bar { Name = "TXChannelMeter", ControlType = "Knob", ControlUnit = "Integer", Min = -60, Max = 0, UserPin = false, Count = input_count, }, -- RX meter horz bar { Name = "RXChannelMeter", ControlType = "Knob", ControlUnit = "Integer", Min = -60, Max = 0, UserPin = false, Count = output_count, }, -- TX Gain text { Name = "TXChannelGain", ControlType = "Text", UserPin = false, Count = input_count, }, -- TX Gain knob { Name = "TXChannelLevelKnob", ControlType = "Knob", ControlUnit = "Integer", Min = 0, Max = 84, UserPin = true, PinStyle = "Both", Count = input_count, }, -- RX Level knob { Name = "RXChannelLevelKnob", ControlType = "Knob", ControlUnit = "Integer", Min = -10, Max = 24, UserPin = true, PinStyle = "Both", Count = output_count, }, -- TX Level text { Name = "TXChannelLevel", ControlType = "Text", UserPin = true, PinStyle = "Output", Count = input_count, }, -- RX Level text { Name = "RXChannelLevel", ControlType = "Text", UserPin = false, Count = output_count, }, -- TX phantom power button { Name = "TXChannelPhantom", ControlType = "Button", ButtonType = "Toggle", UserPin = true, PinStyle = "Both", Count = input_count, }, -- TX AES67 status { Name = "TXChannelAES67Status", ControlType = "Text", UserPin = false, Count = input_count, }, -- RX AES67 status { Name = "RXChannelAES67Status", ControlType = "Text", UserPin = false, Count = output_count, }, -- TX AES67 toggle button { Name = "TXChannelAES67Tgl", ControlType = "Button", ButtonType = "Toggle", UserPin = true, PinStyle = "Both", Count = input_count, }, -- RX AES67 transmitter selection combobox { Name = "SelectedAES67TX", ControlType = "Text", UserPin = true, PinStyle = "Both", Count = output_count, }, } end function GetControlLayout(props) layout = { } graphics = { } local device_info_labels = { "Model Name", "Model No.", "Hardware Rev.", "Serial No.", "Primary MAC" } local fw_vendors = { "AudioScience", "XMOS" } local status_types = { "Sync", "Sys" } local aes67_tx_labels = { 'Stream Name', 'Transport IP'} local aes67_rx_labels = { 'Channel', 'Connected To', 'Stream Ch.'} local model_type = props["ModelType"].Value -- White background table.insert(graphics, { Type = "GroupBox", StrokeWidth = 0, Position = {0, 0}, Size = {680, 680}, Fill = {255, 255, 255}, }) -- Logos table.insert(graphics, --ASI Logo { Type = "Svg", Image = "PHN2ZyAgICAgIHZlcnNpb249IjEuMSIgICAgICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciICAgICAgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiICAgICAgd2lkdGg9IjEyMHB4IiBoZWlnaHQ9IjU1cHgiICAgICAgdmlld0JveD0iMCAwIDEyMCA1NSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+ICAgIDxnPiA8aW1hZ2Ugd2lkdGg9IjEyMCIgaGVpZ2h0PSI1NSIgeGxpbms6aHJlZj0iZGF0YTppbWFnZS9wbmc7YmFzZTY0LCBpVkJPUncwS0dnb0FBQUFOU1VoRVVnQUFBSGdBQUFBM0NBWUFBQURIYW81ckFBQUFBWE5TUjBJQXJzNGM2UUFBQUFSblFVMUJBQUN4and2OFlRVUFBQUFKY0VoWmN3QUFEc1FBQUE3RUFaVXJEaHNBQUFBQllrdEhSQUNJQlIxSUFBQUFKWFJGV0hSa1lYUmxPbU55WldGMFpRQXlNREU0TFRBekxUQTJWREE1T2pFeU9qRTBMVEExT2pBd3FIR2tOUUFBQUNWMFJWaDBaR0YwWlRwdGIyUnBabmtBTWpBeE9DMHdNeTB3TmxRd09Ub3hNam94TkMwd05Ub3dNTmtzSElrQUFCZHlTVVJCVkhoZTdad0pkRTNYL3NlL2R4NHlUNUtJSk9haG5ua09raUJtOVV3MWxiWlVxZUw5cVdwYWFvcTJ0TlZxbjFldHVhZ1dwU2dpZ3d3SVFoRHprSkNrQ1pubjNIbjgvODYrcDRaU2d1anoxcnFmdGU1eXo5NzdubjNPL3U3OUcvWTVJckFTK0IraTRNb1pYRmk3QUphMEUzQnIxaEVkbGtkQndOZlplWkQvR1lHMWRKWEhwb1hDZlBrd1JISWxSQTZ1TUpRWG91SHN0V2d3OEEyK2xaMC9JK1QvZmFISlREcUkyRkF4QkRkVG9MY0EzaE9Xd1NCMWdGQXNnY0ZpWDcrUDRvVVhPUG16dDVFMmR3RGtRZ3NFemJyajVhTlcrQStjQ0VGaEppeDZMVHc3OXVGYjJua1lMNnpBT3ZyRWpQc0hWREhyb2JNS1VIL2VkdlQ4MnVadkwzODFGU0taQWdhZkp2RHk5bVh0N1R5Y0YxTGd5b0piaU9tcGdERDNHZ3hlRGRBM3dZU0dZYS9ZNmxScVZCemFBb3ZKaUthVEZyTXlPMy9OQ3lkd1J1SWVKQTcxaDh5b2c2THZaQXpZZGcxeThkM0xQUC94T01pY1hhRVhLOUNvOXlpKzFNNWY4VUlKZkdaZEJQbmJvUkNKZ0hvUmU5QTFmQlZmWXlQM3hsV29qdStCeGFCRDNXbmY4S1YySHNVTGt5WWRuemNDbFlkM3dlcm1oUzZicnNMVjNZT3Z1VXYwcXk5QlVsa0lnMUNDZm52eitGSTdqK0tGV01GUlk1dWhNbkVYcE0yN29QKyt3b2VLZTNuUGVnZ0xNMkJVbGFQRlJ6L3lwWFlleDM5MUJWZVVGdVBFbUhvd2xhbmdOMmsrMmt5TzRHdnVSMk1HRW51SklIZDJoOG16THZwc1NPRnI3RHlPLzVyQStkZFNrZnBXVzVoSnZIWXJFK0RiTHBTdmVaQWpNL3ZBZVBNTXRHV2w2TFk3SDY1ZTNueU5uY2Z4WHpIUkdiRTdrREt1TFVTdW51aDJvT1NSNHQ0OGVnRGE4M0d3bWsyb05YeW1YZHduNUc5ZndSZTNmSTdmdndxSHVFMEg5TnR3aWk5OU9CYjZSUGFTUTBscGtWYXRSditZcWhjemNYK0IrVnZISy9tTGFmaDllVGhxdmZMbVk4WGxPUEhCRUNnVVNwZ3FpdEZzL2phN3VFL0IzelptaWVGRFViUjVGUUkvV0lHT2M5ZnhwWDlOMnVIOVVDWC9Cb0ZRQkd2RGpxamZmU0JmWStkSitGdE05T0hwb2FoS09veVdxMk1SMENtTUwvMXJ1QXVLQ3BORFJsR3p1aWdQdlE4WklKZEpiSlYybm9qbnVvSTVIeG96dmlVcVV3NmovWTd6MVJLWEkzRm1YMGpsRGpCVHp1cy8rWE83dU0vQWN4WDQ2TmhHTUdWZlFvL1ljdmcwYnNtWFBwb2JNZHRndUJBSGdWZ01rMnR0dEI0L2g2K3g4elE4RnhQTlBlbzdOTkFMSW9FQUlYc0xvUlRaeWgrSDBXaEVUQzhwbEY2MW9TbklSZmM5QlhEMnJNWFgybmthbnNzS2p1L3RBS25TQ1gzM1YxOWNqdmkzdTBGQnVURm5tcjNHZkdBWHR3YW9jWUZqZThzaDlHK09QcjlrUE5ISjAzNWJEMEZXS3RrVUlZeGttanRPVzhyWDJIa1dha3hnbmNtQ2ZVRUNpTnNPcUZhT2V5OEdvd2xweXlkQjR1SUZUV2toZXYyY3p0ZlVESlZsSlNqOC9RWi9kQmNELys5Zm9hTW9NZnZxZVJTVmxQRWxEMUo0T3hzNTF5OUJ6eC8vSFJSbVp5QW43VEtNL1BHanFCRWZyREtZRVJza1JyMVJrOUY2em1xK3RQcEV2OUVXa3JJY21LckswWEQrZGlpOEEzRnEzbkNJckdaNC9uTTZPazhJcHdsa1JkeW9lalFseFZDMDZZTldvMmZnNkx0OUlYTnlZK2V3V2kwUWlzUm85KzVLZUxYcXhzcis0TWpDOFFoZXZJVjl6OHRJdzQzVmMxQjQvQUJrZE4zTy9hbHUwV1pXOXdjVnBTVklqUmdEVlVvc3BBb3BURG9EWkUzYW9kZjYwNnlleXc0dWJmd1lXVDk5QnBsSlEwWkhES1BlQUhIemJ1aXgraWhpaHRXRlVDSUZBbG9pS0h3VmtpYTFnOGpKbmYyV1lUYkNKSE5FdHkramNHeGlLd2lrQ3JoMkhvU3U3MzVscTZaUDVQQjZFSmhOY0JzOEExMG52cy9LTG56N0FiSjNmZzJGd0dTemRIb2psRjJIb3ZPU0hZZ2ZTWDA2M2ZNVXptS0dRYXg0ZG9HcnRIb1NWNDVHTXlMUVl1Sjh2clQ2cE8zN0FWbmZUSUdJMGlKUmt5Q0VmcmtmQjNwSW9WQW9ZSEwzUjU4Zkw3RjIwYSsxZ3FRNEV6cXJFSDBQbGlPYS9MeGM2Y0J1OUE0MGNDVTZNMTZocVAwUEVqNThCVDJXL3NLK0gxczBEcFZ4V3lGMTlZQlFLcWZmQXVxYzIraDMwZ294YXdGY1A3QVphWis4RHFVbnRaRndiUVF3VlJUQmZmZ2N0SHQ3Q2ZKdVhNRzVHVjBodGhvaFZqclRoTE1GR1Z3YnY1bnJVTHovT3hpekwwT3IwYUIzZ2dGeGc3d2c1eTZSYnllZ0xGOWRWSWlRT0FPU1J0ZUR6S0tuaVYyQ3BsOGxJYUJORUdzVFBha3p4Tm5uWWZDcWovNWJMeVA3M0FtY245MExDcWtZSW9wdGJQZHNoYkcwQUlHTGY4T3Q3MmREVUZrSTBBVG5ZSDBVRktMcmpodlBacUpOT2pXaU9zcFIvLzF2bmtwY2pWWkxwbmtpeERTN0RTWXpFemZ1amRaUU9EcEJUUU1VeW90N2V0VThpUExUb2E2c1FzaStjaVJPRFlHY0pvQ0Fia2dnRWxGS0pXRWY3Z1lkZkFMWmJ6ak9ybG1JZHRNL1o2WTJjbEF0YUUvdGc5emJEMEtaZ2drSEN3MlNrK01kY1MvUXFzeGM5anJrVGtyMjNObXNxWVRWb0FVQ1d6TnhMLy8yQTg2OTBSd3ltWlQxYlZKVHZWSEgyaWpiRDRDUmpnMFo1MkNvckVEUUw3azQ5ZUZ3eUdDQ2xWWVQ5eFlLOXpHVUZPS2xyeE54ZWZsa1NJMHF1bTRwZEdMbEhYSFBibHdLWWRaWjZHazhPSEhQYjEyQks5T0Q0T0JFazRtRU5ha3FxRThETERUMmJnTW1vL0xTTWFEc0ZrbHF2ZE9Ic2J3UWZtOHZnNXQvZzZjWDJLelhZbWNiUjNUNWRnOWFqL2tYWC9wa0pMM1REVW9YRCtpSzg5SDV4M1NjWGhjQmExNGFkT1dsYVAzMVlaQ1JRKzZWTXlqNitWUDJOS25wZ3UzSTNQTWRMRGRPd1dveVFpMlVRNlVoQWNnOGM1aTFhZ1FNbXNTKzMwellEUWZ2QU1ETkZ6R2hRc2hGTkFSMERqVU5zRzJ2alBRMWFPQWJOcFo5djdaM1BYSTN6SWRWS2tWZytGYjBqRE9oMW1zZm95SzNCTDNYSmVQNDJrOXc2OHNKdFByZG9GZXI0RFprRmhvdjJBRXJtV0dkVmdPLzhRdVJ2Mm82dVFvckFxWXVSemtOdlBiVWIyekFaY0ZqNFVMdG5RZE5SKzFacXlFMjYxRVp2d1ZDaFJQVmErSFo4MVYyRFlXWjE1Ry9maTdNQmlNNmJjM0FzVy9lUjhINk9SU2J1TUJnTU1CdDVBZG9QSDhIekI0Qk1JcGtjTzh4Rm1XL0xLUGJ0MERTWVRCY2hyNEw1NWRud0dua1BMUjVQWnlkODZsTU5IZlIrem9wMEdwTkl1cDJDT0ZMbjR3ck5LQzNWcjdEWGw1M0gvWWVBZ2U5aVpPakFpaTlVc0t4M3hSMG52VVY4enRSdlJTUXkyVVFOZXVPTmd0K3dyR0J6cEE2T01BcGJBSTZ2YmNTSmVXVk9EUENDeEpuRDZncXkvSHlJUTNLQzI3ajh0b0ZhRE4zUFdKRFJYQW1VVGhyOFkvRk8rRWQxQitIUWlWdzhQQ21YUHMyZXNZYlVKV1hqVk5qRzBLbWxPT2xWV2ZnMStnbDIwVVNSZm01eUQ2MEhTWHJ5UXlTeVJhL0ZJemdyNk53Yi9hWGRpd0dtVXZIUWlLZ1NlVFRDRjNYSk9OUXNBQktaeGRJT3c1R3Q0VjNmYnlXZkhWOG1BektXbjdNaXBob3RUVmRjUlQrTFRvaXNpL2RtOFVJbndtZnNPdXQvSGt4VFJoQUhqUWMzWmY4ekovQk5qM1RrNkp4Yy9Fd3lPUnlpQnEwUjhqWDBiYktQL0hFSzFoUEY3aTd2UUxCMjg4K3RiaGN4SjN4NVNTSUhWd2hkUEZHMjhtTGNHSjhZOGhkM1dIMkRHVGljaVMrMllIRWxjTm9GU0JvK1Q0Y0cxbkgxc2JWajRuTGtmdnIxK1JQRldTV2l0QjgzbzhzQURxL1lBUTZmTFFlUi9vcTRlVGlDbzFGaE9CSUZRSkozT0tUTVpCSkpiQm9xK0E1WkJyNU5RbFNwbmFBUXkwZldFUVNWRjQ5eWM3N0J4V1hrMUc4NWwwU2wwUUplUlU5L2lRdVI4N214VXhjRGEzczBMWEpTQnhTbXlaUUxhZ2t6bWp5M2xyay9aNEJQYTF5anNNVURDbG9jdGxjaEFVYVNKaTRjVFBDSUJPWUlXM1NDU0tQT3FqWXVwQThpQlhPdzkrL1Qxd083dDN3ck85bXNSaUV1emZPa2hUY3lvYTY0c0ZvLzRrRTFtblUyTjlaaHFCZnI4Q3RjUnUrOU1sSm5zWnRhSGl3bEtqTDlwdUlueElFSmExY2JWVUZRamRmWm0xUzFpMkJKZnNpK2JOeWROMlZqK1JaRkRHTFJld211bSt5dFZIcjlNamF1SkRpRnhHRWpUdWpmbzloTFBvTVdYMENDY1BKR3NnVjBKRzVDOXRmQUNjSk55emNhN2V2VVhEa0NMWFJRcFBrUDBqZFJKR3dtUkltOHFsU1IyZmtySmlFNDUvWXpIeCtWanJTRjFBMFQrV3lWbjNRZGQ1YVZuNHZuSiszWko1bGJpVm82MDJjV0RnT0VvcXNRYjdWU1dSRzZrQTVUazl1QzVsQ2ljTWZqWWFFL0s0TnptZHFFVEQ4WDBpTDNnYkw1Y013azltdDlWb0VjcGFOWmhQS2tiTlM3M3pNdDcvTGlhV1RJU3pKQWFpTmcxeUNpeU04Mkt0UEVoZGJSbkV2MVJiWW9LbENmRTlIRElqTGgyL0RabnpwazNQOTRFOHczRWdoZjZuQ1B4YVNUOTN4SDVnelUyR29LRVh6ejZJaEV3bVFtMzRaUlpzV01GdFVmODRHRkNSc2grNWlJb3hWWldqMmFTU1VNbHRZZEhSaWF6aTZlVUpWVllXdzc0OGk4clhXNkxjekU0bmh3eUhXVmtCTmc5NWxTeG9GT2paT2ZERWRjaHA4YlhFUk9xdzh5c3F5dG4wQmtRTkZwZ3pLNDhuVVYyWmNaRWVuSjdXR3pNTURaa2RQQkgreGw1WGRTMkhhZWVSdGpxREZLSVR2R3hFb3k3b0d6ZEZ0ekxkeWNLYlVTRkgvd09oeTNFallBMzBTK1d4S3FReU5LWTJqZUVCQWFhQ2dibXRrZmZFNmErczlld3R5SW9aQTdFaEMrVFZEMElkcjJIbnVKZnRFRE1vUHJxVko1OEtPclhUTkpxTVpJWHNLV2N6eVo2b2xzSlpNei81UVozVFpYd2lGKzlPL01rUFpHOUkvbjBDbTJRV1NoaDNnMHE0M2ZsODVnMFdremdQZlJ0MU92Vmp5Zm5aYVo4ZzQvL1ZTTi9qMkdZZHJuNzdPOGtxblBtK2lZYmQrN0Z4SlM2ZEFYSnBEUVZNeGduZmRSdEw4MFdqLzNpcGswRUFhVXZheTFWRnY5anE0a2VubHlFNUpRTm0rYnlrOU5NSmp6QWZ3YmRxYWxZdUZBbGdvS3YwRE01bnVKcE0reHRIM0JrRkJ2bDlMRmlSb28wM3dlK0d1TS9tZElDamMzQ0h3YllUR2I4N0h0ZkRlRUx0NHdsQld3RDdhN0Z5MFdINkl4UkpYNXcrbGlTQkE0SUxkTUtlZHBJQllCTDNVR1hsYmwwQkU5Ky94NmtLbzl2OGJJcG9zYXJVR0lSdHNPZmU5YUNodlB4ZmVIM0lQSCthU3VENTB0MjZqZnZnUGNLT0orREFlRzJTcHRUckVVS0RUUDdZS2NnZEh2dlRwT0R6M0Zaak9INEtXUXYyUVJBdU85SGVGZ2xJT25VaU9BYjltc3pZeEU5cEJ4TDBlU3lsTUdPVzdVUU04b0JRTGFEQ2MwSjlXSjhmNWJTdFJzSG9tWGIwUURkN2ZDRlB4YldhS20wLzhDTkVVM0xEOWJPK0c2TDMyQkd0Zm5IY2JwMGJWZ2N6SkJkWTZ6UkcybWxJTEhoMlo2cFBUZ21BdXBITnpRWS9DRGZWbnJFVG1Sd05ZNmxWcjRtZG9PV282My9vdU1XOEZRWngzRFRvMVdZOEVJeEpHQkVKaVVNTW9sS0Q5eHF1dzZqVTBtU2dTOXd0QTVMQUF5TlFsa0hRYWdtYnZMTWU1Y1hVaGR1WDIyU2tvbzRCVjVGMGZuditjZ2J5VlUxaEVIQkQrRTVxRURiTjFkQTlSWTVwQ3FpcUMzbWhDaDgzcHpBS1lESHA0KzlmbFd6eklJMWV3Vm0ra1JOMGRRNDlibmxuYy9QUkwwQ2J0WkN1cjVTZjdjR0hSV1BaZlVreXFNblQrTHBtMWlRNS9CYUs4NjlCVmtOL2RXNEpqY3daREtiU3dBS1VQTCs3Rkh6N0Y3VlgvWWhzVjdvT253eXlRb09EeUtiUWljUTlQRGFZZ3pJUHkwd3Ewak5qSjJuTmJpY21qQXlFbi8yU1JPek54SzRyeVdSMkhYQ0tFMzhqWk1PczBsSzRWb1BVM1I1QythQ2d6MVJxUjhnRnhNODhrNGNpcVJSRCtUbTZGWEViN0RaZVFTajVSckNtbmVJSHVaZU1sZUxpNXd0T25OaFAzZU1RYkpFb3hUVkFIQkVkc1Jmcm1qeUdVTzloT1JtdExwNnBFOHk5aW1lOFhLWnloY3d0NFFOejBrNGs0OHZrTVNDamY1Zkx6RHQrZGdtZXRXdkR3OW4ya3VCeC9LVEFGdW9nZTRvdGVrUlYwWkF0UW5vVnpjd2REU29Nc0RHZ0JVZTFHVUIvWnprVFN1L3FUT3pJaGZtcDNpTThmaEZHdFJzdC9IMEZld2s3b3owWXg4eTNwT2dyWG9uYmcwRnVka0U4bVRVYlJvNlRqUCtIZGJRaHl0eTlEOE5KZHlMcDBCcWFyUjhtVWsxa1ZPOERiMXc5WGRxeEV5dGhBT0pDNXJ6UUowWE5YTnVYSGU1RDhmejM0cStLMkxxOGpmZWw0V0hWcXRrMWFjSEFESkNJaE0rV2VuUWV3TnB5Snl6bWRpTDBqR3FEb1VqTDB1NWZSeUluaE5USWN4c3BTbEVldnN3VjZJK1pUVkN4RlptWUdpZ3J5a1hrc0NwVnhtOGpGYWRGMXd3VjJyc0pZeW44cDZ1Y3dsT1NpNVlvanlGei9FU1FLQjFoTUJ0UU9zWW5MbWZXc3BFanNIbHdINWRmUHdCQ3pobzJYcGZkVXRzT1htWm1KN091MllQTlJQTlJFY3dWeHcvd1ErdXZ0TzdzOHowSjY1R1prZlRXWlVpdzl3ZzViY1dpNFB4dzRqMHppMFVpeVdTbFdPSkpQS1lML3JEVm9NR2dDNG5wSW9QUzI1WW93NmlqNTE3TWJOS3NyNFJnOEVqN0RaK0hLbk40WUdGbk0rb2liM2d1QzdBc1FjRnVRVmxyMXBRVzJ2d1JBZmFncEZlbERrVFIzTDVGY0RzenRsTkhxRkxwNlE1UnpnZmxoUmE4SjZQcmhhaHpnK25YbnpDZVphektmS2hKTVdGVUVtYzRFMzluZm9lVFhMd0d5T2libldnamRlZ1d4SVVJb1BXdmJybE5QSmxxamdxbk1pSURsZTVIOUtVWE5GZ044cG4yTEZzT25JT3ZpYWFUUEl0Tk8vVnJJeXNpNkRFZTNCUnNSMlozNjUvSml3cURYUVNPUVFsUkpRWlBPRFA5RlB5UDMrNWwwSHlMeVNMUWV5UUt5SGJNaUl4b3YzWVRHQTE5anYvc3JIcnFDRThZMFFsZ05pY3R4L2F1cHpKLzVqUWxINmZWVVNDc0w3dXliY251MEVncTZ1TWpXZitacU5CLzZGdUtIZUVQaFJjRVJOMmdjdENvNWNiVWxSZkNaOENucWpwdUhsTGZhM2hHWEMzaDBGK0paYXNFZzN5eHpwOStiakRCNDFzUExKQzVYRS90R1cxck5yaENRaVhRVVdhQ3N1QVVodHd2VzdWVW1ibmJxTWJaNnVkOXpmWXVwVDFjSjRPVG9DSmVoYjBOM1BSbldxbUpvYU5XR2tMaUhSamFpSU12cjduWEtiSDkxb1BhSE5CRisrWnpTT2lGRUxYb3ljVG55b3phU2VYYUVsVmFxWHVIS3hMMFJ2NXRXTDc5MVNoK3B6TmFubzRNU1hoUG5venh1QzN2b3dzVGxvTlV2a3NyZ01uclNZOFhsdUU5Z2JxQU9qbStKbmpYNHVDNTE3VUxJUlRTNXpWYTBlV2NaaWs1RnN4WEc5bFAxV3ZaS3JJb0N1YlpyVHFINXNNbUludHdWWWxVcExKcEttS3RLWWFSNmJjRnRtR3MxUUVoa09YdzY5MFBLcTAweDh2aGR3MU5hVkF3aDk0Q0ZPNkRBaHR2RVVORnZQTVl1UUw4ZnpyQTJ4MWZNaHVBRzk3eFpRSDNyeWVlcVVWVmNDTi9wcXhDMmFDTnJJNlpWWXRZWVdEMjNXOGUxNGZyWGlCMGhEMmlHOGdPYm9DOHBSOHR2a3BBYVFmbDAzZzNxUzhXdWsvdVl5dkloYWRJWnh0d2IwSjAveGg2TTlQZ21ocDJiSTNmM0toWjhxUW9LNzBUbTNId3lhN1cyOFdCOXF0aGVzdDZqSHF3MDBWUW5JbW1jTkhmN0tLZklXZW1KN25NZnpNa2Z4bjBtT25GS0Y0U3V0a1dlTlFHM3EzU0F6QSszYzlSdzdrK28zMnNFS3pzeWJ6U3MrVGNCcjBENGhRNUQ0MzYyL2VDSzhuSmtKZTZHa0h1MHhsMFZUUVIzRnljb1h3cUNtNk1TR2FjU2NXSmlENHk4WkNXamV6OUpTeVlnODdjZjRPSG5CNC91dzlCaStuTEtsKzltaGdhakVaZld6RWZGbFJNdzAzZTNOajNRNHEwSVNNWDM3MHVkV2JjRVpjZjNVaTdxQ21XOUZ2QU5IZ3IvZHNGSStYVWo1RTZ1a0VpbGFCd3lFS203MWtIbTZzVXVrNE9iWEdZU3FWNVFYMlFtL0VxcjJSRjFXbldDaDY4L3E5ZFVWZUxta1gyd2lPWHdydHNRUGsxYXNYS09sSlhocUV5Tlk0R2RRNE5XOEFrZGdUb3RPdUwwcnZVVU5IcmU2WU9EYzFYTnV2ZWwwOXZ5NE1mQ0NjeHg0TzBlL0xlYTQ4eUsvN1BHOWxGYUtiem5TNTZlN01ON3JEdGIwT1VhOVh5Sm5lckFCSTU5ZDdEVmJES3lncHBDYjdGYTl3Y0xyQWVEWWIxMTlSeGYrblJjKzIyRGRXOHJXTTBHSFY5aXA3b0l1YjlpRS96Sk5vcDFhaXFrc25IaCsvbVFram1UTmVzQ3Y2WjN6ZEdUY243ZFlweitjQ0plVHFFbytvOGd5azYxRVpSazM3QzYremZnRDJ1T3lEQUZoSlRlZE5oNEJSNzFuMjd2K3VTUzExRVV2Ums5RWt4d2tQejVHWTZkNmlCOEh1SmVqOXdDc2NVRWNjUDJUeTF1L0tTT3lJdjdHWU9TckhaeG40SDcwcVNhSW5mbkNncUF6V2c4NVRPK3BQcHdMNzlIOW5PRnRxSUVReElmOTk2am5jZFI0d0pYcWRYUXBhWEM2dUNHZ0k0OStkTHFjZnZLV1J6b0lJVzhjUWNNL0lYU0tEdlBUSTBMWEJqSEJXeUFSOStKZkVuMXVMaHBHWktIdEVQQVcrSG8rZTlZdnRUT3MxTGpBdWVlakdLYkZOeHozT3JBN1o3RlRtaUxqQzgvUk9zMWU5RjIyakpiaFowYW9jWUZMcnQ0RE53ZmdLMVRqZFRvV3V4T0hPd3NnRG52SmtYS2hXZ1FPcGl2c1ZOVDFMakFNbFVoeEw0TithT0hveVkvemIySGxUNzdGWGowSG9WK1VSVnc5dkRpYSszVUpEVXVNUGNjMmFUWDhrZjN3LzBYbCtPZlQwTkNpQ1AwMTAraTVkcFlkRjJ5amErMTh6eW9jWUVkV29aQVdIb2J1WmRzZjZ5TTg3RTVLZkU0T204azRydUxVYkpqRlR4SHowRC9SRE1DcS9rLy91MDhQUTk5NFA4c2xPUmtJSEZZQXpqS3VaZkV5R1R6VTRqU1lyZ1BHby9tTTcrRmk3T1RyZERPYzZmR0JlWW9LeXJBelhWelVaV2ZBMGVmQURpM0NrV2pBZU5xM2x6WWVTelBSV0E3THdyQS93TUVJU0lJVVBNWlFnQUFBQUJKUlU1RXJrSmdnZz09Ii8+ICAgIDwvZz4gPC9zdmc+", Position = {20, 4}, Size = {120, 55} }) table.insert(graphics, --Iyo Logo { Type = "Svg", Image = "PHN2ZyAgICAgIHZlcnNpb249IjEuMSIgICAgICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciICAgICAgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiICAgICAgd2lkdGg9IjYwcHgiIGhlaWdodD0iMjdweCIgICAgICB2aWV3Qm94PSIwIDAgNjAgMjciIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiPiAgICA8Zz4gPGltYWdlIHdpZHRoPSI2MCIgaGVpZ2h0PSIyNyIgeGxpbms6aHJlZj0iZGF0YTppbWFnZS9wbmc7YmFzZTY0LCBpVkJPUncwS0dnb0FBQUFOU1VoRVVnQUFBRHdBQUFBYkNBWUFBQUFnVmV6OEFBQUFCR2RCVFVFQUFMR09mUHRSa3dBQUFDQmpTRkpOQUFDSER3QUFqQThBQVAxU0FBQ0JRQUFBZlhrQUFPbUxBQUE4NVFBQUdjeHpQSVYzQUFBS05XbERRMUJ6VWtkQ0lFbEZRell4T1RZMkxUSXVNUUFBU01lZGxuZFVWTmNXaDgrOWQzcWh6VERTR1hxVExqQ0E5QzRnSFFSUkdHWUdHTW9Bd3d4TmJJaW9RRVFSRVFGRmtLQ0FBYU9oU0t5SVlpRW9xR0FQU0JCUVlqQ0txS2hrUnRaS2ZIbDU3K1hsOThlOTM5cG43M1AzMlh1ZnRTNEFKRThmTGk4RmxnSWdtU2ZnQjNvNDAxZUZSOUN4L1FBR2VJQUJwZ0F3V2VtcHZrSHV3VUFrTHpjWGVycklDZnlMM2d3QlNQeStaZWpwVDZlRC8wL1NyRlMrQUFESVg4VG1iRTQ2UzhUNUlrN0tGS1NLN1RNaXBzWWtpaGxHaVprdlNsREVjbUtPVytTbG4zMFcyVkhNN0dRZVc4VGluRlBaeVd3eDk0aDRlNGFRSTJMRVI4UUZHVnhPcG9odmkxZ3pTWmpNRmZGYmNXd3loNWtPQUlva3RnczRySGdSbTRpWXhBOE9kQkh4Y2dCd3BMZ3ZPT1lMRm5DeUJPSkR1YVNrWnZPNWNmRUN1aTVMajI1cWJjMmdlM0l5a3pnQ2dhRS9rNVhJNUxQcExpbkpxVXhlTmdDTFovNHNHWEZ0NmFJaVc1cGFXMW9hbWhtWmZsR28vN3I0TnlYdTdTSzlDdmpjTTRqVzk0ZnRyL3hTNmdCZ3pJcHFzK3NQVzh4K0FEcTJBaUIzL3crYjVpRUFKRVY5YTcveHhYbG80bm1KRndoU2JZeU5Nek16amJnY2xwRzRvTC9yZnpyOERYM3hQU1B4ZHIrWGgrN0tpV1VLa3dSMGNkMVlLVWtwUWo0OVBaWEo0dEFOL3p6RS96andyL05ZR3NpSjVmQTVQRkZFcUdqS3VMdzRVYnQ1Yks2QW04S2pjM24vcVluL01PeFBXcHhya1NqMW53QTF5Z2hJM2FBQzVPYytnS0lRQVJKNVVOejEzL3ZtZ3c4RjRwc1hwanF4T1BlZkJmMzdybkNKK0pIT2pmc2M1eElZVEdjSitSbUxhK0pyQ2RDQUFDUUJGY2dERmFBQmRJRWhNQU5Xd0JZNEFqZXdBdmlCWUJBTzFnSVdpQWZKZ0E4eVFTN1lEQXBBRWRnRjlvSktVQVBxUVNOb0FTZEFCemdOTG9ETDREcTRDZTZBQjJBRWpJUG5ZQWE4QWZNUUJHRWhNa1NCNUNGVlNBc3lnTXdnQm1RUHVVRStVQ0FVRGtWRGNSQVBFa0s1MEJhb0NDcUZLcUZhcUJINkZqb0ZYWUN1UWdQUVBXZ1Vtb0oraGQ3RENFeUNxYkF5ckEwYnd3ellDZmFHZytFMWNCeWNCdWZBK2ZCT3VBS3VnNC9CN2ZBRitEcDhCeDZCbjhPekNFQ0lDQTFSUXd3UkJ1S0MrQ0VSU0N6Q1J6WWdoVWc1VW9lMElGMUlMM0lMR1VHbWtYY29ESXFDb3FNTVViWW9UMVFJaW9WS1EyMUFGYU1xVVVkUjdhZ2UxQzNVS0dvRzlRbE5SaXVoRGRBMmFDLzBLblFjT2hOZGdDNUhONkRiMEpmUWQ5RGo2RGNZRElhRzBjRllZVHd4NFpnRXpEcE1NZVlBcGhWekhqT0FHY1BNWXJGWWVhd0IxZzdyaDJWaUJkZ0M3SDdzTWV3NTdDQjJIUHNXUjhTcDRzeHc3cmdJSEErWGh5dkhOZUhPNGdaeEU3aDV2QlJlQzIrRDk4T3o4ZG40RW53OXZndC9BeitPbnlkSUUzUUlkb1JnUWdKaE02R0MwRUs0UkhoSWVFVWtFdFdKMXNRQUlwZTRpVmhCUEU2OFFod2x2aVBKa1BSSkxxUklrcEMwazNTRWRKNTBqL1NLVENacmt4M0pFV1FCZVNlNWtYeVIvSmo4Vm9JaVlTVGhKY0dXMkNoUkpkRXVNU2p4UWhJdnFTWHBKTGxXTWtleVhQS2s1QTNKYVNtOGxMYVVpeFJUYW9OVWxkUXBxV0dwV1dtS3RLbTBuM1N5ZExGMGsvUlY2VWtackl5MmpKc01XeVpmNXJETVJaa3hDa0xSb0xoUVdKUXRsSHJLSmNvNEZVUFZvWHBSRTZoRjFHK28vZFFaV1JuWlpiS2hzbG15VmJKblpFZG9DRTJiNWtWTG9wWFFUdENHYU8rWEtDOXhXc0pac21OSnk1TEJKWE55aW5LT2NoeTVRcmxXdVR0eTcrWHA4bTd5aWZLNzVUdmtIeW1nRlBRVkFoUXlGUTRxWEZLWVZxUXEyaXF5RkFzVlR5amVWNEtWOUpVQ2xkWXBIVmJxVTVwVlZsSDJVRTVWM3E5OFVYbGFoYWJpcUpLZ1VxWnlWbVZLbGFKcXI4cFZMVk05cC9xTUxrdDNvaWZSSytnOTlCazFKVFZQTmFGYXJWcS8ycnk2am5xSWVwNTZxL29qRFlJR1F5TldvMHlqVzJOR1UxWFRWek5YczFuenZoWmVpNkVWcjdWUHExZHJUbHRITzB4N20zYUg5cVNPbkk2WFRvNU9zODVEWGJLdWcyNmFicDN1YlQyTUhrTXZVZStBM2sxOVdOOUNQMTYvU3YrR0FXeGdhY0ExT0dBd3NCUzkxSG9wYjJuZDBtRkRrcUdUWVlaaHMrR29FYzNJeHlqUHFNUG9oYkdtY1lUeGJ1TmU0MDhtRmlaSkp2VW1EMHhsVEZlWTVwbDJtZjVxcG0vR01xc3l1MjFPTm5jMzMyamVhZjV5bWNFeXpyS0R5KzVhVUN4OExiWlpkRnQ4dExTeTVGdTJXRTVaYVZwRlcxVmJEVE9vREg5R01lT0tOZHJhMlhxajlXbnJkemFXTmdLYkV6YS8yQnJhSnRvMjJVNHUxMW5PV1Y2L2ZNeE8zWTVwVjJzM1lrKzNqN1kvWkQvaW9PYkFkS2h6ZU9LbzRjaDJiSENjY05KelNuQTY1dlRDMmNTWjc5em1QT2RpNDdMZTVid3I0dXJoV3VqYTd5YmpGdUpXNmZiWVhkMDl6cjNaZmNiRHdtT2R4M2xQdEtlMzUyN1BZUzlsTDVaWG85Zk1DcXNWNjFmMGVKTzhnN3dydlovNDZQdndmYnA4WWQ4VnZudDhINjdVV3NsYjJlRUgvTHo4OXZnOTh0ZnhUL1AvUGdBVDRCOVFGZkEwMERRd043QTNpQklVRmRRVTlDYllPYmdrK0VHSWJvZ3dwRHRVTWpReXRERjBMc3cxckRSc1pKWHhxdldycm9jcmhIUERPeU93RWFFUkRSR3pxOTFXNzEwOUhta1JXUkE1dEVablRkYWFxMnNWMWlhdFBSTWxHY1dNT2htTmpnNkxib3Ird1BSajFqRm5ZN3hpcW1ObVdDNnNmYXpuYkVkMkdYdUtZOGNwNVV6RTJzV1d4azdHMmNYdGladUtkNGd2ajUvbXVuQXJ1UzhUUEJOcUV1WVMvUktQSkM0a2hTVzFKdU9TbzVOUDhXUjRpYnllRkpXVXJKU0JWSVBVZ3RTUk5KdTB2V2t6Zkc5K1F6cVV2aWE5VTBBVi9VejFDWFdGVzRXakdmWVpWUmx2TTBNelQyWkpaL0d5K3JMMXMzZGtUK1M0NTN5OURyV090YTQ3VnkxM2MrN29lcWYxdFJ1Z0RURWJ1amRxYk16Zk9MN0pZOVBSellUTmladC95RFBKSzgxN3ZTVnNTMWUrY3Y2bS9MR3RIbHViQ3lRSytBWEQyMnkzMVd4SGJlZHU3OTlodm1QL2prK0Y3TUpyUlNaRjVVVWZpbG5GMTc0eS9hcmlxNFdkc1R2N1N5eExEdTdDN09MdEd0cnRzUHRvcVhScFR1bllIdDg5N1dYMHNzS3kxM3VqOWw0dFgxWmVzNCt3VDdodnBNS25vbk8vNXY1ZCt6OVV4bGZlcVhLdWFxMVdxdDVSUFhlQWZXRHdvT1BCbGhybG1xS2E5NGU0aCs3V2V0UzIxMm5YbFIvR0hNNDQvTFErdEw3M2E4YlhqUTBLRFVVTkg0L3dqb3djRFR6YTAyalYyTmlrMUZUU0REY0xtNmVPUlI2NytZM3JONTB0aGkyMXJiVFdvdVBndVBENHMyK2p2eDA2NFgyaSt5VGpaTXQzV3Q5VnQxSGFDdHVoOXV6Mm1ZNzRqcEhPOE02QlV5dE9kWGZaZHJWOWIvVDlrZE5xcDZ2T3lKNHBPVXM0bTM5MjRWek91ZG56cWVlbkw4UmRHT3VPNm41d2NkWEYyejBCUGYyWHZDOWR1ZXgrK1dLdlUrKzVLM1pYVGwrMXVYcnFHdU5heDNYTDYrMTlGbjF0UDFqODBOWnYyZDkrdytwRzUwM3JtMTBEeXdmT0Rqb01YcmpsZXV2eWJhL2IxKytzdkRNd0ZESjBkemh5ZU9RdSsrN2t2YVI3TCs5bjNKOS9zT2toK21IaEk2bEg1WStWSHRmOXFQZGo2NGpseUpsUjE5RytKMEZQSG95eHhwNy9sUDdUaC9IOHArU241Uk9xRTQyVFpwT25wOXluYmo1Yi9XejhlZXJ6K2VtQ242Vi9ybjZoKytLN1h4eC82WnRaTlRQK2t2OXk0ZGZpVi9LdmpyeGU5cnA3MW4vMjhadmtOL056aFcvbDN4NTl4M2pYK3o3cy9jUjg1Z2ZzaDRxUGVoKzdQbmwvZXJpUXZMRHdHL2VFOC9zNnVMNVRBQUFBQ1hCSVdYTUFBQTdFQUFBT3hBR1ZLdzRiQUFBSVAwbEVRVlJZUisyWkNWQlYxeG5ILzNkNVBONWpSellUUUNPeUtRZ1NLNHBValYzVVJJbTJOazBhcmFtV1ptaGoxSW5OWk9xUW1EU3R6aVN0aVFiSHBDWXh0WjNhWnBpeDQ0U0tZOVRHSWtiRktEU3hMSWFBQ0xMSXp0dnZ2ZjNPdllmd1hzR01JSm14Ylg1NEJ1NTN6ajMzZk9kODJ4bUZvOTh4YVJnSlRZV3pWMEhHbG4ySVc3S2VDLy83RWZudi94dStVdmgvbmE4VS9pSVV4d0QvNjh0RFV6MzhyNXZUOWE4emNIWmQ1MCtqWTFSUnV1UFNTUngvNUQ0RXhPdmR3eEJneHJ5aTh3aE5UT01TWDFwT0hVTFpreXRoalFaVXIvY0Zha3hQUVF6RndyZlBJekF1d2VqZ2FQVFRWMWVGaHZmZXhMWGpCNUM2N2xlSVgxNUE0L21BVVNDdFNaVzI4Yi8vQXcyS1UwTk1UaDVDcG1icEVtdk1aRWdXSjlySy9nRnpHSm1IVE0xa05Oa1BjUFlvc0RVMUlIYnBJNlFFVTJNSWo2MEhILzc4SVhxL0V5S05IWHlQTlpNL01OQU16TnQ5RkdFcG1md05nOTVQSzFHeDdTZTQ4T0ptcUlvREM5K3B3SVQwK1JCb2VvK3RGKzBWeDlCKzduMjBueTlCWDBNZG5OMHQ4QXVKaEdTMjhCbDhHVk1lTGwwUkJzOUFOMDFNcS9WNlc5TVVPRHBVNUw1eEFlRXBNN25Vb0tGa0w2cDJGc0FjTE5IVDBORUl0R21kbFc1TS92NXF6TjUyZ0VzTmFnNjhnTm8vYk1kQWd3UHh5eFpoenN0SFNFcmZKQnJlSzhLVmd6dGh2OTRBVFNIellOTXlxNUZrV0NQaUVQZkFZMGg4OUZsOXJEZGpVcmovYWkxTy9pZ0Zza1dGNUdjc1lCQ1B3MDI3REt3b0c1clczdGFFdnkyUGd5V1NMTUhzUFY2RHE4K0RvSHN5TVAvMWkxeG1jT0t4R2VpOFdBVTVBSWg3Y0EyeW52NDk3d0hLTmkxQlUya3BRcE9aRzlBMzdTUWtuVVVyNldzU3lSSlU5SDFHZlNSZThHWXBJcksrcmIvSEdGT1VEb3hMeExTQ25XVEM3TW5YbVUxV0dZb0wrc2tNOG5IUkV4RHA2N0xGeXlMb3Q2WjVhRjlscE9idjRFS0Q2djNQNDBaVkZRSW5zV0ZtSDJVcmQyMUFXM2twd3FmTDBOeUFyWVhXRXpzSDRaa1BRL05ZMGZNcHJZZm1EazN5Z3lXR05tZmpZbHd2SytadmoxRmh4cFR2UG9tSXpHV3d0U3ZHVm5JMFZhQU5JWE44ZXdjY25TM29yZjhFalNWL1JVQXNEV0o3TXpoVzFEQndEWWhkc2c2UnM1WndJZEJkZHc0MSs3Y2ptSlJsRzVxYS96VHZBVnk5cmFqZTk1bytsK3J5NkNjN2Y5OFo4djFqeUg3cFR4VHdMaU4zVndsc2JUUzIyd1cvUUpNZWE4Nzk0bEYwZm54V24yUE1DalBtN1RwTTZ4ZkpuMm1ydlpRV0JUcEp1WmRPZGpPcVhzbUhKWXFFR2pucklEVFcyZWxCZUhvcU1yZTh6b1VHOWUvdW9VRG1wTk1YeVp3RlJNejZGdThCMmo0OFJpWkw4NHNpN0oxQTlzNlRkTkxaa1B3RDlNOWJvdUlSazdzVTk1YzBRdzRLZ1l2V0pabE0xTy9FUDNmL2xDelBmbnNLTTJhOVdBeTN6UStxbTVUMndoUW9vL1gwbjlGVGV4cXl2NWV5aEVKK0R0bUtyTUpEWEdKZ2EybEV4NFVUdEVCNklEODBXY1BnSDNHMzBVa29UaHZZWGlwdWhlSkJBQ0l5RnZBZVg4eWhFNUgyc3plZ09wakZxZlFwRWQyWEs5QmRmZUgyRmI1ci9nckVMWDJZZ2c4OWVKMHlTMHN5K2JOc1ljb09kVkN5STFNSDBncGVRZENrSkM0MUdHaXBvWG11VVNBeXhndDBraUxMZlp5UTVDeTR5TXdsa3dCMzN3RGF6aHpsUGNPSnpsbUc0S256S1lncWxENGwybVRneHNYeTIxZVlNZk9aZCtBZk9ZM3lvdThwTTZWOThyR2d3WEZEeFYzZlhJZkpLL081Y0lodXFxQVVoMGRmSUV1MGJsc24rV01qNzZWQWxIZ3ZJbWVub0w5WmcxOFFVTDU1TVU2c25ZVFNsVkc0L0x0TmZKU0JaTGJTbXRMSTEybVRLWWo1aHdPdDVXK05qOEtNdWIvNUN3YWFLQzRwRk1SdWdzZnVnVGtrRmxsYjkzQ0pMNW9xc2ozUkRVS1FLRGIwcStpNThwSFJ5Y25kWFVacFppN3M3V3c4cU9Cb3BGUHZ4OVFmL0pLUE1OQW9oTHY2R2lDeS9FeHpTdjRpYk0zVjQ2ZHdZT3gwSksxZHJ4Y2VyQlFjaHFqU0I0SE1aNG9vRjV1NTBCZlZZNlFVQmpObkZ0VzdLMDhiQW81ZmNEaHlmbnNDTXd2M1k4cERUeUY5Y3lFV0hxaUFLWUNPM0l1K2hrYjAxbnpBUWdXSFdabzZmZ296cG0vWVExSHpYZ29Xd3k4QXJpNEZNUXUraHVpNWVWd3lIR3QwRkFRcU96V0Z0S1ovbG9reXFuWWRSRTlkSlI5aElKck1pRis4RmhsUHZZelVINytBb0x0VGVjOFExVzlSS1VxQlJaQ05Rc2ZkcjFBTXVHOThGWGIzZDFHUW9Lamk1YmFmUXpMRjRlUVBJeE9TTUp0TzFrb1hDOE10TkxlQTZHemc3Tlk4dWgxMTZMSmI0Y3E3cjZMMTFHR1lna2s5WmpIMGJSWlVRNUxHV1dHRGtiUWxTQ3dJclBLNE9jR0pNekFoWXhFVVZpcHlKRXBwN3A0R3ZMOTZKbHJLRG5QcHlDaXVBWHkwdlFCVnIyNmlPcDhxQklrNU1MTVlWYS9aSTdNWGZSa0tqeDJCQ3VQVXh3dkpTc3dVL0hqRTF3VHlUNWtXMzRUVEcvSndmUFVVS2lLZVFQT3BZc3J4bDlCKzZRUFVIZncxemo2M0NvY1hCdUxxa2IwSWlKWW82QTJsTTQ5ZFFYaGFOc0tUWjkxWkNqTkNwczZtQzBzUmV1cFp4QjlNYzRKZU1RVlBrZUhzcUVkOWNSSE9GNjdDMy9NelViNXhBUzd2M1VvbVhBeHJsRUFGQ2ZOWkw3VW9XcnY3Z2NRZlBxdjcvaDJuTUdOeTNuck0yTGdOL1pUbUZDZFRtb2R1T20wNXdFVFZGN1Z3aVpwb3RBa3kvTU5NeHFueW9ReU5OcXlybXVyeHgxL0N4Sno3ZGRrZHFUQWpaZDF6eUgzdEVGV1k5OERSVFJjRktrZFZkdS9sSVlLWnZ5Q1E2Vkw3WEtoclE3V2NpM0p3cnh2MkcyWms3OWlQcERWYjlHNkdxRkVaY3JQR2lnQ3ZEUnNWUG5PeFdkaEVvNXdzZXM2RGVLQ2tCZ25mZXg3KzBjbDBTZEhvTHU2R3JkVk5mdW5XTC82c1ZsWTl6R3pkZFB0aXpVTW5IWXFvbkZYNHhoOHZZdEx5dFh3MkErSElDbW5rWmRCQ1dhV1R2bVVmNG0veGZ4NGNuYTFVN24wZDlvNWFTSDVHaEJ6RTNhY2dPQ0dkTHZxK09mVldZU25QMFhZVkhaZktxYWo0QkwyMXgrazduMEZ6MlNCWmdtR2RtSUxRYWNzUW1wQ01rT2x6WUkyTTVXOTZBL3diY1BNNHFnQVNGWE1BQUFBQVNVVk9SSzVDWUlJPSIvPiAgICA8L2c+IDwvc3ZnPg==", Position = {500, 20}, Size = {60, 27} }) table.insert(graphics, --Dante Logo { Type = "Svg", Image = "PHN2ZyAgICAgIHZlcnNpb249IjEuMSIgICAgICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciICAgICAgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiICAgICAgd2lkdGg9Ijc2cHgiIGhlaWdodD0iMThweCIgICAgICB2aWV3Qm94PSIwIDAgNzYgMTgiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiPiAgICA8Zz4gPGltYWdlIHdpZHRoPSI3NiIgaGVpZ2h0PSIxOCIgeGxpbms6aHJlZj0iZGF0YTppbWFnZS9wbmc7YmFzZTY0LCBpVkJPUncwS0dnb0FBQUFOU1VoRVVnQUFBRXdBQUFBU0NBWUFBQUR4RXppc0FBQUFBWE5TUjBJQXJzNGM2UUFBQUFSblFVMUJBQUN4and2OFlRVUFBQUFKY0VoWmN3QUFEc1FBQUE3RUFaVXJEaHNBQUFiN1NVUkJWRmhIMVZnSmJFMWJGTjN2OVhYUVFhdGY2VUJOYlJOampWRVJmbUlJTWNkTXBBaGlUTkJHZm1JSUVwV28xQmhLUkVsRWtDQWlwaUJCRUxNbXFDbG1xVkl0V25OVno5OXI5NXo3N3F2WFZQOHZpWldjM0gzVzJlL2NlL2ZkMDNrT3hhQXE4T1RKRTNyMDZCRWRQSGlRVHA4K0xmT2lvaUxhc1dNSGpSczNUblErZi81TXZYcjFvcXRYcjFKd2NEQmhXNGZEUVpHUmtaU1ltRWdEQmd5Z1RwMDZVWHg4dk9qL3NZREJLc09KRXlmVXhJa1RZVkN2SXlzclMyc3E5ZkhqUjlXMmJWdXZldll4Y3VSSXhjYlh2L3J6VUtuQit2YnQ2L0dpb2FHaGF1WEtsZXJXclZzeWJ0eTRvWXFMaTdWMnVjSGF0Mjh2dXJWcTFWSlRwa3hSQXdjT1ZQWHIxL2ZZQjhQWDExZmw1dWJxWC80ZW5EdDNUZzBmUGx6R3k1Y3ZOVnQ5L0dDdzE2OWZxOERBUU92bGtwS1M5SW9uUG4zNnBNckt5dlRNMDJBeE1UR2FkZVBhdFd1cWVmUG0xcjRZSE5wNjlkZGorL2J0MW4zeHNhc0R2T3Y5Ky9kRmR2SUdGdkx6ODRuRGlsaUIyS1BvNU1tVGRPSENCVmw3OWVvVnNZZlIzTGx6cVgvLy90U3hZMGM2Y3VTSXJGWEU5Ky9mdGVRR0c1TnUzNzVOZ3djUDFneForZTkzd00vUFQwdEU3T0ZhK2pta3BxYlMyclZyYWVmT25XeHVHeEJLdkM2RGs3cG1sUm8xYXBTcVc3ZXV0V2JHMXExYnRZYW5oM0dpMTZ4M2NQSzM5amgyN0pobWZ5MXdIM05QL3ZpYXJScHNLTFYzNzE3Vm8wY1BOV2ZPSExlSGRlalFRU3BkUkVRRThjdFQ3ZHExNmZyMTYrUnl1V2pQbmozMDVzMGJpb3VMRXlzUEd6Wk1mbVAvYXRYQi9Qbnp0VVIwOCtaTkxSRzlmLzllS20rVEprMmt3clp1M1pvT0h6NHNGWG5Ka2lXVWtwSkNaOCtlRmQwclY2Nkl0Mk92dkx3OFdyNTh1VHd6bm9sekoxMjhlRkgwQURZV3JWNjlXcytJbGkxYlJ2UG16YU8zYjkvS0hGVi85dXpaRkJJU0l1L0xxWVAyNzk4dmtUSjA2RkE2Zi80OFRaMDZsWnhPTmhlc2VPREFBYkU4UDZUaUJ4TEw4ZzFWVUZDUThOMjZkVk9uVHAwU0h1QWZDODh2cDVucWVSZ0FQUXdPVVptL2VQRkN0V2pSd3VMdEExL1h5R3dZMGQrNGNhUEYxYWxUeDVMdGcxc2MwUjB6Wm96WGRTUi9GREp2YXhqc1JQTDdvMGVQcWsyYk5rblVpY0ZRT2FBd2FOQWdVUUJnUEhCc1ljMjRNWDc4ZUZtckNZTjE3dHhaNXR6RFdSekcyTEZqNWQ1MkRtUFZxbFdpajNSZzV4czFhcVJtelpxbG1qVnJabkY5K3ZRUjNYNzkrbm5vbXNIZTdUSEgreTljdUZENStQaFkzSkFoUTJRUEE3cDM3NTYxYU5DbFN4ZVo5K3paVXpPZW1EQmhncXpYaE1HNGdLam56NTliY3c1RHJWRU9WS2lFaEFScjNadkJKazJhSkp6QmlCRWpoRy9Zc0tGNjhPQ0JjSWdZbzE5YVdpcGM0OGFOWlE3bktDZ29FTTZnZCsvZWxyNGRUaTZYekJHMWF0Vktyc2dwZCs3Y0liWXlMVjY4V0xpYUJqK2Nsb2k2ZCs4dU9jWGcrUEhqV2lvSEZ5SmF0R2lSbm5rSGU0R1d5bEd2WGoyNUloZWJQUFh3NFVPNUFqaTE4THZUbHk5ZlpNNnBnRFpzMkVCTGx5NlZYSm1XbGliNTA0QmJFaTBST1hIY0FaQkFBU1RJZCsvZVNZTG4zQ1ZjUmVCbS93ZnIxNi9YRXNseDZjeVpNeUxqU0lXalZFV01IajJhQWdJQzlPeEhORzNhVkV2bHNMYzEzQ3ZLVlJLMkJ0b0tPTVdIRHg5a25wT1RJODRCWThGb0hKYkVuaXhyQUlxZmdmUHk1Y3NpNE1HQng0OGZ5eFVlaCtySXlaVXlNek5sckZtelJ2cXlzTEF3MGZrdmVQYnNHVzNidGsxa2JsWGtuSW5xREZUV0g2RnkyVis0SXFyYlZ3SGZ2bjJ6akJrYkd5dnZqMmN4QTMxajE2NWRxV1hMbHBiSEFxNm5UNStLZ0EwQU05KzNiNStNaWtDWk5lRmJVbElpMTU5RmRuWTI4WkdKT0dmSkhJMXJlSGk0UEJ6dWkvRDUrdlVyK2Z2N3k3ckJvVU9IcEptdUROWDFlSGdnL2dTQVIyTmY3ak1wUFQxZHI3ckIvUmR4UWRTemNqampFeElvaFBzWDQvTG93eUtqb2doOEhHOXFCdVlSYk9sRVBna0VjNzhDNEdXOW9XSi9oajRHdlJIeUZSK1JoSVBIbU41b3dZSUZjZ1U0MldySmpYWHIxbW1wWnNCSm4vajRaMzF3bkdCd2tyRURxWW1MaDBUVDVzMmJOY3RBNXE4SjJLdGtWWU05VlAvS0RWUkhzODROcU5xeVpZdDAyZndoUFg3cnJVcWkwdHN4YmRvMDRmbURxa3VYTGdtM2E5Y3VTLy91M2J2QzRYeG9PQXcrQXFuZHUzZi8wTTRVRmhhS1B1QXFURTZoMHZ3OENzOU1KMWRzREJXbFpWQkpkZzQ1L0NwUHN0eUdVMWx4QVlYK2swb0JmeWNKQlRjdkxpNFd1VEtnaUV5ZVBKbVNrNU0xNHdhK0tLb1Z1bnJzZzlBMTRON0txbklJV2NDZURpcW1CcU9EazROWk14eGdaSVFsT241NEdKQ1JrU0ZYQTRSc1ZsYVdSeVM1ZkdJaVNmazR5T25uU3c0ZkovbUUvVVd1cUdoeStGZGhzQUIvY2dRRmFvSWtwR2ZPbkVtNXVia2VJUm5GNFkwRFBSSW5ONWMvNUNlRDZPaG9LUWdvN3lnd3lIUFFSN2dpTkZhc1dDR0dURW9xLzBEdDJyV2pHVE5tU0VIQVBlemdSbFdPT2ZnRGdYc3Q0ZHEwYVVQVHAwK1hJeGZ1WllEY3hYMmxISzdaQzZWeU5talFRTG9HOEh5SzBKb0EwYi8yQnFxRmNjVFdrd0FBQUFCSlJVNUVya0pnZ2c9PSIvPiAgICA8L2c+IDwvc3ZnPg==", Position = {595, 30}, Size = {57, 14} }) -- Device controls (Left column) -- 'Select a Device' grouping -- Groupbox table.insert(graphics, { Type = "GroupBox", Text = "Select a Device", StrokeWidth = 1, Position = {2, 72}, Size = {208, 70}, Fill = {255, 255, 255}, CornerRadius = 3, HTextAlign = "Center", }) -- Device selection combobox layout["SelectedDevice"] = { Style = "ComboBox", PrettyName = "Select a device", Position = {10, 92}, Size = {192, 20}, Fill = {255, 255, 255}, TextSize = 10, } -- Selected device connection status text layout["SelectedDeviceStatus"] = { Style = "Text", PrettyName = "Selected device connection status", Position = {10, 117}, Size = {192, 20}, Color = {255, 255, 255}, IsReadOnly = true, TextSize = 10, HTextAlign = "Center", StrokeWidth = 0, } -- 'Device' grouping -- Groupbox table.insert(graphics, { Type = "GroupBox", Text = "Device", StrokeWidth = 1, Position = {2, 150}, Size = {208, 100}, Fill = {255, 255, 255}, CornerRadius = 3, HTextAlign = "Center", }) for k, v in ipairs(device_info_labels) do -- Parameter label table.insert(graphics, { Type = "Label", Text = v, Position = {26, 151 + (15 * k)}, Size = {63, 15}, TextSize = 10, Color = {0, 0, 0}, HTextAlign = "Right", }) -- Value text layout["DeviceInfo "..k] = { Style = "Text", PrettyName = "Device~Info~"..v, Position = {98, 151 + (15 * k)}, Size = {85, 15}, TextSize = 10, Color = {255, 255, 255}, IsReadOnly = true, HTextAlign = "Right", StrokeWidth = 0, } end -- 'Firmware' grouping -- Groupbox table.insert(graphics, { Type = "GroupBox", Text = "Firmware", StrokeWidth = 1, Position = {2, 259}, Size = {208, 60}, Fill = {255, 255, 255}, CornerRadius = 3, HTextAlign = "Center", }) for k, v in ipairs(fw_vendors) do -- Parameter label table.insert(graphics, { Type = "Label", Text = v, Position = {6, 265 + (15 * k)}, Size = {63, 15}, TextSize = 10, Color = {0, 0, 0}, HTextAlign = "Right", }) -- Value text layout["FirmwareInfo "..k] = { Style = "Text", PrettyName = "Device~Firmware~"..v, Position = {78, 265 + (15 * k)}, Size = {125, 15}, TextSize = 10, Color = {255, 255, 255}, IsReadOnly = true, HTextAlign = "Right", StrokeWidth = 0, } end -- 'Status' grouping -- Groupbox table.insert(graphics, { Type = "GroupBox", Text = "Status", StrokeWidth = 1, Position = {2, 329}, Size = {208, 100}, Fill = {255, 255, 255}, CornerRadius = 3, HTextAlign = "Center", }) for k, v in ipairs(status_types) do -- Parameter label table.insert(graphics, { Type = "Label", Text = v, Position = {6, 343 + (15 * k)}, Size = {63, 15}, TextSize = 10, Color = {0, 0, 0}, HTextAlign = "Right", }) -- LEDs layout["StatusLEDs "..k] = { Style = "LED", PrettyName = "Device~Status~"..v, Position = {80, 343 + (15 * k)}, Size = {16, 16}, CornerRadius = 8, Color = {99, 99, 99}, OffColor = {99, 99, 99}, UnlinkOffColor = true, } -- Values text layout["StatusInfo "..k] = { Style = "Text", PrettyName = "Device~Status~"..v, Position = {98, 343 + (15 * k)}, Size = {65, 15}, TextSize = 10, Color = {255, 255, 255}, IsReadOnly = true, HTextAlign = "Right", StrokeWidth = 0, } end -- Identify button layout["Identify"] = { PrettyName = "Device~Status~Identify", Style = "Button", ButtonStyle = "Momentary", Legend = "Identify", Position = {79, 394}, Color = {220, 220, 220}, OffColor = {220, 220, 220}, UnlinkOffColor = true, Size = {55, 25}, TextFontSize = 10, } -- 'Settings' grouping -- Groupbox table.insert(graphics, { Type = "GroupBox", Text = "Settings", StrokeWidth = 1, Position = {2, 438}, Size = {208, 60}, Fill = {255, 255, 255}, CornerRadius = 3, HTextAlign = "Center", }) -- 'LED Brightness' label table.insert(graphics, { Type = "Label", Text = "LED Brightness", Position = {6, 468}, Size = {80, 15}, TextSize = 10, Color = {0, 0, 0}, HTextAlign = "Right", }) -- 'LED Brightness' combobox layout["LED_Brightness"] = { Style = "ComboBox", PrettyName = "Device~Settings~LED Brightness", Position = {105, 466}, Size = {85, 20}, Fill = {255, 255, 255}, TextSize = 10, } -- 'Input/Transmit AES67 Details' grouping -- Groupbox table.insert(graphics, { Type = "GroupBox", Text = "Input/Transmit AES67 Details", StrokeWidth = 1, Position = {2, 508}, Size = {208, 80}, Fill = {255, 255, 255}, CornerRadius = 3, HTextAlign = "Center", }) -- Parameter labels for k, v in pairs(aes67_tx_labels) do table.insert(graphics, { Type = "Label", Text = v, Position = {30 + 80 * (k - 1), 523}, Size = {70, 15}, TextSize = 10, Color = {0, 0, 0}, HTextAlign = "Center", }) end -- AES67 TX details listbox layout["TXAES67Details"] = { Style = "ListBox", PrettyName = "", StrokeWidth = 0, Position = {5, 536}, Size = {201, 49}, Fill = {255, 255, 255}, TextSize = 10, } -- 'Receive/Output AES67 Details' grouping -- Groupbox table.insert(graphics, { Type = "GroupBox", Text = "Receive/Output AES67 Details", StrokeWidth = 1, Position = {2, 598}, Size = {208, 80}, Fill = {255, 255, 255}, CornerRadius = 3, HTextAlign = "Center", }) -- Parameter Labels for k, v in pairs(aes67_rx_labels) do table.insert(graphics, { Type = "Label", Text = v, Position = {70 * (k - 1), 613}, Size = {70, 15}, TextSize = 10, Color = {0, 0, 0}, HTextAlign = "Center", }) end -- AES67 RX details listbox layout["RXAES67Details"] = { Style = "ListBox", PrettyName = "", StrokeWidth = 0, Position = {5, 627}, Size = {201, 49}, Fill = {255, 255, 255}, TextSize = 10, } -- Channel Strip controls -- Lay out 2 8-Channel banks per page -- Determine which channels to display based on the model type and the currently selected page, e.g.: -- 8.8M -> Page 1: Bank 1 (Mic/Line 1-8), Bank 2 (Line 1-8) -- 16.0M -> Page 1: Bank 1 (Mic/Line 1-8), Bank 2 (Mic/Line 9-16) -- 0.32L -> Page 1: Bank 1 (Line 1-8), Bank 2 (Line 9-16) Page 2: Bank 1 (Line 17-24), Bank 2 (Line 25-32) for i = 1, 2 do local start_ch, end_ch = 0 local ctrl_prefix = nil local vert_offset = (i == 2) and 300 or 0 local current_page_idx = props["page_index"].Value if model_type == '8.8M' or model_type == '16.16M' or model_type == '32.32M' then start_ch = ((current_page_idx - 1) * 8) + 1 end_ch = start_ch + 7 ctrl_prefix = (i == 2) and 'RX' or 'TX' elseif model_type == '16.0M' or model_type == '0.16L' then start_ch = ((i - 1) * 8) + 1 end_ch = start_ch + 7 ctrl_prefix = (model_type == '16.0M') and 'TX' or 'RX' elseif model_type == '32.0M' or model_type == '0.32L' then start_ch = ((current_page_idx - 1) * 16) + 1 + ((i - 1) * 8) end_ch = start_ch + 7 ctrl_prefix = (model_type == '32.0M') and 'TX' or 'RX' end -- Labels -- Channel bank label table.insert(graphics, { Type = "GroupBox", Text = (ctrl_prefix == 'TX') and "Mic/Line In" or "Line Out", StrokeWidth = 1, Position = {220, 101 + vert_offset}, Size = {458, 252}, Fill = {255, 255, 255}, CornerRadius = 5, HTextAlign = "Left", }) -- Peak Meter label table.insert(graphics, { Type = "Label", Text = "Meter Peak", Position = {227, 143 + vert_offset}, Size = {83, 15}, Color = {0, 0, 0}, HTextAlign = "Right", }) -- Gain in dB on Input/Transmit side only if ctrl_prefix == 'TX' then -- TX Gain Label table.insert(graphics, { Type = "Label", Text = "Gain", Position = {227, 183 + vert_offset}, Size = {83, 15}, Color = {0, 0, 0}, HTextAlign = "Right", }) end -- Level Label table.insert(graphics, { Type = "Label", Text = "Level", Position = {227, 235 + vert_offset}, Size = {83, 15}, Color = {0, 0, 0}, HTextAlign = "Right", }) -- Phantom power only on Input/Transmit if ctrl_prefix == 'TX' then -- Phantom power label table.insert(graphics, { Type = "Label", Text = "Phantom Power", Position = {227, 260 + vert_offset}, Size = {83, 25}, Color = {0, 0, 0}, HTextAlign = "Right", }) end -- AES67 Status Label table.insert(graphics, { Type = "Label", Text = "AES67 Status", Position = {227, 295 + vert_offset}, Size = {83, 15}, Color = {0, 0, 0}, HTextAlign = "Right", }) -- AES67 Streaming Label table.insert(graphics, { Type = "Label", Text = "AES67", Position = {227, 315 + vert_offset}, Size = {83, 25}, Color = {0, 0, 0}, HTextAlign = "Right", }) -- iterate through the visible channels for the bank for idx = start_ch, end_ch do local horz_offset = 43 * ((idx - 1) % 8) -- Channel strip background table.insert(graphics, { Type = "GroupBox", Position = {320 + horz_offset, 118 + vert_offset}, Size = {40, 230}, Fill = {240, 240, 240}, Color = {101, 101, 81}, CornerRadius = 4, TextSize = 14, }) -- Channel name layout[ctrl_prefix.."ChannelName "..idx] = { Style = "Text", PrettyName = "", Position = {323 + horz_offset, 123 + vert_offset}, Size = {35, 13}, Color = {240, 240, 240}, IsReadOnly = true, WordWrap = true, TextSize = 10, HTextAlign = "Center", StrokeWidth = 0, } -- Channel peak meter (dbFS) text local dBFS_pn = '' if ctrl_prefix == 'TX' then dBFS_pn = "Input/Transmit~Peak Meter (dBFS)~"..idx else dBFS_pn = "Receive/Output~Peak Meter (dBFS)~"..idx end layout[ctrl_prefix.."ChanneldBFS "..idx] = { Style = "Text", PrettyName = dBFS_pn, Position = {321 + horz_offset, 143 + vert_offset}, Size = {38, 15}, Color = {224, 224, 224}, IsReadOnly = true, TextSize = 8, HTextAlign = "Center", CornerRadius = 2, StrokeWidth = 0, } -- Channel bar meter layout[ctrl_prefix.."ChannelMeter "..idx] = { Style = "Meter", MeterStyle = "Level", PrettyName = "Input/Transmit~Peak Meter (dBFS) "..idx, Position = {322 + horz_offset, 164 + vert_offset}, Size = {36, 5}, CornerRadius = 0, Color = {99, 99, 99}, OffColor = {99, 99, 99}, UnlinkOffColor = true, } -- Channel gain (dB) (only on Input/Transmit) if ctrl_prefix == 'TX' then layout[ctrl_prefix.."ChannelGain "..idx] = { Style = "Text", PrettyName = "Input/Transmit~Gain (dB) "..idx, Position = {321 + horz_offset, 183 + vert_offset}, Size = {38, 15}, Color = {224, 224, 224}, IsReadOnly = true, TextSize = 10, HTextAlign = "Center", CornerRadius = 1, StrokeWidth = 0, } end -- Channel Gain/Level Knob (Gain (dB) adjuster for Input/Transmit, Level (dBu) adjuster for Receive/Output) local gain_knob_pn = '' if ctrl_prefix == 'TX' then gain_knob_pn = "Input/Transmit~Gain/Level~Gain (dB)~ "..idx else gain_knob_pn = "Receive/Output~Level (dBu)~ "..idx end layout[ctrl_prefix.."ChannelLevelKnob "..idx] = { Style = "Knob", PrettyName = gain_knob_pn, Position = {325 + horz_offset, 203 + vert_offset}, Size = {30, 30}, Color = {190, 83, 28}, ShowTextBox = false, TextSize = 10, } -- Level (dBu) text local level_pn = '' if ctrl_prefix == 'TX' then level_pn = "Input/Transmit~Gain/Level~Level (dBu)~ "..idx else level_pn = "Receive/Output~Level (dBu)~ "..idx end layout[ctrl_prefix.."ChannelLevel "..idx] = { Style = "Text", PrettyName = level_pn, Position = {321 + horz_offset, 235 + vert_offset}, Size = {38, 15}, Color = {224, 224, 224}, IsReadOnly = true, TextSize = 10, HTextAlign = "Center", CornerRadius = 1, StrokeWidth = 0, } -- Phantom power button (Input/Transmit only) if ctrl_prefix == 'TX' then layout["TXChannelPhantom "..idx] = { Style = "Button", Legend = "48V", PrettyName = "Input/Transmit~Phantom Power~ "..idx, Position = {322 + horz_offset, 260 + vert_offset}, Color = {255, 0, 0}, OffColor = {220, 220, 220}, UnlinkOffColor = true, Size = {35, 25}, TextFontSize = 10, } end -- AES67 Status local aes67_pn = '' if ctrl_prefix == 'TX' then aes67_pn = "Input/Transmit~AES67 Status~ "..idx else aes67_pn = "Receive/Output~AES67 Status~ "..idx end layout[ctrl_prefix.."ChannelAES67Status "..idx] = { Style = "Text", PrettyName = aes67_pn, Position = {321 + horz_offset, 295 + vert_offset}, Size = {38, 15}, Color = {224, 224, 224}, IsReadOnly = true, TextSize = 10, HTextAlign = "Center", CornerRadius = 1, StrokeWidth = 0, } if ctrl_prefix == 'TX' then -- AES67 toggle button (Input/Transmit only) layout["TXChannelAES67Tgl "..idx] = { PrettyName = "Input/Transmit~AES67 State~ "..idx, Style = "Button", Legend = "Off", Position = {322 + horz_offset, 315 + vert_offset}, Color = {0, 0, 255}, OffColor = {220, 220, 220}, UnlinkOffColor = true, Size = {35, 25}, TextFontSize = 10, } else -- AES67 transmitter selection combobox (Receive/Output only) layout["SelectedAES67TX "..idx] = { PrettyName = "Receive/Output~Select AES67 Transmitter~ "..idx, Style = "ComboBox", Position = {321 + horz_offset, 320 + vert_offset}, Size = {38, 15}, Fill = {255, 255, 255}, TextSize = 10, } end end end return layout, graphics end -- Start of Lua runtime code if Controls then rapidjson = require("rapidjson") e = System.IsEmulating -- Device selection control aliases cb_Dev = Controls.SelectedDevice text_DevStatus = Controls.SelectedDeviceStatus text_DevStatus.String = '' -- Device control aliases text_DeviceInfo = Controls.DeviceInfo text_FirmwareInfo = Controls.FirmwareInfo text_StatusInfo = Controls.StatusInfo led_StatusLEDs = Controls.StatusLEDs btn_Identify = Controls.Identify cb_LED_Brightness = Controls.LED_Brightness -- Input / Transmit control aliases text_TXNameLabel = Controls.TXChannelName text_TXChanneldBFS = Controls.TXChanneldBFS meter_TXChannelMeter = Controls.TXChannelMeter knob_TXChannelGain = Controls.TXChannelLevelKnob text_TXChannelGain = Controls.TXChannelGain text_TXChannelLevel = Controls.TXChannelLevel btn_Phantom = Controls.TXChannelPhantom text_TXChannelAES67Status = Controls.TXChannelAES67Status btn_TXChannelAES67Tgl = Controls.TXChannelAES67Tgl -- Receive / Output control aliases text_RXNameLabel = Controls.RXChannelName knob_RXChannelLevel = Controls.RXChannelLevelKnob text_RXChannelLevel = Controls.RXChannelLevel text_RXChanneldBFS = Controls.RXChanneldBFS meter_RXChannelMeter = Controls.RXChannelMeter text_RXChannelAES67Status = Controls.RXChannelAES67Status cb_SelectedAES67TX = Controls.SelectedAES67TX -- Globals DiscoveredIPLookup = {} SelectedDevIP = '' WASPControls = {} QSYSCtrlIdxToWASPCtrl = {} DiscAES67TXFlows = {} QSysCtrlIdxToChIdx = {} DiscAES67TXChannels = {} AES67IPToSessionName = {} AES67SessionNameToIP = {} Timers = {} OutputNames = {} InputCtrls = { text_TXChanneldBFS, meter_TXChannelMeter, knob_TXChannelGain, text_TXChannelGain, text_TXChannelLevel, btn_Phantom, text_TXChannelAES67Status, btn_TXChannelAES67Tgl } OutputCtrls = { knob_RXChannelLevel, text_RXChannelLevel, text_RXChanneldBFS, meter_RXChannelMeter, text_RXChannelAES67Status, cb_SelectedAES67TX } -- Colors RED = '#ff0000' ORANGE = '#ff8c00' GRAY = '#e0e0e0' ALT_GRAY = '#dcdcdc' GREEN = '#00ff00' BLUE = '#0000ff' QSYS_GREEN = '#007c00' -- Enable/Disable all controls function enable_all_ctrls(do_enable) for k, v in ipairs(InputCtrls) do for key, val in ipairs(v) do val.IsDisabled = not do_enable end end for k, v in ipairs(OutputCtrls) do for key, val in ipairs(v) do val.IsDisabled = not do_enable end end btn_Identify.IsDisabled = not do_enable cb_LED_Brightness.IsDisabled = not do_enable end -- Helper functions function split(s, delimiter) result = {} for match in (s..delimiter):gmatch("(.-)"..delimiter) do table.insert(result, match); end return result; end function path_to_ch_idx(s) local path_split = split(s, '/') return path_split[#path_split] end function display_error(str) text_DevStatus.String = str text_DevStatus.Color = RED end -- Event Handlers -- Identify btn_Identify.EventHandler = function() btn_Identify.IsDisabled = true HttpClient.Upload { Url = "http://"..SelectedDevIP.."/asi/r1/status/identify", Method = "PUT", Timeout = 10, Headers = { ["Content-Type"] = "application/json", ["X-HTTP-Method-Override"] = "PATCH" }, Data = "", EventHandler = function(tbl, code, d, e) if code ~= 200 then display_error('Identify device request error') end btn_Identify.IsDisabled = false end } end -- Front panel brightness cb_LED_Brightness.Choices = { '100%', '75%', '50%', '25%' } cb_LED_Brightness.EventHandler = function(ctrl) local brightness_pct = tonumber(cb_LED_Brightness.String:sub(1, -2)) -- remove '%' HttpClient.Upload { Url = "http://"..SelectedDevIP.."/asi/r1/"..QSYSCtrlIdxToWASPCtrl[ctrl.Index]['_self'], Method = "PUT", Timeout = 10, Data = rapidjson.encode({brightness = brightness_pct}), Headers = { ["Content-Type"] = "application/json", ["X-HTTP-Method-Override"] = "PATCH" }, EventHandler = function(tbl, code, d, e) if code ~= 202 then display_error('PATCH LED Brightness error') end end } end -- Event handler for timers associated with gain/level knobs -- Gain/Level changes are submitted after 500ms of inactivity function OnLevelChangeTimeout(timer) for k, v in pairs(Timers) do if v == timer then -- stop the timer v:Stop() -- look up the control local level_dBu = nil local ch_idx = QSysCtrlIdxToChIdx[k] local ctrl = QSYSCtrlIdxToWASPCtrl[k] if string.find(ctrl['loc'][1], "io/analog/in") then level_dBu = 24 - knob_TXChannelGain[ch_idx].Value else level_dBu = knob_RXChannelLevel[ch_idx].Value end -- submit the level change HttpClient.Upload { Url = "http://"..SelectedDevIP.."/asi/r1"..ctrl['_self'], Method = "PUT", Timeout = 10, Data = rapidjson.encode({level = level_dBu}), Headers = { ["Content-Type"] = "application/json", ["X-HTTP-Method-Override"] = "PATCH" }, EventHandler = function(tbl, code, d, e) if code ~= 202 then display_error('PATCH level error') end end } end end end -- Gain/Level knobs for k, v in ipairs(knob_TXChannelGain) do -- Create timer Timers[v.Index] = Timer.New() local new_timer = Timers[v.Index] new_timer.EventHandler = OnLevelChangeTimeout v.EventHandler = function(ctrl) -- display Gain (dB) and Level (dBu) text_TXChannelGain[k].String = ctrl.String.." dB" local level_dBu = 24 - tonumber(ctrl.String) text_TXChannelLevel[k].String = tostring(level_dBu).." dBu" -- disable phantom power btn if level > 10dBu (14dB) btn_Phantom[k].IsDisabled = (ctrl.Value < 14) new_timer:Stop() new_timer:Start(0.5) -- 500ms timeout end end for k, v in ipairs(knob_RXChannelLevel) do -- Create timer Timers[v.Index] = Timer.New() local new_timer = Timers[v.Index] new_timer.EventHandler = OnLevelChangeTimeout v.EventHandler = function(ctrl) -- display Level (dBu) text_RXChannelLevel[k].String = ctrl.String.." dBu" new_timer:Stop() new_timer:Start(0.5) -- 500ms timeout end end -- Phantom power for k,v in ipairs(btn_Phantom) do v.EventHandler = function(ctrl) -- get the Phantom ctrl channel index, WASP control index, state local ch_idx = QSysCtrlIdxToChIdx[ctrl.Index] local ctrl = QSYSCtrlIdxToWASPCtrl[ctrl.Index] local state = btn_Phantom[ch_idx].Boolean -- disable the button while submitting the request btn_Phantom[ch_idx].IsDisabled = true -- submit HttpClient.Upload { Url = "http://"..SelectedDevIP.."/asi/r1"..ctrl['_self'], Method = "PUT", Timeout = 10, Data = rapidjson.encode({active = state}), Headers = { ["Content-Type"] = "application/json", ["X-HTTP-Method-Override"] = "PATCH" }, EventHandler = function(tbl, code, d, e) btn_Phantom[ch_idx].IsDisabled = false if code ~= 202 then display_error('PATCH phantom power error') end end } end end -- Input/Transmit AES67 toggle buttons for k,v in ipairs(btn_TXChannelAES67Tgl) do v.EventHandler = function(ctrl) local ch_idx = k - 1 local new_state = v.Boolean btn_TXChannelAES67Tgl[k].Legend = "..." HttpClient.Upload { Url = "http://"..SelectedDevIP.."/asi/r1/aes67/tx/toggle", Method = "PUT", Timeout = 10, Data = rapidjson.encode({index = ch_idx, enable = new_state}), Headers = { ["Content-Type"] = "application/json", ["X-HTTP-Method-Override"] = "PATCH" }, EventHandler = function(tbl, code, d, e) if code ~= 200 then display_error('PATCH TX AES67 status error') end end } end end -- Event handler for Receive/Output AES67 transmitter selection combobox for k,v in ipairs(cb_SelectedAES67TX) do v.String = '...' v.EventHandler = function(ctrl) local rx_ch_idx = k - 1 local transport_ip = '0.0.0.0' local tx_flow_idx = 0 if v.String ~= 'None' then local tx_flow = split(v.String, ':') transport_ip = AES67SessionNameToIP[tx_flow[1]] tx_flow_idx = tonumber(tx_flow[2]) - 1 end v.String = '...' HttpClient.Upload { Url = "http://"..SelectedDevIP.."/asi/r1/aes67/rx/subscribe", Method = "PUT", Timeout = 10, Data = rapidjson.encode({rx_ch_idx = rx_ch_idx, tx_flow_ip = transport_ip, tx_flow_idx = tx_flow_idx}), Headers = { ["Content-Type"] = "application/json", ["X-HTTP-Method-Override"] = "PATCH" }, EventHandler = function(tbl, code, d, e) if code ~= 200 then display_error('PATCH RX AES67 selected transmitter error') end end } end end -- Control update functions function update_meter(ctrl, peak_dBFS) local ch_idx = path_to_ch_idx(ctrl['loc'][1]) + 1 local dbFS_ctrl, meter_ctrl = nil local is_input = string.find(ctrl['loc'][1], 'io/analog/in') if is_input then dbFS_ctrl = text_TXChanneldBFS[ch_idx] meter_ctrl = meter_TXChannelMeter[ch_idx] else dbFS_ctrl = text_RXChanneldBFS[ch_idx] meter_ctrl = meter_RXChannelMeter[ch_idx] end -- dBFS control text and background local dBFS_text = '-'..tostring(peak_dBFS)..'dBFS' local idx = ctrl['_self'] if peak_dBFS == 0 then dBFS_text = 'Clip' dbFS_ctrl.Color = RED -- hold clip for 1s Timers[idx] = Timer.New() Timers[idx].EventHandler = function(timer) if timer == Timers[idx] then dbFS_ctrl.Color = GRAY end end Timers[idx]:Start(1) elseif peak_dBFS > 60 then dBFS_text = 'Low' end dbFS_ctrl.String = dBFS_text meter_ctrl.Value = -peak_dBFS end function update_input_level(ch_idx, level_dBu) --update knob knob_TXChannelGain[ch_idx].Value = (24 - level_dBu) --dBu -> dB -- display Gain (dB) and Level (dBu) text_TXChannelLevel[ch_idx].String = tostring(level_dBu).." dBu" text_TXChannelGain[ch_idx].String = tostring(24 - level_dBu).." dB" -- disable phantom power btn if level > 10dBu btn_Phantom[ch_idx].IsDisabled = (level_dBu > 10) end function update_output_level(ch_idx, level_dBu) -- update knob knob_RXChannelLevel[ch_idx].Value = level_dBu -- display Level (dBu) text_RXChannelLevel[ch_idx].String = tostring(level_dBu).." dBu" end function update_tx_aes67_ctrls(ch_idx, enabled) btn_TXChannelAES67Tgl[ch_idx].Color = enabled and BLUE or ALT_GRAY btn_TXChannelAES67Tgl[ch_idx].Legend = enabled and 'On' or 'Off' btn_TXChannelAES67Tgl[ch_idx].Boolean = enabled text_TXChannelAES67Status[ch_idx].String = enabled and 'Active' or 'Inactive' text_TXChannelAES67Status[ch_idx].Color = enabled and BLUE or ALT_GRAY end function update_rx_aes67_ctrls(ch_idx, enabled) text_RXChannelAES67Status[ch_idx].String = enabled and 'Active' or 'Inactive' text_RXChannelAES67Status[ch_idx].Color = enabled and BLUE or ALT_GRAY if not enabled then cb_SelectedAES67TX[ch_idx].String = 'None' end end -- Polled functions -- Device status function poll_sync_sys() local reconnected_needed = false HttpClient.Download { Url = "http://"..SelectedDevIP.."/asi/r1/status/sync_sys", Headers = { ["Content-Type"] = "application/json" }, Timeout = 10, -- mark the device as disconnected if this request fails EventHandler = function(tbl, code, data, err, headers) if code ~= 200 then reconnect_needed = true display_error('Error - Trying to Reconnect...') enable_all_ctrls(false) do return end end -- request succeeded, update the status fields and reconnect to the update stream, if necessary text_DevStatus.String = 'OK' text_DevStatus.Color = QSYS_GREEN if reconnect_needed then sock:Disconnect() sock:Connect(SelectedDevIP, 80) reconnect_needed = false enable_all_ctrls(true) end -- update status parameters and LEDs local obj = rapidjson.decode(rapidjson.decode(data)) for k, v in pairs(obj) do local color, text = nil if k == 'sync' then if v == 0 then color = GREEN text = 'PTP Slave' elseif v == 1 then color = ORANGE text = 'PTP Syncing' elseif v == 2 then color = BLUE text = 'PTP Master' elseif v == 3 then color = RED text = 'PTP Error' else color = ALT_GRAY text = 'PTP State Unknown' end led_StatusLEDs[1].Color = color text_StatusInfo[1].String = text elseif k == 'sys' then if v == 0 then color = GREEN text = 'OK' elseif v == 1 then color = ORANGE text = 'Waiting' elseif v == 2 then color = BLUE text = 'Hardware Error' else color = ALT_GRAY text = 'System State Unknown' end led_StatusLEDs[2].Color = color text_StatusInfo[2].String = text end end end } end -- Input/Transmit channels AES67 status function poll_tx_aes67_status() HttpClient.Download { Url = "http://"..SelectedDevIP.."/asi/r1/aes67/tx/status", Headers = { ["Content-Type"] = "application/json" }, Timeout = 10, EventHandler = function(tbl, code, data, err, headers) if code ~= 200 then display_error('GET TX AES67 status request error') do return end end local choices = {''} local session_names = {'Mic/Line In 1-8', 'Mic/Line In 9-16', 'Mic/Line In 17-24', 'Mic/Line In 25-32'} input_count, output_count = GetIOCount(Properties.ModelType.Value) local obj = rapidjson.decode(data) for k, v in pairs(obj) do if k <= input_count then local active = false local tmp = rapidjson.decode(v) local transport_ip = tmp['transport_ip'] if transport_ip ~= '0.0.0.0' then active = true local session_name = session_names[math.floor((k - 1) / 8) + 1] local new_selection = string.format("%20.20s", session_name)..string.format("%20.20s", transport_ip) local found = false for k, v in pairs(choices) do if v == new_selection then found = true end end if not found then table.insert(choices, new_selection) end end update_tx_aes67_ctrls(k, active) end end Controls.TXAES67Details.Choices = choices end } end -- Advertised AES67 transmitters function poll_aes67_disc_tx_flows() HttpClient.Download { Url = "http://"..SelectedDevIP.."/asi/r1/aes67/tx/adv", Headers = { ["Content-Type"] = "application/json" }, Timeout = 10, EventHandler = function(tbl, code, data, err, headers) if code ~= 200 then display_error('GET AES67 advertised flows request error') do return end end DiscAES67TXFlows = rapidjson.decode(data) DiscAES67TXChannels = {'None'} for k, v in pairs(DiscAES67TXFlows) do AES67SessionNameToIP[k] = v['transport_ip_str'] AES67IPToSessionName[v['transport_ip_str']] = k for i = 1, v['channel_count'] do table.insert(DiscAES67TXChannels, k..':'..tostring(i)) end end for k, v in pairs(cb_SelectedAES67TX) do cb_SelectedAES67TX[tonumber(k)].Choices = DiscAES67TXChannels end end } end -- Receive/Output channels AES67 status function poll_rx_aes67_status() HttpClient.Download { Url = "http://"..SelectedDevIP.."/asi/r1/aes67/rx/status", Headers = { ["Content-Type"] = "application/json" }, Timeout = 10, EventHandler = function(tbl, code, data, err, headers) if code ~= 200 then display_error('GET RX AES67 status request error') do return end end local choices = {''} local obj = rapidjson.decode(data) input_count, output_count = GetIOCount(Properties.ModelType.Value) for k, v in pairs(obj) do if k <= output_count then local active = false local aes67_rx_ch = rapidjson.decode(v) local sub_transport_ip = aes67_rx_ch['sub_transport_ip'] local sub_ch_idx = aes67_rx_ch['sub_ch_idx'] if sub_transport_ip ~= '0.0.0.0' then active = true for key, value in pairs(DiscAES67TXChannels) do local session_name = AES67IPToSessionName[sub_transport_ip] if session_name ~= nil then local found = false local new_option = string.format("%9.9s", OutputNames[tonumber(k) - 1])..string.format("%20.20s", session_name)..string.format("%15.15s", tostring(sub_ch_idx + 1)) for k, v in pairs(choices) do if v == new_option then found = true end end if not found then table.insert(choices, new_option) end local option = session_name..":"..tostring(sub_ch_idx + 1) if value == option then local session = split(value, ':') local session_substr = string.sub(session[1], 0, 3) cb_SelectedAES67TX[k].String = session_substr.."...:"..session[2] end end end end update_rx_aes67_ctrls(k, active) end end Controls.RXAES67Details.Choices = choices end } end -- Functions/event handlers called/triggered on device selection -- update stream (meters and unsolicited DOM changes) sock = TcpSocket.New() function update_stream() enable_all_ctrls(true) -- enable all controls sendData = 'GET /asi/u1/controls HTTP/1.1\r\nHost: '..SelectedDevIP..'\r\n\r\n' sock.Connected = function(sock) sock:Write(sendData) end sock.Data = function(sock) --read from update stream local data = sock:Read(sock.BufferLength) data = split(data, '\r\n---\n')[1] if string.sub(data, 1, 1) == '{' then -- JSON -> Lua local obj = rapidjson.decode(data) -- meters if obj['_type'] == '/meta/update/group_prefix' then for wasp_ctrl_idx, peak in pairs(obj['body']) do for k, v in pairs(WASPControls) do if v['_self'] == "/controls/"..wasp_ctrl_idx then update_meter(v, -peak) end end end -- input / output level, phantom power elseif obj['_type'] == '/meta/update/control' then for k, v in pairs(WASPControls) do if v['_self'] == obj['path'] then if v['loc'][1] ~= '/adapter' then local ch_idx = tonumber(path_to_ch_idx(v['loc'][1])) + 1 -- look up the control type if v['_type'] == '/meta/controls/input_trim' then for k, level_dBu in pairs(obj['body']) do update_input_level(ch_idx, level_dBu) end elseif v['_type'] == '/meta/controls/output_trim' then for k, level_dBu in pairs(obj['body']) do update_output_level(ch_idx, level_dBu) end elseif v['_type'] == '/meta/controls/phantom' then for param, state in pairs(obj['body']) do btn_Phantom[ch_idx].Boolean = state end end end end end end end end sock.Closed = function(sock) sock:Connect(SelectedDevIP, 80) -- WASP 1.0 socket closes after 60s, reconnect when this occurs end sock.Error = function(sock, err) display_error("TCP socket had an error:"..err) end sock.Timeout = function(sock, err) display_error("TCP socket timed out"..err) end sock:Connect(SelectedDevIP, 80) end -- All WASP controls enumerated function on_get_controls_done(tbl, code, data, err, headers) if code ~= 200 then display_error('GET controls request error') do return end end local dom = rapidjson.decode(data) WASPControls = dom.controls for k, v in pairs(WASPControls) do -- Initialize and Store controls, Q-SYS control Index -> WASP control Index -- Q-SYS control Index -> channel index (if applicable) local qsys_idx = nil local ch_idx = tonumber(path_to_ch_idx(v['loc'][1])) if v['_type'] == '/meta/controls/input_trim' then qsys_idx = knob_TXChannelGain[ch_idx + 1].Index update_input_level(ch_idx + 1, v['level']) elseif v['_type'] == '/meta/controls/output_trim' then qsys_idx = knob_RXChannelLevel[ch_idx + 1].Index update_output_level(ch_idx + 1, v['level']) elseif v['_type'] == '/meta/controls/phantom' then qsys_idx = btn_Phantom[ch_idx + 1].Index btn_Phantom[ch_idx + 1].Boolean = v['active'] elseif v['_type'] == '/meta/controls/front_panel' then local brightness_pct = v['brightness'] cb_LED_Brightness.String = tostring(brightness_pct)..'%' qsys_idx = cb_LED_Brightness.Index end if qsys_idx ~= nil then QSYSCtrlIdxToWASPCtrl[qsys_idx] = v if ch_idx ~= nil then QSysCtrlIdxToChIdx[qsys_idx] = ch_idx + 1 end end end -- Iterate through I/O controls local io = dom.io.analog for k, v in pairs(io) do if k == 'out' then for idx, obj in pairs(v) do OutputNames[obj['idx']] = obj['label'] text_RXNameLabel[idx].String = tostring(idx) end else for idx, obj in pairs(v) do text_TXNameLabel[idx].String = tostring(idx) end end end -- Polling timer local poll_timer = Timer.New() poll_timer.EventHandler = function(timer) if timer == poll_timer then poll_tx_aes67_status() poll_aes67_disc_tx_flows() poll_rx_aes67_status() poll_sync_sys() end end poll_timer:Start(10) -- 10s -- initiate the control update / meters stream update_stream() end -- WASP adapter info enumerated function on_get_adapter_done(tbl, code, data, err, headers) if code ~= 200 then display_error('GET adapter request error') do return end end obj = rapidjson.decode(data) -- Device Info local dev_info = { 'product_name', 'model_id', 'hw_rev', 'serial' } for k, v in ipairs(dev_info) do text_DeviceInfo[k].String = obj.info[v] end -- Firmware local asi_fw = obj.software['AudioScience']['firmware'] asi_fw = split(asi_fw, '+')[1] local xmos_fw = obj.software['XMOS']['firmware'] xmos_fw = split(xmos_fw, '+')[1] text_FirmwareInfo[1].String = asi_fw text_FirmwareInfo[2].String = xmos_fw -- Get MAC Address HttpClient.Download { Url = "http://"..SelectedDevIP.."/asi/r1/status/mac", Headers = { ["Content-Type"] = "application/json" }, Timeout = 10, EventHandler = function(tbl, code, data, err, headers) if code ~= 200 then display_error('GET MAC Address request error') do return end end text_DeviceInfo[5].String = rapidjson.decode(data) end } -- Get all Controls HttpClient.Download { Url = "http://"..SelectedDevIP.."/asi/r1", Headers = { ["Content-Type"] = "application/json" }, Timeout = 10, EventHandler = on_get_controls_done } end -- Create Device Selection List function CreateDeviceList() deviceList = {} for k, v in pairs(DiscoveredIPLookup) do if k ~= nil and v ~= nil then table.insert(deviceList, k) end end cb_Dev.Choices = deviceList cb_Dev.EventHandler = function() text_DevStatus.String = 'Connecting...' text_DevStatus.Color = BLUE SelectedDevIP = DiscoveredIPLookup[cb_Dev.String] HttpClient.Download { Url = "http://"..SelectedDevIP.."/asi/r1/adapter", Headers = { ["Content-Type"] = "application/json" }, Timeout = 10, EventHandler = on_get_adapter_done } end end -- Bonjour Bits on Core local ResolveBonjourName local function SetRBN() local sixoh = Qlib.gethostbyname~=nil local RBN = { "140000003500000007001117000000c6014000c741c003e48180000682400007c24004248280004102010080020000c142010000030004418301005d428304840200016c030000ca4183836c4300008c03c20300040003a443800180038006c0038004a44300012600800009000000040654696d657204044e657704036f730408746d706e616d6504146e6f68757020646e732d7364202d47207634200404203e2004032026040d4576656e7448616e646c65720406537461727401000000000002000000001a0000002c0000000000085400000006404000078040004500800081c000002480800108000080060040000cc041008100020024008101088000830840808208000082060040000c404200850080002440800106004100224000001e4001800680420041c00200864041005d808000868041002440800106004300074043004500800024400001060043000780430041c0030024000101c6404400c780c4010601410041c10400e480800108c00088220000001e000980c6004100dc0080015f00c5011e000880c5000001cc40c501e4400001c6004400c6c080015f80c5011e000180c5008002c6c00002060144005f0081011e000680c60044000bc10000450180020a41818b450100034c81c402c1410600648180010a41018c450180030a41018dc8008101c50080020601440008018101c50000045f80c5011e800180c5000004e44080001ec00080c680420001c1060045010003e4408001260080001c000000040566696c650403696f04056f70656e040272040472657304046d73670405636f646504057265616404032a610406636c6f736504067072696e7404084572726f723a2004036f73040772656d6f7665040865786563757465041d706772657020646e732d7364207c207861726773206b696c6c202d39040c63616e64696461746549500407737472696e6704066d6174636814365b3031325d3f25643f25642b252e5b3031325d3f25643f25642b252e5b3031325d3f25643f25642b252e5b3031325d3f25643f2564130000000000000000040553746f700004054e616d65040844656661756c74040b282e2b292e6c6f63616c04064d6f64656c04114950206e6f74207265736f6c766564210900000000000108010701030104010101000102010500000000540000001b0000001b0000001b0000001b0000001b0000001b0000001c0000001c0000001c0000001c0000001c0000001c0000001c0000001d0000001d0000001d0000001d0000001e0000001e0000001e0000001e0000001e0000001e0000001e0000001e0000001e0000001f0000001f0000001f0000001f000000200000002000000020000000200000002100000021000000210000002100000021000000210000002200000022000000220000002200000022000000220000002300000023000000230000002400000024000000240000002400000024000000240000002400000024000000240000002500000025000000250000002500000025000000250000002500000025000000250000002500000025000000250000002600000026000000260000002700000027000000270000002700000027000000280000002a0000002a0000002a0000002a0000002c000000030000000873756363657373220000005400000005657865632200000054000000077369676e616c220000005400000009000000055f454e5604746d700a6b696c6c74696d65720a446973635461626c650a49504c755461626c6509667269656e646c79056e616d65066d6f64656c106d616b65646973706c61794c697374002e0000003200000003000c12000000c100000006414000078140024001000081c10000c50180005dc1810224010101c6014100000200024642410081820100c00280029dc20205c002000364028001e44100002600800007000000040104036f730408657865637574650404203e2004076173736572740409746f737472696e67041145786563757465204661696c65643a20020000000000010800000000120000002f00000030000000300000003000000030000000300000003000000030000000310000003100000031000000310000003100000031000000310000003100000031000000320000000700000008636d646c696e650000000012000000087061747465726e0000000012000000086361707475726500000000120000000872737472696e6701000000120000000873756363657373080000001200000005657865630800000012000000077369676e616c080000001200000002000000055f454e5604746d7017000000150000001500000015000000160000001600000016000000170000001700000017000000170000001700000017000000180000002c0000002c00000032000000330000003300000033000000340000003400000034000000350000000e000000056e616d65000000001700000009667269656e646c790000000017000000066d6f64656c00000000170000000a446973635461626c6500000000170000000a49504c755461626c650000000017000000106d616b65646973706c61794c6973740000000017000000096b696c6c74696d6500000000170000000a6b696c6c74696d6572030000001700000004746d70060000001700000008636d646c696e650c00000017000000087061747465726e0d0000001700000008636170747572650d000000170000000872737472696e670d0000001700000004436d64100000001700000001000000055f454e56", "400000004200000001000308000000460040004740c00080000000648000014780c00047c0c0006600000126008000040000000405516c6962040e676574686f737462796e616d65040a6164647265737365731301000000000000000100000000000000000008000000410000004100000041000000410000004100000041000000410000004200000001000000056e616d65000000000800000001000000055f454e56"} local tmpFn = load(bitstring.fromhexstream("1b4c7561530019930d0a1a0a04080408087856000000000000000000000028774001023d"..RBN[sixoh and 2 or 1])) if sixoh then ResolveBonjourName = function(name, friendlyName) DiscoveredIPLookup[friendlyName] = tmpFn(name) CreateDeviceList() end else ResolveBonjourName = tmpFn end end if not e then SetRBN() end -- Delete Bonjour Entry function DeleteBonjourEntry(name) DiscoveredIPLookup[name] = nil if cb_Dev.String == name then cb_Dev.String = "None" end CreateDeviceList() end -- Bonjour Browse function mDNSBrowse(selected_model_type) if not e then mDNS = BonjourBrowser.New() mDNS.EventHandler = function( record ) if record.Type == "_netaudio-cmc._udp." then if record.Added then --resolve record local r = BonjourResolver.New() r.EventHandler = function( rc ) local mf = rc.Values.mf if mf == "AudioSci" then local discovered_model = string.sub(rc.Values.model, -4) local selected_model = ModelTypeToNo(selected_model_type) if tonumber(discovered_model) == selected_model then ResolveBonjourName(rc.Target, record.Name) end end end r:Resolve( record ) else --remove record DeleteBonjourEntry(record.Name) end end end mDNS:Start( "_netaudio-cmc._udp", 0, "local" ) end end if not e then cb_Dev.String = "None" enable_all_ctrls(false) mDNSBrowse(Properties.ModelType.Value) end end