/** 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 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; }
/** 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; }