"symfonion" is a JSON-based music macro language, which enables you to describe a piece of music as easily as 8-bit days' MML but in a much more structured way.
YAML is the recommended input format. The symfonion launcher transparently converts .yaml / .yml files to JSON before passing them to the processor. Because YAML is a strict superset of JSON, every JSON example in this document is also valid YAML and can be written in YAML style instead. The YAML form is generally more readable: it requires no quotes around most strings, allows comments (#), and avoids the syntactic noise of JSON. Where it helps, this document shows both forms side by side.
|
Here is an example piece of music written in symfonion. First in YAML (recommended), then the equivalent JSON:
parts:
testviolin:
channel: 0
sequence:
- beats: "8/8"
parts:
- name: testviolin
body: "C2;C2"
length: "8"
- beats: "8/8"
parts:
- name: testviolin
body: "C2;C2"
length: "8"
The equivalent JSON form (also accepted):
{
"parts":{
"testviolin":{"channel":0}
},
"sequence":[
{
"beats":"8/8",
"parts":[
{"name":"testviolin", "body":"C2;C2", "length":"8"}
]
},
{
"beats":"8/8",
"parts":[
{"name":"testviolin", "body":"C2;C2", "length":"8"}
]
}
]
}
Basically, a symfonion program is a JSON object and its two most important elements are parts and sequence.
| Features that are no longer recommended are documented in Deprecated Features. |
A value for parts is a dictionary which describes 'parts' in music score (like 'piano part', 'guitar part', and so on). sequence organises what each part plays in each bar. For instance, "testviolin" plays a two-note phrase twice in the example above.
Parts
By using 'parts' element you can specify which part uses which midi channel. In the example below, the part 'test' will use the midi channel '0'. Note that in symfonion language, A channel number is 0 origin.
{
"parts":{
"test":{"channel":0}
}
}
If you want to use external MIDI devices, you need to specify "port" attribute for each part,
{
"parts":{
"test":{ "channel":0, "port":"part1" }
}
}
Part names must be defined through system properties. For more detail, please refer to [Command line parameters](CLI.adoc).
Notemaps
"Notemaps" is a bit advanced feature. By using this feature, users can configure a drum map for a pre-General MIDI age’s synthesizer (Yamaha SY-77 for instance). (t.b.d.)
Notes and Strokes
Each part entry in a sequence bar has a body that defines the notes to play, plus optional default parameters (length, gate, velocitybase, etc.) that apply to all notes in that entry.
Notemap
A user can set this attribute to a name defined in the section "notemaps" or predefined note map name.
There are two pre-defined note maps, which are "normal" and "percussion". "normal" is a normal note map where "C" is mapped to 60 and "D" to 62. "percussion" is configured for drum kits defined in General MIDI standard.
Default note map (normal) definition follows
| # | Note name | Number |
|---|---|---|
1 |
C |
60 |
2 |
D |
62 |
3 |
E |
64 |
4 |
F |
65 |
5 |
G |
67 |
6 |
A |
69 |
7 |
B |
71 |
And percussion (percussion) is like below
| # | Name | Number | Note |
|---|---|---|---|
1 |
B |
60 |
Bass drum |
2 |
S |
62 |
Snare drum |
3 |
C |
64 |
Cymbal |
4 |
O |
65 |
Opened Hi-hat |
5 |
H |
67 |
Hi-hat |
6 |
T |
69 |
Tomtom |
Body
body defines the notes to play in a part entry. The preferred form is a semicolon-separated string of note tokens:
{"name":"piano", "body":"C;E;G;C>", "length":"8"}
Each token is a note (see [_notes]) and the semicolons separate successive strokes. This is the most concise way to write melodies and is the recommended style.
Alternatively, body can be a JSON array of stroke objects, which is useful when individual strokes need different parameters (different lengths, MIDI control messages, etc.):
{
"body":[
{"notes":"C", "length":"2", "pan":127, "program":2},
{"notes":"C", "length":"2", "pan":0},
{"...": "..."}
]
}
Relationships between a part entry, strokes, and notes are described in the figure below.
Failed to generate image: Could not load Ditaa. Either require 'asciidoctor-diagram-ditaamini' or specify the location of the Ditaa JAR(s) using the 'DIAGRAM_DITAA_CLASSPATH' environment variable.
+----------+ body+----------------+1 n+----------+
|Part entry|<>-->|Stroke |<>---->| Note |
+----------+1 n+----------------+ +----------+
| |volume:int[] | |int key |
| |pan:int[] | |int accent|
| |reverb:int[] | +----------+
| |chorus:int[] |
| |pitch:int[] |
| |modulation:int[]|
| |program:int |
| |tempo:int |
| +----------------+
|
| +----------+
+-->|Parameters|
defaultParameters 1+----------+
A stroke is a set of Midi messages which are transmitted to a single midi channel of a midi device during a note (or notes belongs to one chord in a score) is being played. A part entry is a sequence of strokes with associated default parameters; its relationships with strokes and notes are described in the figure above.
Midi messages in symfonion can be divided into two groups. One is 'note' messages (or simply "notes") and the other is 'non-note' messages. There are several types of non-note messages, which are 'volume, 'pan', 'reverb', 'chorus', 'pitch', and so on. These are played as independent messages on a midi device.
On the other hand, there are some parameters which directly belong to note messages such as velocity and length. And these parameters modify the note messages directly. In the example below, "velocitybase" is a parameter which modifies a note and "volume" is a non note message which is translated into a control change message (#7).
{
"notes":"C",
"$velocitybasse":100,
"volume":88,
"...": ""
}
But in terms of symfonion syntax, users can not tell which attributes are parameters for a note and which are non-note messages. But the developer of symfonion thought that it is not important for users and symfonion should abstract the midi message/event structure. In other words, users do not need to know if an attribute is a note parameter or a non-note message.
Notes
Notes in a stroke must match a regular expression pattern defined by a string below. (This is a "Java-style" regular expression and its syntax slightly different from other ones such as perl’s. Refer to this document: java.util.regex.Pattern)
"([A-Zac-z])([#b]*)([><]*)([\\+\\-]*)"
For example, strings below are valid for this attribute.
"C"; // Translated to "C3" (Note number 60)
"D#"; // "D sharp" (Note number 63)
"D##"; // "D doublesharp" (Note number 64)
"Eb"; // "E flat" (Note number 63)
"Ebb"; // "E doubleflat" (Note number 62)
"C>"; // "C4" (Note number 72)
"C>>"; // "C5" (Note number 84)
"C<"; // "C2" (Note number 48)
"C<<"; // "C1" (Note number 36)
"C+"; // "C3" but velocity will be velocitybase + velocitydelta
"C++"; // "C3" but velocity will be velocitybase + velocitydelta * 2
"C-"; // "C3" but velocity will be velocitybase - velocitydelta
"C--"; // "C3" but velocity will be velocitybase - velocitydelta * 2
"C>#+"; // You can use ">", "#", and "+" (and other modifiers) in combination.
"CEG"; // Chord C (C3, E3, and G3 will be played at once.)
"C#>>+++E#++G#+"; // You can also use modifier in combination even when you are writing a chord.
"Db>++4;r4;r8;BbDb>8;DbGb>8;GbDb>8;DbAb>+1";
// You can concatenate strokes by using semi colons.
Pickup notation (anacrusis)
The | character in a body string marks a barline: notes written before | are pickup notes (also called an anacrusis in classical music theory) and play in the tail of the preceding bar, overlaid on top of its existing content.
Notes written after | are the main body of the current bar.
{ "beats": "4/4", "parts": [{"name": "piano", "body": "r4;r4;r4;r4"}] },
{ "beats": "4/4", "parts": [{"name": "piano", "body": "E8|E4;D4;C4;r4"}] }
In this example the E8 pickup plays in the last 1/8 of the preceding 4/4 bar, and E4;D4;C4;r4 fills the current bar.
This is more readable than spelling the pickup out explicitly in the previous bar:
{ "beats": "4/4", "parts": [{"name": "piano", "body": "r2;r4;r8;E8"}] },
{ "beats": "4/4", "parts": [{"name": "piano", "body": "E4;D4;C4;r4"}] }
Consecutive bars with pickup notation chain naturally — each bar’s pickup overlays the tail of the bar that precedes it:
{ "beats": "4/4", "parts": [{"name": "piano", "body": "r4;r4;r4;r4"}] },
{ "beats": "4/4", "parts": [{"name": "piano", "body": "E8|E4;D4;C4;r4"}] },
{ "beats": "4/4", "parts": [{"name": "piano", "body": "E8|E4;D4;C4;r4"}] }
| Pickup notes require a preceding bar in the sequence. If a pickup appears in the very first bar of the sequence it will be placed before tick 0 and may not play. |
Non-note messages
Some of non-note messages, for example 'volume', can have arrays as their values.
{
"notes":"C",
"volume":[0,10,20,40]
}
If this stroke is a quarter note, 4 volume messages (control change #7) each of whose length is equal to sixteenth note are sent one after another. The values of the messages will be 0, 10, 20, and 40.
You can omit values in between concrete values like this,
[
{
"notes":"C",
"volume":[0,",..",40]
},
{
"notes":"C",
"volume":[0,"..",80,"..",100]
}
]
symfonion fills the gap by linear interpolation.
This feature is implemented using GSON’s behavior, where missing values are interpreted as null.
`null`s will be replaced with appropriate values by symfonion.
However, this is parser dependent semantics and such data is considered "malformed" JSON, strictly speaking.
You can use a string contains only dots (…) instead.
{
"notes":"C",
"volume":[0,"..",80,"...",100]
}
One . will be interpreted as one null, which will be interpolated by symfonion.
If you give an integer to "volume" attribute, its considered as an array which has only one value. In other words, strokes in the example below are equivalent to each other.
[
{
"notes":"C",
"volume":80
},
{
"notes":"C",
"volume":[80]
}
]
This feature is called "arrayable" and users can use this features for "volume", "pan", "reverb", and so on.
Volume
This feature is 'arrayable'.
{
"notes":"C",
"volume":[0,"..",70,"..",80],
"...": ""
}
Volume change messages (control change #7) are sent with given values to the channel associated with this part entry.
Pan
This feature is 'arrayable'.
{
"notes":"C",
"pan":[0,".....",127],
"...": ""
}
Pan change messages (control change #10) are sent with given values to the channel associated with this part entry.
Reverb
This feature is 'arrayable'.
{
"notes":"C",
"reverb":[0,".....",127],
"...": "..."
}
Reverb change messages (control change #91) are sent with given values to the channel associated with this part entry.
Chorus
This feature is 'arrayable'.
{
"notes":"C",
"chorus":[0,".....",127],
}
Chorus change messages (control change #93) are sent with given values to the channel associated with this part entry.
Pitch
This feature is 'arrayable'.
{
"notes":"C",
"pitch":[0,".....",127],
"...": ""
}
Pitch bend messages are sent with given values to the channel associated with this part entry.
127 maximum upward bend / 64 = nobend / 0 maximum downward bend.
Modulation
This feature is 'arrayable'.
{
"notes":"C",
"modulation":[0,".....",127],
"...": "..."
}
Modulation wheel messages (control change #1) are sent with given values to the channel associated with this part entry.
Program
This feature is NOT 'arrayable'.
{
"notes":"C",
"program":0,
"...": "..."
}
A program change message is sent with a given value to the channel associated with this part entry.
Bank
To select a bank, you can use bank attribute in a stroke.
{
"notes": "C",
"bank": 12.3,
"...": "..."
}
Note that an integer will result in an error because a bank select message needs both MSB and LSB. Use a double and its integer part will be used as MSB and the fractional part will be used LSB part.
Note parameters
Notes are also represented by midi messages in midi device layer. Actually, one note consists of two messages, one is "note on" and the other is "note off".
And usually the time between note on and note off is slight shorter than the time calculated from the tempo and the length of the note. The time between note-on and note-off is usually called 'gate-time'.
Failed to generate image: Could not load Ditaa. Either require 'asciidoctor-diagram-ditaamini' or specify the location of the Ditaa JAR(s) using the 'DIAGRAM_DITAA_CLASSPATH' environment variable.
Note-on Note-off
| gate time | |
|<---------->| 80 |
| note length| |
|<-----------+---->| 100
| | |
/-+ | |
| | | |
\-/
Each note-on message has 'velocity' value. Velocity of a note message represents how 'strong' the note should be played. If a note has a larger velocity value, it will be played louder by a synthesizer. (Modern synthesizers changes not only the volume but also tone and other features of the note.)
Length
length is a string/int value which defines the note length.
Both of below are the same meanin
g.
[
{"notes":"C", "length":"8"},
{"notes":"C", "length":8},
{}
]
But to create a dotted note, you can only use a string for length:
[
{"notes":"C", "length":"8."}, (1)
{}
]
| 1 | dotted eighth note. |
Also, you can write double dotted/triple dotted notes by using a string.
[
{"notes":"C", "length":"8.."}, (1)
{}
]
| 1 | double dotted eighth note. |
[
{"notes":"C", "length":"8..."}, (1)
{}
]
| 1 | // triple dotted eighth note. |
The default value is "4", which means a quarter note.
Velocity base, velocity delta
"velocitybase" is an integer which specifies the velocity value of notes if they have no accent sign ("+" and "-").
And "velocitydelta" is also an integer which specifies the value one accent sign ("+" and "-") is equal to.
So, the velocity value set to midi messages can be calculated by the formula below,
velocity value = velocitybase + ( (number of "+" in notes)
- (number of "-" in notes) ) * velocitydelta
The default value of velocitybase is 64 and velocitydelta is 10
Gate
In symfonion, The note parameter "gate" is a float value which signifies the ratio of the gate time to the note length.
The default value is 0.8.
Transpose
All notes have their own number. For instance, C3 (The center "C") is 60. Users can transpose the notes by using this feature.
[
{"notes":"C", "transpose":1},
{}
]
The example above will be played as note 61 (=C3 +1 = C3#) Negative values are also allowed.
This parameter is most useful when set directly on a bar part entry (alongside name and body) to transpose all notes in that entry uniformly.
the default value is 0.
Defining default values in a bar part entry
Since it is painful to write gate or velocitybase for every note, users can define default values directly on the bar part entry. These defaults apply to all notes in body and can be overridden per-stroke.
Users can write a bar’s part entry like below.
{
"name":"piano",
"body":[
{ "notes":"CEG", "length":"8", "gate":"0.5" },
{ "notes":"CEG", "length":"8", "gate":"0.5" },
":",
":",
{ "notes":"CEG", "length":"8", "gate":"0.5" }
]
}
By using this feature, the part entry above can be rewritten like this,
{
"name":"piano",
"body":[
{ "notes":"CEG" },
{ "notes":"CEG" },
":",
":",
{ "notes":"CEG" }
],
"length":"8",
"gate":"0.5"
}
Users can write note parameters directly under a part entry in parts, and they are in effect for all the notes in the body attribute of that same entry.
Groove
In real musical works, all the sixteenth notes have neither the same length nor the same strength. One comes at the beginning of a bar usually longer and stronger than the others. And if there are 4 forth notes in a bar, second and forth ones are shorter and weaker than the others. These fluctuations are called 'grooves' in the DTM world.
In symfonion syntax, you can define grooves like below,
{
"grooves":{
"16beats":[
{ "length":"16", "ticks":28, "accent":30 },
{ "length":"16", "ticks":20, "accent":-10 },
{ "length":"16", "ticks":26, "accent":10 },
{ "length":"16", "ticks":22, "accent":-5},
{ "length":"16", "ticks":28, "accent":20 },
{ "length":"16", "ticks":20, "accent":-8 },
{ "length":"16", "ticks":26, "accent":10 },
{ "length":"16", "ticks":22, "accent":-4 },
{ "length":"16", "ticks":28, "accent":25 },
{ "length":"16", "ticks":20, "accent":-8 },
{ "length":"16", "ticks":26, "accent":10 },
{ "length":"16", "ticks":22, "accent":-5 },
{ "length":"16", "ticks":28, "accent":15 },
{ "length":"16", "ticks":20, "accent":-8 },
{ "length":"16", "ticks":26, "accent":10 },
{ "length":"16", "ticks":22, "accent":-10 }
]
}
}
In this example, there are 16 sixteenth notes each of which has independent "ticks" and "accent". If a part entry is played with this groove, the first sixteen notes will be length of 28 MIDI ticks, while usually sixteenth notes have only 24 MIDI ticks (currently, a whole note is fixed to 384 MIDI ticks in symfonion). And 30 is added to the original velocity of the note.
Example: swing feel with eighth notes
The following is a complete example using a swing groove (example-groove-swing.json).
The swing8 groove defines 8 eighth-note beats in a 2:1 long-short pattern (64 ticks / 32 ticks, summing to 96 ticks = one quarter note), giving a jazz-swing feel.
Downbeats are accented, upbeats de-emphasised.
{
"settings": {},
"parts": {
"melody": { "channel": 0, "port": "port1" }
},
"grooves": {
"swing8": [
{ "length": "8", "ticks": 64, "accent": 20 },
{ "length": "8", "ticks": 32, "accent": -10 },
{ "length": "8", "ticks": 64, "accent": 15 },
{ "length": "8", "ticks": 32, "accent": -8 },
{ "length": "8", "ticks": 64, "accent": 15 },
{ "length": "8", "ticks": 32, "accent": -8 },
{ "length": "8", "ticks": 64, "accent": 10 },
{ "length": "8", "ticks": 32, "accent": -10 }
]
},
"sequence": [
{
"beats": "1/1",
"parts": [
{ "name": "melody", "body": [{ "tempo": 120, "reverb": 40, "chorus": 0 }] }
]
},
{
"beats": "8/8",
"parts": [
{ "name": "melody", "body": "C;E;G;A;C>;E>;G>;A>", "length": 8, "gate": 0.85 }
],
"groove": "swing8"
},
{
"beats": "8/8",
"parts": [
{ "name": "melody", "body": "A>;G>;E>;C>;A;G;E;C", "length": 8, "gate": 0.85 }
],
"groove": "swing8"
},
{
"beats": "8/8",
"parts": [
{ "name": "melody", "body": "C;E;G;A;C>;E>;G>;A>", "length": 8, "gate": 0.85 }
],
"groove": "swing8"
},
{
"beats": "8/8",
"parts": [
{ "name": "melody", "body": "A>;G>;E>;C>;A;G;E;C", "length": 8, "gate": 0.85 }
],
"groove": "swing8"
}
]
}
Sequence
In the sequence section, users organize music bar by bar.
The value of the sequence attribute is a list of bar objects.
Each bar object has beats, parts, and optionally groove and labels.
{
"sequence":[
{
"beats":"8/8",
"parts":[
{"name":"test", "body":"C2;D2", "length":"8"}
]
},
{
"beats":"8/8",
"parts":[
{"name":"test", "body":"E2;F2", "length":"8"}
]
}
]
}
Beats
beats specifies the length of the bar as a fraction string, like "4/4", "3/4", "16/16", and so on.
"8/8", "4/4", and "16/16" are all equivalent.
If a part’s content is longer than beats, notes beyond the bar boundary are not played.
The default value is "4/4".
Repeating bars
To repeat the same bar, simply list it more than once in sequence:
[
{
"beats":"16/16",
"parts":[{"name":"vocal", "body":"C;D;E;F;G;A;B;C>", "length":"8"}],
"groove":"16beats"
},
{
"beats":"16/16",
"parts":[{"name":"vocal", "body":"C;D;E;F;G;A;B;C>", "length":"8"}],
"groove":"16beats"
}
]
Parts in a bar
parts in a sequence bar is a JSON array of part entries.
Each element must have a name field identifying which part it belongs to (as defined in the top-level parts section), plus inline musical content like body, length, gate, etc.
{
"beats":"16/16",
"parts":[
{"name":"vocal", "body":"C;D;E;F;G;A;B;C>", "length":"8"}
],
"groove":"16beats"
}
If the groove 16beats is not defined in the grooves section, an error will be reported.
Stacking (layering) parts
To play multiple entries simultaneously on the same part (e.g., a melody over a fade-in volume sweep), add multiple entries with the same name:
{
"beats":"4/4",
"parts":[
{"name":"piano", "body":"C4;E4;G4;C>4", "length":"4"},
{"name":"piano", "body":"r1", "volume":[0,"............",127]}
]
}
Both entries start at the beginning of the bar and play simultaneously.
Example: crescendo and decrescendo
The following complete example (example-stacking-crescendo.json) plays a C major arpeggio ascending with a crescendo (volume 20→110), then descending with a decrescendo (volume 110→20).
The melody and the volume sweep are two stacked entries on the same piano part.
{
"settings": {},
"parts": {
"piano": { "channel": 0, "port": "port1" }
},
"sequence": [
{
"beats": "1/1",
"parts": [
{ "name": "piano", "body": [{ "tempo": 92 }] }
]
},
{
"beats": "4/4",
"parts": [
{ "name": "piano", "body": "C;E;G;C>", "length": "4", "velocitybase": 80 },
{ "name": "piano", "body": [{"notes": "r1", "volume": [20, "............", 110]}] }
]
},
{
"beats": "4/4",
"parts": [
{ "name": "piano", "body": "C>;G;E;C", "length": "4", "velocitybase": 80 },
{ "name": "piano", "body": [{"notes": "r1", "volume": [110, "............", 20]}] }
]
}
]
}
Enjoy music!