public void TestAddNamedTrack()
        {
            var          manualMidi     = new MidiEventCollection(1, 200);
            var          noteEvent      = new NoteOnEvent(0, 1, 1, 1, 1);
            const string trackName      = "name";
            var          trackNameEvent = new TextEvent(trackName, MetaEventType.SequenceTrackName, 0);
            var          endTrackEvent  = new MetaEvent(MetaEventType.EndTrack, 0, noteEvent.OffEvent.AbsoluteTime);

            var track = manualMidi.AddTrack();

            track.Add(trackNameEvent);
            track.Add(noteEvent);
            track.Add(noteEvent.OffEvent);
            track.Add(endTrackEvent);

            var extensionMidi = new MidiEventCollection(1, 200);
            var events        = new MidiEvent[] { noteEvent, noteEvent.OffEvent };

            extensionMidi.AddNamedTrack(trackName, events);

            MidiAssert.Equal(manualMidi, extensionMidi);

            // Assert events (not name / end) are the same objects
            var manualTrack    = manualMidi[0];
            var extensionTrack = extensionMidi[0];

            for (var e = 1; e < manualTrack.Count - 1; e++)
            {
                Assert.That(extensionTrack[e], Is.SameAs(manualTrack[e]));
            }
        }
Beispiel #2
0
        public void AddVenueTrack(MidiEventCollection midi)
        {
            var existingTrack = midi.FindTrackNumberByName(TrackName.Venue.ToString());

            if (existingTrack != -1)
            {
                return;
            }

            midi.AddNamedTrack(TrackName.Venue.ToString());
        }
Beispiel #3
0
        public void ConsolidateTracks(MidiEventCollection midi)
        {
            // find all of the names
            var nameCounts = new Dictionary <string, int>();

            for (var t = 0; t < midi.Tracks; t++)
            {
                var names = midi[t].
                            OfType <TextEvent>().
                            Where(e => e.MetaEventType == MetaEventType.SequenceTrackName).
                            ToList();

                if (names.Count > 1)
                {
                    var detail = string.Join(", ", names.Select(n => $"'{n.Text}'"));
                    throw new InvalidOperationException($"Multiple names {detail} on the same track.");
                }

                var name = names.FirstOrDefault()?.Text ?? "";

                int count;
                nameCounts.TryGetValue(name, out count);
                nameCounts[name] = count + 1;
            }

            /* For all of the names that appear on more than one track
             * find all the other tracks that have this name and consolidate them.
             * We iterate multiple times because the track numbers will change every
             * time tracks are consolidated. */
            foreach (var kvp in nameCounts.Where(kvp => kvp.Value > 1))
            {
                var name = kvp.Key;
                var list = new List <MidiEvent>();

                // iterate in reverse so track numbers don't change mid iteration
                for (var t = midi.Tracks - 1; t >= 0; t--)
                {
                    if (!midi[t].OfType <TextEvent>().Any(e => e.IsSequenceTrackName() && e.Text == name))
                    {
                        continue;
                    }

                    var events = midi[t].Where(e => !MidiEvent.IsEndTrack(e) && !e.IsSequenceTrackName());

                    list.AddRange(events);
                    midi.RemoveTrack(t);
                }

                midi.AddNamedTrack(name, list.OrderBy(e => e.AbsoluteTime));
            }
        }
