MusIT

Class MPTKWriter – Build MIDI files and Play It!

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

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

Demonstrations scene: TestMidiGenerator.

MPTKWriter demo and test
MPTKWriter demo and test

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

  • Create a MPTKWriter instance.
  • Add Midi Events (note, preset change, chord, lyric, …).
    • Define timing in millisecond: more easy to understand but initial tempo must be respected.
    • Or in ticks: independent of the tempo.
  • Write or Play directly the Midi file.

See below an extract of the demonstration script.

First example with timing defined in ticks:

/// <summary>@brief /// Four consecutive quarters played independently of the tempo. /// </summary> /// <returns></returns> private MPTKWriter CreateMidiStream_four_notes_ticks() { // 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) MPTKWriter mfw = new MPTKWriter(); mfw.AddTimeSignature(0, 0, 4, 2); // 240 is the default. A classical value for a Midi. define the time precision. int ticksPerQuarterNote = mfw.DeltaTicksPerQuarterNote; // Some textual information added to the track 0 at time=0 mfw.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.AddBPMChange(track0, 0, 120); // Add four consecutive quarters from 60 (C5) to 63. mfw.AddNote(track1, absoluteTime, channel0, 60, 50, ticksPerQuarterNote); // Next note will be played one quarter after the previous absoluteTime += ticksPerQuarterNote; mfw.AddNote(track1, absoluteTime, channel0, 61, 50, ticksPerQuarterNote); absoluteTime += ticksPerQuarterNote; mfw.AddNote(track1, absoluteTime, channel0, 62, 50, ticksPerQuarterNote); absoluteTime += ticksPerQuarterNote; mfw.AddNote(track1, absoluteTime, channel0, 63, 50, ticksPerQuarterNote); return mfw; }
Code language: C# (cs)

Defining timing in milliseconds is straightforward with the ConvertMilliToTick method, which accounts for tempo changes.:

public MPTKEvent AddNoteMilli(MPTKWriter mfw, int track, float timeToPlay, int channel, int note, int velocity, float duration) { long tick = mfw.ConvertMilliToTick(timeToPlay); int length = duration < 0 ? -1 : (int)mfw.DurationMilliToTick(duration); return mfw.AddNote(track, tick, channel, note, velocity, length); }
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)

It is also possible to merge MIDIs using ImportFromEventsList with MPTKWriter:

/// <summary>@brief /// Join two MIDI from the MidiDB /// </summary> /// <returns></returns> private MPTKWriter CreateMidiStream_midi_merge() { // Join two MIDI from the MidiDB // - create an empty MIDI writer // - Import a first one (MIDI index 0 from the MIDI DB) // - Import a second one (MIDI index 1 from the MIDI DB) MPTKWriter mfw = null; try { // Create a Midi File Writer instance // ----------------------------------- mfw = new MPTKWriter(); // A MIDI loader is useful to load all MIDI events from a MIDI file. MidiFilePlayer mfLoader = FindObjectOfType<MidiFilePlayer>(); if (mfLoader == null) { Debug.LogWarning("Can't find a MidiFilePlayer Prefab in the current Scene Hierarchy. Add it with the Maestro menu."); return null; } // No, with v2.10.0 - It's mandatory to keep noteoff when loading MIDI events for merging // mfLoader.MPTK_KeepNoteOff = true; // it's recommended to not keep end track mfLoader.MPTK_KeepEndTrack = false; // Load the initial MIDI index 0 from the MidiDB // --------------------------------------------- mfLoader.MPTK_MidiIndex = 0; mfLoader.MPTK_Load(); // All merge operation will be done with the ticksPerQuarterNote of the first MIDI mfw.ImportFromEventsList(mfLoader.MPTK_MidiEvents, mfLoader.MPTK_DeltaTicksPerQuarterNote, name: mfLoader.MPTK_MidiName, logPerf: true); Debug.Log($"{mfLoader.MPTK_MidiName} Events loaded: {mfLoader.MPTK_MidiEvents.Count} DeltaTicksPerQuarterNote:{mfw.DeltaTicksPerQuarterNote}"); // Load the MIDI index 1 from the MidiDB // ------------------------------------- mfLoader.MPTK_MidiIndex = 1; mfLoader.MPTK_Load(); // All MIDI events loaded will be added to the MidiFileWriter2. // Position and Duration will be converted according the ticksPerQuarterNote initial and ticksPerQuarterNote from the MIDI to be inserted. mfw.ImportFromEventsList(mfLoader.MPTK_MidiEvents, mfLoader.MPTK_DeltaTicksPerQuarterNote, name: "MidiMerged", logPerf: true); Debug.Log($"{mfLoader.MPTK_MidiName} Events loaded: {mfLoader.MPTK_MidiEvents.Count} DeltaTicksPerQuarterNote:{mfw.DeltaTicksPerQuarterNote}"); // Add a silence of a 4 Beat Notes after the last event. // It's optionnal but recommended if you want to automatic restart on the generated MIDI with a silence before looping. long absoluteTime = mfw.MPTK_MidiEvents.Last().Tick + mfw.MPTK_MidiEvents.Last().Length; Debug.Log($"Add a silence at {mfw.MPTK_MidiEvents.Last().Tick} + {mfw.MPTK_MidiEvents.Last().Length} = {absoluteTime} "); mfw.AddSilence(track: 1, absoluteTime, channel: 0, length: mfw.DeltaTicksPerQuarterNote * 4); } catch (Exception ex) { Debug.LogException(ex); } // return mfw; }
Code language: PHP (php)

