Skip to content
This repository has been archived by the owner on Feb 12, 2023. It is now read-only.

MDCONF Standard Version 2 Specification Draft

utz82 edited this page Jun 11, 2018 · 8 revisions

MDCONF Standard Version 2 Specification Draft

Contents

Introduction

MDCONF is a domain-specific data description and translation language. Together with MDMOD, it forms the Music Data Abstraction Language (MDAL). If you have never heard about MDAL and don't know which problems it tries to solve, please read this Introduction to MDAL first.

With MDCONF, authors of hardware-level music players implementations can configure

  1. the key elements of a suitable MDMOD implementation
  2. the process of translating such an MDMOD to assembly language output.

MDCONF is an XML dialect, and adheres to the XML Standard Version 1.0.

Core Concepts

  • MDCONF describes how to translate formalized input (in the form of MDMODs) into generalized output (in the form of assembly language data definitions). It does not aim to be a bi-directional data translation language, ie. it does not help you to derive MDMODs from generic data.

  • MDCONF is a "dumb" language, meaning it does not care about semantics except where strictly necessary. MDCONF describes data in terms of structure, while generally ignoring content. In a nutshell, MDCONF is concerned with how to deal with data, rather than what that data represents. There are some exceptions to this rule. Notably, MDCONF understands the concept of a (musical) note, and the concept of an order list (also known as sequence).

  • Don't bother the user with implementation details. The input structure should be as abstract as possible. For example, consider a player that expects patterns to have a fixed length. Such a requirement complicates the work of composers, hence they should not be burdened with it. Instead, MDAL knows how to translate arbitrary-length patterns into fixed length ones, so it is a good strategy to hide this implementation detail from the user altogether.

  • Enable users to shoot themselves in the foot if necessary. That is, prefer feature richness over safety. Yes, stupid users do stupid things, but that is generally not a good enough reason to prevent intelligent users from exploiting your player implementation to the max. If you can expand the feature set of an MDCONF at the risk of enabling inconsiderate users to enter invalid input, take the risk. This should never be a serious problem, because the default behavior in regards to parsing MDMODs is to warn on and sanitize invalid input data, and only fail on invalid syntax.

Structure

The general structure of an MDCONF configuration file is as follows:

<mdalconfig>

	... <!-- general settings -->
	
	<command />
	...
	
	<igroup>
		<ifield from="?command" />
		...
		<iblock>
			<ifield from="?command" />
			...
		</iblock>
	<igroup>
	
	<ogroup from="?igroup">
		<ofield/>
		...
		<oblock from="?iblock1, ?iblock2, ..." >
			<ofield from="?ifield" />
			...
		</oblock>
	</ogroup>
	
</mdalconfig>

Input Elements

The input elements <command>, <ifield>, <iblock>, <igroup>, and <iorder> describe the input module setup that is presented to the user. In other words, it defines the structure of the MDAL Module. Input order lists (sequences) are configured automatically. However, you can configure additional <ifield>s for order lists with the <iorder> element. This may be useful for enabling additional features such as order jumps and transpose commands. Note that <igroup>s cannot be nested. That means MDAL does not allow for input structures using the sequence-chain-pattern approach. However, you can use this approach for the output data, in which case MDAL will internally derive it from its standard "flat" input structure.

Output Elements

The output elements <olist>, <oorder>, <ofield>, <obitfield>, <oblock>, and <ogroup> describe the module structure that will be output, typically a chunk of assembly data defines as required by the target player. Furthermore, the output elements describe how to translate the input module to the output module.

Identifiers

Most elements require an identifier to be specified with the id attribute. Identifiers must be unique for a given type of element, regardless of hierarchy. Elements can additionally be named with the name attribute. This may be useful in the context of tracker implementations, but has no meaning in MDAL itself. If no name is given, it will be derived from the id.

Keyword Index

Elements

Specifiers

<clone>

Generates clones of the contained <iblock> or <ifield> direct child elements. The element's identifier must utilize a placeholder, from which the actual identifier is derived. The following identifiers are available:

  • %d: Enumerate with decimal values
  • %x: Enumerate with hexadecimal values

