Reputation: 5525
I am trying to write a MIDI file in C#. I am using Sanford MIDI Toolkit. The following is a code snippet I am using to write NoteOn
and NoteOff
events.
private static void InsertNoteOn(Track t, int pitch, int velocity, int position, int duration, int channel)
{
ChannelMessageBuilder builder = new ChannelMessageBuilder();
builder.Command = ChannelCommand.NoteOn;
builder.Data1 = pitch;
builder.Data2 = velocity;
builder.MidiChannel = channel;
builder.Build();
t.Insert(position, builder.Result);
}
private static void InsertNoteOff(Track t, int pitch, int velocity, int position, int duration, int channel)
{
ChannelMessageBuilder builder = new ChannelMessageBuilder();
builder.Command = ChannelCommand.NoteOff;
builder.Data1 = pitch;
builder.Data2 = velocity;
builder.MidiChannel = channel;
builder.Build();
t.Insert((position + duration), builder.Result);
}
First I insert all NoteOn
events for all notes of the track, then I insert all NoteOff
events of the track.
The approach works most of the time. However, sometimes the resulting MIDI file is rendered incorrectly. The problem happens sometimes when two notes of the same pitch are written after one another. The first note will render with its length equaling to both notes' lengths, and the second note will have length zero.
My assumption is that the NoteOff
event of the first note is interpreted as NoteOff
of the second note and vice versa.
I have tried the following variations:
NoteOn
and NoteOff
events in the temporal order in the track
NoteOn
events in temporal order and then add all NoteOff
events in temporal orderNoteOff
events in temporal order and then add all NoteOn
events in temporal orderOnly the last approach works, but notes have shorter length, which does not fix the problem.
Is there a fix to this? Is there a specific order which NoteOn
and NoteOff
events should take in the track? Is there a specific order in which the track insertion method should be called?
EDIT: The problem occurs in the following case:
The higher note was moved from C to C# for visibility. These were supposed to be two notes of the same length, but instead one was rendered with both notes' lengths and the other has zero length.
Upvotes: 2
Views: 547
Reputation: 5525
I solved the problem by sorting all the events before inserting them into the track. I used the following method.
private static void InsertNote(int pitch, int velocity, int position, int duration, int channel, ref List<Tuple<int, bool, ChannelMessage>> messages)
{
ChannelMessageBuilder builder = new ChannelMessageBuilder();
builder.Command = ChannelCommand.NoteOn;
builder.Data1 = pitch;
builder.Data2 = velocity;
builder.MidiChannel = channel;
builder.Build();
messages.Add(new Tuple<int, bool, ChannelMessage>(position, true, builder.Result));
builder.Command = ChannelCommand.NoteOff;
builder.Data1 = pitch;
builder.Data2 = velocity;
builder.MidiChannel = channel;
builder.Build();
messages.Add(new Tuple<int, bool, ChannelMessage>(position + duration, false, builder.Result));
}
The method was used in the following way.
List<Tuple<int, bool, ChannelMessage>> messages = new List<Tuple<int, bool, ChannelMessage>>();
foreach (var n in track.Notes)
InsertNote(n.Pitch, n.Velocity, (int)(n.Position * LENGTH_MULTIPLIER), (int)(n.Length * LENGTH_MULTIPLIER), 0, ref messages);
messages = messages.OrderBy(x => x.Item1).ThenBy(x => x.Item2).ToList();
foreach (var x in messages)
t.Insert(x.Item1, x.Item3);
Upvotes: 1
Reputation: 180210
In MIDI files, it is possible for multiple events to have the same timestamp. In this case, they are sent over the wire in the same order as they are written in the file.
The Sanford MIDI toolkit uses only the timestamp to specify an event's position, and does not document how multiple events with the same timestamp are handled.
To ensure that your note-off events come before the note-on events, you have to use different timestamps, i.e., reduce the length of the notes. (To lower the actual difference, increase the timestamp resolution.)
Upvotes: 1