Exemplo n.º 1
0
        public static void ExportMsb(Stream dataStream, string outPath)
        {
            var file = MsbFile.Read(dataStream);

            var midiFile = new MidiFile();

            var tempoMap = TempoMap.Create(new TicksPerQuarterNoteTimeDivision(960),
                                           Tempo.FromBeatsPerMinute((int)file.BpmEntries[0].Bpm));

            foreach (var score in file.ScoreEntries)
            {
                var track = new TrackChunk();

                var scoreNotes = new List <Note>();

                foreach (var bar in score.Bars)
                {
                    scoreNotes.Add(new Note(new SevenBitNumber((byte)(bar.Note + 24)), bar.Length, bar.Offset));
                }

                track.AddNotes(scoreNotes);

                midiFile.Chunks.Add(track);
            }

            midiFile.ReplaceTempoMap(tempoMap);

            midiFile.Write(outPath);
        }
Exemplo n.º 2
0
        /// <summary>
        /// Creates a track chunk with the specified notes.
        /// </summary>
        /// <param name="notes">Collection of notes to create a track chunk.</param>
        /// <returns><see cref="TrackChunk"/> containing the specified notes.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="notes"/> is <c>null</c>.</exception>
        public static TrackChunk ToTrackChunk(this IEnumerable <Note> notes)
        {
            ThrowIfArgument.IsNull(nameof(notes), notes);

            var trackChunk = new TrackChunk();

            trackChunk.AddNotes(notes);

            return(trackChunk);
        }
