private void CreateBlendShapeUI()
        {
            ClearBlendShapeUI();

            recordButton        = Plugin.CreateToggle(StorableIsRecording);
            recordButton.height = 75;
            recordButton.label  = GetRecordingLabel();

            recordMessage = Plugin.CreateTextField(StorableRecordingMessage, rightSide: true);
            recordMessage.SetLayoutHeight(75);

            minimumChangeSlider      = Plugin.CreateSlider(StorableMinimumChangePct);
            minimumChangeSliderSpace = Plugin.CreateSpacer(rightSide: true);

            foreach (var group in CBlendShape.Groups())
            {
                var isEnabledStorable = StorableIsGroupEnabled[group];

                var toggle = Plugin.CreateToggle(isEnabledStorable);
                var space  = Plugin.CreateSpacer(rightSide: true);
                space.height = toggle.height;

                groupUiElements.Add(toggle);
                groupUiElements.Add(space);

                if (isEnabledStorable.val)
                {
                    var i             = 0;
                    var shapesInGroup = CBlendShape.IdsInGroup(group).ToList();
                    foreach (var shapeId in shapesInGroup)
                    {
                        shapeMultiplierSliders[shapeId] = Plugin.CreateSlider(StorableBlendShapeStrength[shapeId], rightSide: (i % 2 != 0));
                        i++;
                    }
                    // if there are an odd number of shapes in this group, add a spacer that is the same height
                    // and one of the sliders
                    if (shapesInGroup.Count % 2 != 0 && shapesInGroup.Count > 0)
                    {
                        var spacer = Plugin.CreateSpacer(rightSide: true);
                        spacer.height = shapeMultiplierSliders.FirstOrDefault().Value.height;
                        groupUiElements.Add(spacer);
                    }
                }
            }
        }
        private void InitializeStorables()
        {
            var clients = new List <string> {
                "",
                DEVICE_LIVEFACE,
                DEVICE_FACECAP
            };

            StorableDeviceType = new JSONStorableStringChooser("Device", clients, Plugin.SettingsController.GetDevice() ?? "", "Choose Device");

            var ips = Plugin.DeviceController.IPEndPoints.Select(x => x.ToString()).ToList();

            StorableServerIp = new JSONStorableStringChooser("Server IP", ips, Plugin.SettingsController.GetLocalServerIpAddress() ?? "", "Server IP");

            var ipAddress = string.IsNullOrEmpty(Plugin.SettingsController.GetIpAddress()) ? "Enter IP address" : Plugin.SettingsController.GetIpAddress();

            StorableClientIp = new JSONStorableString("IP Address", ipAddress);

            StorableRecordingMessage = new JSONStorableString("recordMessage", "");

            StorableIsRecording = new JSONStorableBool("recording", false, (bool value) => {
                if (value)
                {
                    Plugin.StartRecording();
                }
                else
                {
                    Plugin.StopRecording();
                }
            });

            StorableMinimumChangePct = new JSONStorableFloat("Smoothing", 0.0f, (float value) => {
                Plugin.SettingsController.SetSmoothingMultiplier(value);
            }, 0, 5, constrain: true);

            // group is enabled toggles
            foreach (var groupName in CBlendShape.Groups())
            {
                StorableIsGroupEnabled[groupName] = new JSONStorableBool($"{groupName}Enabled", Plugin.SettingsController.GetGroupEnabled(groupName), (bool value) => {
                    Plugin.SettingsController.SetGroupEnabled(groupName, value);
                    var shapesInGroup = CBlendShape.IdsInGroup(groupName).ToList();
                    foreach (var shapeId in shapesInGroup)
                    {
                        if (groupName == "Head Rotation")
                        {
                            if (Plugin.HeadController != null)
                            {
                                Plugin.HeadController.transform.rotation = Plugin.OriginalHeadRotation;
                            }
                        }
                        else if (groupName == "Looking")
                        {
                            if (Plugin.EyePluginInstalled())
                            {
                                Plugin.EyePlugin.CallAction("Reset");
                            }
                        }
                        else
                        {
                            var shapeName = CBlendShape.IdToName(shapeId);
                            var morphName = Plugin.SettingsController.GetShapeMorph(shapeName);
                            var morph     = Plugin.GetMorph(morphName);
                            if (morph != null)
                            {
                                if (!value)
                                {
                                    // this is being disabled, set the morph back to default
                                    morph.SetDefaultValue();
                                }
                            }
                        }
                    }
                    CreateBlendShapeUI();
                });
                Plugin.RegisterBool(StorableIsGroupEnabled[groupName]);
            }

            // blendshape strength values
            foreach (var shapeName in CBlendShape.Names())
            {
                var shapeId    = CBlendShape.NameToId(shapeName) ?? -1;
                var multiplier = Plugin.SettingsController.GetShapeStrength(shapeName) ?? 1.0f;
                StorableBlendShapeStrength[shapeId] = new JSONStorableFloat(
                    $"{shapeName}",
                    multiplier,
                    (float value) => {
                    Plugin.SettingsController.SetShapeStrength(shapeName, value);
                    Plugin.SettingsController.SaveToGlobal();
                },
                    -10, 10, true, true
                    );
            }
        }