Page created by Beatrice Hoang, May 2022
revived by Yanmi Yu, May 2025
Maestro Midi Player Tool Kit (Maestro MPTK) is a Unity asset that allows you to add MIDI music to your application. With Maestro, you can play MIDI files, change speed or pitch, program specific actions when certain notes are played in the music, control play/pause, and more.
This quickstart tutorial will cover downloading and setting up the free version of Maestro Midi Player Tool Kit and discuss its potentials.
Under "Scene Example" is a walkthrough of how to use scripts to manipulate
You can download the asset from the Unity asset store here:
Once you have clicked "Add to My Assets,"
go to your opened project in Unity and navigate to the Package Manager (in Unity's top menu: Window > Package Manager).
Under Packages: My Assets, search "Maestro." Select the MPTK package and click the Import button. Import all files into your project.
Once the package has finished importing, you should see "MPTK" in Unity's top menu. You're now ready to play and manipulate MIDI files in your project!
Add the MidiFilePlayer prefab to your scene by going to MPTK > Add Prefab MidiFilePlayer
Click on the MidiFilePlayer prefab in your scene to reveal it in the inspector. You should see something like this:
The "Select Midi" attribute allows you to choose a Midi file to play. By default, MPTK has many Midi files to choose from.
Go to MPTK > Midi File Setup
In the box that pops up, you can select either "Add a Midi File" to add 1 midi file, or "Add From Folder" to add multiple midi files within one folder.
Now when you return to the inspector, you should see your files as dropdown options under "Select Midi."
To demonstrate basic use (MIDI file will play on game startup), make sure
there is an audio listener in your scene
usually it is at the main Camera
Play At Startup box is checked
Now when you start the game, you will be able to hear your MIDI file playback.
If you are curious about other control on the panel see below
Let's now explore basic scripting for the MPTK
This example will guide you in creating a Unity midi audio pitch/volume visualizer. We will be using MPTK scripts to manipulate 9 cylinders's heights in our scene, where each cylinder represents the pitch (based on which octave the note is in) and the height represents volume.
Make sure you have followed the setup instructions above about adding the MPTK to your project. You should have the MPTK asset imported in your project and the MidiFilePlayer prefab in your scene.
Before we jump into scripting, we need to make sure we have some gameobjects in our scene to manipulate.
Add a cylinder to your scene (In the hierarchy, right click > 3D Objects > Cylinder). Reset its position and set its Y-scale to 0.1, and rename it to "octaveO"
Duplicate it 8 times so you have a total of 9 cylinders. Rename them all to follow the pattern "octave1", "octave2", ... "octave8"
Now let's get into scripting!
Note: official API/documentation for Maestro MPTK can be found here (https://mptkapi.paxstellar.com). Scroll to see full script from this tutorial.
This section will go over creating a script so you can manipulate the midi data.
If you do not have a script yet, create a new script (in the projects tab, right click > Create > C# Script)
In your script, add the import statement
using MidiPlayerTK;
to the top of your script.
Inside your class, declare the private variable
private MidiFilePlayer midiFilePlayer;
In your Start function, assign midiFilePlayer to the Midi File Player in your hierarchy using FindObjectOfType:
midiFilePlayer = FindObjectOfType<midiFilePlayer>();
In order to trigger events based on MIDI notes, we need to add an event listener method to the midi file player. To do that using scripts, add this to Start:
midiFilePlayer.OnEventNotesMidi.AddListener(NoteActions);
where NoteActions is your function that performs actions. Let's write that method now.
The listener takes in a list of MPTKEvents, with each MPTKEvent holding information about one note.
Useful Maestro API methods that we will use are:
MidiPlayerTK.MPTKEvent.Value - returns the value (pitch) of the note as an integer where 60=C5, 61=C5#, ..., 72=C6, ... .
MidiPlayerTK.HelperNoteLabel.LabelFromMidi - given the value of a note, returns the label of the note as a string (eg. C4, D2, etc.)
The label of a note is in the format "<letter name><octave number><# if the note is a sharp>"
MidiPlayerTK.MPTKEvent.Velocity - returns the velocity (volume) of the note as an integer between 0 and 127
MidiPlayerTK.MPTKEvent.Duration - returns the duration of the note in milliseconds as a long
Using these, we can write:
public void NoteActions(List<MPTKEvent> mptkEvents) {
foreach(MPTKEvent note in mptkEvents) {
if (note.Command == MPTKCommand.NoteOn) { // if the note is being played
int noteValue = note.Value; // get the note value
string noteLabel = HelperNoteLabel.LabelFromMidi(noteValue); // get the note label
char noteOctave = noteLabel[1]; // get the octave of the note
GameObject octaveModel = GameObject.Find("octave" + noteOctave); // get the correct octave gameobject
float volume = note.Velocity; // get the note velocity
long duration = note.Duration; // get the note duration
StartCoroutine(OctaveHeightChanger(octaveModel, duration, volume));
}
}
}
/// <summary>
/// this coroutine changes the octave gameobject's height for the duration
/// + half a second so short notes can be visible to the visualizer
/// </summary>
IEnumerator OctaveHeightChanger(GameObject octaveModel, long duration, float volume) {
octaveModel.transform.localScale = new Vector3(1f, volume / 10f, 1f);
yield return new WaitForSeconds(duration/1000 + 0.5f);
octaveModel.transform.localScale = new Vector3(1f, 0.1f, 1f);
}
Make sure you drag your script onto any gameobject in your hierarchy (eg. a new empty object) and press play to see it in action!
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using MidiPlayerTK;
public class ExampleMidiControl : MonoBehaviour
{
private MidiFilePlayer midiFilePlayer;
// Start is called before the first frame update
void Start()
{
// get midi player object
midiFilePlayer = FindObjectOfType<MidiFilePlayer>();
midiFilePlayer.OnEventNotesMidi.AddListener(NoteActions);
}
public void NoteActions(List<MPTKEvent> mptkEvents) {
foreach(MPTKEvent note in mptkEvents) {
if (note.Command == MPTKCommand.NoteOn) { // if the note is being played
int noteValue = note.Value; // get the note value
string noteLabel = HelperNoteLabel.LabelFromMidi(noteValue); // get the note label
char noteOctave = noteLabel[1]; // get the octave of the note
GameObject octaveModel = GameObject.Find("octave" + noteOctave); // get the correct octave gameobject
float volume = note.Velocity; // get the note velocity
long duration = note.Duration; // get the note duration
StartCoroutine(OctaveHeightChanger(octaveModel, duration, volume));
}
}
}
/// <summary>
/// this coroutine changes the octave gameobject's height for the duration
/// + half a second so short notes can be visible to the visualizer
/// </summary>
IEnumerator OctaveHeightChanger(GameObject octaveModel, long duration, float volume) {
octaveModel.transform.localScale = new Vector3(1f, volume / 10f, 1f);
yield return new WaitForSeconds(duration/1000 + 0.5f);
octaveModel.transform.localScale = new Vector3(1f, 0.1f, 1f);
}
}
Controls
Volume:
Master volume control for the playback. (Set to 0.5 → half volume.)
Transpose:
Shifts all notes up or down by a number of semitones (Currently 0 → no shift.)
Drum Preset Change:
If checked, allows automatic changes of drum kits based on MIDI program changes. (Unchecked → drums won't auto-change.)
Log MIDI Events Played:
If checked, will log each MIDI event (note on/off, program change, etc.) to the console during playback for debugging.(Unchecked → no event spam.)
Spatialization
Spatialization (checkbox):
If enabled ✅, the MIDI audio will behave like a 3D sound in Unity (it will sound louder when closer to the listener and fade when farther away).
If disabled ❌ (like now), the sound is 2D — meaning it plays the same no matter where the player or camera is.
Max Distance (number field):
This sets how far the sound can be heard when spatialization is enabled.
0 means no distance limit is set (or spatialization is OFF, so it doesn't matter).
Example: If set to 50, after 50 units away from the AudioListener, the sound would fade out.
Current distance to AudioListener (readonly field):
This shows the real-time distance between the MIDI sound source (this GameObject) and the main Unity AudioListener (usually the camera).
Right now: 0 → the AudioListener is exactly at the same position as the sound.
Playback Options
Automatic MIDI Start:
When the scene starts or this component is enabled, the MIDI will automatically start playing.
(Checked → auto-play is ON.)
Start Playing at the First:
Forces the playback to begin exactly at the first MIDI event when starting.
(Unchecked → may continue from another position.)
Stop Playing on the Last:
When the last MIDI event is reached, stop playback automatically.
(Unchecked → depends on mode set below.)
Pause on Loss of Focus:
If the app loses focus (like tabbing away), playback pauses.
(Unchecked → keeps playing even when tabbed out.)
Send MIDI to the Synth:
Send the MIDI events to the synth to be actually played (otherwise it just triggers events without sound).
(UnChecked → sound is not produced.)
Automatic MIDI Restart:
Automatically restart the MIDI when it reaches the end (looping).
(Unchecked → no auto-loop.)
Mode Stop At Last Note (dropdown):
Determines when the player stops after finishing the file.
"Stop When All Voices Are Released" →
After the last note is played and all sustained notes are naturally released, it will stop.
(Good for songs with fadeouts or long sustain.)
Follow this tutorial carefully:
Make sure the Meta SDK and Meta Building Blocks are correctly installed in your Unity project!
Create an empty GameObject in your scene.
Name it something like "MidiPlay 1 Visual".
Drag all your related objects (e.g., octave1, octave2, ..., octave8) under this GameObject.
Duplicate your MidiFilePlayer and MidiPlay Visual:
Right-click > Duplicate.
Name them properly:
"MidiFilePlayer 1", "MidiPlay 1 Visual" (original)
"MidiFilePlayer 2", "MidiPlay 2 Visual" (copy)
Assign a different MIDI to each MidiFilePlayer if you want different songs.
In the Inspector:
Select and keep active only the default song and visual (e.g., MidiFilePlayer 1, MidiPlay 1 Visual).
Uncheck and deactivate the others (e.g., MidiFilePlayer 2, MidiPlay 2 Visual) for now.
Follow this page carefully to map your controller buttons:
✅ Assign a button press to trigger switching between the songs.
When you switch between songs, you must reset playback properly. Add this script to your controller or a new manager script:
public MidiFilePlayer midiFilePlayer;
private Dictionary<int, Queue<GameObject>> trackCubes = new Dictionary<int, Queue<GameObject>>();
private Dictionary<int, float> trackXOffsets = new Dictionary<int, float>();
public void Restart()
{
if (midiFilePlayer != null)
{
midiFilePlayer.MPTK_Stop();
}
ClearAllVisuals();
if (midiFilePlayer != null)
{
midiFilePlayer.MPTK_Play();
}
}
// ⚡ Revise this function according to your visualization setup (e.g., cylinders, cubes, or lines)
public void ClearAllVisuals()
{
foreach (var trackPair in trackCubes)
{
foreach (GameObject cube in trackPair.Value)
{
if (cube != null)
Destroy(cube);
}
}
trackCubes.Clear();
trackXOffsets.Clear();
}
Important:
If you're using cylinders instead of dynamic cubes, adjust ClearAllVisuals() to reset cylinder heights instead of destroying cubes.
public void ClearAllVisuals()
{
for (int i = 0; i <= 8; i++) {
GameObject cylinder = GameObject.Find("octave" + i);
if (cylinder != null)
cylinder.transform.localScale = new Vector3(1f, 0.1f, 1f); // Reset height
}
}
In Unity Inspector:
On your controller button action (click "+" to add an event),
Drag the object with your MidiPlayerManager script into the slot,
Select MidiPlayerManager.Restart().
Button 1(X) for Song 1, with proper restart
Button 1(y) for Song 2, with proper restart