Exemplo n.º 3
0
        public static MemoryStream ScrubFile(string filePath)
        {
            MidiFile midiFile;
            IEnumerable <TrackChunk> originalTrackChunks;
            TempoMap tempoMap;

            MidiFile newMidiFile;
            ConcurrentDictionary <int, TrackChunk> newTrackChunks;

            try
            {
                string md5 = CalculateMD5(filePath);
                if (lastMD5.Equals(md5) && lastFile != null)
                {
                    var oldfile = new MemoryStream();
                    lastFile.Write(oldfile, MidiFileFormat.MultiTrack, new WritingSettings {
                        CompressionPolicy = CompressionPolicy.NoCompression
                    });
                    oldfile.Flush();
                    oldfile.Position = 0;
                    return(oldfile);
                }

                if (Path.GetExtension(filePath).ToLower().Equals(".mmsong"))
                {
                    var mmSongStream = MMSong.Open(filePath).GetMidiFile(false, true);
                    lastFile = MidiFile.Read(mmSongStream);
                    lastMD5  = md5;
                    mmSongStream.Position = 0;
                    return(mmSongStream);
                }

                midiFile = MidiFile.Read(filePath, new ReadingSettings
                {
                    ReaderSettings = new ReaderSettings
                    {
                        ReadFromMemory = true
                    },
                    InvalidChunkSizePolicy = InvalidChunkSizePolicy.Ignore,
                    InvalidMetaEventParameterValuePolicy         = InvalidMetaEventParameterValuePolicy.SnapToLimits,
                    InvalidChannelEventParameterValuePolicy      = InvalidChannelEventParameterValuePolicy.SnapToLimits,
                    InvalidSystemCommonEventParameterValuePolicy = InvalidSystemCommonEventParameterValuePolicy.SnapToLimits,
                    MissedEndOfTrackPolicy           = MissedEndOfTrackPolicy.Ignore,
                    NotEnoughBytesPolicy             = NotEnoughBytesPolicy.Ignore,
                    UnexpectedTrackChunksCountPolicy = UnexpectedTrackChunksCountPolicy.Ignore,
                    UnknownChannelEventPolicy        = UnknownChannelEventPolicy.SkipStatusByteAndOneDataByte,
                    UnknownChunkIdPolicy             = UnknownChunkIdPolicy.ReadAsUnknownChunk
                });

                #region Require

                if (midiFile == null)
                {
                    throw new ArgumentNullException();
                }
                else
                {
                    try
                    {
                        if (midiFile.Chunks.Count < 1)
                        {
                            throw new NotSupportedException();
                        }

                        MidiFileFormat fileFormat = midiFile.OriginalFormat;

                        if (fileFormat == MidiFileFormat.MultiSequence)
                        {
                            throw new NotSupportedException();
                        }
                    }
                    catch (Exception exception) when(exception is UnknownFileFormatException || exception is InvalidOperationException)
                    {
                        throw exception;
                    }
                }
                #endregion

                var trackZeroName = midiFile.GetTrackChunks().First().Events.OfType <SequenceTrackNameEvent>().FirstOrDefault()?.Text;

                if (!string.IsNullOrEmpty(trackZeroName) && (trackZeroName.ToLower().Contains("mogamp") || trackZeroName.ToLower().Contains("mognotate")))
                {
                    var notateConfig = NotateConfig.GenerateConfigFromMidiFile(filePath);
                    var mmSongStream = notateConfig.Transmogrify().GetMidiFile(false, true);
                    lastFile = MidiFile.Read(mmSongStream);
                    lastMD5  = md5;
                    mmSongStream.Position = 0;
                    return(mmSongStream);
                }

                Console.WriteLine("Scrubbing " + filePath);
                var loaderWatch = Stopwatch.StartNew();

                originalTrackChunks = midiFile.GetTrackChunks();

                tempoMap       = midiFile.GetTempoMap();
                newTrackChunks = new ConcurrentDictionary <int, TrackChunk>();

                long firstNote = originalTrackChunks.GetNotes().First().GetTimedNoteOnEvent().TimeAs <MetricTimeSpan>(tempoMap).TotalMicroseconds / 1000;

                TrackChunk allTracks = new TrackChunk();
                allTracks.AddNotes(originalTrackChunks.GetNotes());
                midiFile.Chunks.Add(allTracks);
                originalTrackChunks = midiFile.GetTrackChunks();

                Parallel.ForEach(originalTrackChunks.Where(x => x.GetNotes().Count() > 0), (originalChunk, loopState, index) =>
                {
                    var watch = Stopwatch.StartNew();

                    int noteVelocity = int.Parse(index.ToString()) + 1;

                    Dictionary <int, Dictionary <long, Note> > allNoteEvents = new Dictionary <int, Dictionary <long, Note> >();
                    for (int i = 0; i < 127; i++)
                    {
                        allNoteEvents.Add(i, new Dictionary <long, Note>());
                    }

                    foreach (Note note in originalChunk.GetNotes())
                    {
                        long noteOnMS = 0;

                        long noteOffMS = 0;

                        try
                        {
                            noteOnMS  = 5000 + (note.GetTimedNoteOnEvent().TimeAs <MetricTimeSpan>(tempoMap).TotalMicroseconds / 1000) - firstNote;
                            noteOffMS = 5000 + (note.GetTimedNoteOffEvent().TimeAs <MetricTimeSpan>(tempoMap).TotalMicroseconds / 1000) - firstNote;
                        }
                        catch (Exception) { continue; }
                        int noteNumber = note.NoteNumber;

                        Note newNote = new Note(noteNumber: (SevenBitNumber)noteNumber,
                                                time: noteOnMS,
                                                length: noteOffMS - noteOnMS
                                                )
                        {
                            Channel     = (FourBitNumber)0,
                            Velocity    = (SevenBitNumber)noteVelocity,
                            OffVelocity = (SevenBitNumber)noteVelocity
                        };

                        if (allNoteEvents[noteNumber].ContainsKey(noteOnMS))
                        {
                            Note previousNote = allNoteEvents[noteNumber][noteOnMS];
                            if (previousNote.Length < note.Length)
                            {
                                allNoteEvents[noteNumber][noteOnMS] = newNote;
                            }
                        }
                        else
                        {
                            allNoteEvents[noteNumber].Add(noteOnMS, newNote);
                        }
                    }

                    watch.Stop();
                    Debug.WriteLine("step 1: " + noteVelocity + ": " + watch.ElapsedMilliseconds);
                    watch = Stopwatch.StartNew();

                    TrackChunk newChunk = new TrackChunk();
                    for (int i = 0; i < 127; i++)
                    {
                        long lastNoteTimeStamp = -1;
                        foreach (var noteEvent in allNoteEvents[i])
                        {
                            if (lastNoteTimeStamp >= 0 && allNoteEvents[i][lastNoteTimeStamp].Length + lastNoteTimeStamp >= noteEvent.Key)
                            {
                                allNoteEvents[i][lastNoteTimeStamp].Length = allNoteEvents[i][lastNoteTimeStamp].Length - (allNoteEvents[i][lastNoteTimeStamp].Length + lastNoteTimeStamp + 1 - noteEvent.Key);
                            }

                            lastNoteTimeStamp = noteEvent.Key;
                        }
                    }
                    newChunk.AddNotes(allNoteEvents.SelectMany(s => s.Value).Select(s => s.Value).ToArray());
                    allNoteEvents = null;

                    watch.Stop();
                    Debug.WriteLine("step 2: " + noteVelocity + ": " + watch.ElapsedMilliseconds);
                    watch = Stopwatch.StartNew();

                    Note[] notesToFix = newChunk.GetNotes().Reverse().ToArray();
                    for (int i = 1; i < notesToFix.Count(); i++)
                    {
                        int noteNum  = notesToFix[i].NoteNumber;
                        long time    = (notesToFix[i].GetTimedNoteOnEvent().Time);
                        long dur     = notesToFix[i].Length;
                        int velocity = notesToFix[i].Velocity;

                        long lowestParent = notesToFix[0].GetTimedNoteOnEvent().Time;
                        for (int k = i - 1; k >= 0; k--)
                        {
                            long lastOn = notesToFix[k].GetTimedNoteOnEvent().Time;
                            if (lastOn < lowestParent)
                            {
                                lowestParent = lastOn;
                            }
                        }
                        if (lowestParent <= time + 50)
                        {
                            time = lowestParent - 50;
                            if (time < 0)
                            {
                                continue;
                            }
                            notesToFix[i].Time = time;
                            dur = 25;
                            notesToFix[i].Length = dur;
                        }
                    }

                    watch.Stop();
                    Debug.WriteLine("step 3: " + noteVelocity + ": " + watch.ElapsedMilliseconds);
                    watch = Stopwatch.StartNew();

                    notesToFix             = notesToFix.Reverse().ToArray();
                    List <Note> fixedNotes = new List <Note>();
                    for (int j = 0; j < notesToFix.Count(); j++)
                    {
                        var noteNum  = notesToFix[j].NoteNumber;
                        var time     = notesToFix[j].Time;
                        var dur      = notesToFix[j].Length;
                        var channel  = notesToFix[j].Channel;
                        var velocity = notesToFix[j].Velocity;

                        if (j + 1 < notesToFix.Count())
                        {
                            if (notesToFix[j + 1].Time <= notesToFix[j].Time + notesToFix[j].Length + 25)
                            {
                                dur = notesToFix[j + 1].Time - notesToFix[j].Time - 25;
                                dur = dur < 25 ? 1 : dur;
                            }
                        }
                        fixedNotes.Add(new Note(noteNum, dur, time)
                        {
                            Channel     = channel,
                            Velocity    = velocity,
                            OffVelocity = velocity
                        });
                    }
                    notesToFix = null;

                    watch.Stop();
                    Debug.WriteLine("step 4: " + noteVelocity + ": " + watch.ElapsedMilliseconds);
                    watch = Stopwatch.StartNew();

                    int octaveShift  = 0;
                    string trackName = originalChunk.Events.OfType <SequenceTrackNameEvent>().FirstOrDefault()?.Text;
                    if (trackName == null)
                    {
                        trackName = "";
                    }
                    trackName = trackName.ToLower().Trim().Replace(" ", String.Empty);
                    Regex rex = new Regex(@"^([A-Za-z]+)([-+]\d)?");
                    if (rex.Match(trackName) is Match match)
                    {
                        if (!string.IsNullOrEmpty(match.Groups[1].Value))
                        {
                            trackName = match.Groups[1].Value;
                            if (!string.IsNullOrEmpty(match.Groups[2].Value))
                            {
                                if (int.TryParse(match.Groups[2].Value, out int os))
                                {
                                    octaveShift = os;
                                }
                            }

                            (bool success, string parsedTrackName) = TrackNameToEnumInstrumentName(trackName);

                            if (success)
                            {
                                trackName = parsedTrackName;
                            }
                            else
                            {
                                (success, parsedTrackName) = TrackNameToStringInstrumentName(trackName);

                                if (success)
                                {
                                    trackName = parsedTrackName;
                                }
                                else
                                {
                                    var originalInstrument = originalChunk.Events.OfType <ProgramChangeEvent>().FirstOrDefault()?.ProgramNumber;
                                    if (!(originalInstrument is null) && originalInstrument.Equals(typeof(SevenBitNumber)))
                                    {
                                        (success, parsedTrackName) = ProgramToStringInstrumentName((SevenBitNumber)originalInstrument);
                                    }
                                    if (success)
                                    {
                                        trackName = parsedTrackName;
                                    }
                                }
                            }
Exemplo n.º 4
0
        internal static MidiFile Load(string filePath)
        {
            try
            {
                MMSong mmSong = JsonExtensions.DeserializeFromFileCompressed <MMSong>(filePath);

                if (mmSong.schemaVersion < 1 && mmSong.schemaVersion > 2)
                {
                    throw new FileFormatException("Error: This mmsong file format is not understood.");
                }
                // For now, just play the first available song.
                // if (mmSong.songs.Count != 1) throw new FileFormatException("Error: BMP currently only supports mmsong files with 1 song in them.");

                MMSong.Song song = mmSong.songs[0];

                MidiFile sequence = new MidiFile();
                sequence.Chunks.Add(new TrackChunk());
                sequence.TimeDivision = new TicksPerQuarterNoteTimeDivision(600);
                using (TempoMapManager tempoMapManager = sequence.ManageTempoMap()) tempoMapManager.SetTempo(0, Tempo.FromBeatsPerMinute(100));

                foreach (MMSong.Bard bard in song.bards)
                {
                    List <Note> notes   = new List <Note>();
                    bool        failure = false;

                    switch (bard.instrument)
                    {
                    case Instrument.Cymbal:
                    case Instrument.Trumpet:
                    case Instrument.Trombone:
                    case Instrument.Horn:
                    case Instrument.Tuba:
                    case Instrument.Saxophone:
                    case Instrument.Violin:
                    case Instrument.Viola:
                    case Instrument.Cello:
                    case Instrument.DoubleBass:
                        bard.sequence = bard.sequence.ToDictionary(
                            x => x.Key + 2,
                            x => x.Value);
                        break;

                    default:
                        break;
                    }

                    if (bard.sequence.Count % 2 == 0)
                    {
                        long lastTime = 0;
                        int  lastNote = 254;
                        foreach (KeyValuePair <long, int> sEvent in bard.sequence)
                        {
                            if (!failure)
                            {
                                if (lastNote == 254)
                                {
                                    if (sEvent.Value <= 60 && sEvent.Value >= 24 && ((sEvent.Key * 25 % 100) == 50 || (sEvent.Key * 25) % 100 == 0))
                                    {
                                        lastNote = sEvent.Value + 24;
                                        lastTime = sEvent.Key * 25;
                                    }
                                    else
                                    {
                                        failure = true;
                                    }
                                }
                                else
                                {
                                    if (sEvent.Value == 254)
                                    {
                                        long dur = (sEvent.Key * 25) - lastTime;
                                        notes.Add(new Note((SevenBitNumber)lastNote, dur, lastTime)
                                        {
                                            Channel     = (FourBitNumber)14,
                                            Velocity    = (SevenBitNumber)(int)127,
                                            OffVelocity = (SevenBitNumber)(int)0
                                        });
                                        lastNote = 254;
                                        lastTime = sEvent.Key * 25;
                                    }
                                    else
                                    {
                                        failure = true;
                                    }
                                }
                            }
                        }
                    }
                    else
                    {
                        failure = true;
                    }

                    if (failure)
                    {
                        throw new FileFormatException("Error: This mmsong file is corrupted");
                    }

                    TrackChunk currentChunk = new TrackChunk(new SequenceTrackNameEvent(bard.instrument.ToString()));
                    currentChunk.AddNotes(notes);
                    notes = null;
                    sequence.Chunks.Add(currentChunk);
                    currentChunk = null;
                }

                using (var manager = new TimedEventsManager(sequence.GetTrackChunks().First().Events))
                    manager.Events.Add(new TimedEvent(new MarkerEvent(), (sequence.GetDuration <MetricTimeSpan>().TotalMicroseconds / 1000) + 100));

                return(sequence);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
Exemplo n.º 5
0
        public static byte[] CreateMsbFromMidi(string path)
        {
            var midiFile = MidiFile.Read(path);

            //Ensures every chunk in the midi has at least 1 note
            foreach (var chunk in midiFile.GetTrackChunks())
            {
                if (chunk.GetNotes().Count() == 0)
                {
                    List <Note> dummyTrack = new List <Note>();
                    dummyTrack.Add(new Note(new SevenBitNumber((byte)60), 1000, 1));
                    chunk.AddNotes(dummyTrack);
                }
            }

            //Ensures there are at least 7 chunks in the midi
            while (midiFile.Chunks.Count < 7)
            {
                TrackChunk  chunk      = new TrackChunk();
                List <Note> dummyTrack = new List <Note>();
                dummyTrack.Add(new Note(new SevenBitNumber((byte)60), 1000, 1));
                chunk.AddNotes(dummyTrack);
                midiFile.Chunks.Add(chunk);
            }

            //Ensures there are no more than 7 chunks in the midi
            while (midiFile.GetTrackChunks().Count() > 7)
            {
                midiFile.Chunks.RemoveAt(7);
            }

            //Reads BPM from midi if possible; if no BPM is declared, defaults to 100
            ValueChange <Tempo> tempo = midiFile.GetTempoMap().Tempo.FirstOrDefault <ValueChange <Tempo> >();
            float bpm = 100;

            if (tempo != null)
            {
                bpm = (float)tempo.Value.BeatsPerMinute;
            }

            //Assigns time signature based on first midi time signature event, or defaults to 4/4
            ValueChange <TimeSignature> ts = midiFile.GetTempoMap().TimeSignature.FirstOrDefault <ValueChange <TimeSignature> >();
            int numerator   = 4;
            int denominator = 4;

            if (ts != null)
            {
                numerator   = ts.Value.Numerator;
                denominator = ts.Value.Denominator;
            }

            var file = new MsbFile
            {
                BpmEntries = new List <MsbFile.MsbBpm> {
                    new MsbFile.MsbBpm {
                        Bpm = bpm, Measure1 = (byte)numerator, Measure2 = (byte)denominator
                    }
                },
                ScoreEntries = new List <MsbFile.MsbScore>()
            };

            var trackChunks = midiFile.GetTrackChunks();

            //If the first chunk contains only one note (dummy chunk or empty chunk)
            //Instead, re-order list to sort the list with the most notes into the first slot.
            if (trackChunks.ElementAt(0).GetNotes().Count() == 1)
            {
                trackChunks = trackChunks.OrderByDescending(c => c.GetNotes().Count());
            }

            for (var trackIndex = 0; trackIndex < trackChunks.Count(); trackIndex++)
            {
                var notes = trackChunks.ElementAt(trackIndex).GetNotes();
                notes = notes.OrderBy(c => c.Time);

                var msbScore = new MsbFile.MsbScore {
                    Bars = new List <MsbFile.MsbBar>()
                };

                for (var i = 0; i < notes.Count(); i++)
                {
                    var note = notes.ElementAt(i);

                    if ((int)note.NoteNumber < 48 || (int)note.NoteNumber > 84)
                    {
                        throw new Exception("Your Midi contains notes outside the range of FFXIV's playable range. " +
                                            $"Please enter only notes between C3 and C6 (inclusive). Detected incorrect range at Note#{i + 1} in Track#{trackIndex + 1}");
                    }

                    //Detects note overlaps and chops the length to be strictly < the next note's time.
                    var lengthChopOffset = 0L;
                    if (i != notes.Count() - 1)
                    {
                        var nextNote = notes.ElementAt(i + 1);
                        if (note.Time + note.Length > nextNote.Time)
                        {
                            lengthChopOffset = (note.Time + note.Length) - nextNote.Time;
                        }
                    }

                    var msbBar = new MsbFile.MsbBar();

                    msbBar.Note   = (byte)(note.NoteNumber - 24);
                    msbBar.Length = (uint)(note.Length - lengthChopOffset);
                    msbBar.Offset = (uint)note.Time;

                    msbScore.Bars.Add(msbBar);
                }

                file.ScoreEntries.Add(msbScore);
            }

            return(file.GetBytes());
        }
Exemplo n.º 6
0
        public static Sequence ScrubFile(string filePath)
        {
            MidiFile midiFile;
            IEnumerable <TrackChunk> originalTrackChunks;
            TempoMap tempoMap;

            MidiFile newMidiFile;
            ConcurrentDictionary <int, TrackChunk> newTrackChunks;

            Sequence sequence = null;

            try
            {
                midiFile = MidiFile.Read(filePath, new ReadingSettings
                {
                    ReaderSettings = new ReaderSettings
                    {
                        ReadFromMemory = true
                    }
                });

                bool explode = false;

                #region Require

                if (midiFile == null)
                {
                    throw new ArgumentNullException();
                }
                else
                {
                    try
                    {
                        if (midiFile.Chunks.Count < 1)
                        {
                            throw new NotSupportedException();
                        }

                        MidiFileFormat fileFormat = midiFile.OriginalFormat;

                        if (fileFormat == MidiFileFormat.MultiSequence)
                        {
                            throw new NotSupportedException();
                        }
                        else if (fileFormat == MidiFileFormat.SingleTrack)
                        {
                            explode = true;
                        }
                    }
                    catch (Exception exception) when(exception is UnknownFileFormatException || exception is InvalidOperationException)
                    {
                        throw exception;
                    }
                }
                #endregion

                Console.WriteLine("Scrubbing " + filePath);
                var loaderWatch = Stopwatch.StartNew();

                if (explode || midiFile.Chunks.Count == 1)
                {
                    originalTrackChunks = midiFile.GetTrackChunks().First().Explode();
                }
                else
                {
                    originalTrackChunks = midiFile.GetTrackChunks();
                }

                tempoMap       = midiFile.GetTempoMap();
                newTrackChunks = new ConcurrentDictionary <int, TrackChunk>();

                long firstNote = originalTrackChunks.GetNotes().First().GetTimedNoteOnEvent().TimeAs <MetricTimeSpan>(tempoMap).TotalMicroseconds / 1000;

                Parallel.ForEach(originalTrackChunks.Where(x => x.GetNotes().Count() > 0), (originalChunk, loopState, index) =>
                {
                    var watch = Stopwatch.StartNew();

                    int noteVelocity = int.Parse(index.ToString()) + 1;

                    Dictionary <int, Dictionary <long, Note> > allNoteEvents = new Dictionary <int, Dictionary <long, Note> >();
                    for (int i = 0; i < 127; i++)
                    {
                        allNoteEvents.Add(i, new Dictionary <long, Note>());
                    }

                    // Fill the track dictionary and remove duplicate notes
                    foreach (Note note in originalChunk.GetNotes())
                    {
                        long noteOnMS = 0;

                        long noteOffMS = 0;

                        try
                        {
                            noteOnMS  = note.GetTimedNoteOnEvent().TimeAs <MetricTimeSpan>(tempoMap).TotalMicroseconds / 1000 - firstNote;
                            noteOffMS = note.GetTimedNoteOffEvent().TimeAs <MetricTimeSpan>(tempoMap).TotalMicroseconds / 1000 - firstNote;
                        }
                        catch (Exception) { continue; } // malformed note, most common is a note on missing a note off.
                        int noteNumber = note.NoteNumber;

                        Note newNote = new Note(noteNumber: (SevenBitNumber)noteNumber,
                                                time: noteOnMS,
                                                length: noteOffMS - noteOnMS
                                                )
                        {
                            Channel     = (FourBitNumber)0,
                            Velocity    = (SevenBitNumber)noteVelocity,
                            OffVelocity = (SevenBitNumber)noteVelocity
                        };

                        if (allNoteEvents[noteNumber].ContainsKey(noteOnMS))
                        {
                            Note previousNote = allNoteEvents[noteNumber][noteOnMS];
                            if (previousNote.Length < note.Length)
                            {
                                allNoteEvents[noteNumber][noteOnMS] = newNote;                                    // keep the longest of all duplicates
                            }
                        }
                        else
                        {
                            allNoteEvents[noteNumber].Add(noteOnMS, newNote);
                        }
                    }

                    watch.Stop();
                    Debug.WriteLine("step 1: " + noteVelocity + ": " + watch.ElapsedMilliseconds);
                    watch = Stopwatch.StartNew();

                    // Merge all the dictionaries into one collection
                    TrackChunk newChunk = new TrackChunk();
                    for (int i = 0; i < 127; i++)
                    {
                        long lastNoteTimeStamp = -1;
                        foreach (var noteEvent in allNoteEvents[i])
                        {
                            if (lastNoteTimeStamp >= 0 && allNoteEvents[i][lastNoteTimeStamp].Length + lastNoteTimeStamp >= noteEvent.Key)
                            {
                                allNoteEvents[i][lastNoteTimeStamp].Length = allNoteEvents[i][lastNoteTimeStamp].Length - (allNoteEvents[i][lastNoteTimeStamp].Length + lastNoteTimeStamp + 1 - noteEvent.Key);
                            }

                            lastNoteTimeStamp = noteEvent.Key;
                        }
                    }
                    newChunk.AddNotes(allNoteEvents.SelectMany(s => s.Value).Select(s => s.Value).ToArray());
                    allNoteEvents = null;

                    watch.Stop();
                    Debug.WriteLine("step 2: " + noteVelocity + ": " + watch.ElapsedMilliseconds);
                    watch = Stopwatch.StartNew();

                    // auto arpeggiate
                    Note[] notesToFix = newChunk.GetNotes().Reverse().ToArray();
                    for (int i = 1; i < notesToFix.Count(); i++)
                    {
                        int noteNum  = notesToFix[i].NoteNumber;
                        long time    = (notesToFix[i].GetTimedNoteOnEvent().Time);
                        long dur     = notesToFix[i].Length;
                        int velocity = notesToFix[i].Velocity;

                        long lowestParent = notesToFix[0].GetTimedNoteOnEvent().Time;
                        for (int k = i - 1; k >= 0; k--)
                        {
                            long lastOn = notesToFix[k].GetTimedNoteOnEvent().Time;
                            if (lastOn < lowestParent)
                            {
                                lowestParent = lastOn;
                            }
                        }
                        if (lowestParent <= time + 50)
                        {
                            time = lowestParent - 50;
                            if (time < 0)
                            {
                                continue;
                            }
                            notesToFix[i].Time = time;
                            dur = 25;
                            notesToFix[i].Length = dur;
                        }
                    }

                    watch.Stop();
                    Debug.WriteLine("step 3: " + noteVelocity + ": " + watch.ElapsedMilliseconds);
                    watch = Stopwatch.StartNew();

                    notesToFix             = notesToFix.Reverse().ToArray();
                    List <Note> fixedNotes = new List <Note>();
                    for (int j = 0; j < notesToFix.Count(); j++)
                    {
                        var noteNum  = notesToFix[j].NoteNumber;
                        var time     = notesToFix[j].Time;
                        var dur      = notesToFix[j].Length;
                        var channel  = notesToFix[j].Channel;
                        var velocity = notesToFix[j].Velocity;

                        if (j + 1 < notesToFix.Count())
                        {
                            if (notesToFix[j + 1].Time <= notesToFix[j].Time + notesToFix[j].Length + 25)
                            {
                                dur = notesToFix[j + 1].Time - notesToFix[j].Time - 25;
                                dur = dur < 25 ? 1 : dur;
                            }
                        }
                        fixedNotes.Add(new Note(noteNum, dur, time)
                        {
                            Channel     = channel,
                            Velocity    = velocity,
                            OffVelocity = velocity
                        });
                    }
                    notesToFix = null;

                    watch.Stop();
                    Debug.WriteLine("step 4: " + noteVelocity + ": " + watch.ElapsedMilliseconds);
                    watch = Stopwatch.StartNew();

                    // Discover the instrument name from the track title, and from program changes if that fails
                    int octaveShift  = 0;
                    string trackName = originalChunk.Events.OfType <SequenceTrackNameEvent>().FirstOrDefault()?.Text;
                    if (trackName == null)
                    {
                        trackName = "";
                    }
                    trackName = trackName.ToLower().Trim().Replace(" ", String.Empty);
                    Regex rex = new Regex(@"^([A-Za-z]+)([-+]\d)?");
                    if (rex.Match(trackName) is Match match)
                    {
                        if (!string.IsNullOrEmpty(match.Groups[1].Value))
                        {
                            trackName = match.Groups[1].Value;
                            if (!string.IsNullOrEmpty(match.Groups[2].Value))
                            {
                                if (int.TryParse(match.Groups[2].Value, out int os))
                                {
                                    octaveShift = os;
                                }
                            }

                            (bool success, string parsedTrackName) = TrackNameToEnumInstrumentName(trackName);

                            if (success)
                            {
                                trackName = parsedTrackName;
                            }
                            else
                            {
                                (success, parsedTrackName) = TrackNameToStringInstrumentName(trackName);

                                if (success)
                                {
                                    trackName = parsedTrackName;
                                }
                                else
                                {
                                    var originalInstrument = originalChunk.Events.OfType <ProgramChangeEvent>().FirstOrDefault()?.ProgramNumber;
                                    if (!(originalInstrument is null) && originalInstrument.Equals(typeof(SevenBitNumber)))
                                    {
                                        (success, parsedTrackName) = ProgramToStringInstrumentName((SevenBitNumber)originalInstrument);
                                    }
                                    if (success)
                                    {
                                        trackName = parsedTrackName;
                                    }
                                }
                            }