A library to play MML and ABC songs, written in C#
This .NET 2.0 compatible framework is intended for the parsing and timing of songs written in the plaintext MML or ABC formats used for instrument-playing in MMOs like Mabinogi, Archeage or Lord of the Rings Online. The framework will parse songs and time them, notifying derived classes of when notes need to be played, on what channel, at what pitch, etc. The framework itself does not generate audio; this is an implementation detail left up to the user.
You can download the latest binaries in the releases section on the project's github.
To implement a player, derive one of the following classes: ABCPlayer, MMLPlayer, or MultiTrackMMLPlayer. Because MultiTrackMMLPlayer doesn't derive from the base MusicPlayer class and the other two classes do, all of these implement a common IMusicPlayer interface. A basic implementation for any of these classes would look like this:
public class ImplementedPlayer : MultiTrackMMLPlayer {
public ImplementedPlayer()
: base() {
}
protected override void PlayNote(Note note, int channel, TimeSpan time) {
// audio implementation goes here
}
}
Examples are provided with the Framework for a basic single-track MML and basic ABC player which plays notes on channel 0 in a console using Console.Beep.
The Note struct comes with the following properties:
This information can then be used to implement audio. Notes have an additional function GetFrequency(Note? tuningNote = null) method. This will calculate the frequency of the Note based on a specified tuning note, or A4 = 440 Hz if unspecified. This can be then used for synth implementations.
There is also a GetStep() function, which calculates the semitone index as an integer, starting at 12 for C1 and going up by 1 for every semitone. This can be used to pitch audio samples up or down to create the desired tone, in order to avoid the 100 or so samples of audio required otherwise. The below example assumes that a pitch of 1.0 is default, going up to 2.0 for one octave up, 0.5 for one octave down, and an audio sample corresponding to middle-C (C4).
var c4 = new Note() { Type = 'c', Octave = 4 };
var note = new Note() { Type = 'f', Octave = 4 };
var dist = note.GetStep() - c4.GetStep();
var pitch = Math.Pow(1.0594630943592952645618252949463, dist);
While this code works for Unity, it will not work for XNA in which a pitch of 0.0 is default, a pitch of 1.0 is one octave up and a pitch of -1.0 is one octave down. In this case the following code can be used.
var c4 = new Note() { Type = 'c', Octave = 4 };
var note = new Note() { Type = 'f', Octave = 4 };
var dist = note.GetStep() - c4.GetStep();
var pitch = Math.Pow(1.0594630943592952645618252949463, Math.Abs(dist)) - 1;
if (dist < 0)
pitch *= -1;
It is recommended to create at least one sample per octave, due to limits on pitching up or down in most audio libraries, as well as distortion.
All classes can be played using the Play and Update methods. The Play method is called to prepare a song for playback. Then the song is played by repeated calls to Update every frame until the song is done when the Playing property is set to false.
These methods have two overloads, one which takes the current time and one which doesn't. If no time is specified, DateTime.Now is used for the current time. Games which have a main loop which specifies the current game time as a TimeSpan (such as XNA) can simply pass this value to these methods and playback will perform as expected. Songs store their elapsed time since starting in the Elapsed property. Please note that the time argument passed to PlayNote contains the time when the note was supposed to be played, which is not the same as the Elapsed property.
If specifying TimeSpan.Zero as the starting time when calling the Play method, users will have to keep track of the amount of time passed since calling Play manually.
Because this framework is intended to be used inside games where player submitted content cannot be vetted ahead of time, the library comes with features which validate content ahead of time. These Settings can be accessed and customized via the Settings property of any of the player classes. This is always a subclass of the main ValidationSettings class which contains settings common to both MML and ABC.
The first thing validated when loading a song (using the Load or FromFile methods) is its file size. When loading from stream or file an error is immediately thrown when the number of bytes read exceeds Settings.MaxSize, preventing further reading of the file. When loading from a string, if the string length exceeds this property an exception is also thrown. This property defaults to the following:
After this the song's duration is calculated. As soon as the duration exceeds Settings.MaxDuration an exception is thrown and calculation stops to prevent client freezing. This property defaults to 5 minutes.
The allowed range of octaves is specified by the Settings.MinOctave and Settings.MaxOctave properties. These default to 1 and 8 respectively. Tones with octaves outside of this range are clamped to these values.
The final thing validated generally is the tempo, specified by Settings.MinTempo and Settings.MaxTempo, defaulting to values of 32 and 255 respectively. This value specifies the tempo in beats per minute. For MML these values correspond to 'T32' and 'T255'. For ABC this corresponds to 'Q: 1/4 = 32' and 'Q: 1/4 = 255'.
MML validation settings also has the MinVolume and MaxVolume properties. These specify the minimum and maximum volume to accept. Volumes outside of this range are clamped to this range. Defaults to 1 and 15.
ABC validation settings also have the following properties:
MML is fully supported through the MultiTrackMMLPlayer class, with the following caveats:
The above applies for the default MMLMode which corresponds to MMLMode.Mabinogi. This can be changed to an ArcheAge compatible mode by accessing the Mode property. This prevents tempo changes from affecting all tracks, and allows volume commands in a range of 1 (softest) to 127 (loudest), which are then compressed into the usual 1-15 range, so no changes to validation settings need to be made.
The framework attempts to implement the ABC implementation but for security and reasons of complexity really only supports the features supported by Lord of the Rings Online, with the following caveats: