Introduction
Examples are better than words!
On this page, you will discover various examples of how to utilize the Maestro API.
They have been designed as a Minimum Viable Product that focuses on the essentials of functionality but fully tested with Maestro MPTK 2.14 and available with the Unity Maestro packages Free or Pro.
- Each script demonstrates only a few basic functions to help you get started with the API.
- Error handling is minimal,
- The user interface is basic or missing.
- Manipulations in Unity are minimized.
- They are fully functional, look at the chapter “How to Use” for your own testing.
You are welcome to copy and paste these into your project. I will be adding more examples regularly, so feel free to ask for any specific ones you need!
Example with Maestro MPTK Free
Plays a single C5 note
Feature
- Plays a single C5 note when the space key is pressed.
- Stops the note when the space key is released.
How to Use
- Add an empty GameObject to your Unity Scene and attach this script to the GameObject.
- Add a MidiStreamPlayer prefab to your scene (right click on the Hierarchy Tab, menu Maestro)
- Run the scene and press the space key to play the C5 note.
Documentation References
using UnityEngine;
using MidiPlayerTK;
namespace DemoMVP
{
/// <summary>
/// Functionality Demonstrated:
/// - Plays a single C5 note when the space key is pressed.
/// - Stops the note when the space key is released.
///
/// How to Use:
/// 1. Add an empty GameObject to your Unity Scene and attach this script to the GameObject.
/// 2. Add a MidiStreamPlayer prefab to your scene (right click on the Hierarchy Tab, menu Maestro)
/// 3. Run the scene and press the space key to play the C5 note.
///
/// Documentation References:
/// - MIDI Stream Player: https://mptkapi.paxstellar.com/d9/d1e/class_midi_player_t_k_1_1_midi_stream_player.html
/// - MPTKEvent: https://mptkapi.paxstellar.com/d9/d50/class_midi_player_t_k_1_1_m_p_t_k_event.html
///
/// A Minimum Viable Product (MVP) that focuses on the essentials of the Maestro API functionality.
/// This script demonstrates only a few basic functions to help users get started with the API.
///
/// Features and Approach:
/// - Error handling is minimal, the user interface is basic, and manipulations in Unity are minimized.
/// - Prefabs like `MidiFilePlayer` and `MidiStreamPlayer` are essential components of Maestro.
/// While this demo creates these prefabs via script, it is recommended to add them in the Unity editor
/// for real projects to take full advantage of the Inspector's parameters.
///
/// </summary>
public class TheSimplestNotesPlayer : MonoBehaviour
{
// MidiStreamPlayer is a class that can play MIDI events such as notes, chords, patch changes, and effects.
private MidiStreamPlayer midiStreamPlayer;
// MPTKEvent is a class that describes MIDI events such as notes to play.
private MPTKEvent mptkEvent;
private void Awake()
{
// Look for an existing MidiStreamPlayer prefab in the scene
midiStreamPlayer = FindFirstObjectByType<MidiStreamPlayer>();
if (midiStreamPlayer == null)
{
// If no MidiStreamPlayer prefab is found, create it dynamically
Debug.Log("No MidiStreamPlayer Prefab found in the current Scene Hierarchy.");
Debug.Log("A new MidiStreamPlayer prefab will be created via script. For production, add it manually to the scene!");
// Create an empty GameObject to hold the Maestro-related prefab
GameObject go = new GameObject("HoldsMaestroPrefab");
// Add the MidiPlayerGlobal component to manage the SoundFont. This is a singleton, so only one instance will be created.
go.AddComponent<MidiPlayerGlobal>();
// Add the MidiStreamPlayer prefab to the GameObject
midiStreamPlayer = go.AddComponent<MidiStreamPlayer>();
// *** Configure essential parameters for the player ***
// Enable the internal core player for smooth playback
midiStreamPlayer.MPTK_CorePlayer = true;
// Enable logging of MIDI events in the Unity Console
// Use a monospace font in the Console for better readability
midiStreamPlayer.MPTK_LogEvents = true;
// Ensure that MIDI events are sent directly to the player for playback
midiStreamPlayer.MPTK_DirectSendToPlayer = true;
}
Debug.Log("Press the <Space> key to play a note.");
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
// Create an MPTKEvent to describe the note to be played
// Value = 60 corresponds to the C5 note. Duration is set to -1 for infinite playback.
mptkEvent = new MPTKEvent() { Value = 60 };
// Start playing the C5 note
midiStreamPlayer.MPTK_PlayEvent(mptkEvent);
}
if (Input.GetKeyUp(KeyCode.Space))
{
// Stop playing the C5 note
midiStreamPlayer.MPTK_StopEvent(mptkEvent);
}
}
}
}
Load, Modify and Play a MIDI
Feature
This demo enables you to load a MIDI file before playing it, modify some MIDI events, and then play the modified MIDI. The demo is modifying MIDI tempo event, but all others MIDI can be modified!
There is many possibilities to modify MIDI player tempo:
- From the MidiFilePlayer inspector, under the “Show MIDI Parameters” tab, adjust the speed between 0.1 and 10, default is 1. For programmatically change, see also MPTK_Speed.
- Programmatically, at any time when the MIDI is playing, change the tempo using MPTK_Tempo
- Programmatically, change all tempo events in the MIDI. See the sample bellow.
- load the MIDI,
- modify MIDI tempo events,
- then play.
How to Use
- Add a MidiFilePlayer prefab to your scene.
- In the MidiFilePlayer inspector:
- Select the MIDI file you wish to load.
- Uncheck “Automatic MIDI Start”.
- Attach this script to an existing GameObject (whether empty or not).
Documentation References
- Midi File Player
- MidiLoad (internal class for loading and playing MIDI)
using MidiPlayerTK;
using UnityEngine;
public class LoadMidiAndPlay : MonoBehaviour
{
// This demo enables you to load a MIDI file before playing it, modify some MIDI events, and then play the modified MIDI.
// It also demonstrates how to change the tempo using three methods:
// 1) From the MidiFilePlayer inspector, under the "Show MIDI Parameters" tab, adjust the speed (default is 1).
// 2) Programmatically, at any time when the MIDI is playing, change the tempo using midiFilePlayer.MPTK_Tempo = bpm (bpm must be > 0).
// 3) Programmatically, load the MIDI, modify the MIDI tempo events, and then play.
//
// This demo showcases the third method.
// In your Unity scene:
// - Add a MidiFilePlayer prefab to your scene.
// - In the MidiFilePlayer inspector:
// - Select the MIDI file you wish to load.
// - Uncheck "Automatic MIDI Start".
// - Attach this script to an existing GameObject (whether empty or not).
// MPTK component for playing a MIDI file.
// You can set it in the inspector or let this script find it automatically.
public MidiFilePlayer midiFilePlayer;
private void Awake()
{
// Find a MidiFilePlayer added to the scene or set it directly in the inspector.
if (midiFilePlayer == null)
midiFilePlayer = FindFirstObjectByType<MidiFilePlayer>();
}
void Start()
{
if (midiFilePlayer == null)
{
Debug.LogWarning("No MidiFilePlayer Prefab found in the current Scene Hierarchy. See 'Maestro / Add Prefab' in the menu.");
}
else
{
// Index of the MIDI file from the MIDI database (find it using 'Midi File Setup' from the Maestro menu).
// Optionally, the MIDI file to load can also be defined in the inspector. Uncomment to select the MIDI programmatically.
// midiFilePlayer.MPTK_MidiIndex = 0;
// Load the MIDI without playing it.
MidiLoad midiloaded = midiFilePlayer.MPTK_Load();
if (midiloaded != null)
{
Debug.Log($"Duration: {midiloaded.MPTK_Duration.TotalSeconds} seconds, Initial Tempo: {midiloaded.MPTK_InitialTempo}, MIDI Event Count: {midiloaded.MPTK_ReadMidiEvents().Count}");
foreach (MPTKEvent mptkEvent in midiloaded.MPTK_MidiEvents)
{
if (mptkEvent.Command == MPTKCommand.MetaEvent && mptkEvent.Meta == MPTKMeta.SetTempo)
{
// The value contains Microseconds Per Beat, convert it to BPM for clarity.
double bpm = MPTKEvent.QuarterPerMicroSecond2BeatPerMinute(mptkEvent.Value);
// Double the tempo and convert back to Microseconds Per Beat.
mptkEvent.Value = MPTKEvent.BeatPerMinute2QuarterPerMicroSecond(bpm * 2);
Debug.Log($" Tempo doubled at tick position {mptkEvent.Tick} and {mptkEvent.RealTime / 1000f:F2} seconds. New tempo: {MPTKEvent.QuarterPerMicroSecond2BeatPerMinute(mptkEvent.Value)} BPM");
}
}
// Start playback.
midiFilePlayer.MPTK_Play(alreadyLoaded: true);
}
}
}
}
Example with Maestro MPTK Pro
Create a MIDI file and Play
Feature
- Creating, saving, and playing a MIDI file using the Maestro API.
- With MPTKWriter, generates a simple MIDI file containing a few notes.
- Saves the MIDI file to a temporary location.
- Plays the generated MIDI file using the
MidiExternalPlayer
prefab.
How to Use
- Add an empty GameObject to your Unity Scene and attach this script to the GameObject.
- Add a
MidiExternalPlayer
prefab to your scene (right click on the Hierarchy Tab, menu Maestro)
Documentation References
using MidiPlayerTK;
using System.IO;
using UnityEngine;
namespace DemoMVP
{
/// <summary>
/// Demonstrates creating, saving, and playing a MIDI file using the Maestro API.
///
/// Features:
/// - Dynamically generates a simple MIDI file containing a few notes.
/// - Saves the MIDI file to a temporary location.
/// - Plays the generated MIDI file using the `MidiExternalPlayer` prefab.
///
/// Documentation References:
/// - MIDI Writer: https://paxstellar.fr/class-midifilewriter2/
/// - MIDI External Player: https://paxstellar.fr/midi-external-player-v2/
/// - MPTKEvent: https://mptkapi.paxstellar.com/d9/d50/class_midi_player_t_k_1_1_m_p_t_k_event.html
///
/// This example is designed to be the "Hello, World!" equivalent for the MIDI Pro Toolkit (MPTK).
/// </summary>
public class SimplestMidiWriter : MonoBehaviour
{
private void Start()
{
// Generate a temporary file name for the MIDI file
string pathMidiSource = Path.GetTempFileName() + ".mid";
// Initialize the MIDI writer class, which handles reading, writing, and playing MIDI files
MPTKWriter mptkWriter = new MPTKWriter();
// Track and channel constants
const int TRACK1 = 1, CHANNEL0 = 0;
// Number of ticks per quarter note
int ticksPerQuarterNote = 500;
// Starting time for MIDI events (in ticks)
long currentTime = 0;
// Set the instrument preset (e.g., a music patch) on the specified channel
mptkWriter.AddChangePreset(TRACK1, currentTime, CHANNEL0, 10);
// Add MIDI notes with timing
// Each note is added at a specific time, with a duration and velocity (loudness)
// Play a D4 note
currentTime += ticksPerQuarterNote;
mptkWriter.AddNote(TRACK1, currentTime, CHANNEL0, 62, 50, ticksPerQuarterNote);
// Play an E4 note one quarter note later
currentTime += ticksPerQuarterNote;
mptkWriter.AddNote(TRACK1, currentTime, CHANNEL0, 64, 50, ticksPerQuarterNote);
// Play a G4 note one quarter note later
currentTime += ticksPerQuarterNote;
mptkWriter.AddNote(TRACK1, currentTime, CHANNEL0, 67, 50, ticksPerQuarterNote);
// Add a silent note (velocity = 0) two quarter notes later
// This generates only a "Note Off" event
currentTime += ticksPerQuarterNote * 2;
mptkWriter.AddNote(TRACK1, currentTime, CHANNEL0, 80, 0, ticksPerQuarterNote);
// Log all MIDI events for debugging purposes
mptkWriter.LogWriter();
// Write the MIDI file to the specified path
mptkWriter.WriteToFile(pathMidiSource);
Debug.Log($"MIDI file created at {pathMidiSource}");
// Play the generated MIDI file
PlayMidiFromFile(pathMidiSource, mptkWriter);
}
private void PlayMidiFromFile(string filePath, MPTKWriter midiWriter)
{
// Find the MidiExternalPlayer prefab in the scene
MidiExternalPlayer midiPlayer = FindFirstObjectByType<MidiExternalPlayer>();
if (midiPlayer == null)
{
Debug.LogWarning("No MidiExternalPlayer Prefab found in the current Scene Hierarchy. Add it via the Maestro menu.");
return;
}
// Configure the MIDI player with the generated MIDI file
midiPlayer.MPTK_MidiName = "file://" + filePath; // Use the file URI format
midiPlayer.MPTK_MidiAutoRestart = true;
// Prepare the MIDI file for playback
midiWriter.MidiName = filePath;
// Sort events by absolute time to ensure correct playback order
midiWriter.StableSortEvents();
// Calculate timing details for all MIDI events (e.g., time in measures and quarters)
midiWriter.CalculateTiming(logPerf: true);
// Start playback using the prepared MIDI file
midiPlayer.MPTK_Play(mfw2: midiWriter);
}
}
}
Partially MIDI Player Pause
Feature
Demonstrates a technique to partially pause a MIDI player by disabling all channels except the drum channel. Unfortunately, it’s not possible to directly pause a subset of channels in the MIDI player.
To achieve this, two MidiFilePlayers are used:
- MidiFilePlayerMain: Starts playing from the beginning.
- MidiFilePlayerDrum: Paused at the start, with only channel 9 (the drum channel) enabled.
How to Use
- Add an empty GameObject to your Unity Scene and attach this script to the GameObject.
- Add two MidiFilePlayers prefab to your scene (right click on the Hierarchy Tab, menu Maestro).
- Assign these MidiFilePlayers to the script thru the inspector.
Documentation References
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using MidiPlayerTK;
/// <summary>
/// Demonstrates a technique to partially pause a MIDI player by disabling all channels except the drum channel.
/// Unfortunately, it's not possible to directly pause a subset of channels in the MIDI player.
///
/// To achieve this, two MidiFilePlayers are used:
/// - **MidiFilePlayerMain**: Starts playing from the beginning.
/// - **MidiFilePlayerDrum**: Paused at the start, with only channel 9 (the drum channel) enabled.
///
/// The process is as follows:
/// 1. At a specified position (PausePosition), the first player is paused, and the second player is resumed.
/// 2. After a defined duration (PauseDuration), the second player is paused, and the first player resumes from its paused position.
///
/// To test this script, use the provided scene (MidiPlayAndPause) or:
/// 1. Add two MidiFilePlayer to your scene
/// 2. Add a GameObject (can be empty) to your Unity scene.
/// 3. Attach this script to the GameObject.
/// 4. Defined the MIDI to play in the first MidiFilePlayer, the second will use the same.
/// 5. Assign the two MidiFilePlayer in the GameObject inspector in the corresponding field.
///
/// This is a minimal viable product (MVP) demo focusing on the core functionality:
/// - Limited value validation
/// - Limited error handling
/// - No performance optimizations
/// </summary>
public class MidiPlayAndPause : MonoBehaviour
{
[Header("Assign two MidiFilePlayers in the scene to this script")]
public MidiFilePlayer MidiFilePlayerMain;
public MidiFilePlayer MidiFilePlayerDrum;
[Header("Position (in ticks) to switch from MidiFilePlayerMain to MidiFilePlayerDrum")]
public long PausePosition;
[Header("Duration (in ticks) before returning to MidiFilePlayerMain")]
public long PauseDuration;
[Header("Flag to prevent repeated switching")]
public bool SwitchDone;
[Header("Check to restore the initial state")]
public bool ResetLoop;
void Awake()
{
// Ensure both MidiFilePlayers are defined in the Inspector
if (MidiFilePlayerMain == null)
{
Debug.LogWarning("MidiFilePlayerMain is not defined in the Inspector");
return;
}
MidiFilePlayerMain.MPTK_PlayOnStart = true;
if (MidiFilePlayerDrum == null)
{
Debug.LogWarning("MidiFilePlayerDrum is not defined in the Inspector");
return;
}
MidiFilePlayerDrum.MPTK_PlayOnStart = true;
// Set default values for PausePosition and PauseDuration if not defined
if (PausePosition == 0) PausePosition = 2000;
if (PauseDuration == 0) PauseDuration = 1500;
}
void Start()
{
if (MidiFilePlayerMain != null && MidiFilePlayerDrum != null)
{
// Ensure both players use the same MIDI file
MidiFilePlayerDrum.MPTK_MidiIndex = MidiFilePlayerMain.MPTK_MidiIndex;
// Configure the drum player channels once it starts
MidiFilePlayerDrum.OnEventStartPlayMidi.AddListener(info =>
{
// Disable all channels except the drum channel (channel 9)
MidiFilePlayerDrum.Channels.EnableAll = false;
MidiFilePlayerDrum.Channels[9].Enable = true;
Debug.Log("MidiFilePlayerDrum is paused at the start");
// Warning: never use MPTK_Start() because the channels setting will be reset to default (enable all)
MidiFilePlayerDrum.MPTK_Pause();
});
// Log beat events for debugging
MidiFilePlayerMain.OnBeatEvent = (int time, long tick, int measure, int beat) =>
{
Debug.Log($"MidiFilePlayerMain beat event: Tick={tick}, Beat={beat}/{measure}, SwitchDone={SwitchDone}");
};
MidiFilePlayerDrum.OnBeatEvent = (int time, long tick, int measure, int beat) =>
{
Debug.Log($"MidiFilePlayerDrum beat event: Tick={tick}, Beat={beat}/{measure}, SwitchDone={SwitchDone}");
};
}
}
void Update()
{
if (MidiFilePlayerMain != null && MidiFilePlayerDrum != null)
{
if (!SwitchDone)
{
// Switch from main player to drum player when reaching the specified tick
// Warning: never use MPTK_Start() because the channels setting will be reset to default (enable all)
if (!MidiFilePlayerMain.MPTK_IsPaused && MidiFilePlayerMain.MPTK_TickCurrent > PausePosition)
{
Debug.Log("Pausing main player and resuming drum player");
MidiFilePlayerMain.MPTK_Pause();
MidiFilePlayerDrum.MPTK_UnPause();
MidiFilePlayerDrum.MPTK_TickCurrent = PausePosition;
}
// Switch back to main player after the pause duration
if (!MidiFilePlayerDrum.MPTK_IsPaused && MidiFilePlayerDrum.MPTK_TickCurrent > PausePosition + PauseDuration)
{
Debug.Log("Pausing drum player and resuming main player");
MidiFilePlayerDrum.MPTK_Pause();
MidiFilePlayerMain.MPTK_UnPause();
MidiFilePlayerMain.MPTK_TickCurrent = PausePosition;
SwitchDone = true;
}
}
// Restore initial conditions when ResetLoop is checked
if (ResetLoop)
{
Debug.Log("Resetting to initial state");
ResetLoop = false;
SwitchDone = false;
// Reset main player
MidiFilePlayerMain.MPTK_TickCurrent = 0;
// Update internal tick, not updated when the player is paused
MidiFilePlayerMain.MPTK_MidiLoaded.MPTK_TickCurrent = 0;
MidiFilePlayerMain.MPTK_UnPause();
// Reset drum player
MidiFilePlayerDrum.MPTK_TickCurrent = PausePosition;
// Update internal tick, not updated when the player is paused
MidiFilePlayerDrum.MPTK_MidiLoaded.MPTK_TickCurrent = PausePosition;
MidiFilePlayerDrum.MPTK_Pause();
}
}
}
}
Connect a MIDI keyboard with Low Latency
Feature
Demonstrates an implementation for reading MIDI events from a MIDI keyboard connected to your device (MacOS or Windows) and playing each events using the MPTK framework.
This implementation reduces latency by avoiding the Unity main thread for MIDI event reading and MPTK playback. The remaining latency factors include:
- The MIDI input device (especially with USB MIDI interfaces).
- FMOD audio settings (consider using a smaller buffer size for lower latency).
How to Use
- Download and install the MIDI Keyboard tool (if not already installed).
- Add a GameObject (can be empty) to your Unity scene and attach this script to the GameObject.
- Add a MidiStreamPlayer prefab to your scene (right click on the Hierarchy Tab, menu Maestro)
- Connect a MIDI keyboard to your computer and run the Unity scene.
- All MIDI events received from the keyboard will be played through the MPTK MIDI Synth.
- Add your custom logic in Update() method for visualization, game interaction, or other Unity behaviors (optional).
Documentation References
using UnityEngine;
using MidiPlayerTK;
using System.Threading;
using System.Collections.Concurrent;
namespace DemoMPTK
{
/// <summary>
/// Demonstrates an example of a minimal viable product (MVP) implementation for reading MIDI events
/// from a MIDI keyboard and playing them using the MPTK framework.
///
/// This implementation reduces latency by avoiding the Unity main thread for MIDI event reading and playback.
/// The remaining latency factors include:
/// - The MIDI input device (especially with USB MIDI interfaces).
/// - FMOD audio settings (consider using a smaller buffer size for lower latency).
///
/// Documentation References:
/// - MIDI Keyboard API: https://mptkapi.paxstellar.com/da/d70/class_midi_player_t_k_1_1_midi_keyboard.html
/// - MIDI Stream Player API: https://mptkapi.paxstellar.com/d9/d1e/class_midi_player_t_k_1_1_midi_stream_player.html
///
/// To test this script use the provided scene (MidiKeyboardThread) or:
/// 1. Download and install the MIDI Keyboard tool from https://paxstellar.fr/class-midikeyboard/ (if not already installed).
/// 2. Add a GameObject (can be empty) to your Unity scene and attach this script to the GameObject.
/// 3. Add a MidiStreamPlayer prefab to your scene (right click on the Hierarchy Tab, menu Maestro)
/// 4. Connect a MIDI keyboard to your computer and run the Unity scene.
/// 5. All MIDI events received from the keyboard will be played through the MPTK MIDI Synth.
/// 6. Add your custom logic in Update() method for visualization, game interaction, or other Unity behaviors (optional).
/// </summary>
public class MidiKeyboardThread : MonoBehaviour
{
// Indicates whether the MIDI keyboard is ready to use
private bool midiKeyboardReady = false;
// Thread for reading and processing MIDI events without blocking the Unity main thread
private Thread midiThread;
// Handles MIDI event playback, such as notes, chords, patch changes, and effects
private MidiStreamPlayer midiStreamPlayer;
// Queue for storing MIDI events, allowing integration with custom Unity behaviors in the Update() method
private ConcurrentQueue<MPTKEvent> midiQueue = new ConcurrentQueue<MPTKEvent>();
private void Awake()
{
// Look for an existing MidiStreamPlayer prefab in the scene
midiStreamPlayer = FindFirstObjectByType<MidiStreamPlayer>();
if (midiStreamPlayer == null)
{
Debug.LogWarning("No MidiStreamPlayer Prefab found in the current Scene Hierarchy. Add one via the 'Maestro / Add Prefab' menu.");
}
}
private void Start()
{
// Initialize the MIDI keyboard at the start
if (midiStreamPlayer != null && MidiKeyboard.MPTK_Init())
{
midiKeyboardReady = true;
// Log the MIDI Keyboard version
Debug.Log(MidiKeyboard.MPTK_Version());
// Open or refresh all MIDI input devices that can send MIDI messages
MidiKeyboard.MPTK_OpenAllInp();
// Start a dedicated thread for reading MIDI events and playing them
midiThread = new Thread(ThreadMidiPlayer);
midiThread.Start();
}
}
/// <summary>
/// Thread for continuously reading MIDI events from the keyboard and sending them to the MPTK MIDI Synth.
/// This thread avoids Unity's main thread for reduced latency.
/// </summary>
private void ThreadMidiPlayer()
{
while (midiKeyboardReady)
{
try
{
// Check for errors in the MIDI plugin
MidiKeyboard.PluginError status = MidiKeyboard.MPTK_LastStatus;
if (status != MidiKeyboard.PluginError.OK)
{
Debug.LogWarning($"MIDI Keyboard error, status: {status}");
}
// Read a MIDI event if available
MPTKEvent midiEvent = MidiKeyboard.MPTK_Read();
if (midiEvent != null)
{
// Add the event to the queue for custom processing in Unity's Update() method
midiQueue.Enqueue(midiEvent);
// Immediately play the MIDI event
midiStreamPlayer.MPTK_PlayEvent(midiEvent);
}
// Add a short delay to prevent overloading the CPU
Thread.Sleep(1);
}
catch (System.Exception ex)
{
Debug.LogError($"ThreadMidiPlayer - {ex}");
break;
}
}
}
private void Update()
{
if (midiKeyboardReady && !midiQueue.IsEmpty)
{
if (midiQueue.TryDequeue(out MPTKEvent midiEvent))
{
// Add your custom logic here for visualization, game interaction, or other Unity behaviors
// The music played on the MIDI keyboard will continue in the background
Debug.Log(midiEvent.ToString());
}
}
}
private void OnApplicationQuit()
{
if (midiKeyboardReady)
{
// Close all MIDI input devices to prevent Unity crashes
MidiKeyboard.MPTK_CloseAllInp();
}
midiKeyboardReady = false;
}
}
}
Modifying MIDI events at runtime
Feature
- How to change MIDI events at runtime.
- How to programmatically create a MidiFilePlayer, there is no need to add MPTK prefab to the scene.
How to Use
- Attach this script to a GameObject in Unity.
- Run the scene.
- Change option in the GameObject inspector
Documentation References
using UnityEngine;
using MidiPlayerTK;
namespace DemoMVP
{
/// <summary>
/// Functionality Demonstrated:
/// - how to change MIDI events at runtime.
/// - how to dynamically create a MidiFIlePlayer, there is no need to add MPTK prefab to the scene.
///
/// Steps:
/// 1. Attach this script to a GameObject in Unity.
/// 2. Run the scene.
/// 3. Change option in the GameObject inspector
///
/// Documentation References:
/// - MIDI File Player: https://paxstellar.fr/midi-file-player-detailed-view-2/
/// - MPTKEvent: https://mptkapi.paxstellar.com/d9/d50/class_midi_player_t_k_1_1_m_p_t_k_event.html
///
/// A Minimum Viable Product (MVP) that focuses on the essentials of the Maestro API functionality.
/// This script demonstrates only a few basic functions to help users get started with the API.
/// - Error handling is minimal, the user interface is basic, and manipulations in Unity are minimized.
/// - Prefabs like `MidiFilePlayer` and `MidiStreamPlayer` are essential components of Maestro.
/// While this demo creates these prefabs via script, it is recommended to add them in the Unity editor
/// for real projects to take full advantage of the Inspector's parameters.
/// </summary>
public class HackYourMidiPlayer : MonoBehaviour
{
[Header("Enable Arpeggio Effect")]
public bool toggleAddArpeggio = true;
[Header("MIDI Channel for Arpeggio (-1 for all channels)")]
public int channelArpeggio = 0;
[Header("Scale for Arpeggio")]
public MPTKScaleName scaleArpeggio = MPTKScaleName.MajorMelodic;
[Header("Number of Arpeggio Notes")]
public int countArpeggio = 2;
[Header("Skip Preset Change Events")]
public bool toggleEnableChangePreset = false;
[Header("Randomize Tempo Events")]
public bool toggleEnableChangeTempo = false;
[Header("Log MIDI Events in Console")]
public bool toggleLogMidiEvent = false;
private MPTKScaleLib scaleForArpeggio; // Holds intervals for the arpeggio.
private MPTKScaleName currentScale = MPTKScaleName.MajorMelodic; // Checks changes from Inspector.
private MidiFilePlayer midiFilePlayer; // Reference to a dynamically added MidiFilePlayer.
private void Awake()
{
Debug.Log($"Awake: Adding MidiFilePlayer component to '{gameObject.name}'");
// Add the MidiFilePlayer component to this GameObject and configure it.
midiFilePlayer = gameObject.AddComponent<MidiFilePlayer>();
midiFilePlayer.MPTK_CorePlayer = true;
midiFilePlayer.MPTK_DirectSendToPlayer = true;
midiFilePlayer.MPTK_EnableChangeTempo = true;
// Ensure the global MIDI manager is available.
if (MidiPlayerGlobal.Instance == null)
gameObject.AddComponent<MidiPlayerGlobal>();
}
public void Start()
{
// Create a scale (list of intervals) based on the selected scale.
// Note: This must be done on the main Unity thread.
scaleForArpeggio = MPTKScaleLib.CreateScale(index: currentScale, log: false);
Debug.Log("Start: Selecting and playing the first MIDI file in the database.");
midiFilePlayer.MPTK_LogEvents = true;
midiFilePlayer.MPTK_MidiIndex = 0; // Select the first MIDI file in the database.
midiFilePlayer.OnMidiEvent = MaestroOnMidiEvent; // Set the MIDI event callback.
midiFilePlayer.MPTK_Play(); // Start playing the MIDI file.
}
/// <summary>
/// Callback triggered for each MIDI event just before it is sent to the synthesizer.
/// Use this to modify, add, or skip MIDI events.
/// - Avoid heavy processing here as it could disrupt musical timing.
/// - Unity APIs (except Debug.Log) cannot be used because this runs outside the Unity main thread.
/// </summary>
/// <param name="midiEvent">The MIDI event being processed.</param>
/// <returns>True to play the event, false to skip it.</returns>
bool MaestroOnMidiEvent(MPTKEvent midiEvent)
{
// By default, the MIDI event will be sent to the synthesizer.
bool keepEventToPlay = true;
switch (midiEvent.Command)
{
case MPTKCommand.NoteOn:
if (toggleAddArpeggio)
{
// Calculate delay (in milliseconds) between arpeggio notes based on the tempo.
// - 1/16 tick delay, a quarter divides by four: MPTK_DeltaTicksPerQuarterNote / 4
// - transform tick value to milliseconds with MPTK_Pulse
// The current tempo (MPTK_Pulse, millisecond of a tick) is used for this calculation,
// so we need to update the value at each call in case of a tempo change has been done.
long arpeggioDelay = (long)(midiFilePlayer.MPTK_DeltaTicksPerQuarterNote / 4 * midiFilePlayer.MPTK_Pulse);
if (channelArpeggio == -1 || channelArpeggio == midiEvent.Channel)
{
if (scaleForArpeggio != null)
{
// Add additional notes to create the arpeggio.
for (int interval = 0; interval < scaleForArpeggio.Count && interval < countArpeggio; interval++)
{
// Add a note same channel, duration, velocity, but add an interval to the value.
// If delay is not set (or defined to 0), a chord will be played.
MPTKEvent noteArpeggio = new MPTKEvent()
{
Command = MPTKCommand.NoteOn, // midi command
Value = midiEvent.Value + scaleForArpeggio[interval],
Channel = midiEvent.Channel,
Duration = midiEvent.Duration,
Velocity = midiEvent.Velocity,
Delay = interval * arpeggioDelay, // delay in millisecond before playing the arpeggio note.
};
// Add immediately this note to the MIDI synth for immediate playing (with the delay defined in the event)
midiFilePlayer.MPTK_PlayDirectEvent(noteArpeggio);
}
}
}
}
break;
case MPTKCommand.PatchChange:
if (toggleEnableChangePreset)
{
// Transform Patch change event to Meta text event: related channel will played the default preset 0.
// TextEvent has no effect on the MIDI synth but is displayed in the demo windows.
// It would also been possible de change the preset to another instrument.
midiEvent.Command = MPTKCommand.MetaEvent;
midiEvent.Meta = MPTKMeta.TextEvent;
midiEvent.Info = $"Skipping Preset Change: {midiEvent.Value}";
Debug.Log(midiEvent.Info);
}
break;
case MPTKCommand.MetaEvent:
if (midiEvent.Meta == MPTKMeta.SetTempo && toggleEnableChangeTempo)
{
// Randomize tempo (in BPM).
// Warning: this callback is run out of the main Unity thread, Unity API (like UnityEngine.Random) can't be used.
System.Random rnd = new System.Random();
midiFilePlayer.MPTK_Tempo = rnd.Next(30, 240);
// Value contains Microseconds Per Beat Note, convert to BPM for display.
Debug.Log($"Tempo changed from {MPTKEvent.QuarterPerMicroSecond2BeatPerMinute(midiEvent.Value):F0} to {midiFilePlayer.MPTK_Tempo} BPM");
}
break;
}
return keepEventToPlay;
}
private void Update()
{
// Update the scale if it has been changed in the Inspector.
if (currentScale != scaleArpeggio)
{
// Create a scale (a list of intervals) related to the selected scale.
// Be aware that this method must be call from the main Unity thread not from the OnMidiEvent callback.
currentScale = scaleArpeggio;
scaleForArpeggio = MPTKScaleLib.CreateScale(index: currentScale, log: false);
}
// Enable or disable MIDI event logging in real time.
midiFilePlayer.MPTK_LogEvents = toggleLogMidiEvent;
}
}
}