TODO: <clone> must also be available for output elements

Parents <igroup>

Children <iblock> <ifield>

attribute values default description
count uint n/a The number of clones to generate.

Example The following will generate 3 <iblock>s, identified as MYBLK1, MYBLK2, and MYBLK3, respectively. Each of the generated <iblock>s will contain one <ifield> derived from the MYCMD <command>.

<clone count="3">
	<iblock id="MYBLK%d">
		<ifield from="?MYCMD" />
	</iblock>
</clone>

<command>

Command Types

The most basic command types is the Int and UInt commands. They handle regular signed and unsigned integer input. For these types, the size must be specified with the bits attribute. You can additionally limit the range of accepted input values with the <range> specifier.

Key and UKey commands types are a specialization of the Int and UInt types. These commands will perform a substitution on the input, the details of which are controlled by one or more <substitute> specifiers, or by loading a list of key,value pairs from an external file with the map attribute. For these types, the size must be specified with the bits attribute.

The Note command type is used for handling musical notes. They are a specialization of the UKey type. The note name definitions must be loaded from an external file with the map attribute. The file must be a plain-text file containing all available note names and their respective substitution values. It must be formatted as follows:

C-0, 1
C#0, 2
...
rest, 0
noise, 1234

MDAL does not use flats. rest defines a note stop or rest note, and noise defines a seed for random generators for player implementations that use such a feature. Both of these are optional.

The String command type accepts arbitrary text strings as input.

The Label command type allows users to set positional labels within an input block, which can then be used with a Reference command.

The Reference command type is used to reference instances of another input element (usually an <iblock>). The target input element is specified with the to attribute. A typical use case would be a command that sets an instrument, or an arpeggio table. By default, Reference commands can also address Labels, if the target input element uses them. This behaviour can be disabled with the disable_labels flag, see below. By setting the reference target to the special variable ?_ (referring to the current parent element), you can address Labels within the current element.

Input fields using a command of Trigger do not accept user input, so they can only be set. This is useful for features that do not use a parameter, such as a trigger for a non-configurable click drum.

Flags

The enable_modifiers flag enables the use of modifiers. Modifiers allow users to append a simple arithmetic or logical expression to a field input value. This may be useful for Note commands that will generate full frequency dividers, rather than indices to a lookup table.

The disable_labels flag is used on Reference commands. It overrides the standard behaviour of allowing the use of labels if the target input element uses a Label command.

Parents <mdalconfig>

Specifiers <description>, <range>, <substitute>

Attributes

attribute values default description
bits uint 0 Size of the input value, in bits. Optional for <command>s of type String and Trigger. Set it to 0 if it is irrelevant for the output. MDAL supports sizes up to 64 bits.
default any 0 or "" The default value that a field derived from this <command> will return if not set. By default, it is set to 0 if bits > 0, else an empty string.
flags a comma-separated list of one or more of disable_labels
enable_modifiers
n/a Control misc. command behaviour. See below for a detailed description.
id string n/a An identifier. Must be unique among all <command>s.
map filepath n/a Load an list of key/value pairs from an external resource. The external resource must be a plain-text file containing exactly one comma-seperated key/value pair per line. Only works for types Note and Key.
name string n/a A name for the command. If not set, it will be derived from id. This has no effect in MDAL itself, but may be used by tracker implementations.
to element id n/a Specify the target element for Reference type commands. Ignored otherwise.
type Int
Key
Label
Note
Reference
String
Trigger
UInt
UKey
n/a The type of the <command>. See below for a detailed description.

<description>

Describe an element. This makes most sense as a global definition, or for input elements such as <command>, <igroup>, or <iblock>. Implementations are free to ignore <description>s, and they have no effect in MDAL itself.

Parents <mdalconfig>

Example

<description>An example description of an arbitrary element.</description>

<format>

Configure various syntactic aspects of the assembly output.

Parents <mdalconfig>

