Search Results for

    Show / Hide Table of Contents

    Getting objects

    This article describes ways to get different objects (like timed events or notes) from MIDI files, track chunks and collections of another objects.

    GetTimedEvents

    TimedEvent is the basic MIDI object we will describe here. It's just a MIDI event along with its absolute time within a MIDI file or track chunk. To get all timed events in a MIDI file, you can just call GetTimedEvents method:

    using System;
    using Melanchall.DryWetMidi.Core;
    using Melanchall.DryWetMidi.Interaction;
    
    namespace DwmExamples
    {
        class Program
        {
            static void Main(string[] args)
            {
                var midiFile = MidiFile.Read("My Great Song.mid");
                var timedEvents = midiFile.GetTimedEvents();
    
                Console.WriteLine($"{timedEvents.Count} timed events found.");
            }
        }
    }
    

    Please examine the TimedEventsManagingUtilities class to see other GetTimedEvents overloads.

    GetNotes

    There is the NotesManagingUtilities class which provides useful methods GetNotes to get notes from a MIDI file or track chunk. For example, you can get notes a MIDI file contains with this code:

    using System;
    using Melanchall.DryWetMidi.Common;
    using Melanchall.DryWetMidi.Core;
    using Melanchall.DryWetMidi.Interaction;
    
    namespace DwmExamples
    {
        class Program
        {
            static void Main(string[] args)
            {
                var midiFile = new MidiFile(
                    new TrackChunk(
                        new NoteOnEvent(),
                        new NoteOffEvent(),
                        new NoteOnEvent((SevenBitNumber)70, (SevenBitNumber)50)
                        {
                            Channel = (FourBitNumber)5,
                            DeltaTime = 10
                        },
                        new NoteOffEvent((SevenBitNumber)70, (SevenBitNumber)30)
                        {
                            Channel = (FourBitNumber)5,
                            DeltaTime = 70
                        }));
    
                Console.WriteLine("Notes:");
    
                foreach (var note in midiFile.GetNotes())
                {
                    Console.Write($@"
    note {note} (note number = {note.NoteNumber})
      time = {note.Time}
      length = {note.Length}
      velocity = {note.Velocity}
      off velocity = {note.OffVelocity}");
                }
    
                Console.ReadKey();
            }
        }
    }
    

    Running the program, we'll see following output:

    Notes:
    
    note C-1 (note number = 0)
      time = 0
      length = 0
      velocity = 0
      off velocity = 0
    note A#4 (note number = 70)
      time = 10
      length = 70
      velocity = 50
      off velocity = 30
    

    Please examine NotesManagingUtilities class to see other GetNotes overloads.

    Settings

    All GetNotes overloads can accept NoteDetectionSettings as a parameter. Via this parameter you can adjust the process of notes building.

    More than that, notes are built on top of timed events. So you can pass TimedEventDetectionSettings as a separate parameter to control how underlying MIDI events will be constructed.

    Let's see each setting of the NoteDetectionSettings in detail.

    NoteStartDetectionPolicy

    The NoteStartDetectionPolicy property defines how the start event of a note should be found in case of overlapping notes with the same note number and channel. The default value is NoteStartDetectionPolicy.FirstNoteOn.

    To understand how this policy works let's take a look at the following events sequence:

    NoteStartDetectionPolicy-Initial

    where empty circle and filled one mean Note On and Note Off events correspondingly; cross means any other event. So we have two overlapped notes here (we assume all note events have the same note number and channel).

    If we set NoteStartDetectionPolicy to NoteStartDetectionPolicy.FirstNoteOn, notes will be constructed in following way:

    NoteStartDetectionPolicy-FirstNoteOn

    So every Note Off event will be combined with the first free Note On event into a note (events are processed one by one consecutively). But if set NoteStartDetectionPolicy to NoteStartDetectionPolicy.LastNoteOn, we'll get another picture:

    NoteStartDetectionPolicy-LastNoteOn

    So Note Off events will be combined with the last free Note On event into a note.

    GetChords

    There is the ChordsManagingUtilities class which provides useful methods GetChords to get notes from a MIDI file or track chunk. For example, you can get chords a MIDI file contains with this code:

    using System;
    using Melanchall.DryWetMidi.Common;
    using Melanchall.DryWetMidi.Core;
    using Melanchall.DryWetMidi.Interaction;
    
    namespace DwmExamples
    {
        class Program
        {
            static void Main(string[] args)
            {
                var midiFile = new MidiFile(
                    new TrackChunk(
                        new NoteOnEvent(),
                        new NoteOffEvent(),
                        new NoteOnEvent
                        {
                            Channel = (FourBitNumber)5,
                            DeltaTime = 10
                        },
                        new NoteOffEvent
                        {
                            Channel = (FourBitNumber)5
                        },
                        new NoteOnEvent((SevenBitNumber)70, (SevenBitNumber)50)
                        {
                            Channel = (FourBitNumber)5
                        },
                        new NoteOffEvent((SevenBitNumber)70, (SevenBitNumber)30)
                        {
                            Channel = (FourBitNumber)5,
                            DeltaTime = 70
                        }));
    
                Console.WriteLine("Chords:");
    
                foreach (var chord in midiFile.GetChords())
                {
                    Console.Write($@"
    chord
      channel = {chord.Channel}
      time = {chord.Time}
      length = {chord.Length}
      notes:");
    
                    foreach (var note in chord.Notes)
                    {
                        Console.Write($@"
      note {note} (note number = {note.NoteNumber})
        channel = {note.Channel}
        time = {note.Time}
        length = {note.Length}
        velocity = {note.Velocity}
        off velocity = {note.OffVelocity}");
                    }
                }
    
                Console.ReadKey();
            }
        }
    }
    

    Running the program, we'll see following output:

    Chords:
    
    chord
      channel = 0
      time = 0
      length = 0
      notes:
      note C-1 (note number = 0)
        channel = 0
        time = 0
        length = 0
        velocity = 0
        off velocity = 0
    chord
      channel = 5
      time = 10
      length = 70
      notes:
      note C-1 (note number = 0)
        channel = 5
        time = 10
        length = 0
        velocity = 0
        off velocity = 0
      note A#4 (note number = 70)
        channel = 5
        time = 10
        length = 70
        velocity = 50
        off velocity = 30
    

    Please examine the ChordsManagingUtilities class to see other GetChords overloads.

    Settings

    All GetChords overloads can accept ChordDetectionSettings as a parameter. Via this parameter you can adjust the process of chord building.

    Also note that chords are built on top of notes. So to build chords we need to build notes. The process of notes building is adjustable via NoteDetectionSettings which you can pass to the methods too. Properties of the NoteDetectionSettings are described in detail above.

    More than that, notes are built on top of timed events as described above. So you can pass TimedEventDetectionSettings too.

    Let's see each setting of the ChordDetectionSettings in detail.

    NotesTolerance

    The NotesTolerance property defines the maximum distance of notes from the start of the first note of a chord. Notes within this tolerance will be included in a chord. The default value is 0.

    To understand how this property works let's take a look at the following notes (cross means any non-note event):

    NotesTolerance-Initial

    If we set notes tolerance to 0 (which is default value), we'll get three different chords (each of one note):

    NotesTolerance-0

    Different colors denote different chords. If we set notes tolerance to 1, we'll get two chords:

    NotesTolerance-1

    With tolerance of 2 we'll finally get a single chord:

    NotesTolerance-2

    NotesMinCount

    The NotesMinCount property defines the minimum count of notes a chord can contain. So if the count of simultaneously sounding notes is less than this value, they won't make up a chord. The default value is 1 which means a single note can be turned to a chord.

    To understand how this property works let's take a look at the following notes (cross means any non-note event):

    NotesMinCount-Initial

    So we have three notes. For simplicity we'll assume that NotesTolerance is 0 (default value). If we set notes min count to 1 (which is default value), we'll get two different chords:

    NotesMinCount-1

    If we set notes min count to 2, we'll get only one chord:

    NotesMinCount-2

    Last note will not be turned into a chord because the count of notes for a chord will be 1 which is less than the specified minimum count. With minimum count of notes of 3 we'll get no chords:

    NotesMinCount-3

    First possible chord will contain two notes and the second chord will contain one note. In both cases the count of notes is less than the specified minimum count.

    GetObjects

    All methods we saw before return a collection of objects of the same type. So you can get only either notes or chords or timed events. To highlight the problem, let's take a look at the following events sequence:

    GetObjects-Initial

    where empty circle and filled one mean Note On and Note Off events correspondingly; cross means any other event. We assume all note events have the same note number and channel.

    With GetTimedEvents we'll just get all these events as is. GetNotes will give us only notes:

    GetObjects-GetNotes

    GetChords will return only chords (single one in this example):

    GetObjects-GetChords

    So if we run following simple program:

    using System;
    using System.Collections.Generic;
    using Melanchall.DryWetMidi.Core;
    using Melanchall.DryWetMidi.Interaction;
    
    namespace DwmExamples
    {
        class Program
        {
            static void Main(string[] args)
            {
                var midiFile = new MidiFile(
                    new TrackChunk(
                        new TextEvent("1"),
                        new NoteOnEvent { DeltaTime = 1 },
                        new TextEvent("2") { DeltaTime = 1 },
                        new NoteOffEvent { DeltaTime = 1 },
                        new TextEvent("3") { DeltaTime = 1 },
                        new NoteOnEvent { DeltaTime = 1 },
                        new TextEvent("4") { DeltaTime = 1 },
                        new NoteOffEvent { DeltaTime = 1 },
                        new TextEvent("5") { DeltaTime = 1 },
                        new NoteOnEvent { DeltaTime = 1 },
                        new TextEvent("6")),
                    new TrackChunk(
                        new TextEvent("A"),
                        new TextEvent("B") { DeltaTime = 1 },
                        new TextEvent("C") { DeltaTime = 1 },
                        new TextEvent("D") { DeltaTime = 1 },
                        new TextEvent("E") { DeltaTime = 1 },
                        new NoteOnEvent { DeltaTime = 1 },
                        new TextEvent("F") { DeltaTime = 1 },
                        new NoteOffEvent { DeltaTime = 1 },
                        new TextEvent("G") { DeltaTime = 1 },
                        new TextEvent("H") { DeltaTime = 1 },
                        new TextEvent("I")));
    
                Console.WriteLine("Getting timed events...");
                WriteTimedObjects(midiFile.GetTimedEvents());
                Console.WriteLine("Getting notes...");
                WriteTimedObjects(midiFile.GetNotes());
                Console.WriteLine("Getting chords...");
                WriteTimedObjects(midiFile.GetChords(new ChordDetectionSettings
                {
                    NotesMinCount = 2
                }));
    
                Console.ReadKey();
            }
    
            private static void WriteTimedObjects<TObject>(ICollection<TObject> timedObjects)
                where TObject : ITimedObject
            {
                foreach (var timedObject in timedObjects)
                {
                    Console.WriteLine($"[{timedObject.GetType().Name}] {timedObject} (time = {timedObject.Time})");
                }
            }
        }
    }
    

    we'll get this output:

    Getting timed events...
    [TimedEvent] Event at 0: Text (1) (time = 0)
    [TimedEvent] Event at 0: Text (A) (time = 0)
    [TimedEvent] Event at 1: Note On [0] (0, 0) (time = 1)
    [TimedEvent] Event at 1: Text (B) (time = 1)
    [TimedEvent] Event at 2: Text (2) (time = 2)
    [TimedEvent] Event at 2: Text (C) (time = 2)
    [TimedEvent] Event at 3: Note Off [0] (0, 0) (time = 3)
    [TimedEvent] Event at 3: Text (D) (time = 3)
    [TimedEvent] Event at 4: Text (3) (time = 4)
    [TimedEvent] Event at 4: Text (E) (time = 4)
    [TimedEvent] Event at 5: Note On [0] (0, 0) (time = 5)
    [TimedEvent] Event at 5: Note On [0] (0, 0) (time = 5)
    [TimedEvent] Event at 6: Text (4) (time = 6)
    [TimedEvent] Event at 6: Text (F) (time = 6)
    [TimedEvent] Event at 7: Note Off [0] (0, 0) (time = 7)
    [TimedEvent] Event at 7: Note Off [0] (0, 0) (time = 7)
    [TimedEvent] Event at 8: Text (5) (time = 8)
    [TimedEvent] Event at 8: Text (G) (time = 8)
    [TimedEvent] Event at 9: Note On [0] (0, 0) (time = 9)
    [TimedEvent] Event at 9: Text (6) (time = 9)
    [TimedEvent] Event at 9: Text (H) (time = 9)
    [TimedEvent] Event at 9: Text (I) (time = 9)
    Getting notes...
    [Note] C-1 (time = 1)
    [Note] C-1 (time = 5)
    [Note] C-1 (time = 5)
    Getting chords...
    [Chord] C-1 C-1 (time = 5)
    

    As you can see there is a "free" Note On event without corresponding Note Off one so we can't build a note for it. What if we want to get all possible notes and all remaining timed events? DryWetMIDI provides the GetObjectsUtilities class which contains GetObjects methods (for the same MIDI structures as previous methods). We can change printing part of the program above to:

    Console.WriteLine("Getting notes and timed events...");
    WriteTimedObjects(midiFile.GetObjects(ObjectType.Note | ObjectType.TimedEvent));
    

    which will give us following output:

    Getting notes and timed events...
    [TimedEvent] Event at 0: Text (1) (time = 0)
    [TimedEvent] Event at 0: Text (A) (time = 0)
    [Note] C-1 (time = 1)
    [TimedEvent] Event at 1: Text (B) (time = 1)
    [TimedEvent] Event at 2: Text (2) (time = 2)
    [TimedEvent] Event at 2: Text (C) (time = 2)
    [TimedEvent] Event at 3: Text (D) (time = 3)
    [TimedEvent] Event at 4: Text (3) (time = 4)
    [TimedEvent] Event at 4: Text (E) (time = 4)
    [Note] C-1 (time = 5)
    [Note] C-1 (time = 5)
    [TimedEvent] Event at 6: Text (4) (time = 6)
    [TimedEvent] Event at 6: Text (F) (time = 6)
    [TimedEvent] Event at 8: Text (5) (time = 8)
    [TimedEvent] Event at 8: Text (G) (time = 8)
    [TimedEvent] Event at 9: Note On [0] (0, 0) (time = 9)
    [TimedEvent] Event at 9: Text (6) (time = 9)
    [TimedEvent] Event at 9: Text (H) (time = 9)
    [TimedEvent] Event at 9: Text (I) (time = 9)
    

    So all note events that build up a note were turned into instances of Note, and all remaining events (including "free" Note On one) were returned as instances of TimedEvent.

    We can go further and collect all possible chords, notes and timed events:

    Console.WriteLine("Getting chords, notes and timed events...");
    WriteTimedObjects(midiFile.GetObjects(
        ObjectType.Chord | ObjectType.Note | ObjectType.TimedEvent,
        new ObjectDetectionSettings
        {
            ChordDetectionSettings = new ChordDetectionSettings
            {
                NotesMinCount = 2
            }
        }));
    

    which will give us following output:

    Getting chords, notes and timed events...
    [TimedEvent] Event at 0: Text (1) (time = 0)
    [TimedEvent] Event at 0: Text (A) (time = 0)
    [Note] C-1 (time = 1)
    [TimedEvent] Event at 1: Text (B) (time = 1)
    [TimedEvent] Event at 2: Text (2) (time = 2)
    [TimedEvent] Event at 2: Text (C) (time = 2)
    [TimedEvent] Event at 3: Text (D) (time = 3)
    [TimedEvent] Event at 4: Text (3) (time = 4)
    [TimedEvent] Event at 4: Text (E) (time = 4)
    [Chord] C-1 C-1 (time = 5)
    [TimedEvent] Event at 6: Text (4) (time = 6)
    [TimedEvent] Event at 6: Text (F) (time = 6)
    [TimedEvent] Event at 8: Text (5) (time = 8)
    [TimedEvent] Event at 8: Text (G) (time = 8)
    [TimedEvent] Event at 9: Note On [0] (0, 0) (time = 9)
    [TimedEvent] Event at 9: Text (6) (time = 9)
    [TimedEvent] Event at 9: Text (H) (time = 9)
    [TimedEvent] Event at 9: Text (I) (time = 9)
    

    Or in visual representation:

    GetObjects-GetChordsAndNotesAndTimedEvents

    Currently GetObjects can build objects of the following types:

    • TimedEvent
    • Note
    • Chord
    • Rest

    Rests

    To build rests you need to use extension methods from the RestsUtilities class.

    If you take a look into the class, you'll discover two methods – WithRests and GetRests. The first one adds rests to a collection of objects you've passed to the method. The second method returns rests only.

    It will be much easier to understand how rests building works with examples. So let's look at the WithRests (there is no great value to discuss GetRests since it works in the same way but just returns rests only).

    Supposing we have following notes (with two different note numbers on two different channels):

    GetObjects-Rests-Initial

    Using following code:

    var notesAndRests = notes
        .WithRests(new RestDetectionSettings
        {
            KeySelector = obj => 0
        });
    

    we'll get only one rest:

    GetObjects-Rests-NoSeparation

    An important concept we need to discuss is key selection. Key is used to calculate rests. Rests are always calculated only between objects with the same key. If an object with different key is encountered, rests will be calculated for that key.

    In the code above we're saying: The key of each object is 0. So for the rests building algorithm all objects are the same, there is no difference between channels and note numbers, for example. So rests will be constructed only at spaces where there are no notes at all (with any channels and any note numbers).

    Also please take a look at the predefined key selectors available via constants of the RestDetectionSettings. We can rewrite code above:

    var notesAndRests = notes
        .WithRests(RestDetectionSettings.NoNotes);
    

    Using following code:

    var notesAndRests = notes
        .WithRests(new RestDetectionSettings
        {
            KeySelector = obj => (obj as Note)?.Channel
        });
    

    we'll get three rests now:

    GetObjects-Rests-SeparateByChannel

    So rests are separated by channels. Channel is the key of an object. Note number of a note doesn't matter, all numbers are treated as the same one. So rests will be constructed separately for each channel at spaces where there are no notes (with any note numbers).

    The key for which a rest has been built will be stored in the Key property of Rest class. notesAndRests is a collection containing both notes and calculated rests, and elements of this collection are sorted by their times.

    Note that you can build rests for objects of different types. Why not get chords from a MIDI file and add rests between them?

    var chordsAndRests = midiFile
        .GetObjects(ObjectType.Chord)
        .WithRests(new RestDetectionSettings
        {
            KeySelector = obj => (obj as Chord)?.Channel
        });
    

    And a couple of words about return value of the key selector. If null is returned, an object won't participate in rests building process. It allows you to have rests for desired objects only. For example:

    var notesAndChordsAndRests = midiFile
        .GetObjects(ObjectType.Note | ObjectType.Chord)
        .WithRests(new RestDetectionSettings
        {
            KeySelector = obj => (obj as Note)?.Channel
        });
    

    Here we specify that rests will be built for notes only (key selector will return null for an object other than note). So the result collection will have chords, notes and rests between notes with channel as the key.

    And a couple of additional examples with notes presented on the picture above.

    Code:

    var notesAndRests = notes
        .WithRests(new RestDetectionSettings
        {
            KeySelector = obj => (obj as Note)?.NoteNumber
        });
    

    Rests:

    GetObjects-Rests-SeparateByNoteNumber

    As you can see, rests now are separated by note number (channel doesn't matter). So rests will be constructed for each note number at spaces where there are no notes (with any channel).

    Code:

    var notesAndRests = notes
        .WithRests(new RestDetectionSettings
        {
            KeySelector = obj => ((obj as Note)?.NoteNumber, (obj as Note)?.NoteNumber)
        });
    

    Now we'll get rests at every "free" space (since the key is a pair of channel and note's number):

    GetObjects-Rests-SeparateByChannelAndNoteNumber

    In this article
    Back to top 2024 / Generated by DocFX