Maestro - Midi Player Tool Kit for Unity Version 2.19.0
Loading...
Searching...
No Matches
MidiPlayerTK.MPTKWriter

Create, build, write, import, and play MIDI from script. See full examples in: More...

Tempo and Time Conversion

Tempo state, tempo/signature maps, and tick/millisecond conversion helpers.

This section defines the timing model used by the writer. It includes DTPQN, current tempo, cached tempo/signature maps, and helper methods to convert absolute positions and durations between ticks and milliseconds.

int DeltaTicksPerQuarterNote
 Delta Ticks Per Quarter Note (DTPQN) represents the number of ticks in one quarter note.
For example, with 96 ticks per quarter note, an eighth note has a duration of 48 ticks.
In a MIDI file, this value is found in the MIDI header and remains constant for the entire file.
More information: https://paxstellar.fr/2020/09/11/midi-timing/
.
List< MPTKTempoMPTK_TempoMap
 List of tempo segments derived from MPTK_MidiEvents. Rebuild with MPTKTempo.CalculateMap when events change.
See example:
List< MPTKSignatureMPTK_SignMap
 List of time-signature segments derived from MPTK_MidiEvents. Rebuild with MPTKSignature.CalculateMap when events change.
See example:
int MicrosecondsPerQuaterNote [get]
 Gets the current tempo value in microseconds per quarter note: 60 * 1000 * 1000 / BPM.
.
double CurrentTempo [get]
 Gets the current tempo in BPM. Updated by AddTempoChange and AddBPMChange.
https://en.wikipedia.org/wiki/Tempo.
float PulseLenght [get]
 Gets the current duration, in milliseconds, of one MIDI tick (relative to the current tempo).
.
float ConvertTickToMilli (long tick, int indexTempo=-1)
 Converts an absolute tick position to time in milliseconds.
.
long ConvertMilliToTick (float time, int indexTempo=-1)
 Converts a time position in milliseconds to an absolute tick position.
.
long DurationTickToMilli (long tick)
 Converts a tick duration to a millisecond duration using the current tempo.
.
long DurationMilliToTick (float time)
 Converts a millisecond duration to a tick duration using the current tempo.
.

Sequence State and Event Collections

Core state for the current MIDI sequence and generated event list.

This section stores global sequence metadata (name, MIDI format, channels), accessors for the generated event list, and convenience properties such as last event and last tick position.

int ChannelCount = 16
 Gets the MIDI file type of the loaded MIDI (0, 1, or 2).
int MidiFileType
 Gets the MIDI file type of the loaded MIDI (0, 1, or 2).
string MidiName
 Name of this MIDI sequence.
List< MPTKEventMPTK_MidiEvents
 Gets all created MIDI events.
int TrackCount [get]
 Gets the number of tracks. This value is available only when CreateTracksStat() has been called. There is no longer a track-count limit as of V2.9.0.
MPTKEvent MPTK_LastEvent [get]
 Last MIDI event in the created MIDI events list. It is not always the last sound played, especially if a previous event lasts longer.
long TickLast [get]
 Last tick position including event duration.
int CountEvent [get]
 Number of MIDI events in MPTK_MidiEvents.

MIDI Event Creation API

Add musical, control, and meta events to the sequence.

This section is the main authoring API for building MIDI content: notes, chords, program/controller changes, pitch wheel, tempo/signature, and text meta events.

bool ExtendedText [get, set]
 If true, META text (for example lyrics) is written with UTF-8 encoding. Default is false.
Standard MIDI text meta events are ASCII. With this extension, you can also store and display
non-ASCII characters (Korean, Chinese, Japanese, accented Latin characters, and more).
To enable reading UTF8 characters from an MPTK MIDI player, set MidiFilePlayer.MPTK_ExtendedText to true.
.
MPTKEvent AddRawEvent (MPTKEvent mptkEvent)
 Adds an event directly from an MPTKEvent instance.
.
MPTKEvent AddNote (int track, long tick, int channel, int note, int velocity, int length)
 Adds a NoteOn event at an absolute tick position.
A corresponding NoteOff is generated automatically when length is greater than 0.
If length is less than 0, no automatic NoteOff is created.
MPTKEvent AddSilence (int track, long tick, int channel, int length)
 Adds a silence placeholder.
.
MPTKEvent AddOff (int track, long tick, int channel, int note)
 Adds a note-off event.