attribute values default description
byte string db specify the pseudo-opcode for a byte definition.
dword string dl specify the pseudo-opcode for a double-word (long) definition.
endian big
little
little specify the byte order (endianness).
hex_prefix string $ specify the prefix for hexadecimal numbers.
word string dw specify the pseudo-opcode for a word definition.

Example

<format byte="defb" word="defw" hex_prefix="0x" endian="big" />

<iblock>

<iblock>s are used to define a wide range of variable-length structures such as Patterns, Tables, or Samples. Generally speaking, an <iblock> (input block) defines a logical structure containing one or more <ifield>s. An arbitrary number of instances of the contained <ifield>s may be set by the user. (You cannot control this behavior directly. MDAL automatically determines whether the number of field instances needs to be fixed or limited, by analyzing the output structure).

<iblock>s must be contained within an <igroup>, and must have one or more <ifield>s defined as child elements. The type of the <iblock> is inferred from the parent <igroup> element. Therefore, all <iblock>s within an <igroup> have the same type.

You can control the number of instances of the <iblock> that may exist within a module with the instances attribute, or the <instances> specifier. By default, MDAL assumes that at least one instance must exist, with no limit on the maximum number of instances. To set a fixed number of instances, use the instances attribute. To specify a range of possible instances, use the <instances> specifier. If the user has not created the required number of instances in the module, blank instances will automatically be generated. By declaring an <instances> specifier with it's min attribute set to 0, you can disable auto-generation of blank instances.

Parents <igroup>

Children <ifield>

Specifiers <instances>

attribute values default description
flags ordered Register the iblock to the <iorder> of the parent igroup.
instances uint Specify that a fixed number of instances of this <iblock> must exist within a module. If not used, it is assumed that at least one instance must exist, with no limit on the maximum number of instances. Alternatively, you can use the <instances> specifier to set a range of possible instances. If the user has not created the required number of instances in the module, blank instances will automatically be generated.

Example The following example defines an <iblock> of which at most 256 instances can exist in a module. A blank instance will be generated if the user did not create any instances.

<igroup id="Patterns" type="Pattern">
	<iblock id="CH1">
		<instances max="256" /><!-- no more than 256 instances can be created -->
		<ifield from="?MYCMD">
	</iblock>
</igroup>

<ifield>

<ifields> are the basic building blocks of any MDCONF input structure. They allow users to set any type of parameter used by player implementation. The behavior of <ifield>s is controlled through <command>s. Every <ifield> must be derived from a , and inherits its type.

Within the context of an <igroup>, an <ifield> is considered immutable, meaning users can only set it once for any given instance of the <igroup>. Within the context of an <iblock> or <iorder>, <ifield>s are considered mutable and can be set an arbitrary number of times (unless determined otherwise by the output structure).

The use_last_set flag controls what will happen if an <ofield> requests data from the <ifield>, but the user has not set the <ifield> at the point currently being evaluated. The regular behaviour is to return the <command>'s default value. With the use_last_set flag enabled, the <ifield> will instead return the last value it was set to in the current context. An <ifield> derived from a Label or Trigger type <command> will ignore this flag.

Parents <mdalconfig> <igroup> <iblock> <iorder>

attribute values description
flags use_last_set Return the last set value instead of the default value when not set. See above for details.
from command_id Derive the <ifield> from the given <command>.

<igroup>

An <igroup> is a virtual structure containing any number of <ifields> and/or <iblocks>. Within an MDAL module (.mdal), there can be an arbitrary number of instances of the <igroup>, unless configured otherwise, either by specifying a fixed number of instances with the instances attribute, or by specifying a valid range of instances by declaring an <instances> child element. If neither are declared, it is assumed that an empty instance of the <igroup> should be generated if the user did not create one in the module.

There are two types of <igroup>: Generic, and Pattern, which you can specify with the type attribute. By default, <igroup>s are assumed to be of type "Generic". Such an <igroup> could represent samples, instruments, or effects tables.

Specifying type Pattern will require the user to input a sequence (order list) for this <igroup>. In other words, this type works exactly as you might expect a Pattern to work in a tracker module context.

Parents <mdalconfig> <igroup>

