Class MidiFileWriter2 – Build MIDI files and Play It!

The class MidiFileWriter2, available with MPTK Pro V2.88 , is useful to create Midi file by script. It replaces the class MidiFileWriter which is deprecated. MidiFileWriter2 proposes a more consistent behavior with the others classes of MPTK and new helpful functions.

  • Channel from 0 to 15
  • Methods for all Midi events
  • Better errors check

Also look at these page:  Midi Timing and Integration of MPTK by script.

There is two demonstrations scenes: TestMidiGenerator and TinyMidiSequencer. Begin with TestMidiGenerator, it’s very easy to understand.

TestMidiGenerator
TinyMidiSequencer

The schema is classical and you will find all the detail for the class MidiFileWriter2 here in the documentation.

  • Create a MidiFileWriter2.
  • Create Tracks, minimum one. Since v2.88, tracks are created automatically.
  • Add Midi Event (note, preset change, …). There is two groups of methods:
    • defined timing in millisecond: more easy to understand but initial tempo must be respected.
    • defined timing in ticks: independent of the tempo.
  • Close each tracks. Since v2.88, tracks are close automatically
  • Write the Midi file or Play the Midi file.

See below an extract of the demonstration script.

First example with timing defined in millisecond:

/// <summary> /// Play four consecutive quarters from 60 (C5) to 63. /// Use AddNoteMS method for Tempo and duration defined in milliseconds. /// </summary> /// <returns></returns> private MidiFileWriter2 CreateMidiStream_1() { // In this demo, we are using variable to contains tracks and channel values only for better understanding. // Using multiple tracks is not mandatory, you can arrange your song as you want. // But first track (index=0) is often use for general midi information track, lyrics, tempo change. By convention contains no noteon. int track0 = 0; // Second track (index=1) will contains the notes, preset change, .... all events associated to a channel. int track1 = 1; int channel0 = 0; // we are using only one channel in this demo // Create a Midi file of type 1 (recommended) MidiFileWriter2 mfw = new MidiFileWriter2(); mfw.MPTK_AddText(track0, 0, MPTKMeta.Copyright, "Simple Midi Generated. 4 quarter at 120 BPM"); // Playing tempo must be defined at start of the stream. // Defined BPM is mandatory when duration and delay are defined in millisecond in the stream. // The value of the BPM is used to transform duration from milliseconds to internal ticks value. // Obviously, duration in millisecànds depends on the BPM selected. With BPM=120, a quarter duration is 500 milliseconds. mfw.MPTK_AddBPMChange(track0, 0, 120); // Add four consecutive quarters from 60 (C5) to 63. // With BPM=120, quarter duration is 500ms (60000 / 120). So, notes are played at, 0, 500, 1000, 1500 ms from the start. mfw.MPTK_AddNoteMilli(track1, 0f, channel0, 60, 50, 500f); mfw.MPTK_AddNoteMilli(track1, 500f, channel0, 61, 50, 500f); mfw.MPTK_AddNoteMilli(track1, 1000f, channel0, 62, 50, 500f); mfw.MPTK_AddNoteMilli(track1, 1500f, channel0, 63, 50, 500f); // Silent note : velocity=0 (will generate only a noteoff) mfw.MPTK_AddNoteMilli(track1, 3000f, channel0, 80, 0, 250f); return mfw; }
Code language: C# (cs)

Second example with timing defined in ticks. The result is exactly the same that with the first example:

/// <summary> /// Four consecutive quarters played independently of the tempo. /// </summary> /// <returns></returns> private MidiFileWriter2 CreateMidiStream_2() { // In this demo, we are using variable to contains tracks and channel values only for better understanding. // Using multiple tracks is not mandatory, you can arrange your song as you want. // But first track (index=0) is often use for general midi information track, lyrics, tempo change. By convention contains no noteon. int track0 = 0; // Second track (index=1) will contains the notes, preset change, .... all events associated to a channel. int track1 = 1; int channel0 = 0; // we are using only one channel in this demo long absoluteTime = 0; // Create a Midi file of type 1 (recommended) MidiFileWriter2 mfw = new MidiFileWriter2(); mfw.MPTK_AddTimeSignature(0, 0, 4, 4); // 240 is the default. A classical value for a Midi. define the time precision. int ticksPerQuarterNote = mfw.MPTK_DeltaTicksPerQuarterNote; // Some textual information added to the track 0 at time=0 mfw.MPTK_AddText(track0, 0, MPTKMeta.Copyright, "Simple Midi Generated. 4 quarter at 120 BPM"); // Define Tempo is not mandatory when using time in ticks. The default 120 BPM will be used. //mfw.MPTK_AddBPMChange(track0, 0, 120); // Add four consecutive quarters from 60 (C5) to 63. mfw.MPTK_AddNote(track1, absoluteTime, channel0, 60, 50, ticksPerQuarterNote); // Next note will be played one quarter after the previous absoluteTime += ticksPerQuarterNote; mfw.MPTK_AddNote(track1, absoluteTime, channel0, 61, 50, ticksPerQuarterNote); absoluteTime += ticksPerQuarterNote; mfw.MPTK_AddNote(track1, absoluteTime, channel0, 62, 50, ticksPerQuarterNote); absoluteTime += ticksPerQuarterNote; mfw.MPTK_AddNote(track1, absoluteTime, channel0, 63, 50, ticksPerQuarterNote); // Silent note : velocity=0 (will generate only a noteoff) absoluteTime += ticksPerQuarterNote * 2; mfw.MPTK_AddNote(track1, absoluteTime, channel0, 80, 0, ticksPerQuarterNote); return mfw; }
Code language: C# (cs)

Now play directly the Midi list of events. All the methods available for playing a Midi file with MidiFilePlayer are also available. For example: defined callback at start, end of Midi, at each notes, looping, …

private void PlayDirectlyMidiSequence(string name, MidiFileWriter2 mfw) { // Play midi with the MidiExternalPlay prefab without saving midi in a file MidiFilePlayer midiPlayer = FindObjectOfType<MidiFilePlayer>(); midiPlayer.MPTK_Stop(); mfw.MPTK_MidiName = name; midiPlayer.OnEventStartPlayMidi.RemoveAllListeners(); midiPlayer.OnEventStartPlayMidi.AddListener((string midiname) => { startPlaying = DateTime.Now; Debug.Log($"Start playing {midiname} at {startPlaying}"); }); midiPlayer.OnEventEndPlayMidi.RemoveAllListeners(); midiPlayer.OnEventEndPlayMidi.AddListener((string midiname, EventEndMidiEnum reason) => { Debug.Log($"End playing {midiname} {reason} Duration={(DateTime.Now - startPlaying).TotalSeconds:F3}"); }); midiPlayer.OnEventNotesMidi.RemoveAllListeners(); midiPlayer.OnEventNotesMidi.AddListener((List<MPTKEvent> events) => { foreach (MPTKEvent midievent in events) Debug.Log($"At {midievent.RealTime:F1} ms play: {midievent.ToString()}"); }); midiPlayer.MPTK_Loop = true; // Sort the events by ascending absolute time (optional) mfw.MPTK_SortEvents(); mfw.MPTK_Debug(); // Send the midi sequence to internal midi sequencer midiPlayer.MPTK_Play(mfw); }
Code language: C# (cs)

Or write it to a Midi file:

private void WriteMidiSequenceToFile(string name, MidiFileWriter2 mfw) { // build the path + filename to the midi string filename = Path.Combine(Application.persistentDataPath, name + ".mid"); Debug.Log("Write Midi file:" + filename); // Sort the events by ascending absolute time (optional) mfw.MPTK_SortEvents(); mfw.MPTK_Debug(); // Write the midi file mfw.MPTK_WriteToFile(filename); }
Code language: C# (cs)