From 8098f78dd174b0d8200099737d8cd29272cb9309 Mon Sep 17 00:00:00 2001 From: gocha Date: Sat, 16 Jun 2018 22:47:42 +0900 Subject: [PATCH] Fix possible KeyNotFoundException on SMF format 0 --- src/MidiSplit/MidiSplit.cs | 142 ++++++++++++++++++++++++++----------- 1 file changed, 99 insertions(+), 43 deletions(-) diff --git a/src/MidiSplit/MidiSplit.cs b/src/MidiSplit/MidiSplit.cs index 7183dcd..612e77f 100644 --- a/src/MidiSplit/MidiSplit.cs +++ b/src/MidiSplit/MidiSplit.cs @@ -15,7 +15,7 @@ namespace MidiSplit public class MidiSplit { public const string NAME = "MidiSplit"; - public const string VERSION = "1.2"; + public const string VERSION = "1.2.5"; public const string AUTHOR = "gocha"; public const string URL = "http://github.com/gocha/midisplit"; @@ -237,6 +237,11 @@ protected struct MTrkChannelParam public int ProgramNumber; public int BankNumber; public int NoteNumber; + + public override string ToString() + { + return "(" + this.GetType().Name + ")[MidiChannel: " + MidiChannel + ", ProgramNumber: " + ProgramNumber + ", BankNumber: " + BankNumber + ", NoteNumber: " + NoteNumber + "]"; + } } protected class MTrkChunkWithInfo @@ -249,6 +254,8 @@ protected class MTrkChunkWithInfo static IEnumerable SplitMidiTrack(MTrkChunk midiTrackIn, bool copySeparatedControllers, IList percMidiChannels, IList percProgChanges) { + bool verbose = false; + if (percMidiChannels == null) { percMidiChannels = new List(); @@ -351,7 +358,9 @@ static IEnumerable SplitMidiTrack(MTrkChunk midiTrackIn, bool copySep // save the trigger timing if (allNotesOff) { - boundaryEventIndex[midiChannel] = midiEventIndex + 1; + // find the next channel message of the same channel + boundaryEventIndex[midiChannel] = midiEventIndex + 1 + midiEventListIn.Skip(midiEventIndex + 1).TakeWhile(ev => + !(ev.Message is MidiChannelMessage && ((MidiChannelMessage)ev.Message).MidiChannel == midiChannel)).Count(); } } } @@ -389,10 +398,25 @@ static IEnumerable SplitMidiTrack(MTrkChunk midiTrackIn, bool copySep lastTrackMapIndex[midiChannel] = boundaryEventIndex[midiChannel]; } - midiEventMapTo[lastTrackMapIndex[midiChannel]] = channelParam; + + try + { + midiEventMapTo.Add(lastTrackMapIndex[midiChannel], channelParam); + } + catch (ArgumentException e) + { + // debug output and fallback + int key = lastTrackMapIndex[midiChannel]; + Console.WriteLine(e); + Console.WriteLine(" " + key + " => " + midiEventMapTo[key]); + Console.WriteLine(" is overwritten by " + channelParam); + midiEventMapTo[key] = channelParam; + } } - boundaryEventIndex[midiChannel] = midiEventIndex + 1; + // find the next channel message of the same channel + boundaryEventIndex[midiChannel] = midiEventIndex + 1 + midiEventListIn.Skip(midiEventIndex + 1).TakeWhile(ev => + !(ev.Message is MidiChannelMessage && ((MidiChannelMessage)ev.Message).MidiChannel == midiChannel)).Count(); } else if (channelMessage.Command == MidiChannelCommand.ProgramChange) { @@ -425,12 +449,26 @@ static IEnumerable SplitMidiTrack(MTrkChunk midiTrackIn, bool copySep channelParam.ProgramNumber = currentProgramNumber[midiChannel]; channelParam.NoteNumber = -1; // will be filled later - midiEventMapTo[boundaryEventIndex[midiChannel]] = channelParam; + try + { + midiEventMapTo.Add(boundaryEventIndex[midiChannel], channelParam); + } + catch (ArgumentException e) + { + // debug output and fallback + int key = boundaryEventIndex[midiChannel]; + Console.WriteLine(e); + Console.WriteLine(" " + key + " => " + midiEventMapTo[key]); + Console.WriteLine(" is overwritten by " + channelParam); + midiEventMapTo[key] = channelParam; + } lastTrackMapIndex[midiChannel] = boundaryEventIndex[midiChannel]; } // update the trigger timing - boundaryEventIndex[midiChannel] = midiEventIndex + 1; + // find the next channel message of the same channel + boundaryEventIndex[midiChannel] = midiEventIndex + 1 + midiEventListIn.Skip(midiEventIndex + 1).TakeWhile(ev => + !(ev.Message is MidiChannelMessage && ((MidiChannelMessage)ev.Message).MidiChannel == midiChannel)).Count(); } } else if (channelMessage.Command == MidiChannelCommand.ControlChange) @@ -463,8 +501,6 @@ static IEnumerable SplitMidiTrack(MTrkChunk midiTrackIn, bool copySep { if (midiEventMapTo.ContainsKey(midiEventIndex)) { - //Console.WriteLine("event: " + midiEventIndex + ", channel: " + midiEventMapTo[midiEventIndex].MidiChannel + ", bank: " + midiEventMapTo[midiEventIndex].BankNumber + ", program: " + midiEventMapTo[midiEventIndex].ProgramNumber + ", note: " + midiEventMapTo[midiEventIndex].NoteNumber); - // remove if item is exactly identical to previos one if (lastChannelParam.HasValue && midiEventMapTo[midiEventIndex].Equals(lastChannelParam.Value)) { @@ -480,8 +516,12 @@ static IEnumerable SplitMidiTrack(MTrkChunk midiTrackIn, bool copySep // create output tracks foreach (KeyValuePair aMidiEventMapTo in midiEventMapTo) { - //Console.WriteLine(String.Format("Event: {0}, Channel: {1}, Bank: {2}, Program: {3}", - // aMidiEventMapTo.Key, aMidiEventMapTo.Value.MidiChannel, aMidiEventMapTo.Value.BankNumber, aMidiEventMapTo.Value.ProgramNumber)); + if (verbose) + { + Console.WriteLine(String.Format("[MidiEventMap] Index: {0} => MidiChannel: {1}, BankNumber: {2}, ProgramNumber: {3}, NoteNumber: {4}", + aMidiEventMapTo.Key, aMidiEventMapTo.Value.MidiChannel, aMidiEventMapTo.Value.BankNumber, aMidiEventMapTo.Value.ProgramNumber, aMidiEventMapTo.Value.NoteNumber)); + } + if (!trackAssociatedWith.ContainsKey(aMidiEventMapTo.Value)) { MTrkChunkWithInfo trackInfo = new MTrkChunkWithInfo(); @@ -531,49 +571,65 @@ static IEnumerable SplitMidiTrack(MTrkChunk midiTrackIn, bool copySep MTrkChunk targetTrack = null; bool broadcastToAllTracks = false; - // dispatch message - if (midiEvent.Message is MidiChannelMessage) + if (verbose) { - MidiChannelMessage channelMessage = midiEvent.Message as MidiChannelMessage; - byte midiChannel = channelMessage.MidiChannel; + Console.Write("[MidiEvent] Index: " + midiEventIndex); + Console.Write(", AbsoluteTime: " + midiEvent.AbsoluteTime); + Console.Write(", MessageType: " + midiEvent.Message.GetType().Name); + if (midiEvent.Message is MidiChannelMessage) + { + MidiChannelMessage channelMessage = midiEvent.Message as MidiChannelMessage; + Console.Write(", MidiChannel: " + channelMessage.MidiChannel); + Console.Write(", Command: " + channelMessage.Command); + Console.Write(", Parameter1: " + channelMessage.Parameter1); + Console.Write(", Parameter2: " + channelMessage.Parameter2); + } + Console.WriteLine(); + } - // switch output track if necessary - if (midiEventMapTo.ContainsKey(midiEventIndex)) + // switch output track if necessary + if (midiEventMapTo.ContainsKey(midiEventIndex)) + { + MTrkChannelParam aMidiEventMapTo = midiEventMapTo[midiEventIndex]; + MTrkChunkWithInfo newTrackInfo = trackAssociatedWith[aMidiEventMapTo]; + int channel = aMidiEventMapTo.MidiChannel; + + MTrkChunkWithInfo oldTrackInfo = null; + if (currentOutputTrackInfo.ContainsKey(channel)) { - MTrkChannelParam aMidiEventMapTo = midiEventMapTo[midiEventIndex]; - MTrkChunkWithInfo newTrackInfo = trackAssociatedWith[aMidiEventMapTo]; - int channel = aMidiEventMapTo.MidiChannel; + oldTrackInfo = currentOutputTrackInfo[channel]; + } - MTrkChunkWithInfo oldTrackInfo = null; - if (currentOutputTrackInfo.ContainsKey(channel)) - { - oldTrackInfo = currentOutputTrackInfo[channel]; - } + if (oldTrackInfo != newTrackInfo) + { + // switch output track + currentOutputTrackInfo[channel] = newTrackInfo; - if (oldTrackInfo != newTrackInfo) + // copy separated controller values + if (copySeparatedControllers) { - // switch output track - currentOutputTrackInfo[channel] = newTrackInfo; + // readahead initialization events for new track (read until the first note on) + MidiChannelStatus initStatus = new MidiChannelStatus(); + initStatus.DataEntryForRPN = status[channel].DataEntryForRPN; + initStatus.ParseMidiEvents(midiEventListIn.Skip(midiEventIndex).TakeWhile(ev => + !(ev.Message is MidiChannelMessage && ((MidiChannelMessage)ev.Message).Command == MidiChannelCommand.NoteOn)), (byte)channel); - // copy separated controller values - if (copySeparatedControllers) - { - // readahead initialization events for new track (read until the first note on) - MidiChannelStatus initStatus = new MidiChannelStatus(); - initStatus.DataEntryForRPN = status[channel].DataEntryForRPN; - initStatus.ParseMidiEvents(midiEventListIn.Skip(midiEventIndex).TakeWhile(ev => - !(ev.Message is MidiChannelMessage && ((MidiChannelMessage)ev.Message).Command == MidiChannelCommand.NoteOn)), midiChannel); - - status[channel].AddUpdatedMidiEvents(newTrackInfo.Track, newTrackInfo.Status, midiEvent.AbsoluteTime, channel, initStatus); - } + status[channel].AddUpdatedMidiEvents(newTrackInfo.Track, newTrackInfo.Status, midiEvent.AbsoluteTime, channel, initStatus); + } - // save current controller values - if (oldTrackInfo != null) - { - oldTrackInfo.Status = new MidiChannelStatus(status[channel]); - } + // save current controller values + if (oldTrackInfo != null) + { + oldTrackInfo.Status = new MidiChannelStatus(status[channel]); } } + } + + // dispatch message + if (midiEvent.Message is MidiChannelMessage) + { + MidiChannelMessage channelMessage = midiEvent.Message as MidiChannelMessage; + byte midiChannel = channelMessage.MidiChannel; // determine output track targetTrack = currentOutputTrackInfo[midiChannel].Track;