Children <igroup> <iblock> <iorder> <ifield>

Specifiers <instances>

attribute values description
flags ordered Register the igroup to the <iorder> of the parent igroup.

<instances>

The <instances> specifier is used to limit the number of instances that can exist of the given parent element.

If the max attribute is not set, no upper limit is imposed. By setting the min attribute to 0 you can disable auto-generation of blank instances for the given element.

Parents <igroup> <iblock>

attribute values default description
min uint 1 Set the minimum number of element instances that must be generated.
max uint n/a Set the minimum number of element instances that must be generated.

<iorder>

Normally, order lists (sequences) are configured automatically based on whether any children of the parent <igroup> have been flagged as ordered. However, you can use the <iorder> element to define additional input fields for the sequence. This can be used to implement additional commands, such as transpose commands. To implement an order list jump, define a Label type <command>, and Reference type command which uses the special variable ?_ to target the current parent element.

For each igroup, only one iorder can be defined.

Parents <igroup>

Children <ifield>

Example

<command id="SEQLBL" type="Label" />
<command id="SEQJMP" bits="8" type="Reference" to="?_" />
<igroup>
	<iorder>
		<ifield from="SEQLBL">
		<ifield from="SEQJMP">
	</iorder>
	<iblock />
	...
</igroup>

<mdalconfig>

This element must occur in every MDCONF file, as parent node containing all other elements. The version attribute must be set to 2.

The <mdalconfig> element also functions as a global <igroup>. As such, it automatically includes two <ifield>s of type "String", which derive input from the TITLE and AUTHOR <command>s (which are also automatically generated).

Children <command> <igroup> <ifield> <ogroup>

Specifiers <description> <format>

attribute values description
target string Specify the target platform of the configuration. This is a hint for implementations, and has no effect in MDAL itself.
version n set the version number to n

Example

<mdalconfig version="2" target="zxspectrum48"> ... </mdalconfig>

<obitfield>

An <obitfield> is a special output structure that will map input from an <iblock> to bits in a chain of output bytes. <obitfield>s ignore the global endianness formatting and always use big endian order by default. You can override this behavior with the endian attribute. Within the context of an <obitfield>, all input fields will be evaluated as if they were derived from a <command> of type Trigger.

Parents <ogroup>

attribute values default description
bytes uint n/a Size of the output field in bytes.
endian big
little
big Specify the endianness of the output.
from iblock_id n/a The source <iblock> identifier.
order ascending
descending
descending Configure the order of the output. With descending order, the input block is mapped from the most significant bit to the least significant bit, so that the first input field will set the most significant bit, the second field will set the second most significant bit, and so forth. Setting order="ascending"reverses this.

<oblock>

An <oblock> commonly represents a pattern or table structure. Each <oblock> derives input from at least one <iblock>. <oblock>s may not be nested, and may only contain <ofield>s as child elements. Child elements will be output in the order in which they are defined.

An <oblock> can source from multiple <iblock>s, but each <iblock> can be sourced only once. That means it is not possible to define two different <oblock>s deriving input from the same <iblock>.

Within the context of an <oblock>, a variable number of instances of the contained <ofield>s may be output depending on user input in the module, unless specified ptherwise with the length or max_length attributes. Do not set length="1", instead make <ofield>s that should produce only a single instance within a given <ogroup> a direct child element of that <ogroup>.

Parents <ogroup>

Children <ofield>

attribute values default description
from comma seperated list of iblock_ids n/a
instances uint n/a Specify that a fixed number of instances of this <oblock> must exist within a module. If not used, it is assumed that at least one instance must exist, with no limit on the maximum number of instances. Alternatively, you can use the <instances> specifier to set a range of possible instances. If the user has not created the required number of instances in the module, blank instances will automatically be generated.
length uint n/a Specify that instances of this block must have a fixed length.
max_length uint n/a Limit the length of instances of this block.

<ofield>

<ofield>s derive output from a combination of one or more source <ifield>s, specialized variable, and fixed values, depending on definable conditions.