It must always follow the corresponding NoteOn, on the same channel.
void AddChordFromScale (int track, long tick, int channel, MPTKScaleLib scale, MPTKChordBuilder chord)
 Adds a chord built from a scale range.
void AddChordFromLib (int track, long tick, int channel, MPTKChordName chordName, MPTKChordBuilder chord)
 Adds a chord from a chord library.
MPTKEvent AddChangePreset (int track, long tick, int channel, int preset)
 Adds a preset change.
MPTKEvent AddChannelAfterTouch (int track, long tick, int channel, int afterTouchPressure)
 Adds a Channel After-Touch event.
MPTKEvent AddControlChange (int track, long tick, int channel, MPTKController controller, int controllerValue)
 Creates a general Control Change event (CC).
MPTKEvent AddPitchWheelChange (int track, long tick, int channel, float pitchWheel)
 Creates a Pitch Wheel event from a normalized value.
pitchWheel:
MPTKEvent AddBPMChange (int track, long tick, int bpm)
 Adds a tempo change to the MIDI stream. There is no channel parameter because a tempo change is applied to all tracks and channels.
Subsequent duration/time conversions use the new BPM value.
MPTKEvent AddTempoChange (int track, long tick, int microsecondsPerQuarterNote)
 Adds a tempo change to the MIDI stream in microseconds per quarter note.
There is no channel parameter because a tempo change is applied to all tracks and channels.
Subsequent duration/time conversions use the new tempo value.
MPTKEvent AddTimeSignature (int track, long tick, int numerator=4, int denominator=2, int ticksInMetronomeClick=24, int no32ndNotesInQuarterNote=32)
 Creates a TimeSignature meta event (optional). The internal sequencer default is 4,2,24,32. No channel is required because time signature applies globally. More information: https://paxstellar.fr/2020/09/11/midi-timing/.
MPTKEvent AddText (int track, long tick, MPTKMeta typeMeta, string text)
 Creates a MIDI text event.

Initialization and Import

Initialize, reset, and merge event lists into the writer.

Includes constructor defaults, full reset logic, and list import/merge behavior with DTPQN conversion and post-import timing recalculation.

 MPTKWriter (int deltaTicksPerQuarterNote=240, int midiFileType=1, int bpm=120, int channelcount=16)
 Creates an empty MPTKWriter with default or custom MIDI header values.
Default:
.
void Clear ()
 Removes all MIDI events and restores default attributes:
bool ImportFromEventsList (List< MPTKEvent > midiEventsToInsert, int deltaTicksPerQuarterNote, long position=-1, string name=null, bool logPerf=false, bool logDebug=false)
 Imports a list of MPTKEvent instances.
.

Loading Existing MIDI Content

Load sequence data from file system or MidiDB.

These methods replace the current in-memory event list with events loaded from an external source and restore timing header values such as DTPQN.

bool LoadFromFile (string filename)
 Loads a MIDI file from the OS file system (behavior may depend on the OS).
bool LoadFromMidiDB (int indexMidiDb)
 Loads a MIDI from MPTK MidiDB into this writer. Existing events are replaced. If you add events afterward, sort the list before writing.

Track Statistics

Build and maintain per-track counters derived from event content.

Track statistics are used by export/diagnostic operations to summarize event distribution by track (count, notes, preset changes).

Dictionary< long, MPTKStat > CreateTracksStat ()
 Builds track statistics (MPTK_TrackStat) from events in MPTK_MidiEvents. The dictionary key is the track number, and each value contains aggregated counters.

Event List Maintenance and Timing Recalculation

Modify event collections and recompute sorted/timed state.

Use these methods to delete subsets of events, apply stable sorting, and rebuild real-time/measure/beat values after content changes.

void DeleteChannel (int channel)
 Deletes all MIDI events on this channel.
void DeleteTrack (int track)
 Deletes all MIDI events on this track.
void StableSortEvents (bool logPerf=false)
 Sorts events in MPTK_MidiEvents in place by ascending tickFromTime position. Priority is applied for 'bank change' and 'preset change' within a group of events at the same position (but 'end track' is placed at the end of the group).
void CalculateTiming (bool logPerf=false, bool logDebug=false)
 Calculates real-time position, measure, and beat for each event.

Export and Diagnostics

Export MIDI files and inspect generated output/logs.

This section converts MPTK events to NAudio structures, writes files to disk or MidiDB, and provides detailed logging helpers for validation.

