/** Return a deep copy clone of this MidiTrack. */ public MidiTrack Clone() { MidiTrack track = new MidiTrack(Number); track.instrument = instrument; foreach (MidiNote note in notes) { track.notes.Add( note.Clone() ); } if (lyrics != null) { track.lyrics = new List<MidiEvent>(); foreach (MidiEvent ev in lyrics) { track.lyrics.Add(ev); } } return track; }
/** Parse the given Midi file, and return an instance of this MidiFile * class. After reading the midi file, this object will contain: * - The raw list of midi events * - The Time Signature of the song * - All the tracks in the song which contain notes. * - The number, starttime, and duration of each note. */ public void parse(MidiFileReader file, string filename) { string id; int len; this.filename = filename; tracks = new List<MidiTrack>(); trackPerChannel = false; id = file.ReadAscii(4); if (id != "MThd") { throw new MidiFileException("Doesn't start with MThd", 0); } len = file.ReadInt(); if (len != 6) { throw new MidiFileException("Bad MThd header", 4); } trackmode = file.ReadShort(); int num_tracks = file.ReadShort(); quarternote = file.ReadShort(); events = new List<MidiEvent>[num_tracks]; for (int tracknum = 0; tracknum < num_tracks; tracknum++) { events[tracknum] = ReadTrack(file); MidiTrack track = new MidiTrack(events[tracknum], tracknum); if (track.Notes.Count > 0) { tracks.Add(track); } } /* Get the length of the song in pulses */ foreach (MidiTrack track in tracks) { MidiNote last = track.Notes[track.Notes.Count-1]; if (this.totalpulses < last.StartTime + last.Duration) { this.totalpulses = last.StartTime + last.Duration; } } /* If we only have one track with multiple channels, then treat * each channel as a separate track. */ if (tracks.Count == 1 && HasMultipleChannels(tracks[0])) { tracks = SplitChannels(tracks[0], events[tracks[0].Number]); trackPerChannel = true; } CheckStartTimes(tracks); /* Determine the time signature */ int tempo = 0; int numer = 0; int denom = 0; foreach (List<MidiEvent> list in events) { foreach (MidiEvent mevent in list) { if (mevent.Metaevent == MetaEventTempo && tempo == 0) { tempo = mevent.Tempo; } if (mevent.Metaevent == MetaEventTimeSignature && numer == 0) { numer = mevent.Numerator; denom = mevent.Denominator; } } } if (tempo == 0) { tempo = 500000; /* 500,000 microseconds = 0.05 sec */ } if (numer == 0) { numer = 4; denom = 4; } timesig = new TimeSignature(numer, denom, quarternote, tempo); }
/* Split the given MidiTrack into two tracks, top and bottom. * The highest notes will go into top, the lowest into bottom. * This function is used to split piano songs into left-hand (bottom) * and right-hand (top) tracks. */ public static List<MidiTrack> SplitTrack(MidiTrack track, int measurelen) { List<MidiNote> notes = track.Notes; int count = notes.Count; MidiTrack top = new MidiTrack(1); MidiTrack bottom = new MidiTrack(2); List<MidiTrack> result = new List<MidiTrack>(2); result.Add(top); result.Add(bottom); if (count == 0) return result; int prevhigh = 76; /* E5, top of treble staff */ int prevlow = 45; /* A3, bottom of bass staff */ int startindex = 0; foreach (MidiNote note in notes) { int high, low, highExact, lowExact; int number = note.Number; high = low = highExact = lowExact = number; while (notes[startindex].EndTime < note.StartTime) { startindex++; } /* I've tried several algorithms for splitting a track in two, * and the one below seems to work the best: * - If this note is more than an octave from the high/low notes * (that start exactly at this start time), choose the closest one. * - If this note is more than an octave from the high/low notes * (in this note's time duration), choose the closest one. * - If the high and low notes (that start exactly at this starttime) * are more than an octave apart, choose the closest note. * - If the high and low notes (that overlap this starttime) * are more than an octave apart, choose the closest note. * - Else, look at the previous high/low notes that were more than an * octave apart. Choose the closeset note. */ FindHighLowNotes(notes, measurelen, startindex, note.StartTime, note.EndTime, ref high, ref low); FindExactHighLowNotes(notes, startindex, note.StartTime, ref highExact, ref lowExact); if (highExact - number > 12 || number - lowExact > 12) { if (highExact - number <= number - lowExact) { top.AddNote(note); } else { bottom.AddNote(note); } } else if (high - number > 12 || number - low > 12) { if (high - number <= number - low) { top.AddNote(note); } else { bottom.AddNote(note); } } else if (highExact - lowExact > 12) { if (highExact - number <= number - lowExact) { top.AddNote(note); } else { bottom.AddNote(note); } } else if (high - low > 12) { if (high - number <= number - low) { top.AddNote(note); } else { bottom.AddNote(note); } } else { if (prevhigh - number <= number - prevlow) { top.AddNote(note); } else { bottom.AddNote(note); } } /* The prevhigh/prevlow are set to the last high/low * that are more than an octave apart. */ if (high - low > 12) { prevhigh = high; prevlow = low; } } top.Notes.Sort(track.Notes[0]); bottom.Notes.Sort(track.Notes[0]); return result; }
/** Combine the notes in the given tracks into a single MidiTrack. * The individual tracks are already sorted. To merge them, we * use a mergesort-like algorithm. */ public static MidiTrack CombineToSingleTrack(List<MidiTrack> tracks) { /* Add all notes into one track */ MidiTrack result = new MidiTrack(1); if (tracks.Count == 0) { return result; } else if (tracks.Count == 1) { MidiTrack track = tracks[0]; foreach (MidiNote note in track.Notes) { result.AddNote(note); } return result; } int[] noteindex = new int[64]; int[] notecount = new int[64]; for (int tracknum = 0; tracknum < tracks.Count; tracknum++) { noteindex[tracknum] = 0; notecount[tracknum] = tracks[tracknum].Notes.Count; } MidiNote prevnote = null; while (true) { MidiNote lowestnote = null; int lowestTrack = -1; for (int tracknum = 0; tracknum < tracks.Count; tracknum++) { MidiTrack track = tracks[tracknum]; if (noteindex[tracknum] >= notecount[tracknum]) { continue; } MidiNote note = track.Notes[ noteindex[tracknum] ]; if (lowestnote == null) { lowestnote = note; lowestTrack = tracknum; } else if (note.StartTime < lowestnote.StartTime) { lowestnote = note; lowestTrack = tracknum; } else if (note.StartTime == lowestnote.StartTime && note.Number < lowestnote.Number) { lowestnote = note; lowestTrack = tracknum; } } if (lowestnote == null) { /* We've finished the merge */ break; } noteindex[lowestTrack]++; if ((prevnote != null) && (prevnote.StartTime == lowestnote.StartTime) && (prevnote.Number == lowestnote.Number) ) { /* Don't add duplicate notes, with the same start time and number */ if (lowestnote.Duration > prevnote.Duration) { prevnote.Duration = lowestnote.Duration; } } else { result.AddNote(lowestnote); prevnote = lowestnote; } } return result; }
/** Split the given track into multiple tracks, separating each * channel into a separate track. */ private static List<MidiTrack> SplitChannels(MidiTrack origtrack, List<MidiEvent> events) { /* Find the instrument used for each channel */ int[] channelInstruments = new int[16]; foreach (MidiEvent mevent in events) { if (mevent.EventFlag == EventProgramChange) { channelInstruments[mevent.Channel] = mevent.Instrument; } } channelInstruments[9] = 128; /* Channel 9 = Percussion */ List<MidiTrack> result = new List<MidiTrack>(); foreach (MidiNote note in origtrack.Notes) { bool foundchannel = false; foreach (MidiTrack track in result) { if (note.Channel == track.Notes[0].Channel) { foundchannel = true; track.AddNote(note); } } if (!foundchannel) { MidiTrack track = new MidiTrack(result.Count + 1); track.AddNote(note); track.Instrument = channelInstruments[note.Channel]; result.Add(track); } } return result; }
/** Return true if this track contains multiple channels. * If a MidiFile contains only one track, and it has multiple channels, * then we treat each channel as a separate track. */ static bool HasMultipleChannels(MidiTrack track) { int channel = track.Notes[0].Channel; foreach (MidiNote note in track.Notes) { if (note.Channel != channel) { return true; } } return false; }