Within the context of an <oblock> or <oorder>, <ofield>s generate output a variable number of times depending on user input in the module, unless specified otherwise by the parent element. When used outside of an <oblock> or <oorder>, <ofield>s generate output only once per <ogroup> instance.

To configure the details of <ofield> output generation, use the <required> and <set> specifiers. In simple cases where output is unconditional, you can use the value attribute in place of a full specification.

Parents <ogroup> <oblock> <oorder>

Specifiers <required> <set>

attribute values default description
bytes uint n/a Size of the output field in bytes.
value arithmetic expression n/a Shorthand for defining a field that is always <required> and is unconditionally <set> by the given expression.

<ogroup>

An <ogroup> is used to group together related <oblock>s, <obitfield>s, <ofield>s, <olist>s, and <ogroup>s and manage the order in which generated data will be output. Child elements will be output in the order in which they are defined. <ogroup>s can be nested.

Parents <mdalconfig> <ogroup>

Children <ogroup> <oblock> <obitfield> <ofield> <olist> <ogroup>

<olist>

An <olist> will cause the generation of a list of the generated instances of <oblock>s within the given <ogroup>.

Parents <ogroup>

attribute values default description
from igroup_id n/a Specify the source input group.
bytes uint 2 The size of the list elements, in bytes.
type numeric
offset
pointer
pointer

<oorder>

An <oorder> will cause an order list (sequence) to be generated for the given <ogroup>. Furthermore, it will automatically set up an <iorder> input order list for the associated <igroup>s.

The layout of the output is normally configured automatically depending on the type and layout attributes. Defaults can however be overridden by defining additional <ofield>s. A common use case would be an order list that uses additional features such as order jumps or transpose commands. <ofield>s within an <oorder> work the same way as they would within an <oblock>.

You can define more than one <oorder> per <ogroup>. This is useful for cases where the order list uses pointers that need to be split in two parts containing the MSB and LSB of the pointer, respectively.

Parents <ogroup>

Children <ofield>

attribute values default description
bytes uint 2 The size of the sequence elements, in bytes.
from igroup_id n/a Specify the source input group.
layout custom
matrix
merge
split
virtual
merge Configure the layout of the order list.
  • custom enables manual configuration using <ofield>s.
  • matrix will output seperate sequence items for each associated <oblock>.
  • merge will address the parent <ogroup>.
  • split will generate a seperate order list for each associated <oblock>.
  • virtual will not generate an actual order list at all, but instead combine input blocks into a monolithic output block according to the input order specified.
type numeric
offset
pointer
pointer Configure how order list items are formatted.
  • numeric outputs a simple numbers that correspond to the order in which the <oblock>s in question are defined in the file.
  • offset outputs a binary file offset to the <oblock> in question.
  • pointer outputs a pointer to the <oblock> in question.

<range>

The <range> specifier limits the accepted input range for <command>s of type Int and Uint.

Parents <command>

attribute values default description
min int/uint 0 Set the lower limit.
max int/uint n/a Set the upper limit.

<required>

Specify under which condition an <ofield> must generate output. When the if attribute is not specified, the given <ofield> must always generate output.

This specifier is only effective when used on an <ofield> contained within an <oblock>.

An <ofield> whose corresponding <ifield>(s) have been set by the user on the step being currently evaluated is always considered to require outout generation. Hence the following <required> declaration is redundant:

<ofield bytes="2">
	<required if=(set(IFIELD_ID))>
	<set value="?IFIELD_ID">	
</ofield>

Multiple requirement clauses can be specified for a given <ifield>.

Parents <ofield>

attribute values default description
if conditinal expression "" Specify a key.

<set>

Parents <ofield>

<substitute>

Specify a key/value pair for <command>s of type Key.

Parents: <command>

attribute values default description
key string n/a Specify a key.
value int/uint n/a Specify value or string that will replace the given key.

Example

