This is a library that compiles scores written in Music Macro Language ("MML
Codes" written for Mabinogi or Archeage) into the network packets for the
"Perform" action in Final Fantasy 14.
But why?
This library was mainly created to make it a lot simpler to compose scores that
can be played in FFXIV. Currently, typing out notes in a format like
A (+0), Bb (-1), C# (+1)
is somewhat readable, but very verbose and there
aren't really any tools out there to visualize scores in this format. Also,
there's no standard way of specifying note length or tempo.
MML is a lot more convenient since there exist thousands of scores out there
in MML format, and you can use 3ML Editor to convert MIDI to MML as well as to
visualize the song.
See the For developers section for why the output format
is network packets.
Usage
Performgen receives the input string from Stdin and returns comma separated
values with the first column being the 32 byte segments written as hexadecimal
strings and the second column being the duration of the segment in milliseconds.
Example workflow
- Download
performgen.exe
from the
GitHub releases page.
- On Windows, save the following MML string to a file like
song.mml
:
t80o3a2b2c2d2e2f2g2
- Execute the following command in command prompt:
type song.mml | performgen.exe > segments.csv
- The contents of
segments.csv
should look like:
data,duration(ms)
1d16ffbb15ffbb16ffbb18fffaff7d16ffbb18ffbb1bfffaff7d1affbb180000,1872
1dffbb16fffafffafffaffbb1ffffaff7d21fffaff7d22fffaff7d24fffa0000,2499
1eff7d0affbb09ffbb0affbb0cfffaff7d0afffaff7d0afffaff7d09ffbb0a00,1998
0cffbb0cfffaff7d0afffaff7d00000000000000000000000000000000000000,937
- These segments can be sent from a private server implementation of FFXIV
or used with a FFXIV packet injector (there is no public one yet as far as
I know).
For developers
Performgen can be used as a Golang library with no additional dependencies.
Simply add import "github.com/ff14wed/performgen"
and pass the MML string
to the perform.Generate()
call to receive byte data for an FFXIV performance,
split into segments.
The reason for the specific choice of output format is that the network
protocol for the "Perform" action is actually way more powerful than the
action itself. While using the "Perform" action manually generates about
1-3 notes per packet, the protocol allows you to play up to 10 notes per
packet with at minimum a few milliseconds of delay in between them. It also
allows you to specify exact number of milliseconds of delay to achieve a very
high degree of accuracy with note length and tempo.
The network protocol format for a single segment of a performance is a
32 byte block structured as defined here. The data
for a segment is simply a list of either perform note IDs (1 byte), or delays
(2 bytes, first byte is 0xFF
, second byte is a number from 0-250 for number
of milliseconds).
When the FFXIV client receives a single segment from the server, it queues
it for the performing character and reads the data from this queue as the notes
play. If the client receives multiple segments at once, they will be read in
order instead of overlapping.
This is useful because instead of sending one note at a time with delay
in between, you can send an entire section of music and it will be played
with the correct timing. However, this queue has a limited size buffer,
so sending an entire song at once would result in only the last section
of the song playing.
Internal Documentation
https://godoc.org/github.com/ff14wed/performgen
MML Reference
The MML understood by this compiler is a slight variation of what is understood
by Mabinogi or generated by the 3MLE tool.
The major difference is that it can only understand one track at a time, so
while you can import a song in the MML@Melody,Harmony1,Harmony2,Song;
format
into 3MLE, this compiler can only understand each of Melody
, Harmony1
,
Harmony2
, or Song
without commas or semicolons or the MML@
header.
Most of these commands behave the same way as in other MML, so some of this
reference is borrowed from existing documentation. The parser is case
insensitive, so the symbols used could be uppercase or lowercase.
Note Command
Symbols: A, B, C, D, E, F, G
A note command will produce a sound with the letters A
to G
corresponding
to the pitches.
A sharp note can be produced by adding a +
or a #
directly after the pitch
letter, and flat notes by adding a -
.
The length of a note is specified by appending a positive integer representing
the denominator of 1/x, which translates mean the length is this fraction a
whole note. For example, c8
would produce a C eighth note, and f+2
would
produce a F♯ half note. If the length of the note is not specified, the default
length specified by the length command will be assumed.
If the length is explicitly set to 0, this note will be used to form a chord
with the next note. For example, c0e0g0
forms a C Major triad. Implementation
wise, this is achieved by making an arpeggiated chord with 20 milliseconds
of delay in between each note.
Adding a dot or a period .
after the number specifying the length increases
the length of the note by 50%. For example f+8.
plays the an F# note for 3/16
of a whole note.
It's important to note that FFXIV doesn't currently support any sort of sustain
for notes, so adding a length for a note is pretty much equivalent to a rest
after the note.
Rest Command
Symbol: R
A rest is an interval of silence in the music. The length of the rest is
specified in the same manner as the length of the note. It also supports
the dot for increasing its length by 50%. For example, r8.
is an interval
of silence for 3/16 of a whole note.
Octave Command
Symbol: O
The octave can by set for all notes after this command by specifying o
followed by a number. Currently, FFXIV only supports octaves 3, 4, 5, or 6,
so setting any other octave generates an error in this tool. These octaves
correspond to the -1, 0, 1, and 2 numbers in-game. For example, o5 g a b g
plays G (+1), A (+1), B (+1), G (+1)
in game.
Keep in mind you can only play the C note on octave 6 (C (+2)
). Any other
note on this octave will generate an error.
Before using this tool, it is advisable to import the MML into an editor like
3MLE first to make sure it doesn't require going out of the 3-6 octave range,
and if it does, change some notes around accordingly or shift the song up or
down an octave.
Octave Up/Down Command
Symbol: >, <
These commands don't take any arguments. The >
steps the octave up by one,
and <
steps the octave down by one. Shifting the octave outside of the
3-6 range will generate an error.
Length Command
Symbol: L
The default length of all notes/rests without an explicit length after this
command can be set by specifying l
followed by a number. This number is
specified in the same manner as the length of the note. This number can also
be modified with the dot to make the default length 50% longer. For example,
l8.
makes the default length 3/16 of a whole note.
If this command is never called, notes without an explicit length will be
quarter notes.
Tempo Command
Symbol: T
The tempo of the song can be changed by specifying t
followed by the tempo
in beats per minute. For example t88
sets the tempo to 88 bpm. The default
tempo is 120 beats per minute.
Volume Command
Symbol: V
This command does nothing since FFXIV doesn't support volume. It is only
implemented to parse without error scores from other games. It must still be
followed by a number. For example, v120
would be parsed and ignored.
Extend Command
Symbol: &
This command is originally used to extend the duration of the previous note.
For example e-1&e-1&e-1&e-1
would extend the note E♭ to one full measure.
However, since FFXIV doesn't currently support sustained notes, this would
only translate into a single E♭ and a full measure of silence.
In order to support compatibility with MML used in other games, this command
has the following behavior:
- It is used by specifying a
&
followed by a full note command or a full rest
command. It doesn't matter whether there is anything before the &
or not.
- It always adds a rest with the duration of the following note or rest
command. For example,
&a+8
is equivalent to a r8
, and &r4
is equivalent
to a r4
.