Beispiel #4
0
        public void ConvertLastBeatToEnd(MidiEventCollection midi)
        {
            var beatTrack = midi.GetTrackByName(TrackName.Beat.ToString());

            var lastBeatOn = beatTrack.OfType <NoteOnEvent>().OrderBy(e => e.AbsoluteTime).LastOrDefault(MidiEvent.IsNoteOn);

            if (lastBeatOn == null)
            {
                throw new InvalidBeatTrackException($"No notes were found on the {TrackName.Beat} track");
            }

            var eventsTrack = midi.FindTrackByName(TrackName.Events.ToString());

            if (eventsTrack != null)
            {
                var existingEvent = eventsTrack.FindFirstTextEvent(EventName.End.ToString());
                if (existingEvent != null)
                {
                    AddInfo($"{EventName.End} event already exists at {GetBarInfo(midi, existingEvent)}, left last beat in place.");
                    return;
                }
            }
            else
            {
                eventsTrack = midi.AddNamedTrack(TrackName.Events.ToString());
            }

            beatTrack.Remove(lastBeatOn);
            beatTrack.Remove(lastBeatOn.OffEvent);

            // Fix beat track end
            var newLastEvent = beatTrack.OfType <NoteEvent>().OrderBy(e => e.AbsoluteTime).Last();

            UpdateTrackEnd(beatTrack, newLastEvent.AbsoluteTime);

            eventsTrack.Add(new TextEvent(EventName.End.ToString(), MetaEventType.TextEvent, lastBeatOn.AbsoluteTime));
            UpdateTrackEnd(eventsTrack, lastBeatOn.AbsoluteTime);
        }
Beispiel #5
0
        public void ConsolidateTimeTracks(MidiEventCollection midi)
        {
            // Duplicate events will be ignored, events with the same time
            // but other differing properties will not
            var allTimeSignatureEvents = new HashSet <TimeSignatureEvent>(new TimeSignatureEventEqualityComparer());
            var allTempoEvents         = new HashSet <TempoEvent>(new TempoEventEqualityComparer());

            for (var t = midi.Tracks - 1; t >= 0; t--)
            {
                var timeSignatureEvents = midi[t].OfType <TimeSignatureEvent>().ToList();
                allTimeSignatureEvents.UnionWith(timeSignatureEvents);
                foreach (var midiEvent in timeSignatureEvents)
                {
                    midi[t].Remove(midiEvent);
                }

                var tempoEvents = midi[t].OfType <TempoEvent>().ToList();
                allTempoEvents.UnionWith(tempoEvents);
                foreach (var midiEvent in tempoEvents)
                {
                    midi[t].Remove(midiEvent);
                }

                if (timeSignatureEvents.Any() || tempoEvents.Any())
                {
                    RemoveTrackIfEmpty(midi, t);
                }
            }

            var groupedTimeSignatureEvents = allTimeSignatureEvents.
                                             GroupBy(e => e.AbsoluteTime).
                                             ToList();

            var hasConflict = false;
            var conflictingTimeSignatures = groupedTimeSignatureEvents.Where(g => g.Count() > 1).ToList();

            if (conflictingTimeSignatures.Any())
            {
                foreach (var conflict in conflictingTimeSignatures)
                {
                    var details = string.Join(", ", conflict.Select(t => $"[{t.TimeSignature}]"));
                    AddError($"Conflicting signatures {details} at {GetBarInfo(midi, conflict.Key)}");
                }
                hasConflict = true;
            }

            var groupedTempoEvents = allTempoEvents.
                                     GroupBy(e => e.AbsoluteTime).
                                     ToList();

            var conflictingTempos = groupedTempoEvents.Where(g => g.Count() > 1).ToList();

            if (conflictingTempos.Any())
            {
                foreach (var conflict in conflictingTempos)
                {
                    var details = string.Join(", ", conflict.Select(t => $"[{t.Tempo}]"));
                    AddError($"Conflicting tempos {details} at {GetBarInfo(midi, conflict.Key)}");
                }
                hasConflict = true;
            }

            if (hasConflict)
            {
                throw new InvalidOperationException("Conflicting time signature/tempo events");
            }

            var events = new List <MidiEvent>();

            events.AddRange(groupedTimeSignatureEvents.Select(g => g.First()));
            events.AddRange(groupedTempoEvents.Select(g => g.First()));

            midi.AddNamedTrack(TrackName.TempoMap.ToString(), events);
        }
