MDMOD Version 2 Specification Draft
This specification draft outlines the upcoming MDAL Module (MMOD) standard Version 2. The standard is not yet finalized, and may change at any point.
The MMOD syntax is based on Symbolic Expressions, as used by the Scheme programming language. A full explanation of Symbolic Expressions is out of scope for this document. Refer to the R5RS Standard for details.
Brief overview of Symbolic Expressions
Symbolic Expressions, better known as s-expressions, are nested lists. They consist of 2 basic elements: Atoms and Pairs, also known as cons cells. An atom is a single element, which can be a symbol, a keyword, a number, a character, a string delimited with double quotes, or the
null value, which is represented by the empty list
(). A pair is a structure consisting of two elements, in the form
(element-1 . element-2). The short-hand form
(element-1 element-2 ... element-n) denotes a list of nested pairs. For example, the list
(1 2 3) expands to (1 . (2 . (3 . ()))). The first element of a pair (and by extension, a list) is called the car. The second element of a pair is called the cdr. In a list, the cdr represents all elements except the first.
A symbol can contain any alphanumeric character, plus the following characters:
! $ % & * + - . / : < = > ? @ ^ _ ~ #. A symbol must not start with a number or with the character
#. Symbols and keywords are case-sensitive.
A semicolon denotes the start of a comment, delimited by a newline.
; a comment foo ; a symbol, which is an atom +bar/ ; also a symbol (1 #\c "baz") ; a list of three atoms: the number 1, the character "c", and the string "baz" (foo (bar (baz))) ; a nested list
Deviations from Standard Scheme
- Values are implicitly quoted.
- Keywords may use the alternative form
keyword:in place of
- Text enclosed by
#|...|#represents a block comment.
MDAL modules are nested lists consisting of Nodes. MDAL defines three node types: Group, Block, and Field. The underlying MDEF configuration defines which nodes are available. Each node may have zero or more Node Instances. A MDEF configuration may set limits on the number of instances that a node can have.
Node instances are represented as lists, using the following syntax:
(NODE-ID #:id INSTANCE-ID #:name NAME VALUE ...)
NODE-ID is the symbol identifying the node,
INSTANCE-ID is the numeric identifier for the node instance, NAME is a string describing the node instance, and
VALUE is the node instance's value. Depending on the node type, a node instance may have several values.
Instance identifiers and names are omitted for Field members of Block nodes. If the instance identifier is 0, it may be omitted for any node type. The name is always optional, and MDAL implementations may ignore it.
The contents of an MDAL module are wrapped in an implicit root node, called the
GLOBAL node. It takes the form
(mdal-module #:version VERSION #:mdef DEF #:engine-version VERSION2 SUBNODE ...)
where VERSION is the MMOD standard version (eg. 2), DEF is a string naming the module's MDEF configuration, VERSION2 is the required MDEF engine version, followed by one or more SUBNODEs.
The simplest node type is the Field. An instance of a Field node contains a single value. The type of value is specified by the underlying MDAL engine definition.
MDAL supports the following types:
uint: a signed resp. unsigned integer with a given range.
ukey: a list of named parameters, which evaluate to a signed resp. unsigned integer.
modifier: an arithmetic type that modifies another field.
string: a text string. Must be enclosed in double quotes.
triggera field that takes only one value,
label: used to assign an identifier to the given step.
reference: a reference to a group or block instance, or a LABEL within that group or block.
defines instance 0 of the field
BPM, and assigns the value
120 to it.
Block nodes contain one or more Field nodes. The value of a Block instance consists of one or more rows, where each row holds a single instance of each of the Block's Fields. In traditional tracker terms, a Block can represent any logical unit such as a pattern, an order list, or a sample.
A row is expressed as a plain list. Assuming the Block's Field nodes are
BAZ, a row could be written like so:
((FOO 1) (BAR 2) (BAZ 3))
If all fields are set on a given row, the field identifiers may be omitted. The following is equivalent to the previous example:
(1 2 3)
Fields that are not changed on a given row are omitted.
would set field
2, leaving the other fields unchanged.
One or more consecutive rows with no field change may be replaced by an integer denoting the number of rows with no change that follow. See below for a full example.
Group nodes are the highest level meta-structure in MMOD. Groups can contain other Groups, Blocks, and Fields, the details of which are specified by the underlying MDAL Engine Definition. The
GLOBAL node is also a group node. See below for a full example.
The global Fields
LICENSE, are available in every MDAL engine definition, and may be used to specify the author, title, and license information for a module.
Including External Resources
Instead of assigning a value directly to a node instance, you can include an external resource in plain text format with the
include keyword. Some configurations may also allow assignment to binary files, using the
incbin keyword. The argument to these keywords is a file path.
The following example assumes an underlying MDAL engine definition that specifies
- a global Field called
BPMwhich is of type uint
- a Group called
PATTERNS, which contains 3 Blocks:
ORDERblock of which only one instance can exist, containing a Field of type uint called
ROW_LENGTH, and a Field of type reference called
- a Block called
CH1, containing two Fields called
SAMPLE, which are of type ukey and reference, respectively.
- a Group called
SAMPLES, which contains a Block called
SAMPLE, which contains a single field (name and type are irrelevant for this example)
; This is a line comment, which will be ignored by the parser. (mdal-module #:version 2 ;; required: MMOD standard version #:mdef "MyConfig" ;; specify the MDAL engine definition to use. #:engine-version: 1.0 ;; specify the version of the MDAL engine definition to use. (AUTHOR "Great Artist") ;; This is "My Great Song" by "Great Artist" (TITLE "My Great Song") ;; AUTHOR and TITLE are available for all configurations. (BPM 120) ;; BPM is an additional global field defined by the configuration. (PATTERNS ;; Begin of instance 0 of group PATTERNS. For instance 0, the ;; instance identifier may be omitted. (ORDER ;; Defines an order (sequence) block instance for the blocks in the ;; PATTERNS group instance. Orders blocks follow the same syntax ;; rules as ordinary blocks. ((ROW_LENGTH 16) (R_CH1 0)) ;; First row, setting field ROW_LENGTH to 16 and field R_CH1 to 0. ;; In this case, R_CH1 is assumed to be a reference to ;; instances of the the CH1 Block. (16 0) ;; Same as above. Since all fields are specified, field qualifiers ;; can be omitted. ((R_CH1 1)) ;; Set R_CH1 to 1. ROW_LENGTH is still set to 16. ) ;; end of the ORDER block instance (CH1 #:id 0 #:name "intro" ;; Define instance 0 of block CH1 and name it "intro". ((NOTE a3) (SAMPLE 0)) ;; First row, setting field NOTE to "a3", and field SAMPLE to 0. 1 ;; A row that does not set any fields. ((NOTE c4)) ;; Set field NOTE to "c4". SAMPLE is still set to 0. (e4 1) ;; Field qualifiers can be omitted if all fields are set. 4 ;; Do not set any fields for the next 4 rows. ) (CH1 #:id 1 #:include "ptn1.txt") ;; load the contents of the block from an external text file. ) ;; End of group PATTERNS. (SAMPLES ;; Begin of group SAMPLES. (SAMPLE #:id 0 #:name "kick" ;; Load the contents of the block from an external #:incbin "kick.raw") ;; binary file. ) ;; End of group SAMPLES. ) ;; end of the module.
Changes from Version 1
Notable changes from Version 1 include:
- Name change: MDMOD -> MMOD
- The custom syntax is replaced by Symbolic Expressions.
- Node instances are addressed through numerical identifiers instead of named strings.
- Automatic scope resolution is dropped, and replaced by explicit Assignments.
- The Group element is introduced.
- Order lists (sequences) no longer have special syntax, and instead are treated as ordinary Blocks. Order Lists are now entirely virtual and always use a matrix style like in Famitracker or LSDJ, as opposed to glob style used by XM.