Examples in any logical sequence, alter the tempo, switch instrument presets, incorporate lyrics, adjust the pitch wheel, execute chords, and insert text in UTF8 format including languages such as Chinese, Japanese, Korean, and others.

mfw.AddTempoChange(track0, absoluteTime, MPTKEvent.BeatPerMinute2QuarterPerMicroSecond(beatsPerMinute * 2)); mfw.AddChangePreset(track1, absoluteTime, channel0, preset: 50); // synth string mfw.AddText(track0, absoluteTime, MPTKMeta.Lyric, "Pitch wheel effect"); mfw.AddPitchWheelChange(track1, absoluteTime, channel0, pitch); // We need degrees in major, so build a major range MPTKScaleLib scaleMajor = MPTKScaleLib.CreateScale(MPTKScaleName.MajorHarmonic); // Build chord degree 1 MPTKChordBuilder chordDegreeI = new MPTKChordBuilder() { // Parameters to build the chord Tonic = 60, // play in C Count = 3, // 3 notes to build the chord (between 2 and 20, of course it doesn't make sense more than 7, its only for fun or experiementation ...) Degree = 1, // Midi Parameters how to play the chord Duration = duration, // millisecond, -1 to play indefinitely Velocity = 80, // Sound can vary depending on the iQuarter // Optionnal MPTK specific parameters Arpeggio = 0, // delay in milliseconds between each notes of the chord Delay = 0, // delay in milliseconds before playing the chord }; // Build chord degree V MPTKChordBuilder chordDegreeV = new MPTKChordBuilder() { Tonic = 60, Count = 3, Degree = 5, Duration = duration, Velocity = 80, }; // Build chord degree IV MPTKChordBuilder chordDegreeIV = new MPTKChordBuilder() { Tonic = 60, Count = 3, Degree = 4, Duration = duration, Velocity = 80, }; // Add degrees I - V - IV - V in the MIDI (all in major) mfw.AddChordFromScale(track1, absoluteTime, channel0, scaleMajor, chordDegreeI); absoluteTime += ticksPerQuarterNote; mfw.AddChordFromScale(track1, absoluteTime, channel0, scaleMajor, chordDegreeV); absoluteTime += ticksPerQuarterNote; mfw.AddChordFromScale(track1, absoluteTime, channel0, scaleMajor, chordDegreeIV); absoluteTime += ticksPerQuarterNote; mfw.AddChordFromScale(track1, absoluteTime, channel0, scaleMajor, chordDegreeV); absoluteTime += ticksPerQuarterNote; // Add chords from MPTK library MPTKChordBuilder chordLib = new MPTKChordBuilder() { Tonic = 60, Duration = duration, Velocity = 80, }; mfw.AddChordFromLib(track1, absoluteTime, channel0, MPTKChordName.Major, chordLib); absoluteTime += ticksPerQuarterNote; chordLib.Tonic = 62; mfw.AddChordFromLib(track1, absoluteTime, channel0, MPTKChordName.mM7, chordLib); absoluteTime += ticksPerQuarterNote; chordLib.Tonic = 67; mfw.AddChordFromLib(track1, absoluteTime, channel0, MPTKChordName.m7b5, chordLib); absoluteTime += ticksPerQuarterNote; chordLib.Tonic = 65; mfw.AddChordFromLib(track1, absoluteTime, channel0, MPTKChordName.M7, chordLib); absoluteTime += ticksPerQuarterNote; // Some textual UTF8 information mfw.ExtendedText = true; mfw.AddText(track: 1, tick: absoluteTime, typeMeta: MPTKMeta.Lyric, text: "only ASCII"); mfw.AddText(track: 1, tick: absoluteTime, typeMeta: MPTKMeta.Lyric, text: "リリックテキスト"); absoluteTime += ticksPerQuarterNote; mfw.AddText(track: 1, tick: absoluteTime, typeMeta: MPTKMeta.Lyric, text: "抒情文字"); absoluteTime += ticksPerQuarterNote; mfw.AddText(track: 1, tick: absoluteTime, typeMeta: MPTKMeta.Lyric, text: "가사 텍스트"); mfw.AddText(track: 1, tick: absoluteTime, typeMeta: MPTKMeta.Lyric, text: "français éôè"); absoluteTime += ticksPerQuarterNote; mfw.AddText(track: 1, tick: absoluteTime, typeMeta: MPTKMeta.Lyric, text: "Norsk språk");
Code language: C# (cs)

Get MPTK from the Unity store

If you like Midi Player Tool Kit, please leave a review on the Asset Store. It’s very appreciated!!!

Contact

If you have any questions, please don’t hesitate to reach out to us with the dedicated Unity forum or on our Discord  channel

We always enjoy discussing your projects!

Add MIDI Music With 3 Clicks for Free

Sound Spatialisation, MPTK is ready for Virtual Reality [Pro]

Sound Spatialisation, MPTK is ready for Virtual Reality [free]

Midi Synth : Real Time Voice Effect Change

Euclidean Rhythm demo

New on Asset Store

Asset Store Partners