bool WriteToFile (string filename)
 Writes a MIDI file to an OS folder.
bool WriteToMidiDB (string filename)
 Writes a MIDI file to MidiDB.
To be used only in Edit mode, not in a standalone application.
A call to AssetDatabase.Refresh() is required after the file has been added to the Resources folder.
MidiFile BuildNAudioMidi ()
 Builds an NAudio MidiFile object from MPTK_MidiEvents. WriteToMidiDB and WriteToFile call this method before exporting.
bool LogWriter ()
 Logs writer state and MIDI events.
void LogTempoMap ()
 Logs information about the tempo map.
void LogSignMap ()
 Logs information about the signature map.
void LogTrackStat ()
 Logs information about track statistics.
bool LogRaw ()
 Logs raw NAudio MIDI events built from the current writer state.

Detailed Description

Create, build, write, import, and play MIDI from script. See full examples in:

  • TestMidiGenerator.cs for MIDI creation examples.
  • TinyMidiSequencer.cs for a light sequencer.
  • MidiEditorProWindow.cs.cs for a full MIDI editor.
    Note
    Methods such as MPTK_AddxxxxMilli will be deprecated in a future version.
    MIDI events can be added either with tick-based positions (AddNote) or millisecond-based positions (AddNoteMilli). For consistency, the recommended approach is to use tick-based methods and convert with ConvertTickToMilli when needed. More information here: https://paxstellar.fr/class-MPTKWriter/
    Version
    Maestro Pro
    private MPTKWriter CreateMidiStream_preset_tempo_pitchWheel()
    {
    // 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
    // https://paxstellar.fr/2020/09/11/midi-timing/
    int beatsPerMinute = 60;
    // a classical value for a Midi. define the time precision
    int ticksPerQuarterNote = 500;
    // Create a Midi file of type 1 (recommended)
    MPTKWriter mfw = new MPTKWriter(ticksPerQuarterNote, 1);
    // Time to play a note expressed in ticks.
    // All durations are expressed in ticks, so this value can be used to convert
    // duration notes as quarter to ticks. https://paxstellar.fr/2020/09/11/midi-timing/
    // If ticksPerQuarterNote = 120 and absoluteTime = 120 then the note will be played a quarter delay from the start.
    // If ticksPerQuarterNote = 120 and absoluteTime = 1200 then the note will be played a 10 quarter delay from the start.
    long absoluteTime = 0;
    // Some textual information added to the track 0 at time=0
    mfw.AddText(track0, absoluteTime, MPTKMeta.SequenceTrackName, "MIDI Generated with MPTK with tempo, preset, pitch wheel change");
    // TimeSignature event (optional) https://paxstellar.fr/2020/09/11/midi-timing/
    // Numerator = number of beats in a bar.
    // Denominator = beat unit exponent: 1->2, 2->4 (quarter), 3->8 (eighth), 4->16, 5->32.
    mfw.AddTimeSignature(track0, absoluteTime, 4, 2); // for a 4/4 signature
    // Tempo is defined in beats per minute (optional, default MIDI tempo is 120).
    // beatsPerMinute is set to 60 at start: a slow tempo, one quarter note per second.
    // Tempo is global for the whole MIDI, independent of each track and channel.
    mfw.AddBPMChange(track0, absoluteTime, beatsPerMinute);
    // Preset for channel 1. Program 25 is usually Acoustic Guitar:
    // https://en.wikipedia.org/wiki/General_MIDI
    // Some players (such as Media Player) may reject a MIDI file if the preset change is defined in track 0,
    // so we set it in track 1.
    mfw.AddChangePreset(track1, absoluteTime, channel0, preset: 25);
    //
    // Build first bar
    // ---------------
    // Creation of the first bar in the partition :
    // add four quarter with a tick duration of one quarter (one second with BPM=60)
    // 57 --> A4
    // 60 --> C5
    // 62 --> D5
    // 65 --> F5
    // Some lyrics added to the track 0
    mfw.AddText(track0, absoluteTime, MPTKMeta.Lyric, "Build first bar");
    mfw.AddNote(track1, absoluteTime, channel0, note: 57, velocity: 50, length: ticksPerQuarterNote);
    absoluteTime += ticksPerQuarterNote; // Next note will be played one quarter after the previous (time signature is 4/4)
    mfw.AddNote(track1, absoluteTime, channel0, note: 60, velocity: 80, length: ticksPerQuarterNote);
    absoluteTime += ticksPerQuarterNote;
    mfw.AddNote(track1, absoluteTime, channel0, note: 62, velocity: 100, length: ticksPerQuarterNote);
    absoluteTime += ticksPerQuarterNote;
    mfw.AddNote(track1, absoluteTime, channel0, note: 65, velocity: 100, length: ticksPerQuarterNote);
    absoluteTime += ticksPerQuarterNote;
    //
    // Build second bar: Same notes but dobble tempo (using the microsecond per quarter change method)
    // -----------------------------------------------------------------------------------------------
    mfw.AddTempoChange(track0, absoluteTime, MPTKEvent.BeatPerMinute2QuarterPerMicroSecond(beatsPerMinute * 2));
    //return mfw;
    mfw.AddNote(track1, absoluteTime, channel0, note: 57, velocity: 50, length: ticksPerQuarterNote);
    absoluteTime += ticksPerQuarterNote; // Next note will be played one quarter after the previous (time signature is 4/4)
    mfw.AddNote(track1, absoluteTime, channel0, note: 60, velocity: 80, length: ticksPerQuarterNote);
    absoluteTime += ticksPerQuarterNote;
    mfw.AddNote(track1, absoluteTime, channel0, note: 62, velocity: 100, length: ticksPerQuarterNote);
    absoluteTime += ticksPerQuarterNote;
    mfw.AddNote(track1, absoluteTime, channel0, note: 65, velocity: 100, length: ticksPerQuarterNote);
    absoluteTime += ticksPerQuarterNote;
    //
    // Build third bar : one note with a pitch change along the bar
    // -------------------------------------------------------------
    mfw.AddChangePreset(track1, absoluteTime, channel0, preset: 50); // synth string
    // Some lyrics added to the track 0
    mfw.AddText(track0, absoluteTime, MPTKMeta.Lyric, "Pitch wheel effect");
    // Play an infinite note A4 (duration = -1) don't forget the noteoff!
    mfw.AddNote(track1, absoluteTime, channel0, note: 57, velocity: 100, length: -1);
    // Apply pitch wheel on the channel 0
    for (float pitch = 0f; pitch <= 2f; pitch += 0.05f) // 40 steps of 0.05
    {
    mfw.AddPitchWheelChange(track1, absoluteTime, channel0, pitch);
    // Advance position 40 steps and for a total duration of 4 quarters
    absoluteTime += (long)((float)ticksPerQuarterNote * 4f / 40f);
    }
    // The noteoff for A4
    mfw.AddOff(track1, absoluteTime, channel0, 57);
    // Reset pitch change to normal value
    mfw.AddPitchWheelChange(track1, absoluteTime, channel0, 0.5f);
    //
    // Build fourth bar : arpeggio of 16 sixteenth along the bar
    // --------------------------------------------------------
    // Some lyrics added to the track 0
    mfw.AddText(track0, absoluteTime, MPTKMeta.Lyric, "Arpeggio");
    // Dobble the tempo with a variant of AddBPMChange,
    // change tempo defined in microsecond. Use BeatPerMinute2QuarterPerMicroSecond to convert or use directly AddBPMChange
    //mfw.AddTempoChange(track0, absoluteTime, MidiFileWriter2.BeatPerMinute2QuarterPerMicroSecond(beatsPerMinute));
    // Patch/preset to use for channel 1. Generally 11 is Music Box, see https://en.wikipedia.org/wiki/General_MIDI
    mfw.AddChangePreset(track1, absoluteTime, channel0, preset: 11);
    // Add sixteenth notes (duration = quarter / 4) : need 16 sixteenth to build a bar of 4 quarter
    int note = 57;
    for (int i = 0; i < 16; i++)
    {
    mfw.AddNote(track1, absoluteTime, channel0, note: note, velocity: 100, ticksPerQuarterNote / 4);
    // Advance the position by one sixteenth
    absoluteTime += ticksPerQuarterNote / 4;
    note += 1;
    }
    //
    // Build fifth bar : one whole note with vibrato
    // ----------------------------------------------
    // Some lyrics added to the track 0
    mfw.AddText(track0, absoluteTime, MPTKMeta.Lyric, "Vibrato");
    // Add a last whole note (4 quarters duration = 1 bar)
    mfw.AddNote(track1, absoluteTime, channel0, note: 85, velocity: 100, length: ticksPerQuarterNote * 4);
    // Apply modulation change, (vibrato)
    mfw.AddControlChange(track1, absoluteTime, channel0, MPTKController.Modulation, 127);
    absoluteTime += ticksPerQuarterNote * 4;
    // Reset modulation change to normal value
    mfw.AddControlChange(track1, absoluteTime, channel0, MPTKController.Modulation, 0);
    //
    // wrap up : add a silence
    // -----------------------------
    mfw.AddText(track0, absoluteTime, MPTKMeta.Lyric, "Silence");
    absoluteTime += ticksPerQuarterNote;
    //
    // optional : build tempo and signature map, measure and beat
    // ----------------------------------------------------------
    // Sort the events by ascending absolute time
    // Calculate time, measure and beat for each events
    mfw.CalculateTiming(logDebug: true, logPerf: true);
    return mfw;
    }

