MVH/NAudio-2.2.1/NAudio.Midi/Midi/MidiEventCollection.cs

295 lines
9.4 KiB
C#
Raw Normal View History

2024-06-07 00:47:07 +02:00
using System;
using System.Collections.Generic;
using NAudio.Utils;
namespace NAudio.Midi
{
/// <summary>
/// A helper class to manage collection of MIDI events
/// It has the ability to organise them in tracks
/// </summary>
public class MidiEventCollection : IEnumerable<IList<MidiEvent>>
{
private int midiFileType;
private readonly List<IList<MidiEvent>> trackEvents;
/// <summary>
/// Creates a new Midi Event collection
/// </summary>
/// <param name="midiFileType">Initial file type</param>
/// <param name="deltaTicksPerQuarterNote">Delta Ticks Per Quarter Note</param>
public MidiEventCollection(int midiFileType, int deltaTicksPerQuarterNote)
{
this.midiFileType = midiFileType;
DeltaTicksPerQuarterNote = deltaTicksPerQuarterNote;
StartAbsoluteTime = 0;
trackEvents = new List<IList<MidiEvent>>();
}
/// <summary>
/// The number of tracks
/// </summary>
public int Tracks => trackEvents.Count;
/// <summary>
/// The absolute time that should be considered as time zero
/// Not directly used here, but useful for timeshifting applications
/// </summary>
public long StartAbsoluteTime { get; set; }
/// <summary>
/// The number of ticks per quarter note
/// </summary>
public int DeltaTicksPerQuarterNote { get; }
/// <summary>
/// Gets events on a specified track
/// </summary>
/// <param name="trackNumber">Track number</param>
/// <returns>The list of events</returns>
public IList<MidiEvent> GetTrackEvents(int trackNumber)
{
return trackEvents[trackNumber];
}
/// <summary>
/// Gets events on a specific track
/// </summary>
/// <param name="trackNumber">Track number</param>
/// <returns>The list of events</returns>
public IList<MidiEvent> this[int trackNumber] => trackEvents[trackNumber];
/// <summary>
/// Adds a new track
/// </summary>
/// <returns>The new track event list</returns>
public IList<MidiEvent> AddTrack()
{
return AddTrack(null);
}
/// <summary>
/// Adds a new track
/// </summary>
/// <param name="initialEvents">Initial events to add to the new track</param>
/// <returns>The new track event list</returns>
public IList<MidiEvent> AddTrack(IList<MidiEvent> initialEvents)
{
List<MidiEvent> events = new List<MidiEvent>();
if (initialEvents != null)
{
events.AddRange(initialEvents);
}
trackEvents.Add(events);
return events;
}
/// <summary>
/// Removes a track
/// </summary>
/// <param name="track">Track number to remove</param>
public void RemoveTrack(int track)
{
trackEvents.RemoveAt(track);
}
/// <summary>
/// Clears all events
/// </summary>
public void Clear()
{
trackEvents.Clear();
}
/// <summary>
/// The MIDI file type
/// </summary>
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();
}
}
}
}
/// <summary>
/// Adds an event to the appropriate track depending on file type
/// </summary>
/// <param name="midiEvent">The event to be added</param>
/// <param name="originalTrack">The original (or desired) track number</param>
/// <remarks>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</remarks>
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<MidiEvent>());
}
}
private void ExplodeToManyTracks()
{
IList<MidiEvent> 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();
}
}
/// <summary>
/// Sorts, removes empty tracks and adds end track markers
/// </summary>
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++;
}
}
}
}
/// <summary>
/// Gets an enumerator for the lists of track events
/// </summary>
public IEnumerator<IList<MidiEvent>> GetEnumerator()
{
return trackEvents.GetEnumerator();
}
/// <summary>
/// Gets an enumerator for the lists of track events
/// </summary>
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return trackEvents.GetEnumerator();
}
}
}