/performgen

MML -> FFXIV Perform network data compiler

Primary LanguageGoMIT LicenseMIT

Performgen - FFXIV Performance Generator

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

  1. Download performgen.exe from the GitHub releases page.
  2. On Windows, save the following MML string to a file like song.mml:
    t80o3a2b2c2d2e2f2g2
    
  3. Execute the following command in command prompt:
    type song.mml | performgen.exe > segments.csv
    
  4. The contents of segments.csv should look like:
    data,duration(ms)
    1d16ffbb15ffbb16ffbb18fffaff7d16ffbb18ffbb1bfffaff7d1affbb180000,1872
    1dffbb16fffafffafffaffbb1ffffaff7d21fffaff7d22fffaff7d24fffa0000,2499
    1eff7d0affbb09ffbb0affbb0cfffaff7d0afffaff7d0afffaff7d09ffbb0a00,1998
    0cffbb0cfffaff7d0afffaff7d00000000000000000000000000000000000000,937
    
  5. 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.