Constructor & Destructor Documentation

◆ MPTKWriter()

MidiPlayerTK.MPTKWriter.MPTKWriter ( int deltaTicksPerQuarterNote = 240,
int midiFileType = 1,
int bpm = 120,
int channelcount = 16 )

Creates an empty MPTKWriter with default or custom MIDI header values.
Default:
.

  • Delta ticks per quarter note = 240
  • MIDI file type = 1
  • Tempo = 120 BPM
Parameters
deltaTicksPerQuarterNoteDelta ticks per quarter note, default is 240. See DeltaTicksPerQuarterNote.
midiFileTypeType of MIDI format. Must be 0 or 1. Default is 1.
bpmInitial tempo in beats per minute. Default is 120.

Member Function Documentation

◆ Clear()

void MidiPlayerTK.MPTKWriter.Clear ( )

Removes all MIDI events and restores default attributes:

  • MPTK_DeltaTicksPerQuarterNote = 240
  • MPTK_MidiFileType = 1
  • Tempo = 120

◆ ImportFromEventsList()

bool MidiPlayerTK.MPTKWriter.ImportFromEventsList ( List< MPTKEvent > midiEventsToInsert,
int deltaTicksPerQuarterNote,
long position = -1,
string name = null,
bool logPerf = false,
bool logDebug = false )

