- Notifications
You must be signed in to change notification settings - Fork3
A library to play MML and ABC songs, written in C#
License
Enichan/textplayer
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
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 candownload the latest binaries in the releases section on theproject's github.
To implement a player, derive one of the following classes:ABCPlayer,MMLPlayer, orMultiTrackMMLPlayer. BecauseMultiTrackMMLPlayer doesn't derive from the baseMusicPlayer class and the other two classes do, all of these implement a commonIMusicPlayer interface. A basic implementation for any of these classes would look like this:
publicclassImplementedPlayer:MultiTrackMMLPlayer{publicImplementedPlayer():base(){}protectedoverridevoidPlayNote(Notenote,intchannel,TimeSpantime){// 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.
TheNote struct comes with the following properties:
- Octave: octave of the note, normally ranging from 1 up to 8. The fourth octave corresponds to the octave of middle-C.
- Length: aTimeSpan containing the length or duration of the note.
- Type: a lower-case character in the form of 'abcdefg' that denotes the base type of the note.
- Sharp: a boolean value which indicates whether or not the note is sharp or not.
- Volume: a floating point value indicating the volume of the note, between 0.0 and 1.0.
This information can then be used to implement audio. Notes have an additional functionGetFrequency(Note? tuningNote = null) method. This will calculate the frequency of theNote based on a specified tuning note, or A4 = 440 Hz if unspecified. This can be then used for synth implementations.
There is also aGetStep() 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).
varc4=newNote(){Type='c',Octave=4};varnote=newNote(){Type='f',Octave=4};vardist=note.GetStep()-c4.GetStep();varpitch=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.
varc4=newNote(){Type='c',Octave=4};varnote=newNote(){Type='f',Octave=4};vardist=note.GetStep()-c4.GetStep();varpitch=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 thePlay andUpdate methods. ThePlay method is called to prepare a song for playback. Then the song is played by repeated calls toUpdate every frame until the song is done when thePlaying 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 aTimeSpan (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 theElapsed property. Please note that thetime argument passed toPlayNote contains the time when the note was supposed to be played, which is not the same as theElapsed property.
If specifyingTimeSpan.Zero as the starting time when calling thePlay method, users will have to keep track of the amount of time passed since callingPlay 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 theSettings property of any of the player classes. This is always a subclass of the mainValidationSettings class which contains settings common to both MML and ABC.
The first thing validated when loading a song (using theLoad orFromFile methods) is its file size. When loading from stream or file an error is immediately thrown when the number of bytes read exceedsSettings.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:
- ABCPlayer: 12,288 bytes or 12 kilobytes.
- MultiTrackMMLPlayer: 12,288 bytes or 12 kilobytes.
- MMLPlayer: 4,096 bytes or 4 kilobytes.
After this the song's duration is calculated. As soon as the duration exceedsSettings.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 theSettings.MinOctave andSettings.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 bySettings.MinTempo andSettings.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 theMinVolume andMaxVolume 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:
- ShortestNote andLongestNote: The shortest and longest notes allowable. These default to 1/64th of a measure and 4 measures long respectively. Shorter or longer notes are clamped to these values.
- MaxChordNotes: The maximum number of notes in a chord which are actually played, excluding rests. Chordscan contain more notes, and these notes will be used to determine the duration of the chord, but they will not result in anyPlayNote calls.
MML is fully supported through theMultiTrackMMLPlayer class, with the following caveats:
- The version of MML supported is the non-verbose version used by Mabinogi and Archeage, with code starting with 'MML@' and ending in a semi-colon ';', with tracks split up by a comma. This means that the extended markup available to more traditional usage of MML is not parsed.
- Mabinogi's note command (ex 'N60') would allow musicians in that game to play notes in octaves above or below the maximum. All notes in the TextPlayer Framework are validated for maximum and minimum values, including the note command.
- Default values correspond to the following commands: 'O4', 'L4', 'T120', V8'.
- When using the single-trackMMLPlayer classonly the code for that track should be provided. This should not be preceeded by 'MML@' or end in a semi-colon ';'.
The above applies for the defaultMMLMode which corresponds toMMLMode.Mabinogi. This can be changed to an ArcheAge compatible mode by accessing theMode 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:
- LotRO treats c as middle-C, this is wrong and this library treats C as middle-C as per the ABC specification. This means LotRO music will play one octave higher. To remedy this markers for popular LotRO exporters are detected on load and, if found, LotroCompatible is set to true. While this property is true all notes play one octave lower than normal. This auto-detection behavior can be disabled by setting AutoDetectLotro to false before loading ABC songs. A list of markers is contained in LotroAutoDetect.LotroMarkers.
- If AutoDetectLotro is true any line in an ABC song matching '%%lotro-compatible' will also set LotroCompatible to true. This line can be added to songs written for LotRO if auto-detection fails.
- ABC files that contain multiple tunes (denoted by the header command 'X: {track}') are parsed as separate tracks and can be played separately. By default the first tune is played. The TextPlayer Framework will not play multiple tunes in sequence as Lord of the Rings Online does.
- Many advanced features such as repetitions and tuples are unsupported. The currently supported featureset is:
- Commented lines (lines starting with the percent sign '%') are supported.
- A global header may be specified, its values are set before the header values specific to the currently playing tune are set.
- The information fields for key ('K'), meter ('M'), default note length ('L') and tempo ('Q') are supported.
- The 'Q', 'L', and 'K' commands are also supported as inline information fields inside a tune body.
- Chords are supported. Chord duration is set to the lowest duration note contained inside the chord.
- Rests inside chords are supported, and can modify the duration of the chord.
- Measures are supported and will clear accidentals.
- The following dynamics for setting volume are supported: '+pppp+', '+ppp+', '+pp+', '+p+', '+mp+', '+mf+', '+f+', '+ff+', '+fff+', '+ffff+'.
- Notes are supported and default to octave 4 for upper case notes and octave 5 for lower case. The shortest possible note length is 1/64 and the longest is 4 measures.
- Rests ('x', 'z' or 'Z') are supported and subject to the above length restrictions.
- Notes can be tied using the '-' symbol. Only notes of the same pitch and octave can be tied in such a manner.
- Octave can be changed, and any number of accidental symbols will compute correctly when attached to a note. If an accidentals reset symbol is found anywhere ('=') the accidental is reset and all other modifiers ignored.
- Accidentals can be set to propagate to apply to all notes of the same pitch in the same octave (default as per Lord of the Rings Online), all notes of the same pitch regardless of octave, or set to only apply to the one note and not propagate at all.
- When ABC v2.1 strict is indicated an error will be thrown if files do not start with a version string in the form of '%abc-{majorVersion}.{minorVersion}', or '%abc-2.1'.
- In strict mode an error will be thrown if the 'T: {title}' information field is not preceeded by the 'X: {track}' information field.
- In strict mode an error will be thrown if the 'Q: {tempo}' information field is not in the form of 'Q: {noteLength} = {bpm}', for example 'Q: 1/4 = 120'.
- When not in strict mode the tempo field will be parsed as best as possible, where simple numbers are allowed (note length is assumed to be 1/4 or inferred from the meter), and a reverse notation of '{bpm} = {noteLength}' is also allowed.
- The default octave for ABC notated songs is never clearly specified.ABCPlayer uses octave 4 by default. This can be changed by settingABCPlayer.DefaultOctave.
- The default method for propagating accidentals in ABC notation is specified to beAccidentalPropagation.Pitch. TheABCPlayer usesAccidentalPropagation.Octave by default however in order to be compatible with Lord of the Rings Online playback. This can be changed by settingABCPlayer.DefaultAccidentalPropagation.
About
A library to play MML and ABC songs, written in C#