/// <summary>Trims a MIDI file to a specified length.</summary> /// <param name="sequence">The sequence to be copied and trimmed.</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 Sequence Trim(this Sequence sequence, long totalTime) { // Create a new sequence to mimic the old var newSequence = new Sequence(sequence.DivisionType, sequence.Resolution, 0, sequence.MidiFileType); // Copy each track up to the specified time limit foreach (Track track in sequence.Tracks) { // Create a new track in the new sequence to match the old track in the old sequence var newTrack = newSequence.CreateTrack(); // Copy over all events that fell before the specified time for (int i = 0; i < track.Events.Count && track.Events[i].Tick < totalTime; i++) { newTrack.Events.Add(track.Events[i].DeepClone()); // add at the end //newTrack.Add(track.Events[i].DeepClone()); // insert at correct timing } // If the new track lacks an end of track, add one if (!newTrack.HasEndOfTrack) { newTrack.Add(MetaEvent.CreateMetaEvent("EndOfTrack", "", newTrack.Ticks(), 0)); } } // Return the new sequence return(newSequence); }
public void SaveMidiSequence(string filePath) { long ticks = midiTrack.Ticks(); midiTrack.Add(MetaEvent.CreateMetaEvent((int)MidiHelper.MetaEventType.EndOfTrack, "", ticks, midiTickResolution)); midiSequence.DumpMidi("output.mid.txt"); new MidiFileWriter().Write(midiSequence, midiSequence.MidiFileType, new FileInfo(filePath)); }
void InitMidiSequence() { // Generate midi file midiSequence = new Sequence(Sequence.PPQ, midiTickResolution, 0, (int)MidiHelper.MidiFormat.SingleTrack); midiTrack = midiSequence.CreateTrack(); midiTrack.Add(MetaEvent.CreateMetaEvent((int)MidiHelper.MetaEventType.SequenceOrTrackName, "Audio2Midi", 0, midiTickResolution)); midiTrack.Add(MetaEvent.CreateMetaEvent((int)MidiHelper.MetaEventType.CopyrightNotice, "*****@*****.**", 0, midiTickResolution)); midiTrack.Add(MetaEvent.CreateMetaEvent((int)MidiHelper.MetaEventType.Tempo, "" + midiBPM, 0, midiTickResolution)); midiTrack.Add(MetaEvent.CreateMetaEvent((int)MidiHelper.MetaEventType.TimeSignature, "4/4", 0, midiTickResolution)); // Convert from ticks to duration // Midi timings are explained here // http://sites.uci.edu/camp2014/2014/05/19/timing-in-midi-files/ // http://stackoverflow.com/questions/2038313/midi-ticks-to-actual-playback-seconds-midi-music // The formula is 60000 / (BPM * PPQ) (milliseconds). // Where BPM is the tempo of the track (Beats Per Minute). // (i.e. a 120 BPM track would have a MIDI time of: // (60000 / (120 * 192)) or 2.604 ms for 1 tick. double timeBufferMs = ((double)bufferSize / sampleRate) * 1000; double timeTickMs = (60000 / (double)(midiBPM * midiTickResolution)); tickMultiplier = timeBufferMs / timeTickMs; }
/// <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> /// <param name="newResolution">the new resolution (disable using 0 or -1)</param> /// <returns>The new, converted sequence.</returns> /// <remarks>This is based on the excellent MidiSharp package by Stephen Toub.</remarks> public static Sequence Convert(this Sequence sequence, int format, FormatConversionOption options, int newResolution, string trackName) { if (sequence.MidiFileType == format) { // If the desired format is the same as the original, just return a copy. // No transformation is necessary. sequence = new Sequence(sequence); } else if (format != 0 || 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 Sequence(sequence.DivisionType, sequence.Resolution, 0, format); foreach (Track t in sequence.Tracks) { newSequence.Tracks.Add(new Track(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. int originalResolution = sequence.Resolution; if (newResolution <= 0) { newResolution = originalResolution; } sequence = new Sequence(sequence); sequence.MidiFileType = (int)MidiHelper.MidiFormat.SingleTrack; // Add all events to new track (except for end of track markers and SequenceOrTrackName events) int trackNumber = 0; var newTrack = new Track(); foreach (Track track in sequence.Tracks) { foreach (MidiEvent midiEvent in track.Events) { bool doAddEvent = true; var msg = midiEvent.Message; // check if this is a meta message var mm = msg as MetaMessage; if (mm != null) { // we have a meta message // add all meta messages except the end of track markers (we'll add our own) int type = mm.GetMetaMessageType(); if (type == (int)MidiHelper.MetaEventType.EndOfTrack) { doAddEvent = false; } else if (type == (int)MidiHelper.MetaEventType.SequenceOrTrackName) { doAddEvent = false; // store track name, will be used later if (string.IsNullOrEmpty(trackName)) { byte[] data = mm.GetMetaMessageData(); string text = MidiHelper.GetString(data); trackName = MidiHelper.TextString(text); } } } // check if this is a short message var sm = msg as ShortMessage; if (sm != null) { // get the data var channel = sm.GetChannel(); var cmd = sm.GetCommand(); var data1 = sm.GetData1(); var data2 = sm.GetData2(); // If this event has a channel, and if we're storing tracks as channels, copy to it if ((options & FormatConversionOption.CopyTrackToChannel) > 0 && trackNumber >= MidiHelper.MIN_CHANNEL && trackNumber <= MidiHelper.MAX_CHANNEL) { if (sm.IsChannelMessage()) { // store the track number as the channel sm.SetMessage(cmd, trackNumber, data1, data2); } } if ((options & FormatConversionOption.NoteOffZero2NoteOnZero) > 0) { // If the event is a NoteOff with Volume 0 if (cmd == (int)MidiHelper.MidiEventType.NoteOff && data2 == 0) { // convert to a NoteOn instead sm.SetMessage((int)MidiHelper.MidiEventType.NoteOn, channel, data1, data2); } } } // convert ticks if resolution has changed if (originalResolution != newResolution) { if (midiEvent.Tick != 0) { double fraction = (double)midiEvent.Tick / (double)originalResolution; int tick = (int)(fraction * newResolution); midiEvent.Tick = tick; } } // Add all events, except for end of track markers (we'll add our own) if (doAddEvent) { //newTrack.Events.Add(midiEvent); // add to end of list newTrack.Add(midiEvent); // add in the right position based on the tick } } trackNumber++; } if (originalResolution != newResolution) { sequence.Resolution = newResolution; } // Sort the events by total time // newTrack.Events.Sort((x, y) => x.Tick.CompareTo(y.Tick)); // Note! using newTrack.Add instead of newTrack.Events.Add, already ensures a correct sort order // Top things off with an end-of-track marker. newTrack.Add(MetaEvent.CreateMetaEvent("EndOfTrack", "", newTrack.Ticks(), 0)); // add a new track name as the very first event newTrack.Events.Insert(0, MetaEvent.CreateMetaEvent((int)MidiHelper.MetaEventType.SequenceOrTrackName, trackName, 0, 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); }