Example #1
0
        // Let's add something new here: Either save data we can use to do it, or do it
        // Detect the best Flute instrument

        // The best flute instrument: Has many notes, has long notes, covers a large octave area
        // So while we already track the octaves covered, we should track numNotes and avgNoteLength of each track or channel

        // That also might mean we need to start tracking data for tracks, instead of just channels, and allow a toggle to show those instead

        private void HandleLoadCompleted(object sender, AsyncCompletedEventArgs e)
        {
            tickMetaNotes.Clear();
            var startTime = DateTime.Now;

            lock (loadLock)
            {
                if (e.Error == null)
                {
                    //channelNames = new Dictionary<int, string>();
                    //trackNames = new Dictionary<int, string>();

                    channels = new Dictionary <int, MidiChannelItem>();
                    tracks   = new Dictionary <int, TrackItem>();

                    // Set default channel names
                    foreach (var channelNum in sequence.Channels)
                    {
                        var midiChannel = new MidiChannelItem();
                        midiChannel.Id       = channelNum;
                        midiChannel.Name     = MidiChannelTypes.Names[channelNum];
                        midiChannel.Active   = true;
                        channels[channelNum] = midiChannel;
                    }
                    foreach (var midiTrack in sequence.Tracks)
                    {
                        var track = new TrackItem();
                        track.Id = midiTrack.Id;
                        if (!string.IsNullOrWhiteSpace(midiTrack.Name))
                        {
                            track.Name = midiTrack.Name;
                        }
                        else
                        {
                            track.Name = "Untitled Track";
                        }
                        track.lowestNote     = midiTrack.MinNoteId;
                        track.highestNote    = midiTrack.MaxNoteId;
                        track.Active         = true;
                        tracks[midiTrack.Id] = track;
                    }

                    //Parallel.ForEach(sequence, track =>
                    //{
                    foreach (var track in sequence)
                    {
                        foreach (var midiEvent in track.Iterator())
                        {
                            if (midiEvent.MidiMessage is ChannelMessage c)
                            {
                                if (!channels.ContainsKey(c.MidiChannel))
                                {
                                    var midiChannel = new MidiChannelItem();
                                    midiChannel.Id          = c.MidiChannel;
                                    midiChannel.Name        = MidiChannelTypes.Names[c.MidiChannel];
                                    midiChannel.Active      = true;
                                    channels[c.MidiChannel] = midiChannel;
                                }


                                if (c.Data2 > 0 && c.Command == ChannelCommand.NoteOn)
                                {
                                    if (c.MidiChannel == 9)                     // Glockenspiel - always channel 9
                                    {
                                        drumNoteCounts[c.Data1]++;              // They're all 0 by default
                                        channels[c.MidiChannel].Active = false; // Disabled by default
                                    }

                                    if (c.Data1 > channels[c.MidiChannel].highestNote)
                                    {
                                        channels[c.MidiChannel].highestNote = c.Data1;
                                    }
                                    if (c.Data1 < channels[c.MidiChannel].lowestNote)
                                    {
                                        channels[c.MidiChannel].lowestNote = c.Data1;
                                    }
                                    // Track noteOn events and when we get a noteOff or velocity 0, add up note lengths
                                    channels[c.MidiChannel].noteTicks[c.Data1] = midiEvent.AbsoluteTicks;
                                    channels[c.MidiChannel].numNotes++;

                                    if (!channels[c.MidiChannel].tickNotes.ContainsKey(midiEvent.AbsoluteTicks))
                                    {
                                        channels[c.MidiChannel].tickNotes[midiEvent.AbsoluteTicks] = new List <MidiNote>();
                                    }

                                    channels[c.MidiChannel].tickNotes[midiEvent.AbsoluteTicks].Add(new MidiNote()
                                    {
                                        note = c.Data1, tickNumber = midiEvent.AbsoluteTicks, track = track.Id, channel = c.MidiChannel
                                    });
                                    // Ideally I'd like to parse out chords of size 3 or more, using some setting like RemoveLowest, RemoveHighest, RemoveMiddle
                                    // Though I'm unsure how I'd really do that to anything except lutemod; lutebot is reading straight from the midi data isn't it?
                                }
                                else if (c.Command == ChannelCommand.ProgramChange)
                                {
                                    string channelName = MidiChannelTypes.Names[c.Data1];
                                    channels[c.MidiChannel].Name = channelName;
                                }
                                else if (c.Command == ChannelCommand.NoteOff || (c.Command == ChannelCommand.NoteOn && c.Data2 == 0))
                                {
                                    if (channels[c.MidiChannel].noteTicks.ContainsKey(c.Data1))
                                    {
                                        // Find the original note...
                                        var startTick = channels[c.MidiChannel].noteTicks[c.Data1];
                                        var length    = midiEvent.AbsoluteTicks - startTick;
                                        var note      = channels[c.MidiChannel].tickNotes[startTick].Where(n => n.note == c.Data1).FirstOrDefault();
                                        note.length = length;
                                        // If we already have a note with a length on this same tick, we don't need to add to totalNoteLength...?
                                        // I want to avoid a chord of 3 having 3x the detected total length...
                                        // I'll just have to deal with that later, and we won't bother tracking totalLength here

                                        // Just add all the lengths, we'll divide after we read everything
                                        channels[c.MidiChannel].avgNoteLength += length;
                                        channels[c.MidiChannel].noteTicks.Remove(c.Data1);
                                    }
                                }
                            }
                            else if (midiEvent.MidiMessage is MetaMessage meta)
                            {
                                if (meta.MetaType == MetaType.TrackName)
                                {
                                    var bytes = meta.GetBytes();
                                    // Byte 3 is the length, bytes 4+ are the text in ascii
                                    // But apparently the library trims out that for me, and the entire bytes array is what we want
                                    if (bytes.Length > 0)
                                    {
                                        string trackName = ASCIIEncoding.ASCII.GetString(bytes);
                                        tracks[track.Id].Name = trackName;
                                    }
                                }
                                else if (meta.MetaType == MetaType.Tempo)
                                {
                                    var newTempo = BytesToInt(meta.GetBytes()); // We divide by division when converting for lutemod only, apparently

                                    if (!tickMetaNotes.ContainsKey(midiEvent.AbsoluteTicks))
                                    {
                                        tickMetaNotes[midiEvent.AbsoluteTicks] = new List <MidiNote>();
                                    }

                                    tickMetaNotes[midiEvent.AbsoluteTicks].Add(new MidiNote()
                                    {
                                        note = newTempo, tickNumber = midiEvent.AbsoluteTicks, track = track.Id
                                    });
                                }
                            }
                        }
                    }//);

                    // So, now we have a totalNoteLength... except, no.  That's not quite useful
                    // We do have the list of all notes with lengths on them, though.
                    // So we can get a total length of parts by, for each tick/entry, take the longest length for that tick
                    // And then only count new notes if their start+length > thatTick+thatLength


                    // Compile noteLengths of channels
                    foreach (var kvp in channels)
                    {
                        var channel = kvp.Value;
                        if (channel.numNotes > 0)
                        {
                            channel.avgNoteLength = channel.avgNoteLength / channel.numNotes;
                        }

                        int nextValidTick = 0;

                        foreach (var noteKvp in channel.tickNotes.OrderBy(n => n.Key))
                        {
                            var noteList = noteKvp.Value;
                            var note     = noteList.OrderByDescending(n => n.length).First();

                            if (note.tickNumber > nextValidTick)
                            {
                                channel.totalNoteLength += note.length;
                                nextValidTick            = note.tickNumber + note.length;
                            }
                            else if (note.tickNumber + note.length > nextValidTick)
                            {
                                var diff = (note.tickNumber + note.length) - nextValidTick;
                                channel.totalNoteLength += diff;
                                nextValidTick            = nextValidTick + diff;
                            }
                        }

                        if (channel.tickNotes.Count > 0)
                        {
                            channel.maxChordSize = channel.tickNotes.Max(t => t.Value.Count);
                        }
                    }

                    // Add a note on the drum channel
                    if (channels.ContainsKey(9))
                    {
                        channels[9].Name += " (DRUMS)";
                    }

                    // Remove any channels or tracks that don't contain any notes
                    channels = channels.Values.Where(c => c.numNotes > 0).ToDictionary(c => c.Id);

                    Console.WriteLine("Read track data in " + (DateTime.Now - startTime).TotalMilliseconds);
                    // Now let's get a sorted list of drum note counts
                    // I can't figure out how to do this nicely.
                    //for (int i = 0; i < drumNoteCounts.Length; i++)
                    //{
                    //    DrumNoteCounts.Add(new KeyValuePair<int, int>(i, drumNoteCounts[i]));
                    //}
                    //DrumNoteCounts = DrumNoteCounts.OrderBy((kvp) => kvp.Value).ToList();

                    base.LoadCompleted(this, e);

                    mordhauOutDevice.HighMidiNoteId = sequence.MaxNoteId;
                    mordhauOutDevice.LowMidiNoteId  = sequence.MinNoteId;
                    rustOutDevice.HighMidiNoteId    = sequence.MaxNoteId;
                    rustOutDevice.LowMidiNoteId     = sequence.MinNoteId;
                }
            }
        }