Imports a list of MPTKEvent instances.
.

Multiple imports can be chained to merge events from different sources.

  • The first import defines DeltaTicksPerQuarterNote.
  • Subsequent imports convert tick and duration values using the DTPQN ratio between source and destination.
  • Real time, measure, and beat are recalculated for the full event list at the end with CalculateTiming(). Example from MIDI Generator
    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 = FindFirstObjectByType<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;
    }

    Example from MIDI Join And Import
    public void InsertMidi()
    {
    if (mfWriter == null)
    Debug.LogWarning("First, create a MidiFileWriter instance");
    else
    {
    // Load the MIDI file selected
    mfLoader.MPTK_MidiIndex = indexSelectedMidi;
    Debug.Log($"MPTK_KeepNoteOff:{mfLoader.MPTK_KeepNoteOff} MPTK_KeepEndTrack:{mfLoader.MPTK_KeepEndTrack} ");
    mfLoader.MPTK_Load();
    // From the UI, get the tick position where to insert
    if (string.IsNullOrWhiteSpace(InputPosition.text)) InputPosition.text = "0";
    long position = Convert.ToInt64(InputPosition.text);
    if (position < 0) { InputPosition.text = "-1"; position = -1; }
    // Insert the MPTK_MidiEvents of the MIDI loaded in the MPTK_MidiEvents of the MIDI Writer.
    mfWriter.ImportFromEventsList(mfLoader.MPTK_MidiEvents, mfLoader.MPTK_DeltaTicksPerQuarterNote, position: position, name: "MidiJoined", logPerf: true, logDebug: true); ;
    /*
    If a MIDI player is playing the MIDI sequence we have to recalculate the duration.
    The MIDI file player (mfPlayer) share the MIDI event list with the MIDI Writer (mfWriter).
    Any changes made to the mfWriter.MPTK_MidiEvents will have a direct effect on the Player.
    But we need to update some properties of the Player to get a correct value of the new duration.
    */
    if (mfPlayer != null && mfPlayer.MPTK_IsPlaying)
    {
    mfPlayer.MPTK_MidiLoaded.MPTK_EventLastNote = mfPlayer.MPTK_MidiLoaded.MPTK_FindLastNote();
    Debug.Log($"Last note-on: {(mfPlayer.MPTK_MidiLoaded.MPTK_EventLastNote.ToString() ?? "not found")} Duration: {mfPlayer.MPTK_MidiLoaded.MPTK_DurationMS / 1000f} second");
    }
    Debug.Log($"{mfLoader.MPTK_MidiName} Loaded {mfLoader.MPTK_MidiEvents.Count} events added, total events: {mfWriter.MPTK_MidiEvents.Count}");
    UpdatePosition();
    }
    }
    public void PlayMidi()
    {
    if (mfWriter != null && mfWriter.MPTK_MidiEvents != null)
    {
    /*
    The MIDI file player will play the MIDI event list found in the MIDI Writer.
    In fact the mfPlayer.MPTK_MidiEvents will directly use the mfWriter.MPTK_MidiEvents to play the MIDI.
    Any changes made to the mfWriter.MPTK_MidiEvents will have a direct effect on the Player.
    During playback it's possible to add, modify or insert MIDI events in mfWriter.MPTK_MidiEvents,
    Changes are automatically taken into account by the player if the change position is after the current tick player (mfPlayer.MPTK_TickPlayer).
    Insertion before mfPlayer.MPTK_TickPlayer may cause the player to malfunction.
    */
    mfPlayer.MPTK_Play(mfWriter);
    }
    else
    Debug.LogWarning("No MidiWriter ready for playing");
    }

    Parameters
    midiEventsToInsertList of MPTKEvent instances to insert.
    deltaTicksPerQuarterNoteDTPQN value of the source events.
    If MPTK_MidiEvents is empty, this value becomes DeltaTicksPerQuarterNote.
  • If events already exist, source timing is converted to the writer DTPQN.
