static void SimpleStreamingDemo() { // just grab the first output stream using (var stm = MidiDevice.Streams[0]) { // open it stm.Open(); // read a MIDI file var mf = MidiFile //.ReadFrom(@"..\..\Feel_good_4beatsBass.mid"); .ReadFrom(@"..\..\Bohemian-Rhapsody-1.mid"); // > 64kb! //.ReadFrom(@"..\..\A-Warm-Place.mid"); // merge the tracks for playback var seq = MidiSequence.Merge(mf.Tracks); // set the stream timebase stm.TimeBase = mf.TimeBase; // start the playback stm.Start(); Console.Error.WriteLine("Press any key to exit..."); // if we weren't looping // we wouldn't need to // hook this: stm.SendComplete += delegate(object s, EventArgs e) { // loop stm.Send(seq.Events); }; // kick things off stm.Send(seq.Events); // wait for exit Console.ReadKey(); } }
static void TestPlaybackTiming() { var mf = MidiFile.ReadFrom(@"..\..\GORILLAZ_-_Feel_Good_Inc.mid"); var seq = MidiSequence.Merge(mf.Tracks); Console.WriteLine(mf.Duration); var st = _PreciseUtcNowTicks; var et = 0L; using (var stm = MidiDevice.Streams[0]) { stm.SendComplete += delegate(object s, EventArgs ea) { Interlocked.Exchange(ref et, _PreciseUtcNowTicks); }; stm.Open(); stm.TimeBase = mf.TimeBase; stm.Start(); stm.Send(seq.Events); while (0 == et) { Thread.Sleep(1); } } Console.WriteLine(new TimeSpan(et - st)); }
public Main() { MinimumSize = new Size(200, 130); InitializeComponent(); MidiInputComboBox.DisplayMember = "Name"; MidiOutputComboBox.DisplayMember = "Name"; _RefreshDeviceList(); Visualizer.Sequence = MidiSequence.Merge(MidiFile.ReadFrom(@"..\..\GORILLAZ_-_Feel_Good_Inc.mid").Tracks); }
static void SimpleStreamingDemo() { // just grab the first output stream using (var stm = MidiDevice.Streams[0]) { // open it stm.Open(); // read a MIDI file var midiFile = MidiFile //.ReadFrom(@"..\..\Feel_good_4beatsBass.mid"); //.ReadFrom(@"..\..\Bohemian-Rhapsody-1.mid"); // > 64kb! .ReadFrom(@"..\..\A-Warm-Place.mid"); //.ReadFrom(@"..\..\50_cent_-_In_Da_Club.mid"); //.ReadFrom(@"..\..\Beethoven-Moonlight-Sonata.mid"); //.ReadFrom(@"..\..\Peter-Gunn-1.mid"); //.ReadFrom(@"..\..\THE BEASTIE BOYS.Sabotage.mid"); Console.WriteLine(Path.GetFileName(midiFile.FilePath) + " @ " + midiFile.TimeBase + " PQN"); var tsig = midiFile.TimeSignature; Console.WriteLine("Tempo: " + midiFile.Tempo + " BPM @ " + tsig.Numerator + "/" + tsig.Denominator + " time"); Console.WriteLine("Tracks:"); for (var i = 0; i < midiFile.Tracks.Count; ++i) { var track = midiFile.Tracks[i]; Console.Write("\t"); var name = track.Name; if (string.IsNullOrEmpty(name)) { name = "Track " + i; } Console.WriteLine(name + " \tEvents: " + track.Events.Count); } Console.WriteLine(); // merge the tracks for playback var seq = MidiSequence.Merge(midiFile.Tracks); // set the stream timebase stm.TimeBase = midiFile.TimeBase; // start the playback stm.Start(); Console.Error.WriteLine("Press any key to exit..."); // if we weren't looping // we wouldn't need to // hook this: stm.SendComplete += delegate(object s, EventArgs e) { // loop stm.Send(seq.Events); }; // kick things off stm.Send(seq.Events); // wait for exit Console.ReadKey(); } }
private void _UpdateMidiFile() { MidiFile file; try { file = MidiFile.ReadFrom(FileTextBox.Text); } catch { file = null; } if (null == file) { PlayButton.Enabled = false; FileTextBox.ForeColor = Color.Red; Visualizer.Sequence = null; return; } PlayButton.Enabled = true; FileTextBox.ForeColor = SystemColors.WindowText; var seq = MidiSequence.Merge(file.Tracks); Visualizer.Sequence = seq; Visualizer.Width = seq.Length; }
private void SaveAsButton_Click(object sender, EventArgs e) { var res = SaveMidiFile.ShowDialog(this); if (DialogResult.OK == res) { var f = _file; if (ResampleUpDown.Value != _file.TimeBase) { f = f.Resample(unchecked ((short)ResampleUpDown.Value)); } var trks = new List <MidiSequence>(f.Tracks.Count); for (int ic = TrackList.Items.Count, i = 0; i < ic; ++i) { if (TrackList.CheckedItems.Contains(TrackList.Items[i])) { trks.Add(f.Tracks[i]); } } var mf = new MidiFile(1, f.TimeBase); if (!MergeTracksCheckBox.Checked) { foreach (var tr in trks) { mf.Tracks.Add(_ProcessTrack(tr)); } } else { mf.Tracks.Add(_ProcessTrack(MidiSequence.Merge(trks))); } using (var stm = File.OpenWrite(SaveMidiFile.FileName)) { mf.WriteTo(stm); } } }
void _UpdateMidiFile() { var exists = false; try { if (File.Exists(MidiFileBox.Text)) { exists = true; } } catch { } TrackList.Items.Clear(); if (!exists) { TracksLabel.Text = ""; MidiFileBox.ForeColor = Color.Red; _file = null; TrackList.Enabled = false; PreviewButton.Enabled = false; UnitsCombo.Enabled = false; StartCombo.Enabled = false; OffsetUpDown.Enabled = false; LengthUpDown.Enabled = false; StretchUpDown.Enabled = false; MergeTracksCheckBox.Enabled = false; CopyTimingPatchCheckBox.Enabled = false; AdjustTempoCheckBox.Enabled = false; ResampleUpDown.Enabled = false; NormalizeCheckBox.Enabled = false; LevelsUpDown.Enabled = false; TransposeUpDown.Enabled = false; WrapCheckBox.Enabled = false; DrumsCheckBox.Enabled = false; SaveAsButton.Enabled = false; TempoUpDown.Enabled = false; Visualizer.Sequence = null; Visualizer.Size = VisualizerPanel.Size; } else { MidiFileBox.ForeColor = SystemColors.WindowText; using (Stream stm = File.OpenRead(MidiFileBox.Text)) _file = MidiFile.ReadFrom(stm); var i = 0; foreach (var trk in _file.Tracks) { var s = trk.Name; if (string.IsNullOrEmpty(s)) { s = "Track #" + i.ToString(); } TrackList.Items.Add(s, true); ++i; } var sig = _file.TimeSignature; var key = _file.KeySignature; TracksLabel.Text = string.Format(_tracksLabelFormat, sig.Numerator, sig.Denominator, key); TrackList.Enabled = true; PreviewButton.Enabled = true; UnitsCombo.Enabled = true; StartCombo.Enabled = true; OffsetUpDown.Enabled = true; LengthUpDown.Enabled = true; StretchUpDown.Enabled = true; MergeTracksCheckBox.Enabled = true; CopyTimingPatchCheckBox.Enabled = true; AdjustTempoCheckBox.Enabled = true; ResampleUpDown.Enabled = true; NormalizeCheckBox.Enabled = true; LevelsUpDown.Enabled = true; TransposeUpDown.Enabled = true; WrapCheckBox.Enabled = true; DrumsCheckBox.Enabled = true; SaveAsButton.Enabled = true; TempoUpDown.Enabled = true; StretchUpDown.Value = 1; UnitsCombo.SelectedIndex = 0; StartCombo.SelectedIndex = 0; ResampleUpDown.Value = _file.TimeBase; UnitsCombo.SelectedIndex = 0; LengthUpDown.Maximum = _file.Length / (decimal)_file.TimeBase; OffsetUpDown.Maximum = LengthUpDown.Maximum - 1; LengthUpDown.Value = LengthUpDown.Maximum; OffsetUpDown.Value = 0; AdjustTempoCheckBox.Checked = false; MergeTracksCheckBox.Checked = false; NormalizeCheckBox.Checked = false; CopyTimingPatchCheckBox.Checked = true; LevelsUpDown.Value = 1; TransposeUpDown.Value = 0; WrapCheckBox.Checked = false; DrumsCheckBox.Checked = false; TempoUpDown.Value = (decimal)_file.Tempo; _dirty = true; _processedFile = null; Visualizer.Sequence = MidiSequence.Merge(_file.Tracks); Visualizer.Width = Math.Max(VisualizerPanel.Width, Visualizer.Sequence.Length / 4); } }
MidiFile _ProcessFile() { // first we clone the file to be safe // that way in case there's no modifications // specified in the UI we'll still return // a copy. var result = _file.Clone(); // transpose it if specified if (0 != TransposeUpDown.Value) { result = result.Transpose((sbyte)TransposeUpDown.Value, WrapCheckBox.Checked, !DrumsCheckBox.Checked); } // resample if specified if (ResampleUpDown.Value != _file.TimeBase) { result = result.Resample(unchecked ((short)ResampleUpDown.Value)); } // compute our offset and length in ticks or beats/quarter-notes var ofs = OffsetUpDown.Value; var len = LengthUpDown.Value; if (0 == UnitsCombo.SelectedIndex) // beats { len = Math.Min(len * _file.TimeBase, _file.Length); ofs = Math.Min(ofs * _file.TimeBase, _file.Length); } switch (StartCombo.SelectedIndex) { case 1: ofs += result.FirstDownBeat; break; case 2: ofs += result.FirstNoteOn; break; } // nseq holds our patch and timing info var nseq = new MidiSequence(); if (0 != ofs && CopyTimingPatchCheckBox.Checked) { // we only want to scan until the // first note on // we need to check all tracks so // we merge them into mtrk and scan // that var mtrk = MidiSequence.Merge(result.Tracks); var end = mtrk.FirstNoteOn; if (0 == end) // break later: { end = mtrk.Length; } var ins = 0; for (int ic = mtrk.Events.Count, i = 0; i < ic; ++i) { var ev = mtrk.Events[i]; if (ev.Position >= end) { break; } var m = ev.Message; switch (m.Status) { // the reason we don't check for MidiMessageMetaTempo // is a user might have specified MidiMessageMeta for // it instead. we want to handle both case 0xFF: var mm = m as MidiMessageMeta; switch (mm.Data1) { case 0x51: // tempo case 0x54: // smpte if (0 == nseq.Events.Count) { nseq.Events.Add(new MidiEvent(0, ev.Message.Clone())); } else { nseq.Events.Insert(ins, new MidiEvent(0, ev.Message.Clone())); } ++ins; break; } break; default: // check if it's a patch change if (0xC0 == (ev.Message.Status & 0xF0)) { if (0 == nseq.Events.Count) { nseq.Events.Add(new MidiEvent(0, ev.Message.Clone())); } else { nseq.Events.Insert(ins, new MidiEvent(0, ev.Message.Clone())); } // increment the insert count ++ins; } break; } } // set the track to the loop length nseq.Events.Add(new MidiEvent((int)len, new MidiMessageMetaEndOfTrack())); } // see if track 0 is checked var hasTrack0 = TrackList.GetItemChecked(0); // slice our loop out of it if (0 != ofs || result.Length != len) { result = result.GetRange((int)ofs, (int)len, CopyTimingPatchCheckBox.Checked, false); } // normalize it! if (NormalizeCheckBox.Checked) { result = result.NormalizeVelocities(); } // scale levels if (1m != LevelsUpDown.Value) { result = result.ScaleVelocities((double)LevelsUpDown.Value); } // create a temporary copy of our // track list var l = new List <MidiSequence>(result.Tracks); // now clear the result result.Tracks.Clear(); for (int ic = l.Count, i = 0; i < ic; ++i) { // if the track is checked in the list // add it back to result if (TrackList.GetItemChecked(i)) { result.Tracks.Add(l[i]); } } if (0 < nseq.Events.Count) { // if we don't have track zero we insert // one. if (!hasTrack0) { result.Tracks.Insert(0, nseq); } else { // otherwise we merge with track 0 result.Tracks[0] = MidiSequence.Merge(nseq, result.Tracks[0]); } } // next adjust the tempo if (_file.Tempo != (double)TempoUpDown.Value) { result = result.AdjustTempo((double)TempoUpDown.Value); } // stretch the result. we do this // here so the track lengths are // correct and we don't need ofs // or len anymore if (1m != StretchUpDown.Value) { result = result.Stretch((double)StretchUpDown.Value, AdjustTempoCheckBox.Checked); } // if merge is checked merge the // tracks if (MergeTracksCheckBox.Checked) { var trk = MidiSequence.Merge(result.Tracks); result.Tracks.Clear(); result.Tracks.Add(trk); } _dirty = false; _reseekDirty = false; return(result); }
private void PreviewButton_Click(object sender, EventArgs e) { if ("Stop" == PreviewButton.Text) { if (null != _play) { _play.Close(); } MidiFileBox.Enabled = true; BrowseButton.Enabled = true; OutputComboBox.Enabled = true; PreviewButton.Text = "Preview"; return; } if (null != _play) { _play.Close(); } MidiFileBox.Enabled = false; BrowseButton.Enabled = false; OutputComboBox.Enabled = false; PreviewButton.Text = "Stop"; if (_dirty) { _processedFile = _ProcessFile(); } var mf = _processedFile; _play = (OutputComboBox.SelectedItem as MidiOutputDevice).Stream; var stm = _play; // we use 100 events, which should be safe and allow // for some measure of SYSEX messages in the stream // without bypassing the 64kb limit const int MAX_EVENT_COUNT = 100; const int RATE_TICKS = 500; // our current cursor pos var pos = 0; // for tracking deltas var ofs = 0; // for tracking the song position var songPos = 0; // merge our file for playback var seq = MidiSequence.Merge(mf.Tracks); var events = seq.Events; // the number of events in the seq var len = events.Count; // stores the next set of events var eventList = new List <MidiEvent>(MAX_EVENT_COUNT); // open the stream stm.Open(); // start it stm.Start(); // first set the timebase stm.TimeBase = mf.TimeBase; // set up our send complete handler stm.SendComplete += delegate(object s, EventArgs ea) { try { BeginInvoke(new Action(delegate() { // clear the list eventList.Clear(); mf = _processedFile; if (_dirty) { if (_reseekDirty) { var time = _processedFile.Tracks[0].GetContext(songPos, _processedFile.TimeBase).Time; _processedFile = _ProcessFile(); songPos = _processedFile.Tracks[0].GetPositionAtTime(time, _processedFile.TimeBase); mf = _processedFile; seq = MidiSequence.Merge(mf.Tracks); events = new List <MidiEvent>(seq.GetNextEventsAtPosition(songPos, true)); len = events.Count; pos = 0; } else { _processedFile = _ProcessFile(); mf = _processedFile; seq = MidiSequence.Merge(mf.Tracks); events = seq.Events; } Visualizer.Sequence = seq; Visualizer.Width = Math.Max(VisualizerPanel.Width, Visualizer.Sequence.Length / 4); } ofs = 0; len = events.Count; // iterate through the next events var next = pos + MAX_EVENT_COUNT; for (; pos < next && ofs <= RATE_TICKS; ++pos) { // if it's past the end, loop it if (len <= pos) { pos = 0; songPos = 0; events = seq.Events; break; } var ev = events[pos]; ofs += ev.Position; songPos += pos; if (ev.Position < RATE_TICKS && RATE_TICKS < ofs) { break; } // otherwise add the next event eventList.Add(ev); } // send the list of events if (MidiStreamState.Closed != stm.State && 0 != eventList.Count) { stm.SendDirect(eventList); } })); } catch { } }; // add the first events for (pos = 0; pos < MAX_EVENT_COUNT && ofs <= RATE_TICKS; ++pos) { // if it's past the end, loop it if (len <= pos) { pos = 0; songPos = 0; events = seq.Events; break; } var ev = events[pos]; ofs += ev.Position; if (ev.Position < RATE_TICKS && RATE_TICKS < ofs) { break; } // otherwise add the next event eventList.Add(ev); } // send the list of events if (0 != eventList.Count) { stm.SendDirect(eventList); } }
static void ComplexRecordingDemo() { using (var idev = MidiDevice.Inputs[0]) { // TODO: currently this doesn't let you // change the tempo in the middle of recording // match these two variables to your input rate short timeBase = 480; var microTempo = MidiUtility.TempoToMicroTempo(120); // track 0 - meta track for tempo info var tr0 = new MidiSequence(); // our seq for recording var seq = new MidiSequence(); // compute our timing based on current microTempo and timeBase var ticksusec = microTempo / (double)timeBase; var tickspertick = ticksusec / (TimeSpan.TicksPerMillisecond / 1000) * 100; var pos = 0; // set this to _PreciseUtcNowTicks in order // to start recording now. Otherwise it will // not record until the first message is // recieved: var startTicks = 0L; using (var odev = MidiDevice.Outputs[0]) { // hook up the delegate idev.Input += delegate(object s, MidiInputEventArgs ea) { // initialize start ticks with the current time in ticks if (0 == startTicks) { startTicks = _PreciseUtcNowTicks; } // compute our current MIDI ticks var midiTicks = (int)Math.Round((_PreciseUtcNowTicks - startTicks) / tickspertick); // pass through to play odev.Send(ea.Message); // HACK: technically the sequence isn't threadsafe but as long as this event // is not reentrant and the MidiSequence isn't touched outside this it should // be fine seq.Events.Add(new MidiEvent(midiTicks - pos, ea.Message)); // this is to track our old position // so we can compute deltas pos = midiTicks; }; // open the input device idev.Open(); // open the output device odev.Open(); // add our tempo to the beginning of track 0 tr0.Events.Add(new MidiEvent(0, new MidiMessageMetaTempo(microTempo))); // start listening idev.Start(); Console.Error.WriteLine("Recording started."); // wait Console.Error.WriteLine("Press any key to stop recording..."); Console.ReadKey(); // stop the buffer and flush any pending events idev.Stop(); idev.Reset(); } // create termination track var endTrack = new MidiSequence(); var len = seq.Length; // comment the following to terminate // without the trailing empty score: len = unchecked ((int)((_PreciseUtcNowTicks - startTicks) / tickspertick)); endTrack.Events.Add(new MidiEvent(len, new MidiMessageMetaEndOfTrack())); // terminate the tracks tr0 = MidiSequence.Merge(tr0, endTrack); seq = MidiSequence.Merge(seq, endTrack); // build a type 1 midi file var mf = new MidiFile(1, timeBase); // add both tracks mf.Tracks.Add(tr0); mf.Tracks.Add(seq); // now stream the file to the output // just grab the first output stream using (var stm = MidiDevice.Streams[0]) { // open it stm.Open(); // merge the tracks for playback seq = MidiSequence.Merge(mf.Tracks); // set the stream timebase stm.TimeBase = mf.TimeBase; // start the playback stm.Start(); Console.Error.WriteLine("Press any key to exit..."); // if we weren't looping // we wouldn't need to // hook this: stm.SendComplete += delegate(object s, EventArgs e) { // loop stm.Send(seq.Events); }; // kick things off stm.Send(seq.Events); // wait for exit Console.ReadKey(); } } }
static void SimpleRecordingDemo() { MidiFile mf; using (var idev = MidiDevice.Inputs[0]) { using (var odev = MidiDevice.Outputs[0]) { idev.Input += delegate(object s, MidiInputEventArgs e) { // this is so we can pass through and hear // our input while recording odev.Send(e.Message); }; idev.TempoChanged += delegate(object s, EventArgs e) { Console.WriteLine("New Tempo: " + idev.Tempo); }; // open the input // and output idev.Open(); // set our timebase idev.TimeBase = 384; odev.Open(); idev.Start(); // start recording, waiting for input idev.StartRecording(true); // wait to end it Console.Error.WriteLine("Press any key to stop recording..."); Console.ReadKey(); // get our MidiFile from this mf = idev.EndRecording(); // the MIDI file is always two // tracks, with the first track // being the tempo map } } if (null == mf) { return; } // merge for playback var seq = MidiSequence.Merge(mf.Tracks); // now stream the file to the output // just grab the first output stream using (var stm = MidiDevice.Streams[0]) { // open it stm.Open(); // merge the tracks for playback seq = MidiSequence.Merge(mf.Tracks); // set the stream timebase stm.TimeBase = mf.TimeBase; // start the playback stm.Start(); Console.Error.WriteLine("Press any key to exit..."); // if we weren't looping // we wouldn't need to // hook this: stm.SendComplete += delegate(object s, EventArgs e) { // loop stm.Send(seq.Events); }; // kick things off stm.Send(seq.Events); // wait for exit Console.ReadKey(); } }
static void ComplexStreamingDemo() { // demonstrate streaming a midi file 100 events at a time // this allows you to handle files with more than 64kb // of in-memory events (not the same as "on disk" size) // this replays the events in a loop var midiFile = MidiFile //.ReadFrom(@"..\..\Bohemian-Rhapsody-1.mid"); // > 64kb! .ReadFrom(@"..\..\A-Warm-Place.mid"); //.ReadFrom(@"..\..\GORILLAZ_-_Feel_Good_Inc.mid"); //.ReadFrom(@"..\..\Feel_good_4beatsBass.mid"); //.ReadFrom(@"..\..\THE BEASTIE BOYS.Sabotage.mid"); //.ReadFrom(@"..\..\Peter-Gunn-1.mid"); Console.WriteLine(Path.GetFileName(midiFile.FilePath) + " @ " + midiFile.TimeBase + " PQN"); var tsig = midiFile.TimeSignature; Console.WriteLine("Tempo: " + midiFile.Tempo + " BPM @ " + tsig.Numerator + "/" + tsig.Denominator + " time"); Console.WriteLine("Tracks:"); for (var i = 0; i < midiFile.Tracks.Count; ++i) { var track = midiFile.Tracks[i]; Console.Write("\t"); var name = track.Name; if (string.IsNullOrEmpty(name)) { name = "Track " + i; } Console.WriteLine(name + " \tEvents: " + track.Events.Count); } Console.WriteLine(); // we use 100 events, which should be safe and allow // for some measure of SYSEX messages in the stream // without bypassing the 64kb limit const int EVENT_COUNT = 100; // our current cursor pos int pos = 0; // merge our file for playback var seq = MidiSequence.Merge(midiFile.Tracks); // the number of events in the seq int len = seq.Events.Count; // stores the next set of events var eventList = new List <MidiEvent>(EVENT_COUNT); // just grab the first output stream // should be the wavetable synth using (var stm = MidiDevice.Streams[0]) { // open the stream stm.Open(); // start it stm.Start(); // first set the timebase stm.TimeBase = midiFile.TimeBase; // set up our send complete handler stm.SendComplete += delegate(object sender, EventArgs eargs) { // clear the list eventList.Clear(); // iterate through the next events var next = pos + EVENT_COUNT; for (; pos < next; ++pos) { // if it's past the end, loop it if (len <= pos) { pos = 0; break; } // otherwise add the next event eventList.Add(seq.Events[pos]); } // send the list of events stm.SendDirect(eventList); }; // add the first events for (pos = 0; pos < EVENT_COUNT; ++pos) { // if it's past the end, loop it if (len <= pos) { pos = 0; break; } // otherwise add the next event eventList.Add(seq.Events[pos]); } // send the list of events stm.SendDirect(eventList); // loop until a key is pressed Console.Error.WriteLine("Press any key to exit..."); Console.ReadKey(); // close the stream stm.Close(); } }
private void PlayButton_Click(object sender, EventArgs e) { if ("Stop" == PlayButton.Text) { PlayButton.Text = "Play"; if (null != _outputStream) { _outputStream.Close(); } MidiOutComboBox.Enabled = true; FileTextBox.Enabled = true; FileBrowseButton.Enabled = true; Visualizer.ShowCursor = false; return; } PlayButton.Text = "Stop"; Visualizer.CursorPosition = 0; Visualizer.ShowCursor = true; MidiOutComboBox.Enabled = false; FileTextBox.Enabled = false; FileBrowseButton.Enabled = false; var mf = MidiFile.ReadFrom(FileTextBox.Text); if (null != _outputStream) { // BUG: For some reason recycling the output stream // screws up playback on successive uses. I have had // no luck tracking down why so far. The following // causes the MidiStream class to be recreated // instead of recycled var stm = _outputStream = MidiDevice.Streams[_outputStream.Index]; // we use 100 events, which should be safe and allow // for some measure of SYSEX messages in the stream // without bypassing the 64kb limit const int MAX_EVENT_COUNT = 100; // the lower this is, the more more CPU it takes. // the higher it is, the less accurate the cursor // position will be: const int RATE_TICKS = 10; // our current cursor pos var pos = 0; // for tracking deltas var ofs = 0; // for tracking the song position var songPos = 0; var songTicks = 0; // merge our file for playback var seq = MidiSequence.Merge(mf.Tracks); var events = seq.Events; // the number of events in the seq var len = events.Count; // stores the next set of events var eventList = new List <MidiEvent>(MAX_EVENT_COUNT); // open the stream stm.Open(); if (MidiOutputDeviceVolumeSupport.None != stm.VolumeSupport) { stm.Volume = new MidiVolume((byte)VolumeKnob.Value, (byte)VolumeKnob.Value); } // start it stm.Start(); // first set the timebase stm.TimeBase = mf.TimeBase; // set up our send complete handler stm.SendComplete += delegate(object s, EventArgs ea) { try { BeginInvoke(new Action(delegate() { // clear the list eventList.Clear(); ofs = 0; len = events.Count; // iterate through the next events var next = pos + MAX_EVENT_COUNT; for (; pos < next && ofs <= RATE_TICKS; ++pos) { // if it's past the end, loop it if (len <= pos) { pos = 0; songPos = 0; songTicks = 0; events = seq.Events; break; } var ev = events[pos]; ofs += ev.Position; songTicks += ev.Position; songPos += pos; if (ev.Position < RATE_TICKS && RATE_TICKS < ofs) { break; } // otherwise add the next event eventList.Add(ev); } // send the list of events if (MidiStreamState.Closed != stm.State && 0 != eventList.Count) { stm.SendDirect(eventList); } Visualizer.CursorPosition = songTicks; })); } catch { } }; // add the first events for (pos = 0; pos < MAX_EVENT_COUNT && ofs <= RATE_TICKS; ++pos) { // if it's past the end, loop it if (len <= pos) { pos = 0; songPos = 0; songTicks = 0; events = seq.Events; break; } var ev = events[pos]; ofs += ev.Position; songTicks += ev.Position; if (ev.Position < RATE_TICKS && RATE_TICKS < ofs) { break; } // otherwise add the next event eventList.Add(ev); } Visualizer.CursorPosition = songTicks; // send the list of events if (0 != eventList.Count) { stm.SendDirect(eventList); } } }
private void PlayButton_Click(object sender, EventArgs e) { // we use 100 events, which should be safe and allow // for some measure of SYSEX messages in the stream // without bypassing the 64kb limit const int MAX_EVENT_COUNT = 100; const int RATE_TICKS = 500; if ("Stop" == PlayButton.Text) { if (null != _play) // sanity check { _play.Close(); } PlayButton.Text = "Play"; PatternComboBox.Enabled = true; BarsUpDown.Enabled = true; OutputComboBox.Enabled = true; return; } PatternComboBox.Enabled = false; BarsUpDown.Enabled = false; OutputComboBox.Enabled = false; PlayButton.Text = "Stop"; _play = (OutputComboBox.SelectedItem as MidiOutputDevice).Stream; var mf = _CreateMidiFile(); var stm = _play; // our current cursor pos int pos = 0; // for tracking deltas var ofs = 0; // merge our file for playback var seq = MidiSequence.Merge(mf.Tracks); // the number of events in the seq int len = seq.Events.Count; // stores the next set of events var eventList = new List <MidiEvent>(MAX_EVENT_COUNT); // open the stream stm.Open(); // start it stm.Start(); // first set the timebase stm.TimeBase = mf.TimeBase; // set up our send complete handler stm.SendComplete += delegate(object s, EventArgs ea) { try { BeginInvoke(new Action(delegate() { // clear the list eventList.Clear(); mf = _CreateMidiFile(); seq = MidiSequence.Merge(mf.Tracks); ofs = 0; len = seq.Events.Count; // iterate through the next events var next = pos + MAX_EVENT_COUNT; for (; pos < next && ofs <= RATE_TICKS; ++pos) { // if it's past the end, loop it if (len <= pos) { pos = 0; break; } var ev = seq.Events[pos]; ofs += ev.Position; if (ev.Position < RATE_TICKS && RATE_TICKS < ofs) { break; } // otherwise add the next event eventList.Add(ev); } // send the list of events if (MidiStreamState.Closed != stm.State) { stm.SendDirect(eventList); } })); } catch { } }; // add the first events for (pos = 0; pos < MAX_EVENT_COUNT && ofs <= RATE_TICKS; ++pos) { // if it's past the end, loop it if (len <= pos) { pos = 0; break; } var ev = seq.Events[pos]; ofs += ev.Position; if (ev.Position < RATE_TICKS && RATE_TICKS < ofs) { break; } // otherwise add the next event eventList.Add(ev); } // send the list of events stm.SendDirect(eventList); }
MidiFile _CreateMidiFile() { var file = new MidiFile(); // we'll need a track 0 for our tempo map var track0 = new MidiSequence(); // set the tempo at the first position track0.Events.Add(new MidiEvent(0, new MidiMessageMetaTempo((double)TempoUpDown.Value))); // compute the length of our loop var len = ((int)BarsUpDown.Value) * 4 * file.TimeBase; // add an end of track marker just so all // of our tracks will be the loop length track0.Events.Add(new MidiEvent(len, new MidiMessageMetaEndOfTrack())); // here we need a track end with an // absolute position for the MIDI end // of track meta message. We'll use this // later to set the length of the track var trackEnd = new MidiSequence(); trackEnd.Events.Add(new MidiEvent(len, new MidiMessageMetaEndOfTrack())); // add track 0 (our tempo map) file.Tracks.Add(track0); // create track 1 (our drum track) var track1 = new MidiSequence(); // we're going to create a new sequence for // each one of the drum sequencer tracks in // the UI var trks = new List <MidiSequence>(BeatsPanel.Controls.Count); foreach (var ctl in BeatsPanel.Controls) { var beat = ctl as BeatControl; // get the note for the drum var note = beat.NoteId; // it's easier to use a note map // to build the drum sequence var noteMap = new List <MidiNote>(); for (int ic = beat.Steps.Count, i = 0; i < ic; ++i) { // if the step is pressed create // a note for it if (beat.Steps[i]) { noteMap.Add(new MidiNote(i * (file.TimeBase / 4), 9, note, 127, file.TimeBase / 4 - 1)); } } // convert the note map to a sequence // and add it to our working tracks trks.Add(MidiSequence.FromNoteMap(noteMap)); } // now we merge the sequences into one var t = MidiSequence.Merge(trks); // we merge everything down to track 1 track1 = MidiSequence.Merge(track1, t, trackEnd); // .. and add it to the file file.Tracks.Add(track1); return(file); }
private void PreviewButton_Click(object sender, EventArgs e) { if ("Stop" == PreviewButton.Text) { if (null != _previewThread) { _previewThread.Abort(); _previewThread.Join(); _previewThread = null; } PreviewButton.Text = "Preview"; return; } var f = _file; if (ResampleUpDown.Value != _file.TimeBase) { f = _file.Resample(unchecked ((short)ResampleUpDown.Value)); } var trks = new List <MidiSequence>(f.Tracks.Count); for (int ic = TrackList.Items.Count, i = 0; i < ic; ++i) { if (TrackList.CheckedItems.Contains(TrackList.Items[i])) { trks.Add(f.Tracks[i]); } } var trk = MidiSequence.Merge(trks); var ofs = (int)OffsetUpDown.Value; var len = (int)LengthUpDown.Value; if (0 == UnitsCombo.SelectedIndex) // beats { len = (int)Math.Min(Math.Ceiling(len * (decimal)f.TimeBase), f.Length); ofs = (int)Math.Min(Math.Ceiling(ofs * (decimal)f.TimeBase), f.Length); } switch (StartCombo.SelectedIndex) { case 1: ofs += f.FirstDownBeat; break; case 2: ofs += f.FirstNoteOn; break; } if (0 != ofs && CopyTimingPatchCheckBox.Checked) { var end = trk.FirstNoteOn; if (0 == end) { end = trk.Length; } var trk2 = trk.GetRange(ofs, len); var ins = 0; for (int ic = trk.Events.Count, i = 0; i < ic; ++i) { var ev = trk.Events[i]; if (ev.Position >= end) { break; } var m = ev.Message; switch (m.Status) { case 0xFF: var mm = m as MidiMessageMeta; switch (mm.Data1) { case 0x51: case 0x54: trk2.Events.Insert(ins, ev.Clone()); ++ins; break; } break; default: if (0xC0 == (ev.Message.Status & 0xF0)) { trk2.Events.Insert(ins, ev.Clone()); ++ins; } break; } } trk = trk2; } else { if (trk.Length != len || 0 != ofs) { trk = trk.GetRange(ofs, len); } } if (1m != StretchUpDown.Value) { trk = trk.Stretch((double)StretchUpDown.Value, AdjustTempoCheckBox.Checked); } if (null != _previewThread) { _previewThread.Abort(); _previewThread.Join(); _previewThread = null; } PreviewButton.Text = "Stop"; _previewThread = new Thread(() => { trk.Preview(f.TimeBase, 0, true); }); _previewThread.Start(); }