Beispiel #6
0
        /// <summary>
        /// Used to specify TextEvents from DAWs/Midi creators that can only use Track Names.
        /// A SequenceTrackName event is used as the Text, and a NoteOn event is used as the time.
        ///
        /// Upon completion, there will be a single EVENTS track in the required format.
        /// </summary>
        public void ProcessEventTracks(MidiEventCollection midi, IEnumerable <string> practiceEvents)
        {
            var validEventNames = new HashSet <string>(EventName.SpecialEventNames.Concat(practiceEvents));

            var tracksToRemove     = new HashSet <int>();
            var existingTextEvents = new List <TextEvent>();
            var exsitingNoteEvents = new List <MidiEvent>();
            var newEvents          = new List <TextEvent>();

            for (var t = 0; t < midi.Tracks; t++)
            {
                var textEvents      = midi[t].OfType <TextEvent>();
                var trackNameEvents = textEvents.
                                      Where(e => e.MetaEventType == MetaEventType.SequenceTrackName).
                                      OrderBy(e => e.AbsoluteTime).
                                      ToList();

                if (trackNameEvents.Count == 0)
                {
                    continue;
                }

                var eventsRequiringConversion = trackNameEvents.Where(e => validEventNames.Contains(e.Text)).ToList();
                if (trackNameEvents.Any(e => e.Text == TrackName.Events.ToString()))
                {
                    // These don't mix well, so we don't allow it.
                    if (eventsRequiringConversion.Any())
                    {
                        // TODO: probably not the best exception type??
                        throw new NotSupportedException($"You cannot have '{TrackName.Events}' and '[event]' events on the same track");
                    }

                    FilterEventNotes(midi, t, exsitingNoteEvents);

                    // These are regular TextEvents already on the events track
                    // (not SequenceTrackName events that would require a conversion)
                    existingTextEvents.AddRange(textEvents.
                                                Where(e => e.MetaEventType == MetaEventType.TextEvent && validEventNames.Contains(e.Text)));

                    tracksToRemove.Add(t);
                }

                foreach (var range in eventsRequiringConversion.GetRanges())
                {
                    var notes = midi[t].
                                OfType <NoteOnEvent>().
                                Where(n => n.AbsoluteTime >= range.Start && n.AbsoluteTime < range.End);

                    var eventsForRange = notes.
                                         Select(n => new TextEvent(range.Name, MetaEventType.TextEvent, n.AbsoluteTime)).
                                         ToList();

                    tracksToRemove.Add(t);

                    if (eventsForRange.Count == 0)
                    {
                        AddWarning($"Cannot convert '{range.Name}' to an EVENT as it has no notes.");
                        continue;
                    }

                    if (eventsForRange.Count > 1)
                    {
                        AddWarning($"Cannot have more than one note for '{range.Name}'; " +
                                   "only the first will be converted to an EVENT.");
                    }

                    var convertedEvent = eventsForRange.First();
                    AddInfo($"{range.Name} event converted at {GetBarInfo(midi, convertedEvent)}");
                    newEvents.Add(convertedEvent);
                }
            }

            RemoveTracks(midi, tracksToRemove);

            var textEventsGroups = existingTextEvents.Concat(newEvents).
                                   GroupBy(e => e.Text).
                                   ToList();

            var duplicateEvents   = textEventsGroups.Where(kvp => kvp.Count() > 1).Select(kvp => $"'{kvp.Key}'");
            var duplicateWarnings = string.Join(", ", duplicateEvents);

            if (!string.IsNullOrEmpty(duplicateWarnings))
            {
                AddWarning($"Duplicate events {duplicateWarnings}; using first of each.");
            }

            var uniqueTextEvents = textEventsGroups.Select(kvp => kvp.OrderBy(e => e.AbsoluteTime).First());

            var consolidatedEvents = new List <MidiEvent>(uniqueTextEvents);

            consolidatedEvents.AddRange(exsitingNoteEvents);

            midi.AddNamedTrack(TrackName.Events.ToString(), consolidatedEvents);
        }
 public static IList <MidiEvent> AddNamedTrack(this MidiEventCollection midiEventCollection, string name, params MidiEvent[] initialEvents)
 {
     return(midiEventCollection.AddNamedTrack(name, (IEnumerable <MidiEvent>)initialEvents));
 }