Parameters
positionTick position for insertion: -1 to append, 0 for beginning, or any tick within the sequence.
nameOptional sequence name (sets MidiName).
logDebugEnable debug logs.
Returns
True if import succeeds.

◆ ConvertTickToMilli()

float MidiPlayerTK.MPTKWriter.ConvertTickToMilli ( long tick,
int indexTempo = -1 )

Converts an absolute tick position to time in milliseconds.
.

Parameters
tickAbsolute tick position.
indexTempoOptional index in MPTK_TempoMap for this position. If -1, the map is recalculated and the segment is searched automatically.
Returns
Absolute time in milliseconds.

◆ ConvertMilliToTick()

long MidiPlayerTK.MPTKWriter.ConvertMilliToTick ( float time,
int indexTempo = -1 )

Converts a time position in milliseconds to an absolute tick position.
.

Parameters
timeTime position in milliseconds
indexTempoOptional index in MPTK_TempoMap for this position. If -1, the map is recalculated and the segment is searched automatically.
Returns
Absolute tick position.

◆ DurationTickToMilli()

long MidiPlayerTK.MPTKWriter.DurationTickToMilli ( long tick)

Converts a tick duration to a millisecond duration using the current tempo.
.

Note
Previous calls to AddBPMChange and AddTempoChange have a direct impact on this calculation.
Parameters
tickDuration in ticks.
Returns
Duration in milliseconds.

◆ DurationMilliToTick()

long MidiPlayerTK.MPTKWriter.DurationMilliToTick ( float time)

Converts a millisecond duration to a tick duration using the current tempo.
.

Note
Previous calls to AddBPMChange and AddTempoChange have a direct impact on this calculation.
Parameters
timeDuration in milliseconds.
Returns
Duration in ticks.

◆ LoadFromFile()

bool MidiPlayerTK.MPTKWriter.LoadFromFile ( string filename)

Loads a MIDI file from the OS file system (behavior may depend on the OS).

Parameters
filenameFull path to the MIDI file.
Returns
True if loading succeeds.

◆ LoadFromMidiDB()

bool MidiPlayerTK.MPTKWriter.LoadFromMidiDB ( int indexMidiDb)

Loads a MIDI from MPTK MidiDB into this writer. Existing events are replaced. If you add events afterward, sort the list before writing.

