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])); } }
public void AddVenueTrack(MidiEventCollection midi) { var existingTrack = midi.FindTrackNumberByName(TrackName.Venue.ToString()); if (existingTrack != -1) { return; } midi.AddNamedTrack(TrackName.Venue.ToString()); }
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)); } }
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); }
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); }
/// <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)); }