/// <summary>Transposes a MIDI sequence up/down the specified number of half-steps.</summary> /// <param name="sequence">The sequence to be transposed.</param> /// <param name="steps">The number of steps up(+) or down(-) to transpose the sequence.</param> /// <param name="includeDrums">Whether drum tracks should also be transposed.</param> /// <remarks>If the step value is too large or too small, notes may wrap.</remarks> public static void Transpose(this MidiSequence sequence, int steps, bool includeDrums) { Validate.NonNull("sequence", sequence); // Modify each track foreach (MidiTrack track in sequence) { // Modify each event foreach (MidiEvent ev in track.Events) { // If the event is not a voice MIDI event but the channel is the // drum channel and the user has chosen not to include drums in the // transposition (which makes sense), skip this event. NoteVoiceMidiEvent nvme = ev as NoteVoiceMidiEvent; if (nvme == null || (!includeDrums && nvme.Channel == (byte)SpecialChannel.Percussion)) { continue; } // If the event is a NoteOn, NoteOff, or Aftertouch, shift the note // according to the supplied number of steps. nvme.Note = (byte)((nvme.Note + steps) % 128); } } }
/// <summary>Adds a collection of tracks to this collection.</summary> /// <param name="tracks">The tracks to add.</param> public void AddRange(IEnumerable <MidiTrack> tracks) { Validate.NonNull("tracks", tracks); foreach (MidiTrack track in tracks) { Add(track); } }
} // Prevent external instantiation /// <summary>Adds a collection of events to this collection.</summary> /// <param name="events">The events to add.</param> public void AddRange(IEnumerable <MidiEvent> events) { Validate.NonNull("events", events); foreach (MidiEvent ev in events) { Add(ev); } }
/// <summary>Dumps the MIDI track to the writer in human-readable form.</summary> /// <param name="writer">The writer to which the track should be written.</param> public void ToString(TextWriter writer) { Validate.NonNull("writer", writer); foreach (MidiEvent ev in Events) { writer.WriteLine(ev.ToString()); } }
/// <summary>Initializes the track with a copy of the data in another track.</summary> /// <returns>The track to copy.</returns> public MidiTrack(MidiTrack source) : this() { Validate.NonNull("source", source); m_requireEndOfTrack = source.RequireEndOfTrack; foreach (var e in source.Events) { m_events.Add(e.DeepClone()); } }
/// <summary>Writes a MIDI file header out to the stream.</summary> /// <param name="outputStream">The output stream to which the header should be written.</param> /// <param name="numTracks">The number of tracks that will be a part of this sequence.</param> /// <remarks>This functionality is automatically performed during a Save.</remarks> private void WriteHeader(Stream outputStream, int numTracks) { // Check parameters Validate.NonNull("outputStream", outputStream); Validate.InRange("numTracks", numTracks, 1, int.MaxValue); // Write out the main header for the sequence MThdChunkHeader mainHeader = new MThdChunkHeader(Format, numTracks, m_division); mainHeader.Write(outputStream); }
/// <summary>Initialize the MIDI sequence with a copy of the data from another sequence.</summary> /// <param name="source">The source sequence from which to copy.</param> public MidiSequence(MidiSequence source) { Validate.NonNull("source", source); Format = source.Format; Division = source.Division; Tracks = new MidiTrackCollection(this); foreach (MidiTrack t in source) { Tracks.Add(new MidiTrack(t)); } }
/// <summary>Dumps the MIDI sequence to the writer in human-readable form.</summary> /// <param name="writer">The writer to which the sequence should be written.</param> public void ToString(TextWriter writer) { Validate.NonNull("writer", writer); // Info on sequence writer.WriteLine("MIDI Sequence"); writer.WriteLine("Format: " + m_format); writer.WriteLine("Tracks: " + Tracks.Count); writer.WriteLine("Division: " + m_division); writer.WriteLine(""); // Print out each track foreach (MidiTrack track in this) { track.ToString(writer); } }
/// <summary>Inserts an element into the collection at the specified index.</summary> /// <param name="index">The index at which to insert the element..</param> /// <param name="item">The item to insert.</param> protected override void InsertItem(int index, MidiTrack item) { // Make sure the track is valid and that is hasn't already been added. Validate.NonNull("item", item); if (Contains(item)) { throw new ArgumentException("This track is already part of the sequence."); } // If this is format 0, we can only have 1 track if (m_owningSequence.Format == Format.Zero && Count >= 1) { throw new InvalidOperationException("Format 0 MIDI files can only have 1 track."); } base.InsertItem(index, item); }
/// <summary>Trims a MIDI file to a specified length.</summary> /// <param name="sequence">The sequence to be copied and trimmed.</param> /// <param name="startTime"></param> /// <param name="totalTime">The requested time length of the new MIDI sequence.</param> /// <returns>A MIDI sequence with only those events that fell before the requested time limit.</returns> public static MidiSequence Trim(this MidiSequence sequence, long startTime, long totalTime) { Validate.NonNull("sequence", sequence); Validate.InRange("startTime", startTime, 0, long.MaxValue); Validate.InRange("totalTime", totalTime, 0, long.MaxValue); // Create a new sequence to mimic the old MidiSequence newSequence = new MidiSequence(sequence.Format, sequence.Division); // Copy each track up to the specified time limit foreach (MidiTrack track in sequence) { // Create a new track in the new sequence to match the old track in the old sequence MidiTrack newTrack = newSequence.Tracks.AddNewTrack(); // Convert all times in the old track to deltas track.Events.ConvertDeltasToTotals(); int i = 0; for (i = 0; track.Events[i].DeltaTime < startTime; i++) { } // Copy over all events that fell before the specified time for (int j = i; j < track.Events.Count && track.Events[j].DeltaTime < totalTime; j++) { newTrack.Events.Add(track.Events[j].DeepClone()); } // Convert all times back (on both new and old tracks; the new one inherited the totals) track.Events.ConvertTotalsToDeltas(); newTrack.Events.ConvertTotalsToDeltas(); newTrack.Events[0].DeltaTime = 0; // If the new track lacks an end of track, add one if (!newTrack.HasEndOfTrack) { newTrack.Events.Add(new EndOfTrackMetaMidiEvent(0)); } } // Return the new sequence return(newSequence); }
/// <summary>Writes the MIDI sequence to the output stream.</summary> /// <param name="outputStream">The stream to which the MIDI sequence should be written.</param> public void Save(Stream outputStream) { // Check parameters Validate.NonNull("outputStream", outputStream); // Check valid state (as best we can check) if (Tracks.Count < 1) { throw new InvalidOperationException("No tracks have been added."); } // Write out the main header for the sequence WriteHeader(outputStream, Tracks.Count); // Write out each track in the order it was added to the sequence for (int i = 0; i < Tracks.Count; i++) { // Write out the track to the stream Tracks[i].Write(outputStream); } }
/// <summary>Write the track to the output stream.</summary> /// <param name="outputStream">The output stream to which the track should be written.</param> internal void Write(Stream outputStream) { Validate.NonNull("outputStream", outputStream); // Make sure we have an end of track marker if we need one if (!HasEndOfTrack && m_requireEndOfTrack) { throw new InvalidOperationException("The track cannot be written until it has an end of track marker."); } using (MemoryStream memStream = new MemoryStream()) { // Get the event data and write it out for (int i = 0; i < m_events.Count; i++) { m_events[i].Write(memStream); } // Tack on the header and write the whole thing out to the main stream. MTrkChunkHeader header = new MTrkChunkHeader(memStream.ToArray()); header.Write(outputStream); } }
/// <summary>Reads a MIDI stream into a new MidiSequence.</summary> /// <param name="inputStream">The stream containing the MIDI data.</param> /// <returns>A MidiSequence containing the parsed MIDI data.</returns> public static MidiSequence Open(Stream inputStream) { Validate.NonNull("inputStream", inputStream); // Read in the main MIDI header MThdChunkHeader mainHeader = MThdChunkHeader.Read(inputStream); // Read in all of the tracks MTrkChunkHeader[] trackChunks = new MTrkChunkHeader[mainHeader.NumberOfTracks]; for (int i = 0; i < mainHeader.NumberOfTracks; i++) { trackChunks[i] = MTrkChunkHeader.Read(inputStream); } // Create the MIDI sequence MidiSequence sequence = new MidiSequence(mainHeader.Format, mainHeader.Division); for (int i = 0; i < mainHeader.NumberOfTracks; i++) { sequence.Tracks.Add(MidiParser.ParseToTrack(trackChunks[i].Data)); } return(sequence); }
/// <summary>Initializes the collection.</summary> /// <param name="sequence">The sequence with which this collection is associated.</param> internal MidiTrackCollection(MidiSequence sequence) { Validate.NonNull("sequence", sequence); m_owningSequence = sequence; }
/// <summary>Gets whether the sequence contains any lyric events.</summary> /// <param name="sequence">The sequence to examine.</param> /// <returns>true if the sequence contains any lyric events; otherwise, false.</returns> public static bool HasLyrics(this MidiSequence sequence) { Validate.NonNull("sequence", sequence); return(sequence.SelectMany(t => t.Events).Any(e => e is LyricTextMetaMidiEvent)); }
/// <summary>Converts the MIDI sequence into a new one with the desired format.</summary> /// <param name="sequence">The sequence to be converted.</param> /// <param name="format">The format to which we want to convert the sequence.</param> /// <param name="options">Options used when doing the conversion.</param> /// <returns>The new, converted sequence.</returns> public static MidiSequence Convert(this MidiSequence sequence, Format format, FormatConversionOption options) { Validate.NonNull("sequence", sequence); Validate.InRange("format", (int)format, (int)Format.Zero, (int)Format.Two); if (sequence.Format == format) { // If the desired format is the same as the original, just return a copy. // No transformation is necessary. sequence = new MidiSequence(sequence); } else if (format != Format.Zero || sequence.Tracks.Count == 1) { // If the desired format is is not 0 or there's only one track, just copy the sequence with a different format number. // If it's not zero, then multiple tracks are acceptable, so no transformation is necessary. // Or if there's only one track, then there's no possible transformation to be done. var newSequence = new MidiSequence(format, sequence.Division); foreach (MidiTrack t in sequence) { newSequence.Tracks.Add(new MidiTrack(t)); } sequence = newSequence; } else { // Now the harder cases, converting to format 0. We need to combine all tracks into 1, // as format 0 requires that there only be a single track with all of the events for the song. sequence = new MidiSequence(sequence); // Iterate through all events in all tracks and change deltaTimes to actual times. // We'll then be able to sort based on time and change them back to deltas later. foreach (MidiTrack track in sequence) { track.Events.ConvertDeltasToTotals(); } // Add all events to new track (except for end of track markers!) int trackNumber = 0; MidiTrack newTrack = new MidiTrack(); foreach (MidiTrack track in sequence) { foreach (MidiEvent midiEvent in track.Events) { // If this event has a channel, and if we're storing tracks as channels, copy to it if ((options & FormatConversionOption.CopyTrackToChannel) > 0 && trackNumber >= 0 && trackNumber <= 0xF) { var vme = midiEvent as VoiceMidiEvent; if (vme != null) { vme.Channel = (byte)trackNumber; } } // Add all events, except for end of track markers (we'll add our own) if (!(midiEvent is EndOfTrackMetaMidiEvent)) { newTrack.Events.Add(midiEvent); } } trackNumber++; } // Sort the events by total time, then convert back to delta time, // and top things off with an end-of-track marker. newTrack.Events.Sort((x, y) => x.DeltaTime.CompareTo(y.DeltaTime)); newTrack.Events.ConvertTotalsToDeltas(); newTrack.Events.Add(new EndOfTrackMetaMidiEvent(0)); // We now have all of the combined events in newTrack. Clear out the sequence, replacing all the tracks // with this new one. sequence.Tracks.Clear(); sequence.Tracks.Add(newTrack); } return(sequence); }