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.
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)