```

Expressions

Variables

In MDCONF, variables are almost always references to element identifiers. There are no general purpose variables. Variables are always prefixed with an ? (question mark).

A few specialized variables exist for certain elements. These are

?_

Evaluates as a reference to the parent instance of the element instance currently being evaluated.

?_blocklength

Within the context of an <oblock>, evaluates to the length of the current <oblock>, in steps. Within the context of an <oorder> or <olist>, evaluates to the length of referenced <oblock>, in steps.

?_blocksize

Within the context of an <oblock>, evaluates to the size of the current <oblock>, in bytes. Within the context of an <oorder> or <olist>, evaluates to the size of referenced <oblock>, in bytes. Note that it is not possible to measure the size of generated code with this variable.

?_blockpos

Evaluates to the position (step) within the current <oblock>.

?_start

Evaluates to true when evaluating the first row of an <oblock>, <oorder>, or <olist>.

?_end

Evaluates to true when evaluating the last row of an <oblock>, <oorder>, or <olist>.

?_listlength

Evaluates to the length of the current <oorder> <olist>, in steps.

?_listsize

Evaluates the size of the current <oorder> or <olist>, in bytes.

?_listpos

Evaluates to the position (step) within the current <oorder>, or <olist>.

?_orderstart

Evaluates to true when evaluating the first row of a block that is used at the start of a song.

Arithmetic and Bitwise Expressions

Arithmetic expressions are used for the value attribute of <ofield>s. Prefix notation is used throughout. Accepted operands are <ifield>s, the ?_ variable denoting the current [<ofield>], or another arithmetic expression.

Note that for all arithmetic and bitwise operations, the operand size is extended to match the size of the <ofield>.

All expressions use standard Lisp syntax.

The following operators are available:

(+ operand_1, operand_2, ... operand_n)

Calculate the sum of the given operands. Note that the + operater can also be used to concatenate string operands.

(- operand_1, operand_2, ... operand_n), (* operand_1, operand_2, ... operand_n), (/ operand_1, operand_2, ... operand_n)

Calculate the difference, product, or quotient of the given integer operands.

(mod operand_1, operand_2)

Calculate the modulo of the given integer operands.

(lsl operand, amount), lsr(operand, amount)

Perform a bitwise logical shift left or right by amount bits on the given integer operand.

(asr operand, amount)

Perform a bitwise arithmetic (sign-extended) shift right by amount bits on the given integer operand.

(rol operand, amount)

Perform a bitwise rotation to the left by amount bits on the given integer operand.

sign(operand)

Sign-extend the given integer operand.

Conditional Expressions

Conditional expressions are used in two cases:

  1. To specify either the conditions needed to fulfill a certain requirement as specified by a <required> element
  2. To specify the circumstances under which a certain <set> action is to be performed.

Conditional expressions are formed using one or more of the conditional operators described below. Conditional expressions can be nested.

(set field_1, field_2, ... field_n)

Evaluates true if any of the given <ifield>s are currently set. A field that generated output through the use_last_set command attribute is not considered set.

(not expression)

Evaluates to true if the given expression evaluates to false.

(= field, value)

Evaluates to true if the given field holds the given value. This works for fields derived from any command type except the trigger type.

(le field, value)

Evaluates to true if the given field holds a value that is less than or equal to the given value. This only works for fields derived from a command of type value.

(lt field, value)

Evaluates to true if the given field holds a value that is less than the given value. This only works for fields derived from a command of type value.

ge(field, value)

Evaluates to true if the given field holds a value that is greater than or equal to the given value. This only works for fields derived from a command of type value.

(gt field, value)

Evaluates to true if the given field holds a value that is greater than the given value. This only works for fields derived from a command of type value.

(and expression_1, expression_2, ... expression_n)

Evaluates to true if all of the given expressions evaluate to true.

(or expression_1, expression_2, ... expression_n)

Evaluates to true if any of the given expressions evaluate to true.

(eor expression_1, expression_2, ... expression_n)

Evaluates to true if exactly one of the given expressions evaluates to true.

Contributors

The MDAL Configuration Standard is developed by utz, with the help of the following people:

Alone Coder, Garvalf, Sven Oliver Moll, Brandon "Jangler" Mulcahy, Lasse Öörni, Saga Musix, Shiru