private MPTKWriter LoadMidi_add_four_notes_at_beginning()
{
// 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();
// Load MIDI from DB at index 2 (for testing with just a drum line so in channel 9)
// Select preset 10 for channel 0
mfw.AddChangePreset(track: 1, tick: 0, channel: 0, 10);
// Add four notes (here, we consider Delta Ticks Per Beat Note = 1024)
mfw.AddNote(track: 1, tick: 0, channel: 0, note: 60, velocity: 100, length: 512);
mfw.AddNote(track: 1, tick: 512, channel: 0, note: 60, velocity: 100, length: 512);
mfw.AddNote(track: 1, tick: 1024, channel: 0, note: 60, velocity: 100, length: 512);
mfw.AddNote(track: 1, tick: 1536, channel: 0, note: 60, velocity: 100, length: 512);
// MPTK_Addxxxx methods add event at the end of the MIDI events list
// We need to sort the events by ascending absolute time
// Optional if you want just playing the MIDI
// Calculate real time, measure and quarter for each events
// Without this call, you will get the warning "No tempo map detected".
// @li Calculate #MPTK_TempoMap with #MPTKTempo.MPTK_CalculateMap
// @li Calculate #MPTK_SignMap with #MPTKSignature.MPTK_CalculateMap
// @li Calculate time and duration of each events from the tick value and from the tempo map.
// @li Calculate measure and quarter position taking into account time signature.
// Find a MidiFilePlayer in the hierarchy
// MidiFilePlayer midiPlayer = FindFirstObjectByType<MidiFilePlayer>();
// And play!
// midiPlayer.MPTK_Play(mfw2: mfw);
}
catch (Exception ex) { Debug.LogException(ex); }
//
return mfw;
}
private MPTKWriter CreateMidi_with_text_information()
{
MPTKWriter mfw = null;
int ticksPerQuarterNote = 500;
long absoluteTime = 0;
try
{
// Create a Midi File Writer instance
// -----------------------------------
mfw = new MPTKWriter(deltaTicksPerQuarterNote: ticksPerQuarterNote);
mfw.ExtendedText = true;
// Some textual information added to the track 0 at time=0
mfw.AddNote(track: 1, tick: absoluteTime, channel: 0, note: 60, velocity: 100, length: ticksPerQuarterNote);
mfw.AddText(track: 1, tick: absoluteTime, typeMeta: MPTKMeta.Lyric, text: "only ASCII");
absoluteTime += ticksPerQuarterNote;
mfw.AddNote(track: 1, tick: absoluteTime, channel: 0, note: 62, velocity: 100, length: ticksPerQuarterNote);
mfw.AddText(track: 1, tick: absoluteTime, typeMeta: MPTKMeta.Lyric, text: "リリックテキスト");
absoluteTime += ticksPerQuarterNote;
mfw.AddNote(track: 1, tick: absoluteTime, channel: 0, note: 64, velocity: 100, length: ticksPerQuarterNote);
mfw.AddText(track: 1, tick: absoluteTime, typeMeta: MPTKMeta.Lyric, text: "抒情文字");
absoluteTime += ticksPerQuarterNote;
mfw.AddNote(track: 1, tick: absoluteTime, channel: 0, note: 66, velocity: 100, length: ticksPerQuarterNote);
mfw.AddText(track: 1, tick: absoluteTime, typeMeta: MPTKMeta.Lyric, text: "가사 텍스트");
absoluteTime += ticksPerQuarterNote;
mfw.AddNote(track: 1, tick: absoluteTime, channel: 0, note: 68, velocity: 100, length: ticksPerQuarterNote);
mfw.AddText(track: 1, tick: absoluteTime, typeMeta: MPTKMeta.Lyric, text: "français éôè");
absoluteTime += ticksPerQuarterNote;
mfw.AddNote(track: 1, tick: absoluteTime, channel: 0, note: 68, velocity: 100, length: ticksPerQuarterNote);
mfw.AddText(track: 1, tick: absoluteTime, typeMeta: MPTKMeta.Lyric, text: "Norsk språk");
absoluteTime += ticksPerQuarterNote;
//mfw.AddText(0, 0, MPTKMeta.SequenceTrackName, "It's a sequence track name");
//mfw.AddText(0, 0, MPTKMeta.Lyric, "It's a lyric");
}
catch (Exception ex) { Debug.LogException(ex); }
//
return mfw;
}
Parameters
indexMidiDbIndex in the MidiDB list.
Returns
True if loading succeeds.

◆ CreateTracksStat()

Dictionary< long, MPTKStat > MidiPlayerTK.MPTKWriter.CreateTracksStat ( )

Builds track statistics (MPTK_TrackStat) from events in MPTK_MidiEvents. The dictionary key is the track number, and each value contains aggregated counters.

