internal IEnumerable <IContainer> ToContainers(ModuleSchema schema, ModuleJson module, ModuleAddress parentAddress, string?parentPath, SchemaVariables variables) { variables = variables.WithVariables(ExtraVariables); ModuleOffset offset = ModuleOffset.FromDisplayValue(Offset.Value); if (Repeat is null) { yield return(resolvedContainer.ToContainer(schema, module, ResolvedName, ResolvedDescription, parentAddress + offset, parentPath, variables)); } else { int gap = ModuleOffset.FromDisplayValue(Repeat.Gap.Value).LogicalValue; foreach (var tuple in module.GetRepeatSequence(Repeat.Items, variables)) { var itemVariables = variables.WithVariable(Repeat.IndexVariable, tuple.index, Repeat.IndexTemplate); var formattedDescription = tuple.variables.Replace(ResolvedDescription); var formattedName = Invariant($"{ResolvedName}[{tuple.index}]"); yield return(resolvedContainer.ToContainer(schema, module, formattedName, formattedDescription, parentAddress + offset, parentPath, itemVariables)); offset += gap; } } }
private IReadOnlyList <FieldBase> ResolveFields(ModuleJson module) { var resolved = new List <FieldBase>(); foreach (var field in Fields) { resolved.AddRange(field.ToFields(module, CurrentSize())); } var actualSize = CurrentSize().LogicalValue; var expectedSize = ModuleOffset.FromDisplayValue(Size.Value).LogicalValue; if (actualSize != expectedSize) { throw new InvalidOperationException( $"Incorrect size in container {NameInModuleDictionary}: expected {expectedSize}; was {actualSize}"); } // Skip placeholder fields so they're never exposed. return(resolved.Where(field => !(field is PlaceholderField)).ToReadOnlyList()); ModuleOffset CurrentSize() { var field = resolved.LastOrDefault(); return(field?.Offset + field?.Size ?? ModuleOffset.Zero); } }
public IEnumerable <IField> ToFields(ModuleJson module, ModuleOffset offset) { AssertNotNull(ResolvedName); AssertNotNull(ResolvedDescription); if (Repeat is null) { yield return(ToField(module, ResolvedName, ResolvedDescription, offset)); } else { Preconditions.AssertNotNull(Repeat.Items); var variables = SchemaVariables.Empty; int?gap = Repeat.Gap?.Value; foreach (var tuple in module.GetRepeatSequence(Repeat.Items, variables)) { var itemVariables = variables.WithVariable(Repeat.IndexVariable, tuple.index); var formattedDescription = tuple.variables.Replace(ResolvedDescription); var formattedName = Invariant($"{ResolvedName}[{tuple.index}]"); var field = ToField(module, formattedName, formattedDescription, offset); yield return(field); offset += gap ?? field.Size; } } }
internal FieldBase ToField(ModuleJson module, string name, string description, ModuleOffset offset) { return(Type switch { "boolean" => new BooleanField(null, BuildCommon(1), NumericCodec.Range8, GetDefaultValue()), "boolean32" => new BooleanField(null, BuildCommon(4), NumericCodec.Range32, GetDefaultValue()), string ph when ph.StartsWith("placeholder") => new PlaceholderField(null, BuildCommon(int.Parse(ph.Substring("placeholder".Length)) / 8)), "enum" => BuildEnumField(NumericCodec.Range8), "enum16" => BuildEnumField(NumericCodec.Range16), "enum24" => BuildEnumField(NumericCodec.Full24), "enum32" => BuildEnumField(NumericCodec.Range32), "instrument" => new InstrumentField(null, BuildCommon(4), BankOffset is null ? (ModuleOffset?)null : ModuleOffset.FromDisplayValue(BankOffset.Value)), "midi32" => new NumericField(null, BuildCommon(4), 0, 128, Default ?? 0, NumericCodec.Range32, null, null, null, null, (128, "Off")), "overlay" => BuildOverlay(), "range8" => BuildNumericField(NumericCodec.Range8), "range16" => BuildNumericField(NumericCodec.Range16), "urange16" => BuildNumericField(NumericCodec.URange16), "range32" => BuildNumericField(NumericCodec.Range32), "string" => BuildStringField(1), "string16" => BuildStringField(2), "tempo" => BuildTempoField(), "volume32" => new NumericField(null, BuildCommon(4), -601, 60, 0, NumericCodec.Range32, 10, null, 0, "dB", (-601, "-INF")), "fixme_enum32" => BuildEnumField(NumericCodec.Fixme32), "fixme_range32" => BuildNumericField(NumericCodec.Fixme32), "fixme_boolean32" => new BooleanField(null, BuildCommon(4), NumericCodec.Fixme32, GetDefaultValue()), _ => throw new InvalidOperationException($"Invalid field type: '{Type}'") });
private IReadOnlyList <FieldBase> ResolveFields(ModuleJson module) { var resolved = new List <FieldBase>(); foreach (var field in Fields) { var lastField = resolved.LastOrDefault(); var offset = lastField?.Offset + lastField?.Size ?? ModuleOffset.Zero; resolved.AddRange(field.ToFields(module, offset)); } // Skip placeholder fields so they're never exposed. return(resolved.Where(field => !(field is PlaceholderField)).ToReadOnlyList()); }
internal override void ValidateJson(ModuleJson module) { base.ValidateJson(module); // Offset must always be specified Validation.ValidateNotNull(Offset, nameof(Offset)); if (Repeat is object) { Validation.ValidateNotNull(Repeat.Gap, $"{nameof(Repeat)}.{nameof(Repeat.Gap)}"); } string containerName = Container ?? Name ?? throw new InvalidOperationException($"Either {nameof(Container)} or {nameof(Name)} must be specified"); Validation.Validate(module.Containers.TryGetValue(containerName, out resolvedContainer), "Container '{0}' not found.", containerName); }
internal void ValidateAndResolve(ModuleJson module) { Validation.Validate(Fields is object ^ Containers is object, "Each container must either specify fields or containers as children, but not both."); if (Fields is object) { Validation.Validate(Size is object, "Field containers must specify a size."); Fields.ForEach(f => f.ValidateJson(module)); resolvedFields = ResolveFields(module); requiresOverlayResolution = resolvedFields.Any(f => f is OverlayField overlay && overlay.SwitchPath.Contains('{')); } else { Containers?.ForEach(f => f.ValidateJson(module)); } }
internal INodeDetail ToNodeDetail(ModuleJson module, IContainer nodeContainer, SchemaVariables nodeVariables) { Validation.Validate(Path is null ^ FormatPaths is null, "Exactly one of Path and FormatPaths must be specified"); if (Path is object) { var container = nodeContainer.ResolveContainer(nodeVariables.Replace(Path)); return(container is FieldContainer fc ? new FieldContainerNodeDetail(Description, fc) : throw new ArgumentException($"'{Path}' does not resolve to a field container")); } else { var formattableFields = module.GetRepeatSequence(Repeat, SchemaVariables.Empty) .ToReadOnlyList(tuple => FieldFormattableString.Create(nodeContainer, Format, FormatPaths, tuple.variables)); return(new ListNodeDetail(Description, formattableFields)); } }
internal FieldBase ToField(ModuleJson module, string name, string description, ModuleOffset offset) { return(Type switch { "boolean" => new BooleanField(null, BuildCommon(1)), "boolean32" => new BooleanField(null, BuildCommon(4)), string ph when ph.StartsWith("placeholder") => new PlaceholderField(null, BuildCommon(int.Parse(ph.Substring("placeholder".Length)) / 8)), "enum" => BuildEnumField(1), "enum16" => BuildEnumField(2), "enum24" => BuildEnumField(3), "enum32" => BuildEnumField(4), "instrument" => new InstrumentField(null, BuildCommon(4), ModuleOffset.FromDisplayValue(ValidateNotNull(BankOffset, nameof(BankOffset)).Value)), "midi32" => new NumericField(null, BuildCommon(4), 0, 128, Default ?? 0, null, null, null, null, (128, "Off")), "overlay" => BuildOverlay(), "range8" => BuildNumericField(1, 0, 127), "range16" => BuildNumericField(2, -128, 127), "range32" => BuildNumericField(4, short.MinValue, short.MaxValue), "string" => BuildStringField(1), "string16" => BuildStringField(2), "tempo" => BuildTempoField(), "volume32" => new NumericField(null, BuildCommon(4), -601, 60, 0, 10, null, 0, "dB", (-601, "-INF")), _ => throw new InvalidOperationException($"Invalid field type: '{Type}'") });
public IContainer ToContainer(ModuleSchema schema, ModuleJson module, string name, string description, ModuleAddress address, string?parentPath, SchemaVariables variables) { string path = PathUtilities.AppendPath(parentPath, name); if (Fields is object) { var fieldList = requiresOverlayResolution ? resolvedFields.Select(FinalizeField) : resolvedFields; var realSize = ModuleOffset.FromDisplayValue(Size.Value).LogicalValue; return(new FieldContainer(schema, name, description, address, path, realSize, fieldList)); FieldBase FinalizeField(FieldBase field) => field is OverlayField overlay?overlay.WithPath(variables.Replace(overlay.SwitchPath)) : field; } else { var containers = new List <IContainer>(); foreach (var container in Containers) { containers.AddRange(container.ToContainers(schema, module, address, path, variables)); } return(new ContainerContainer(schema, name, description, address, path, containers)); } }
internal IEnumerable <TreeNode> ToTreeNodes(ModuleJson json, string?parentNodePath, IContainer parentContainer) { Preconditions.AssertNotNull(Name); Preconditions.AssertNotNull(Path); Preconditions.AssertNotNull(Format); if (Repeat is null) { yield return(ToTreeNode(json, Name, parentContainer, SchemaVariables.Empty)); } else { foreach (var tuple in json.GetRepeatSequence(Repeat, SchemaVariables.Empty)) { string name = Invariant($"{Name}[{tuple.index}]"); yield return(ToTreeNode(json, name, parentContainer, tuple.variables)); } } TreeNode ToTreeNode(ModuleJson json, string name, IContainer parentContainer, SchemaVariables variables) { string nodePath = PathUtilities.AppendPath(parentNodePath, name); string?midiNotePath = MidiNotePath is null ? null : variables.Replace(MidiNotePath); var resolvedContainerPath = variables.Replace(Path !); var container = parentContainer.ResolveContainer(resolvedContainerPath); var formattableString = FieldFormattableString.Create(container, Format !, FormatPaths, variables); var childTreeNodes = new List <TreeNode>(); foreach (var child in Children) { childTreeNodes.AddRange(child.ToTreeNodes(json, nodePath, container)); } var treeDetails = Details.Select(detail => detail.ToNodeDetail(json, container, variables)).ToList().AsReadOnly(); return(new TreeNode(name, nodePath, container, formattableString, midiNotePath, childTreeNodes.AsReadOnly(), treeDetails)); } }
private IField ToField(ModuleJson module, string name, string description, ModuleOffset offset) { return(Type switch { "boolean" => new BooleanField(BuildCommon(1)), "boolean32" => new BooleanField(BuildCommon(4)), "placeholder8" => new PlaceholderField(BuildCommon(1)), "placeholder16" => new PlaceholderField(BuildCommon(2)), "placeholder32" => new PlaceholderField(BuildCommon(4)), "enum" => BuildEnumField(1), "enum16" => BuildEnumField(2), "enum32" => BuildEnumField(4), "instrument" => new InstrumentField(BuildCommon(4), ModuleOffset.FromDisplayValue(ValidateNotNull(BankOffset, nameof(BankOffset)).Value)), "midi32" => new NumericField(BuildCommon(4), 0, 128, 0, null, null, null, null, (128, "Off")), "overlay" => BuildOverlay(), "range8" => BuildNumericField(1), "range16" => BuildNumericField(2), "range32" => BuildNumericField(4), "string" => BuildStringField(1), "string16" => BuildStringField(2), "tempo" => BuildTempoField(), "volume32" => new NumericField(BuildCommon(4), -601, 60, 0, 10, null, 0, "dB", (-601, "-INF")), _ => throw new InvalidOperationException($"Invalid field type: '{Type}'") });
internal IField ToFieldForOverlay(ModuleJson module, ModuleOffset offset) { AssertNotNull(ResolvedName); Validate(Repeat is null, "Repeated fields are not valid in overlays"); return(ToField(module, AssertNotNull(ResolvedName), AssertNotNull(ResolvedDescription), offset)); }
internal virtual void ValidateJson(ModuleJson module) { ResolvedName = Validation.ValidateNotNull(Name ?? ToPascalCase(Description), "Name or Description"); ResolvedDescription = Description ?? Name; }