public void Write(Stream strm) { this.stream = strm; trackData.Clear(); stream.Write(TrackHeader, 0, TrackHeader.Length); foreach (MidiEvent e in track.Iterator()) { WriteVariableLengthValue(e.DeltaTicks); switch (e.MidiMessage.MessageType) { case MessageType.Channel: Write((ChannelMessage)e.MidiMessage); break; case MessageType.SystemExclusive: Write((SysExMessage)e.MidiMessage); break; case MessageType.Meta: Write((MetaMessage)e.MidiMessage); break; case MessageType.SystemCommon: Write((SysCommonMessage)e.MidiMessage); break; case MessageType.SystemRealtime: Write((SysRealtimeMessage)e.MidiMessage); break; case MessageType.Short: Write((ShortMessage)e.MidiMessage); break; } } byte[] trackLength = BitConverter.GetBytes(trackData.Count); if (BitConverter.IsLittleEndian) { Array.Reverse(trackLength); } stream.Write(trackLength, 0, trackLength.Length); foreach (byte b in trackData) { stream.WriteByte(b); } }
public static NotationTrack parseMidiTrack(Midi.Track track, int division, ref Regex fingerPattern) { int microsecondsPerBeat = Midi.PpqnClock.DefaultTempo; float time = 0; TrackStatus trackStatus = new TrackStatus(); FingerChordMap fingerMap = new FingerChordMap(); List <Note> notes = new List <Note>(); Regex fingerMetaPattern = new Regex("fingering-marker-pattern:(.*)"); string name = ""; foreach (Midi.MidiEvent e in track.Iterator()) { time += e.DeltaTicks * microsecondsPerBeat / (division * 1000); switch (e.MidiMessage.MessageType) { case Midi.MessageType.Meta: Midi.MetaMessage mm = e.MidiMessage as Midi.MetaMessage; switch (mm.MetaType) { case Midi.MetaType.Tempo: Midi.TempoChangeBuilder builder = new Midi.TempoChangeBuilder(mm); microsecondsPerBeat = builder.Tempo; break; case Midi.MetaType.Text: { string text = Encoding.Default.GetString(mm.GetBytes()); var match = fingerMetaPattern.Match(text); if (match.Success) { fingerPattern = new Regex(match.Groups[1].ToString()); Debug.LogFormat("Finger Pattern found: {0}", fingerPattern.ToString()); } } break; case Midi.MetaType.Marker: if (fingerPattern != null) { string text = Encoding.Default.GetString(mm.GetBytes()); var match = fingerPattern.Match(text); if (match.Success) { //Debug.LogFormat("Finger: {0}", text); try { int pitch = int.Parse(match.Groups[1].ToString()); Finger finger = (Finger)int.Parse(match.Groups[2].ToString()); if (!fingerMap.ContainsKey(e.AbsoluteTicks)) { fingerMap[e.AbsoluteTicks] = new FingerChord(); } fingerMap[e.AbsoluteTicks][pitch] = finger; } catch (System.Exception except) { Debug.LogWarningFormat("fingering marker parse failed: {0}, {1}", text, except.Message); } } //else // Debug.LogWarningFormat("fail marker: {0}", text); } break; case Midi.MetaType.TrackName: name = Encoding.Default.GetString(mm.GetBytes()); break; } break; case Midi.MessageType.Channel: Midi.ChannelMessage cm = e.MidiMessage as Midi.ChannelMessage; if (!trackStatus.ContainsKey(cm.MidiChannel)) { trackStatus[cm.MidiChannel] = new ChannelStatus(); } var commandType = cm.Command; if (commandType == Midi.ChannelCommand.NoteOn && cm.Data2 == 0) { commandType = Midi.ChannelCommand.NoteOff; } switch (commandType) { case Midi.ChannelCommand.NoteOn: { int pitch = cm.Data1; int velocity = cm.Data2; if (pitch >= Piano.PitchMin && pitch <= Piano.PitchMax) { trackStatus[cm.MidiChannel][pitch] = new PitchStatus { tick = e.AbsoluteTicks, startTime = time, velocity = velocity } } ; } break; case Midi.ChannelCommand.NoteOff: { int pitch = cm.Data1; if (!trackStatus[cm.MidiChannel].ContainsKey(pitch)) { Debug.LogWarningFormat("Unexpected noteOff: {0}, {1}", e.AbsoluteTicks, pitch); } else { PitchStatus status = trackStatus[cm.MidiChannel][pitch]; Note note = new Note { tick = status.tick, start = status.startTime, duration = time - status.startTime, pitch = pitch, velocity = status.velocity }; if (fingerMap.ContainsKey(note.tick) && fingerMap[note.tick].ContainsKey(note.pitch)) { note.finger = fingerMap[note.tick][note.pitch]; } notes.Add(note); } } break; } break; } } NotationTrack notation = new NotationTrack(); notation.notes = notes.ToArray(); notation.name = name; return(notation); }
// usage : // string mydocpath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); // StreamWriter outputFile = new StreamWriter(mydocpath + @"\WriteLines.txt") public void Write(StreamWriter strm) { this.stream = strm; int trackid = 0; bool bHeaderCreated = false; if (sequence.tracks[0].ContainsNotes == false) { // first track has no notes // Create header //0, 0, Header, format, nTracks, division stream.WriteLine(string.Format("0, 0, Header, {0}, {1}, {2}", sequence.Format, sequence.tracks.Count, sequence.Division)); } else { // If first track contains notes, create an empty track with header information (so tracks.count + 1) //0, 0, Header, format, nTracks, division stream.WriteLine(string.Format("0, 0, Header, {0}, {1}, {2}", sequence.Format, sequence.tracks.Count + 1, sequence.Division)); trackid++; // First track is for informations stream.WriteLine("1, 0, Start_track"); // informations stream.WriteLine(string.Format("1, 0, Title_t, \"{0}\"", song)); stream.WriteLine("1, 0, Text_t, \"Midi file dump made with Karaboss\""); stream.WriteLine("1, 0, Copyright_t, \"No copyright\""); // Track, Time, Time_signature, Num, Denom, Click, NotesQ // FAB: TO BE CHECKED stream.WriteLine(string.Format("1, 0, Time_signature, {0}, {1}, {2}, {3}", sequence.Time.Numerator, sequence.Time.Denominator, 24, 8)); stream.WriteLine(string.Format("1, 0, Tempo, {0}", sequence.Tempo)); stream.WriteLine("1, 0, End_track"); bHeaderCreated = true; } for (int i = 0; i < sequence.tracks.Count; i++) { runningStatus = 0; track = sequence.tracks[i]; trackid++; #region track informations // Start new track stream.WriteLine(string.Format("{0}, 0, Start_track", trackid)); if (!bHeaderCreated) { // first track has no notes // header was not created // informations stream.WriteLine(string.Format("1, 0, Title_t, \"{0}\"", song)); stream.WriteLine("1, 0, Text_t, \"Midi file dump made with Karaboss\""); stream.WriteLine("1, 0, Copyright_t, \"No copyright\""); // Track, Time, Time_signature, Num, Denom, Click, NotesQ // FAB: TO BE CHECKED stream.WriteLine(string.Format("1, 0, Time_signature, {0}, {1}, {2}, {3}", sequence.Time.Numerator, sequence.Time.Denominator, 24, 8)); stream.WriteLine(string.Format("1, 0, Tempo, {0}", sequence.Tempo)); bHeaderCreated = true; } else { // Track, Time, Title_t, Text stream.WriteLine(string.Format("{0}, 0, Title_t, \"{1}\"", trackid, track.Name)); // Track, Time, Instrument_name_t, Text stream.WriteLine(string.Format("{0}, 0, Instrument_name_t, \"{1}\"", trackid, MidiFile.PCtoInstrument(track.ProgramChange))); // Track, Time, Program_c, Channel, Program_num stream.WriteLine(string.Format("{0}, 0, Program_c, {1}, {2}", trackid, track.MidiChannel, track.ProgramChange)); } #endregion #region events foreach (MidiEvent e in track.Iterator()) { switch (e.MidiMessage.MessageType) { case MessageType.Channel: Write((ChannelMessage)e.MidiMessage, trackid, e.AbsoluteTicks, track.MidiChannel); break; case MessageType.SystemExclusive: Write((SysExMessage)e.MidiMessage); break; case MessageType.Meta: Write((MetaMessage)e.MidiMessage, trackid, e.AbsoluteTicks); break; case MessageType.SystemCommon: Write((SysCommonMessage)e.MidiMessage); break; case MessageType.SystemRealtime: Write((SysRealtimeMessage)e.MidiMessage); break; } } #endregion //Track, Time, End_track stream.WriteLine(string.Format("{0}, 0, End_track", trackid)); } // Last record // 0, 0, End_of_file stream.WriteLine("0, 0, End_of_file"); // Close the stream stream.Close(); }
private IEnumerable <int> GetTickIterator(MidiToolkit.Track track, Vst.Plugin plugin, int startPosition) { IEnumerator <MidiToolkit.MidiEvent> enumerator = track.Iterator().GetEnumerator(); bool hasNext; bool breakLoop = false; for (hasNext = enumerator.MoveNext(); hasNext && enumerator.Current.AbsoluteTicks < startPosition; hasNext = enumerator.MoveNext()) { } int ticks = startPosition; while (hasNext) { while (ticks < enumerator.Current.AbsoluteTicks) { if (ticks >= _length) { breakLoop = true; break; } yield return(ticks); ticks++; } if (breakLoop) { break; } yield return(ticks); while (hasNext && enumerator.Current.AbsoluteTicks == ticks) { if (enumerator.Current.MidiMessage is MidiToolkit.ChannelMessage cm) { if (cm.Command == MidiToolkit.ChannelCommand.NoteOn) { _masterSequencer.SendNoteOn(plugin, (byte)cm.Data1, (byte)cm.Data2); _pressedNotes[cm.Data1] = true; } else if (cm.Command == MidiToolkit.ChannelCommand.NoteOff) { _masterSequencer.SendNoteOff(plugin, (byte)cm.Data1); _pressedNotes[cm.Data1] = false; } } hasNext = enumerator.MoveNext(); } ticks++; } for (byte i = 0; i < 128; i++) { if (_pressedNotes[i]) { _masterSequencer.SendNoteOff(plugin, i); } } _numOfPlayingScores--; }
/// <summary> /// Creates a notechart from the specified midi path and the actual charttype /// (i.e. ExpertSingle from notes.mid). Due to the overhead necessary to /// parse a midi file. I am going to cram all midi->chart operations into /// one function call. /// This function uses the Sanford midi parser. While it is horribly slow /// on larger (e.g. RB) midis, it works without a hitch on every midi I've /// come across. /// </summary> /// <param name="chartSelection"> /// The information on which particular notechart to use. /// </param> /// <param name="chartInfo">The metadata on the chart.</param> /// <param name="BPMChanges">The list of BPM changes for this chart.</param> /// <returns> /// A filled out Notechart containing the needed information from the *.mid file. /// </returns> public static Notes ParseMidiInformationSanford(ChartSelection chartSelection, Info chartInfo, List<BPMChange> BPMChanges) { Notes notechartToReturn = new Notes(); notechartToReturn.instrument = chartSelection.instrument; notechartToReturn.difficulty = chartSelection.difficulty; // The following two switch's are used to get the proper midi terminology for // the selected track and difficulty. string instrumentPart = null; int greenKey = 0; int redKey = 0; int yellowKey = 0; int blueKey = 0; int orangeKey = 0; switch (chartSelection.instrument) { case "Single": instrumentPart = "PART GUITAR"; break; case "DoubleGuitar": instrumentPart = "PART GUITAR COOP"; break; case "DoubleBass": instrumentPart = "PART BASS"; break; case "Drums": instrumentPart = "PART DRUMS"; break; default: instrumentPart = "PART GUITAR"; break; } switch (chartSelection.difficulty) { case "Expert": greenKey = 96; redKey = 97; yellowKey = 98; blueKey = 99; orangeKey = 100; break; case "Hard": greenKey = 84; redKey = 85; yellowKey = 86; blueKey = 87; orangeKey = 88; break; case "Medium": greenKey = 72; redKey = 73; yellowKey = 74; blueKey = 75; orangeKey = 76; break; case "Easy": greenKey = 60; redKey = 61; yellowKey = 62; blueKey = 63; orangeKey = 64; break; default: greenKey = 96; redKey = 97; yellowKey = 98; blueKey = 99; orangeKey = 100; break; } Sequence mySequence = new Sequence(chartSelection.directory + "\\notes.mid"); Track trackToUse = new Track(); chartInfo.resolution = mySequence.Division; // Go through each event in the first track (which contains the BPM changes) // and parse the resulting string. Track sanTrack = mySequence[0]; foreach (Sanford.Multimedia.Midi.MidiEvent currEvent in sanTrack.Iterator()) { if (currEvent.MidiMessage.MessageType == MessageType.Meta) { MetaMessage currMessage = currEvent.MidiMessage as MetaMessage; //currTickValue += Convert.ToUInt32(splitEventString[1]); if (currMessage.MetaType == MetaType.Tempo) { TempoChangeBuilder tempoBuilder = new TempoChangeBuilder(currMessage); int midiBPMChange = tempoBuilder.Tempo; // In midi files, bpm chages are stored as "microseconds per quarter note" // and must be converted to BPM, and then into the non decimal format the game // uses. double currBPMDouble = 60000000 / (double)midiBPMChange; uint BPMToAdd = (uint)(currBPMDouble * 1000); BPMChanges.Add(new BPMChange((uint)currEvent.AbsoluteTicks, (uint)BPMToAdd)); } } } // Find the specified instrument's track for (int i = 1; i < mySequence.Count; i++) { sanTrack = mySequence[i]; Sanford.Multimedia.Midi.MidiEvent currEvent = sanTrack.GetMidiEvent(0); if (currEvent.MidiMessage.MessageType == MessageType.Meta) { MetaMessage currMessage = currEvent.MidiMessage as MetaMessage; if (currMessage.MetaType == MetaType.TrackName) { MetaTextBuilder trackName = new MetaTextBuilder(currMessage); // -If we come across a "T1 GEMS" track, we're in GH1 territory. // -GH2/FoF has both PART BASS and PART RHYTHM (one or the other depending // on the chart). if ((trackName.Text == instrumentPart) || (trackName.Text == "T1 GEMS") || ((trackName.Text == "PART RHYTHM") && (instrumentPart == "PART BASS"))) { trackToUse = sanTrack; } } } } Note currNote = new Note(); bool blankNote = true; // Scan through and record every note specific to the selected difficulty foreach (Sanford.Multimedia.Midi.MidiEvent currEvent in trackToUse.Iterator()) { // We need to specify wether a note is blank or not so we don't add // blank notes from other difficulties into the chart, but if we have // a filled out note, any nonzero tick value means we are moving to a // new note, so we must cut our ties and add this note to the chart. if ((currEvent.DeltaTicks != 0) && !blankNote) { notechartToReturn.notes.Add(currNote); currNote = new Note(); blankNote = true; } if (currEvent.MidiMessage.MessageType == MessageType.Channel) { ChannelMessage currMessage = currEvent.MidiMessage as ChannelMessage; if (currMessage.Command == ChannelCommand.NoteOn) { // Only consider notes within the octave our difficulty is in. if (((currMessage.Data1 == greenKey) || (currMessage.Data1 == redKey) || (currMessage.Data1 == yellowKey) || (currMessage.Data1 == blueKey) || (currMessage.Data1 == orangeKey)) && (currMessage.Data2 != 0)) { // If it's a new note, we need to setup the tick value of it. if (blankNote) { //currNote.TickValue = totalTickValue; currNote.tickValue = (uint)currEvent.AbsoluteTicks; blankNote = false; } if (currMessage.Data1 == greenKey) { currNote.addNote(0); } else if (currMessage.Data1 == redKey) { currNote.addNote(1); } else if (currMessage.Data1 == yellowKey) { currNote.addNote(2); } else if (currMessage.Data1 == blueKey) { currNote.addNote(3); } else if (currMessage.Data1 == orangeKey) { currNote.addNote(4); } } } } } return notechartToReturn; }
static Dictionary<int, List<int[]>> getInstrumentNoteTimes(Track track, int[,] instrumentsAtTicks, Dictionary<int, List<int[]>> eventsAtTicksDict) { //Dictionary<int, List<int[]>> eventsAtTicksDict = new Dictionary<int, List<int[]>>(); //dict to be modified to allow note duration calculations Dictionary<Tuple<int, int>, int[]> noteDurationData = new Dictionary<Tuple<int, int>, int[]>(); //key = <channel,pitch> data = <abs, vel> IEnumerable<MidiEvent> midievents = track.Iterator(); foreach (MidiEvent midievent in midievents) { //Console.Write("{0:X}, {1}, {2}", midievent.MidiMessage.Status, midievent.AbsoluteTicks, "bytes: "); //foreach (byte b in midievent.MidiMessage.GetBytes()) Console.Write("{0:X}, {1}", b, " "); //Console.WriteLine(); switch (midievent.MidiMessage.Status >> 4) { case 0x9: byte[] byteHold = new byte[4]; byteHold = midievent.MidiMessage.GetBytes(); int channel = byteHold[0] & 0x0F;//bitwise and to get last 4 bits int pitch = byteHold[1]; int vel = byteHold[2]; int abs = midievent.AbsoluteTicks; Tuple<int, int> key = new Tuple<int, int>(channel, pitch); int[] data = new int[3] { abs, vel, channel }; if (!noteDurationData.ContainsKey(key)) { noteDurationData.Add(key, data); } else { //Console.WriteLine("too stupid for real msg"); //NOD stands for note-on duplicate. We need this because occasionally, songs like to send multiple NONs before a NOFF. I find this confusing and frustrating. byte[] byteHoldNOFF_NOD = new byte[4]; byteHoldNOFF_NOD = midievent.MidiMessage.GetBytes(); int channelNOFF_NOD = byteHoldNOFF_NOD[0] & 0x0F;//bitwise and to get last 4 bits int pitchNOFF_NOD = byteHoldNOFF_NOD[1]; int absNOFF_NOD = midievent.AbsoluteTicks; Tuple<int, int> keyNOFF_NOD = new Tuple<int, int>(channelNOFF_NOD, pitchNOFF_NOD); int[] NONdata_NOD = noteDurationData[keyNOFF_NOD]; noteDurationData.Remove(keyNOFF_NOD); int absNON_NOD = NONdata_NOD[0]; int velNON_NOD = NONdata_NOD[1]; int dur = absNOFF_NOD - absNON_NOD; int instr = -1; //deal with it hopfully/ //CALCULATE current instrument for (int i = 0; i < instrumentsAtTicks.GetLength(0); ++i) { if (instrumentsAtTicks[i, 16] > absNON_NOD) { instr = instrumentsAtTicks[i - 1, channelNOFF_NOD]; //this makes sense. finding a bigger time then backtracking should never fail } else if (i == instrumentsAtTicks.GetLength(0) - 1) { instr = instrumentsAtTicks[i, channelNOFF_NOD]; //added this case because I was wrong in the previous comment. } } int[] eventdata = new int[5] { instr, pitchNOFF_NOD, velNON_NOD, dur, channelNOFF_NOD }; //to be added to the dictionary of lists of lists if (!(NONdata_NOD[1] == 0)) { eventsAtTicksDict = addToDictHandleCollisions(absNON_NOD, eventdata, eventsAtTicksDict); } //pretend nothing happened. To be clear, in this try block, we are killing the duplicated note and restarting with a new note. noteDurationData.Add(key, data); } break; case 0x8: byte[] byteHoldNOFF = new byte[4]; byteHoldNOFF = midievent.MidiMessage.GetBytes(); int channelNOFF = byteHoldNOFF[0] & 0x0F;//bitwise and to get last 4 bits int pitchNOFF = byteHoldNOFF[1]; int absNOFF = midievent.AbsoluteTicks; Tuple<int, int> keyNOFF = new Tuple<int, int>(channelNOFF, pitchNOFF); if (noteDurationData.ContainsKey(keyNOFF)) { int[] NONdata = noteDurationData[keyNOFF]; noteDurationData.Remove(keyNOFF); int absNON = NONdata[0]; int velNON = NONdata[1]; int dur = absNOFF - absNON; int instr = -1; //CALCULATE current instrument for (int i = 0; i < instrumentsAtTicks.GetLength(0); ++i) { if (instrumentsAtTicks[i, 16] > absNON) { instr = instrumentsAtTicks[i - 1, channelNOFF]; //this makes sense. finding a bigger time then backtracking should never fail } else if (i == instrumentsAtTicks.GetLength(0) - 1) { instr = instrumentsAtTicks[i, channelNOFF]; //added this case because I was wrong in the previous comment. } } int[] eventdata = new int[5] { instr, pitchNOFF, velNON, dur, channelNOFF }; eventsAtTicksDict = addToDictHandleCollisions(absNON, eventdata, eventsAtTicksDict); } break; default: break; } } return eventsAtTicksDict; }
static void Main(string[] args) { bool outputMidiVelocity = false; //var filename = "01-v-drac.s3m"; //S3MFile f = S3MFile.Parse(filename); //var tempo = 60000000 / (f.Header.InitialTempo); //byte numerator = 4; //byte denominator = 4; //var speed = 3; //var skip = 0; var filename = "02-v-bewm.s3m"; S3MFile f = S3MFile.Parse(filename); var tempo = 60000000 / (f.Header.InitialTempo); byte numerator = 4; byte denominator = 4; var speed = 3; var skip = 0; outputMidiVelocity = true; /* var filename = "V-OPTION.S3M"; S3MFile f = S3MFile.Parse(filename); var tempo = 60000000 / (f.Header.InitialTempo); byte numerator = 6; byte denominator = 8; var speed = 3; var skip = 4; */ /* var filename = "V-FALCON.S3M"; S3MFile f = S3MFile.Parse(filename); var tempo = 60000000 / (1 * f.Header.InitialTempo); byte numerator = 4; byte denominator = 4; var speed = 3; var skip = 0; */ /* var filename = "V-CONTRA.IT"; S3MFile f = S3MFile.Parse(filename); var tempo = 60000000 / (1 * f.Header.InitialTempo); byte numerator = 4; byte denominator = 4; var speed = 3; var skip = 0; * */ /* var filename = "V-BLAST.S3M"; S3MFile f = S3MFile.Parse(filename); var tempo = 60000000 / (1 * f.Header.InitialTempo); byte numerator = 4; byte denominator = 4; var speed = 3; var skip = 0;*/ Sequence s = new Sequence(); Track t1 = new Track(); Track t2 = new Track(); Track t3 = new Track(); Track drums = new Track(); Track kick = new Track(); Track timeTrack = new Track(); s.Add(timeTrack); s.Add(t1); s.Add(t2); s.Add(t3); //s.Add(drums); //s.Add(kick); var drumPC = new ChannelMessageBuilder(); drumPC.MidiChannel = 09; drumPC.Command = ChannelCommand.ProgramChange; drumPC.Data1 = 0;// 79 + ce.Instrument; drumPC.Build(); drums.Insert(0, drumPC.Result); kick.Insert(0, drumPC.Result); TimeSignatureBuilder tsb = new TimeSignatureBuilder(); tsb.Numerator = numerator; tsb.Denominator = denominator; tsb.ClocksPerMetronomeClick = 24; tsb.ThirtySecondNotesPerQuarterNote = 8; tsb.Build(); timeTrack.Insert(0, tsb.Result); TempoChangeBuilder tcb = new TempoChangeBuilder(); tcb.Tempo = tempo; tcb.Build(); timeTrack.Insert(0, tcb.Result); var outputPatterns = new int[] { 5, 6, 7}; //var c1 = (from p in f.OrderedPatterns // where patterns.Contains(p.PatternNumber) // select p) // .SelectMany(p => p.Channels) // .Where(c => c.ChannelNumber == 1) // .SelectMany(ce => ce.ChannelEvents); //var c2 = (from p in f.OrderedPatterns // where patterns.Contains(p.PatternNumber) // select p) // .SelectMany(p => p.Channels) // .Where(c => c.ChannelNumber == 2) // .SelectMany(ce => ce.ChannelEvents); //var c3 = (from p in f.OrderedPatterns // where patterns.Contains(p.PatternNumber) // select p) // .SelectMany(p => p.Channels) // .Where(c => c.ChannelNumber == 3) // .SelectMany(ce => ce.ChannelEvents); var patterns = from p in f.OrderedPatterns.Skip(skip) /*where outputPatterns.Contains(p.PatternNumber) */select p; //.SelectMany(p => p.Rows); Dictionary<int, TrackInfo> tracks = new Dictionary<int, TrackInfo>(); tracks.Add(1, new TrackInfo(t1)); tracks[1].Channel = 0; tracks.Add(2, new TrackInfo(t2)); tracks[2].Channel = 1; tracks.Add(3, new TrackInfo(t3)); tracks[3].Channel = 2; var di = new TrackInfo(drums); di.Channel = 9; var mapper = new Func<ChannelEvent, int>(ce => { switch (ce.Instrument) { case 6: return 42; case 8: return 35; case 9: return 38; case 10: return 47; default: Debug.Fail(ce.Instrument.ToString()); return 0; } }); di.NoteMapper = mapper; var kickInfo = new TrackInfo(kick); kickInfo.Channel = 9; kickInfo.NoteMapper = mapper; tracks.Add(4, di); tracks.Add(5, kickInfo); var tick = 0; foreach (var pattern in patterns) { bool breakPattern = false; foreach (var row in pattern.Rows) { //if ((row.RowNumber-1) % 16 == 0) //{ // drums.Insert(tick, kickOn); // drums.Insert(tick + speed, kickOff); //} foreach (var ce in row.ChannelEvents) { if (ce == null) { //Console.Out.WriteLine("skip"); continue; } if (ce.Command == 3) { var modulo = row.RowNumber % 32; if (modulo != 0) { // sad to say, not sure why mod 8 but divide by 16 works var m8 = row.RowNumber % 8; if (m8 == 0) { var restore = tsb.Result; tsb.Numerator = (byte)(row.RowNumber / 16); tsb.Build(); var change = tsb.Result; timeTrack.Insert(tick, change); timeTrack.Insert(tick + 3, restore); } } //Debugger.Break(); // pattern break // figure out the time signature of the pattern up to this point // then go back and insert a time signature change message at the beginning // of the pattern // and another one at the end to revert //var restore = tsb.Result; //tsb.Numerator = 2; //tsb.Build(); //var change = tsb.Result; //timeTrack.Insert(rowStartTick, change); //timeTrack.Insert(tick + 3, restore); breakPattern = true; } if (!tracks.ContainsKey(ce.ChannelNumber)) continue; TrackInfo ti = tracks[ce.ChannelNumber]; if (ce.Note == -1 ) { // skip } else if( ce.Note == 0xFF) { // re-use current note, but change instrument //Console.Out.WriteLine("skip"); } else if (ce.Note == 0xFE) //off { if (ti.LastPitch != -1) { NoteOff(tick, ti); } } else { //if (ce.Volume != -1 && ce.Volume < 32) continue; if (ti.LastPitch != -1) { NoteOff(tick, ti); } //p = ProgramChange(ti.Track, tick, p, ce); var delay = 0; if (ce.Command == 19) { if (208 <= ce.Data && ce.Data <= 214) // HACK: 214 is a guess at the top range of SD commands { delay = ce.Data - 208; Debug.Assert(delay >= 0); } } NoteOn(tick + delay, ti, ce); } } tick += speed; if (breakPattern) { break; } } } foreach (var pair in tracks) { if (pair.Value.LastPitch != -1) { NoteOff(tick, pair.Value); } } foreach (MidiEvent m in drums.Iterator().Take(5)) { if (m.MidiMessage is ChannelMessage) { ChannelMessage cm = (ChannelMessage)m.MidiMessage; Console.Out.WriteLine("{0} {1}", m.AbsoluteTicks, cm.Command); } } s.Save(Path.ChangeExtension(filename, ".mid")); /* var tick = 0; var speed = 3; var lastNote = -1; var p = -1; foreach (var ce in c1) { if (ce == null || ce.Note == -1 || ce.Note == 0xFF) { tick += speed; Console.Out.WriteLine("skip"); continue; } else if (ce.Note == 0xFE) //off { lastNote = NoteOff(t, tick, lastNote); } else { if (lastNote != -1) { lastNote = NoteOff(t, tick, lastNote); } p = ProgramChange(t, tick, p, ce); var delay = 0; if (ce.Command == 19) { if (208 <= ce.Data && ce.Data <= 214) // HACK: 214 is a guess at the top range of SD commands { delay = ce.Data - 208; Debug.Assert(delay >= 0); } } lastNote = NoteOn(t, tick + delay, ce.Note); } tick += speed; } lastNote = NoteOff(t, tick, lastNote); * * */ }