Returns
Track statistics dictionary.
Exceptions
MaestroException

◆ DeleteChannel()

void MidiPlayerTK.MPTKWriter.DeleteChannel ( int channel)

Deletes all MIDI events on this channel.

Parameters
channelChannel to remove (0-15).

◆ DeleteTrack()

void MidiPlayerTK.MPTKWriter.DeleteTrack ( int track)

Deletes all MIDI events on this track.

Parameters
trackTrack index to remove.

◆ StableSortEvents()

void MidiPlayerTK.MPTKWriter.StableSortEvents ( bool logPerf = false)

Sorts events in MPTK_MidiEvents in place by ascending tickFromTime position. Priority is applied for 'bank change' and 'preset change' within a group of events at the same position (but 'end track' is placed at the end of the group).

Parameters
logPerfEnable performance logging.

◆ WriteToFile()

bool MidiPlayerTK.MPTKWriter.WriteToFile ( string filename)

Writes a MIDI file to an OS folder.

Parameters
filenameOutput file path.
Returns
True if writing succeeds.

◆ WriteToMidiDB()

bool MidiPlayerTK.MPTKWriter.WriteToMidiDB ( string filename)

Writes a MIDI file to MidiDB.
To be used only in Edit mode, not in a standalone application.
A call to AssetDatabase.Refresh() is required after the file has been added to the Resources folder.

Parameters
filenameFilename of the MIDI file, without folder or extension.
Returns
True if writing succeeds.

Member Data Documentation

◆ MPTK_TempoMap

List<MPTKTempo> MidiPlayerTK.MPTKWriter.MPTK_TempoMap

List of tempo segments derived from MPTK_MidiEvents. Rebuild with MPTKTempo.CalculateMap when events change.
See example:

// A MidiFileWriter2 (mfw) has been created with new MidiFileWriter2() With a set of MIDI events.
// Sort the events by ascending absolute time (optional)
// Calculate time, measure and beat for each events
mfw.CalculateTiming(logDebug: true, logPerf: true);
mfw.LogWriter();
Version
2.10.0

◆ MPTK_SignMap

List<MPTKSignature> MidiPlayerTK.MPTKWriter.MPTK_SignMap

List of time-signature segments derived from MPTK_MidiEvents. Rebuild with MPTKSignature.CalculateMap when events change.
See example:

// A MidiFileWriter2 (mfw) has been created with new MidiFileWriter2() With a set of MIDI events.
// Sort the events by ascending absolute time (optional)
// Calculate time, measure and beat for each events
mfw.CalculateTiming(logDebug: true, logPerf: true);
mfw.LogWriter();
Version
2.10.0

◆ MPTK_MidiEvents

List<MPTKEvent> MidiPlayerTK.MPTKWriter.MPTK_MidiEvents

Gets all created MIDI events.

midiFileWriter.MPTK_MidiEvents.ForEach(midiEvent =>
{
midiEvent.Tick += shiftTick;
midiEvent.RealTime += shiftTime;
});

Property Documentation

◆ MicrosecondsPerQuaterNote

int MidiPlayerTK.MPTKWriter.MicrosecondsPerQuaterNote
get

Gets the current tempo value in microseconds per quarter note: 60 * 1000 * 1000 / BPM.
.

Tempo in a MIDI file is stored in microseconds per quarter note. To convert this to BPM, use #MPTKEvent.QuarterPerMicroSecond2BeatPerMinute.
This value can change while generating MIDI when AddTempoChange is called.
More information: https://paxstellar.fr/2020/09/11/midi-timing/

◆ PulseLenght

float MidiPlayerTK.MPTKWriter.PulseLenght
get

Gets the current duration, in milliseconds, of one MIDI tick (relative to the current tempo).
.

This depends on CurrentTempo and DeltaTicksPerQuarterNote.
PulseLength = 60000 / CurrentTempo / DeltaTicksPerQuarterNote

◆ MPTK_LastEvent

MPTKEvent MidiPlayerTK.MPTKWriter.MPTK_LastEvent
get

Last MIDI event in the created MIDI events list. It is not always the last sound played, especially if a previous event lasts longer.

Note
Returns null if no MIDI event is found.

◆ TickLast

long MidiPlayerTK.MPTKWriter.TickLast
get

Last tick position including event duration.

Note
This is not always the last audible sound. A previous event may still ring longer.