using System;
using System.Collections.Generic;
using NAudio.Utils;
namespace NAudio.Midi
{
///
/// A helper class to manage collection of MIDI events
/// It has the ability to organise them in tracks
///
public class MidiEventCollection : IEnumerable>
{
private int midiFileType;
private readonly List> trackEvents;
///
/// Creates a new Midi Event collection
///
/// Initial file type
/// Delta Ticks Per Quarter Note
public MidiEventCollection(int midiFileType, int deltaTicksPerQuarterNote)
{
this.midiFileType = midiFileType;
DeltaTicksPerQuarterNote = deltaTicksPerQuarterNote;
StartAbsoluteTime = 0;
trackEvents = new List>();
}
///
/// The number of tracks
///
public int Tracks => trackEvents.Count;
///
/// The absolute time that should be considered as time zero
/// Not directly used here, but useful for timeshifting applications
///
public long StartAbsoluteTime { get; set; }
///
/// The number of ticks per quarter note
///
public int DeltaTicksPerQuarterNote { get; }
///
/// Gets events on a specified track
///
/// Track number
/// The list of events
public IList GetTrackEvents(int trackNumber)
{
return trackEvents[trackNumber];
}
///
/// Gets events on a specific track
///
/// Track number
/// The list of events
public IList this[int trackNumber] => trackEvents[trackNumber];
///
/// Adds a new track
///
/// The new track event list
public IList AddTrack()
{
return AddTrack(null);
}
///
/// Adds a new track
///
/// Initial events to add to the new track
/// The new track event list
public IList AddTrack(IList initialEvents)
{
List events = new List();
if (initialEvents != null)
{
events.AddRange(initialEvents);
}
trackEvents.Add(events);
return events;
}
///
/// Removes a track
///
/// Track number to remove
public void RemoveTrack(int track)
{
trackEvents.RemoveAt(track);
}
///
/// Clears all events
///
public void Clear()
{
trackEvents.Clear();
}
///
/// The MIDI file type
///
public int MidiFileType
{
get => midiFileType;
set
{
if (midiFileType != value)
{
// set MIDI file type before calling flatten or explode functions
midiFileType = value;
if (value == 0)
{
FlattenToOneTrack();
}
else
{
ExplodeToManyTracks();
}
}
}
}
///
/// Adds an event to the appropriate track depending on file type
///
/// The event to be added
/// The original (or desired) track number
/// When adding events in type 0 mode, the originalTrack parameter
/// is ignored. If in type 1 mode, it will use the original track number to
/// store the new events. If the original track was 0 and this is a channel based
/// event, it will create new tracks if necessary and put it on the track corresponding
/// to its channel number
public void AddEvent(MidiEvent midiEvent, int originalTrack)
{
if (midiFileType == 0)
{
EnsureTracks(1);
trackEvents[0].Add(midiEvent);
}
else
{
if(originalTrack == 0)
{
// if its a channel based event, lets move it off to
// a channel track of its own
switch (midiEvent.CommandCode)
{
case MidiCommandCode.NoteOff:
case MidiCommandCode.NoteOn:
case MidiCommandCode.KeyAfterTouch:
case MidiCommandCode.ControlChange:
case MidiCommandCode.PatchChange:
case MidiCommandCode.ChannelAfterTouch:
case MidiCommandCode.PitchWheelChange:
EnsureTracks(midiEvent.Channel + 1);
trackEvents[midiEvent.Channel].Add(midiEvent);
break;
default:
EnsureTracks(1);
trackEvents[0].Add(midiEvent);
break;
}
}
else
{
// put it on the track it was originally on
EnsureTracks(originalTrack + 1);
trackEvents[originalTrack].Add(midiEvent);
}
}
}
private void EnsureTracks(int count)
{
for (int n = trackEvents.Count; n < count; n++)
{
trackEvents.Add(new List());
}
}
private void ExplodeToManyTracks()
{
IList originalList = trackEvents[0];
Clear();
foreach (MidiEvent midiEvent in originalList)
{
AddEvent(midiEvent, 0);
}
PrepareForExport();
}
private void FlattenToOneTrack()
{
bool eventsAdded = false;
for (int track = 1; track < trackEvents.Count; track++)
{
foreach (MidiEvent midiEvent in trackEvents[track])
{
if (!MidiEvent.IsEndTrack(midiEvent))
{
trackEvents[0].Add(midiEvent);
eventsAdded = true;
}
}
}
for (int track = trackEvents.Count - 1; track > 0; track--)
{
RemoveTrack(track);
}
if (eventsAdded)
{
PrepareForExport();
}
}
///
/// Sorts, removes empty tracks and adds end track markers
///
public void PrepareForExport()
{
var comparer = new MidiEventComparer();
// 1. sort each track
foreach (var list in trackEvents)
{
MergeSort.Sort(list, comparer);
// 2. remove all End track events except one at the very end
int index = 0;
while (index < list.Count - 1)
{
if(MidiEvent.IsEndTrack(list[index]))
{
list.RemoveAt(index);
}
else
{
index++;
}
}
}
int track = 0;
// 3. remove empty tracks and add missing
while (track < trackEvents.Count)
{
var list = trackEvents[track];
if (list.Count == 0)
{
RemoveTrack(track);
}
else
{
if(list.Count == 1 && MidiEvent.IsEndTrack(list[0]))
{
RemoveTrack(track);
}
else
{
if(!MidiEvent.IsEndTrack(list[list.Count-1]))
{
list.Add(new MetaEvent(MetaEventType.EndTrack, 0, list[list.Count - 1].AbsoluteTime));
}
track++;
}
}
}
}
///
/// Gets an enumerator for the lists of track events
///
public IEnumerator> GetEnumerator()
{
return trackEvents.GetEnumerator();
}
///
/// Gets an enumerator for the lists of track events
///
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return trackEvents.GetEnumerator();
}
}
}