{"id":3043,"date":"2009-03-01T13:00:00","date_gmt":"2009-03-01T13:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/vbteam\/2009\/03\/01\/veni-midi-vici-generating-a-simple-midi-file-using-vb-part-2-matt-gertz\/"},"modified":"2024-07-05T13:31:52","modified_gmt":"2024-07-05T20:31:52","slug":"veni-midi-vici-generating-a-simple-midi-file-using-vb-part-2-matt-gertz","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/vbteam\/veni-midi-vici-generating-a-simple-midi-file-using-vb-part-2-matt-gertz\/","title":{"rendered":"Veni, MIDI, Vici: Generating a simple MIDI file using VB, part 2 (Matt Gertz)"},"content":{"rendered":"<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">In part 1 of this series, I constructed a pair of classes to supporting persisting MIDI data to files.<span>&nbsp; <\/span>In this entry, I\u2019ll now leverage that code to support an (admittedly limited) music editor experience.<\/font><\/p>\n<div>\n<p class=\"MsoNormal\"><font face=\"Calibri\"><font size=\"3\"><b>Caveat<\/b>:<span>&nbsp; <\/span>As I mentioned in the first post, I\u2019m sure many readers will be far more knowledgeable about MIDI than I am, and will justifiably roll their eyes at this code due to its limited scope.<span>&nbsp; <\/span>I will only be generating simple type-1 MIDI files that only use the Note On and Note Off events, and which don\u2019t use metadata, targeting a \u201cSave\u201d scenario only.<span>&nbsp; <\/span>The idea of this exercise was to understand MIDI and how to code against it in VB, not to generate a fully-functional music generation program.<span>&nbsp; <\/span>Coding up \u201cLoad\u201d and supporting more of the MIDI functionality would be a more time-consuming process which maybe I\u2019ll get to one day.<\/font><\/font><\/p>\n<\/div>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">I would love to be able to write a full-fledged editor with all of the music printed out, all WYSIWYG, but unfortunately I have a day job which would be impacted and so I\u2019m opting for the Commodore-128 experience I mentioned in the previous post.<span>&nbsp; <\/span>Basically, I will (arbitrarily) support four tracks of music, each track containing notes, and each note derived from the following format:<\/font><\/p>\n<p class=\"MsoNormal\"><font face=\"Calibri\"><font size=\"3\"><b>d<\/b>T<i>v<\/i><\/font><\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Where:<\/font><\/p>\n<p class=\"MsoListParagraphCxSpFirst\"><span><span><font size=\"3\">\u00b7<\/font><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><\/span><\/span><font face=\"Calibri\"><font size=\"3\"><b>d<\/b> is the duration of the note (a floating-point value; i.e., since 4\/4 time is assumed, 1.0 would be a quarter note, 1.5 would be a dotted quarter note, 0.5 an eighth note, etc.)<\/font><\/font><\/p>\n<p class=\"MsoListParagraphCxSpMiddle\"><span><span><font size=\"3\">\u00b7<\/font><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><\/span><\/span><font size=\"3\" face=\"Calibri\">T is the tone of the note (i.e., anything from &#8221;&#8221;&#8217;C through G&#8221;&#8221;&#8221; or R (for rest))<\/font><\/p>\n<p class=\"MsoListParagraphCxSpLast\"><span><span><font size=\"3\">\u00b7<\/font><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><\/span><\/span><font size=\"3\"><font face=\"Calibri\"><i>v<\/i> is the volume from 0 to 127 (i.e., &amp;H0 through &amp;HFF), which is never used for a rest.<\/font><\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">So, in my design, a track which played a whole-note (4 beats) middle C, rested for a half-note (2 beats), and then dropped down to &#8216;G for dotted quarter-note (1.5 beats), all at a volume of 60, would look like this:<\/font><\/p>\n<p class=\"MsoNormal\"><b><font size=\"3\"><font face=\"Calibri\">4.0C60, 2.0R, 1.5&#8217;G60<\/font><\/font><\/b><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">That\u2019s not nearly as nice to look at as the actual musical score, but it will allow me to give an example of parsing text data, so on we go\u2026<\/font><\/p>\n<h2><font color=\"#4f81bd\" size=\"4\" face=\"Cambria\">The form<\/font><\/h2>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\">On my Windows application, I have four combo boxes in a vertical pattern, with a long text box adjacent to each.<span>&nbsp; <\/span>The combos\u2019 DropDownStyle properties are all set to DropDownList, and the Items property for each is preset to be the numbers 1 through 15 (these numbers indicate MIDI channels), plus \u201cblank\u201d (a simple carriage return) \u2013 blank is the default value (i.e., unused track).<span>&nbsp; <\/span>The text boxes (which will contain the \u201c<b>d<\/b>T<i>v<\/i>\u201d notes) all start out as ReadOnly = True, and switch to editable only if the corresponding combo channel is non-blank.<span>&nbsp; <\/span>I also have a \u201cSave\u201d button to generate the MIDI output, and I added a SaveDialog object as well.<span>&nbsp; <\/span>That\u2019s pretty much the extent of the form.<span>&nbsp; <\/span><\/font><\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Back in the code, I\u2019ll add an instance of the MIDI object I defined in the previous post:<\/font><\/p>\n<p class=\"MsoNormal\"><span>Public<\/span><span> <span>Class<\/span> Form1<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Private<\/span> Song <span>As<\/span> <span>New<\/span> MIDI<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Now, I need to deal with the notes!<\/font><\/p>\n<h2><font color=\"#4f81bd\" size=\"4\" face=\"Cambria\">Specifying the notes that can be used<\/font><\/h2>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">In order to support the parsing of the track information, I\u2019ll need to translate the tone values from characters to the numbers that MIDI expects (of which there are 128).<span>&nbsp; <\/span>The best way to do this is to create a collection mapping the text value to the numeric value, so we can create the mapping once and be done with it:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Private<\/span> Notes <span>As<\/span> <span>New<\/span> Collection<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Const<\/span> NumberOfNotes <span>As<\/span> <span>Integer<\/span> = 128<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Now, I could do something lame like:<\/font><\/p>\n<p class=\"MsoNoSpacing\"><span>Notes.Add(60, <\/span><span>&#8220;<\/span><span>C<\/span><span>&#8220;<\/span><span>)<\/span><\/p>\n<p class=\"MsoNoSpacing\"><span>Notes.Add(61, <\/span><span>&#8220;<\/span><span>C#<\/span><span>&#8220;<\/span><span>)<\/span><\/p>\n<p class=\"MsoNoSpacing\"><span><font size=\"3\">&nbsp;<\/font><\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">And so on, but there are 128 potential values, and some of them have multiple names (for every sharp, there\u2019s a corresponding flat), so that would be a lot of work.<span>&nbsp; <\/span>So, I wrote some helper routines to try to do this in a smarter way.<\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">First, I need to find out how many single quote marks I need to add to the note.<span>&nbsp; <\/span>Each quote preceding the note indicates an octave one lower than the one occupied by middle C; conversely, a quote following a note is indicative of a higher octave.<span>&nbsp; <\/span>(That is, \u2018C indicates a note that is one octave lower than middle C, whereas C\u2019 indicates a note that is one octave higher.)<span>&nbsp; <\/span>There are twelve distinct notes in an octave, including accidentals (flats and sharps), so for a given value, I divide by twelve to determine its octave, and use the remainder (modulus) to determine its position within that octave:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Private<\/span> <span>Sub<\/span> InitializeNotes()<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>For<\/span> i <span>As<\/span> <span>Integer<\/span> = 0 <span>To<\/span> NumberOfNotes &#8211; 1<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> octave <span>As<\/span> <span>Integer<\/span> = i \\ 12<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> tone <span>As<\/span> <span>Integer<\/span> = i <span>Mod<\/span> 12<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Then, for each number, I call a helper function to do the actual work of creating the textual representation of the corresponding note.<span>&nbsp; <\/span>\u201cC\u201d is the first note in MIDI, and so would have a remainder of zero:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Select<\/span> <span>Case<\/span> tone<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Case<\/span> 0<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>AddNote(i, octave, <span>&#8220;C&#8221;<\/span>)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>AddNote(i, octave &#8211; 1, <span>&#8220;B#&#8221;<\/span>) <span>&#8216; A sharped B is technically in the next lower octave<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Case<\/span> 1<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>AddNote(i, octave, <span>&#8220;C#&#8221;<\/span>)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>AddNote(i, octave, <span>&#8220;Db&#8221;<\/span>)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Case<\/span> 2<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>AddNote(i, octave, <span>&#8220;D&#8221;<\/span>)<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">(And so on.)<span>&nbsp; <\/span>Note that I am adding two notes for each accidental (one for its sharp representation, and one for its flat representation), since I don\u2019t want to force the user to use a specific one.<span>&nbsp; <\/span>I also accommodate B# (= C), E# (= F), Fb ( = E), and Cb (= B), though these are rarely used in most music.<span>&nbsp; <\/span>(I don\u2019t currently support double-flats or double-sharps, though it would be easy enough to do \u2013 just add more entries.) <\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">At the end of these 128 additions, I also add an entry for rests directly:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<\/span><span>Case<\/span> 11<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>AddNote(i, octave, <span>&#8220;B&#8221;<\/span>)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>AddNote(i, octave + 1, <span>&#8220;Cb&#8221;<\/span>) <span>&#8216; A flatted C is technically in the next higher octave<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>Select<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Next<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>Notes.Add(NumberOfNotes, <span>&#8220;R&#8221;<\/span>)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>Sub<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">AddNote() is a simple method that determine the quotes (if any) for the note and appends them or prepends them in the right order.<span>&nbsp; <\/span>(The 5<sup>th<\/sup> octave is the middle octave and therefore has no quotes.)<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Private<\/span> <span>Sub<\/span> AddNote(<span>ByVal<\/span> value <span>As<\/span> <span>Integer<\/span>, <span>ByVal<\/span> octave <span>As<\/span> <span>Integer<\/span>, <span>ByVal<\/span> noteName <span>As<\/span> <span>String<\/span>)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> octaveQuotes <span>As<\/span> <span>String<\/span> = GetOctaveQuotes(octave)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> octave &lt; 5 <span>Then<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>Notes.Add(value, octaveQuotes &amp; noteName)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>ElseIf<\/span> octave &gt; 5 <span>Then<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>Notes.Add(value, noteName &amp; octaveQuotes)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Else<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>Notes.Add(value, noteName)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>Sub<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">You\u2019ll note that I use yet another helper function (GetOctaveQuotes) to get the actual number of quotes which are appended or prepended.<span>&nbsp; <\/span>The number of quotes increases as you move away from the middle octave:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Private<\/span> <span>Function<\/span> GetOctaveQuotes(<span>ByVal<\/span> octave <span>As<\/span> <span>Integer<\/span>) <span>As<\/span> <span>String<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> octaveQuotes <span>As<\/span> <span>New<\/span> StringBuilder<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> octave &lt; 5 <span>Then<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>octaveQuotes.Append(<span>&#8220;&#8216;&#8221;<\/span>, 5 &#8211; octave)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Else<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>octaveQuotes.Append(<span>&#8220;&#8216;&#8221;<\/span>, octave &#8211; 5)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Return<\/span> octaveQuotes.ToString<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>Function<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">And now we have everything we need to initialize the notes.<span>&nbsp; <\/span>My form\u2019s Load() event calls that initialization, and also creates the four tracks that we\u2019ll support:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Private<\/span> <span>Sub<\/span> Form1_Load(<span>ByVal<\/span> sender <span>As<\/span> System.Object, <span>ByVal<\/span> e <span>As<\/span> System.EventArgs) <span>Handles<\/span> <span>MyBase<\/span>.Load<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>Song.AddTrack()<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>Song.AddTrack()<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>Song.AddTrack()<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>Song.AddTrack()<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>InitializeNotes()<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>Sub<\/span><\/span><span><\/span><\/p>\n<h2><font color=\"#4f81bd\" size=\"4\" face=\"Cambria\">Form events<\/font><\/h2>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">There are only two interesting events in my code \u2013 changing the channel on a track, and saving the music to a MIDI file.<\/font><\/p>\n<h3><font color=\"#4f81bd\" size=\"3\" face=\"Cambria\">Track changes<\/font><\/h3>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">When the track changes, I want to disable or enable the corresponding text box based on whether or not the channel is blank.<span>&nbsp; <\/span>If it is blank, then I disable the text box and clear its contents; otherwise, I enable it.<span>&nbsp; <\/span>Rather than create a separate event handler for each combo box, I address them all in one handler:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Private<\/span> <span>Sub<\/span> Track1Channel_SelectedIndexChanged(<span>ByVal<\/span> sender <span>As<\/span> System.Object, <span>ByVal<\/span> e <span>As<\/span> System.EventArgs) _<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Handles<\/span> Track1Channel.SelectedIndexChanged, Track2Channel.SelectedIndexChanged, Track3Channel.SelectedIndexChanged, Track4Channel.SelectedIndexChanged<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">By casting the <b>sender<\/b> object to a ComboBox, I\u2019ll be able to determine which combo box sent the event, and which text box corresponds with it.<span>&nbsp; <\/span>(The <b>index<\/b> value will be used later to identify the song\u2019s specific track which corresponds to a given combo\/text set.)<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> combo <span>As<\/span> ComboBox = <span>CType<\/span>(sender, ComboBox)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> text <span>As<\/span> TextBox<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> index <span>As<\/span> <span>Integer<\/span> = 0<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> combo <span>Is<\/span> Track1Channel <span>Then<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>text = Track1<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>index = 0<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>ElseIf<\/span> combo <span>Is<\/span> Track2Channel <span>Then<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>text = Track2<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>index = 1<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>ElseIf<\/span> combo <span>Is<\/span> Track3Channel <span>Then<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>text = Track3<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>index = 2<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Else<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>text = Track4<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>index = 3<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Now, it\u2019s pretty straightforward to the activations or deactivations.<span>&nbsp; <\/span>The \u201cblank\u201d choice is in index 0 in my combobox, so if the selected index is 0, I turn everything off (after a prompt, of course), otherwise, I turn everything on.<span>&nbsp; <\/span>In either case, I update the appropriate track with its new channel:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> combo.SelectedIndex = 0 <span>Then<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> MsgBox(<span>&#8220;Delete all of the track information?&#8221;<\/span>) = MsgBoxResult.Ok <span>Then<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>text.ReadOnly = <span>True<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>text.Text = <span>&#8220;&#8221;<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>Song.Tracks(index).TrackData.Clear()<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>Song.Tracks(index).Channel = -1<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Else<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>&#8216; Change the index back &#8212; Channel must be non-zero<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>combo.SelectedIndex = Song.Tracks(index).Channel &#8211; 1<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Else<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>text.ReadOnly = <span>False<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>Song.Tracks(index).Channel = combo.SelectedIndex &#8211; 1<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Note that I\u2019m cheating a little in that code \u2013 I happen to know that channel 0 is in index 1, channel 1 is in index 2, etc.<span>&nbsp; <\/span>This allows me to take a shortcut and <i>a priori<\/i> know that the channel is simply the selected index minus 1, rather than getting the text from the combobox and converting it to an integer.<\/font><\/p>\n<h3><font color=\"#4f81bd\" size=\"3\" face=\"Cambria\">Saving the music to a MIDI file<\/font><\/h3>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\">In order to save the music the user has specified, I need to translate the text he or she entered into MIDI format bytes and insert them into the tracks.<span>&nbsp; <\/span>I do the bulk of this work via a function called GenerateTrack, which takes a textbox and it\u2019s corresponding track index as arguments, and which returns False if the track generation failed (because of a bad format or because it was not a valid track). Throughout this method, I will throw an exception any time I see a badly formatted music string so I can handle those errors all in one place.<span>&nbsp; <\/span><\/font><\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Private<\/span> <span>Function<\/span> GenerateTrack(<span>ByVal<\/span> trackTextBox <span>As<\/span> TextBox, <span>ByVal<\/span> index <span>As<\/span> <span>Integer<\/span>) <span>As<\/span> <span>Boolean<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">The first thing to do verify that that track is valid (i.e., has a valid channel assigned to it), and clear out any existing track information:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> <span>Not<\/span> Song.Tracks(index).ValidTrack <span>Then<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Return<\/span> <span>False<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>Song.Tracks(index).TrackData.Clear()<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Next, I retrieve the music text and remove any spaces in the text:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> musicText <span>As<\/span> <span>String<\/span> = trackTextBox.Text<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>musicText = musicText.Replace(<span>&#8221; &#8220;<\/span>, <span>&#8220;&#8221;<\/span>) <span>&#8216; Remove whitespace characters<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">I\u2019ve then defined a couple of index counters to allow me to walk through the <b>musicText<\/b>, as well as a Double which will allow me to remember rests (which, in my application, are just delays between two notes and not really a thing in and of themselves):<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> currentIndex <span>As<\/span> <span>Integer<\/span> = 0<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> count <span>As<\/span> <span>Integer<\/span> = 0<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> restBeats <span>As<\/span> <span>Double<\/span> = 0.0<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Now, I loop over the characters in the string to begin parsing them:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>While<\/span> currentIndex &lt; musicText.Length() <\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Step 1 is to get the beats associated with a note.<span>&nbsp; <\/span>I start with the current character and keep stepping until I find something that isn\u2019t a number or a decimal point.<span>&nbsp; <\/span>(And If I find a decimal point, I make sure that I haven\u2019t already found one on this note.)<span>&nbsp; <\/span>If I find no numbers, or too many decimals, or a decimal without a fractional part, I throw an error.<span>&nbsp; <\/span>Otherwise, I convert the number to a true Double.<span>&nbsp; <\/span>During all of this, <b>currentIndex<\/b> points to the presumed beginning of the <b>beats<\/b>, and <b>count<\/b> contains the number of characters to consume in the <b>beats<\/b>:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> beats <span>As<\/span> <span>Double<\/span> = 0.0<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> dot <span>As<\/span> <span>Boolean<\/span> = <span>False<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>While<\/span> (musicText(currentIndex + count) &gt;= <span>&#8220;0&#8221;<\/span> _<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>AndAlso<\/span> musicText(currentIndex + count) &lt;= <span>&#8220;9&#8221;<\/span>) _<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>OrElse<\/span> musicText(currentIndex + count) = <span>&#8220;.&#8221;<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> musicText(currentIndex + count) = <span>&#8220;.&#8221;<\/span> <span>Then<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> dot = <span>True<\/span> <span>Then<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Throw<\/span> <span>New<\/span> ApplicationException(<span>&#8220;Bad beat format in track &#8220;<\/span> &amp; _<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>index.ToString())<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Else<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>dot = <span>True<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>count += 1<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>While<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>&#8216; Now get the beats!<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> count = 0 <span>OrElse<\/span> (count = 1 <span>AndAlso<\/span> dot = <span>True<\/span>) <span>Then<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Throw<\/span> <span>New<\/span> ApplicationException(<span>&#8220;No beats found for a note in track &#8220;<\/span> &amp; _<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>index.ToString())<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>beats = <span>Double<\/span>.Parse(musicText.Substring(currentIndex, count))<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Having gotten this far, I update currentIndex point past the beats and reset count to zero \u2013 time to get the note!<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>currentIndex = currentIndex + count<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>count = 0<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> note <span>As<\/span> <span>Integer<\/span> = 0<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Getting the note is very similar to getting the beats, except in this case I\u2019m paying attention to quotes and accidentals instead of decimal points.<span>&nbsp; <\/span>Quotes can appear on either the left side or the right, but not both, so I need to have a Boolean to indicate if I\u2019ve already found quotes on the left side:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> foundQuotes <span>As<\/span> <span>Boolean<\/span> = <span>False<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>While<\/span> musicText(currentIndex + count) = <span>&#8220;&#8216;&#8221;<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>count += 1<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>foundQuotes = <span>True<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>While<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Next I\u2019ll look for the note. <span>&nbsp;<\/span>Since the note (excluding the # or b) only takes one character, this is an easy check to see if the next character is A through G (notes) or R (a rest):<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> (musicText(currentIndex + count) &lt; <span>&#8220;A&#8221;<\/span> _<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>&nbsp;&nbsp;<\/span>OrElse<\/span><span> musicText(currentIndex + count) &gt; <span>&#8220;G&#8221;<\/span>) _<\/span><\/p>\n<p class=\"MsoNormal\"><span>AndAlso<\/span><span> musicText(currentIndex + count) &lt;&gt; <span>&#8220;R&#8221;<\/span> <span>Then<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Throw<\/span> <span>New<\/span> ApplicationException(<span>&#8220;Unrecognized note in track &#8220;<\/span> &amp; _<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>index.ToString())<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>count += 1<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\">And then I check for a flat or sharp and update the counter if I find one.<span>&nbsp; <\/span><\/font><\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> musicText(currentIndex + count) = <span>&#8220;#&#8221;<\/span> _<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>OrElse<\/span><span> musicText(currentIndex + count) = <span>&#8220;b&#8221;<\/span> <span>Then<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>count += 1<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Now I check again for quote marks, remembering that if I found them on the left side, I\u2019d better not find them on the right side (a note can\u2019t be both higher and lower than the middle octave):<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>While<\/span> musicText(currentIndex + count) = <span>&#8220;&#8216;&#8221;<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> foundQuotes = <span>True<\/span> <span>Then<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Throw<\/span> <span>New<\/span> ApplicationException(<span>&#8220;Quotes on both side of a note in track &#8220;<\/span> &amp; _<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>index.ToString())<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>count += 1<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>While<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Now I can get the note and look it up in my collection, and then update the counters again to grab the volume:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> key <span>As<\/span> <span>String<\/span> = musicText.Substring(currentIndex, count)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>note = Notes(key)<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>currentIndex = currentIndex + count<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>count = 0<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Okay, that\u2019s two down.<span>&nbsp; <\/span>The final thing I need to retrieve from a given \u201c<b>d<\/b>T<i>v\u201d <\/i>triplet is the volume, which is an integer.<span>&nbsp; <\/span>However, rests don\u2019t have volumes, and since I\u2019ve defined a rest as NumberOfNotes, I can just check that value to see if I need to read a volume:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> volume <span>As<\/span> <span>Integer<\/span> = 0<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> note &lt;&gt; NumberOfNotes <span>Then<\/span> <span>&#8216; Rests don&#8217;t have volumes<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">(If the user provided a volume for a rest, I\u2019ll catch that later and correctly throw an error when I try to parse the next note.) <\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Since this is just an integer, the rest of the code is easy \u2013 I keep crawling while the current character is 0 through 9, and if I get to the end and have found nothing, I\u2019ll throw an error.<span>&nbsp; <\/span>Otherwise, I\u2019ll just translate the volume to a true Integer and update the counters for the next thing to find:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>While<\/span> currentIndex + count &lt; musicText.Length() _<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>&nbsp;&nbsp;<\/span><span>AndAlso<\/span> musicText(currentIndex + count) &gt;= <span>&#8220;0&#8221;<\/span> _<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>AndAlso<\/span><span> musicText(currentIndex + count) &lt;= <span>&#8220;9&#8221;<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>count += 1<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>While<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> currentIndex = 0 <span>Then<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Throw<\/span> <span>New<\/span> ApplicationException(<span>&#8220;No volume found for a note in track &#8220;<\/span> &amp; index.ToString())<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>volume = <span>Integer<\/span>.Parse(musicText.Substring(currentIndex, count))<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>currentIndex = currentIndex + count<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>count = 0<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">That\u2019s it for the parsing of that note; now I can update the track with the results.<span>&nbsp; <\/span>There are two cases here: either I have a note, or I have a rest.<span>&nbsp; <\/span>In the case of a rest, all I do is cache the duration of the rest.<span>&nbsp; <\/span>If it\u2019s a note, I turn the note on after any cached rest, turn it off after a certain number of beats, and then clear the rest cache:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> note = NumberOfNotes <span>Then<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>restBeats = beats <span>&#8216; A rest; just have the next note start later<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Else<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>Song.Tracks(index).AddNoteOnOffEvent(restBeats,_<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>MIDI.Track.NoteEvent.NoteOn, <span>CByte<\/span>(note), <span>CByte<\/span>(volume))<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>Song.Tracks(index).AddNoteOnOffEvent(beats, _<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>MIDI.Track.NoteEvent.NoteOff, <span>CByte<\/span>(note), 0)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>restBeats = 0<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">I\u2019ve finished working with the note, so I\u2019ll see if there\u2019s another note by checking the value of <b>currentIndex<\/b> compared to the length of the string.<span>&nbsp; <\/span>If I\u2019m at the end, I can exit the while loop:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> currentIndex &gt;= musicText.Length <span>Then<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Exit<\/span> <span>While<\/span> <span>&#8216; No more notes!<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Otherwise, I\u2019ll verify that the next character is a comma.<span>&nbsp; <\/span>If it is, then I\u2019ll increment appropriately and loop back.<span>&nbsp; <\/span>Otherwise, it\u2019s an error:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> musicText(currentIndex) = <span>&#8220;,&#8221;<\/span> <span>Then<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>currentIndex += 1<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Else<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Throw<\/span> <span>New<\/span> ApplicationException<\/span><span> <\/span><span>(<span>&#8220;Missing comma in track &#8220;<\/span> &amp; index.ToString())<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>While<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Being out of the <b>While<\/b> loop, I need to verify that data got saved.<span>&nbsp; <\/span>If currentIndex is non-zero, then I know that I saved data (because it I ran into trouble later, I would have thrown an error). If I didn\u2019t save data, then I need to return False:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> currentIndex = 0 <span>Then<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Return<\/span> <span>False<\/span> <span>&#8216; Nothing got written out<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">If we\u2019ve gotten this far, then the generation was successful, and we\u2019re done:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Return<\/span> <span>True<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>Function<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Now, finally, I can support the \u201cSave\u201d button.<span>&nbsp; <\/span>I\u2019ll double click on it to generate the handler, and then start adding code.<span>&nbsp; <\/span>First, I need try to generate each track.<span>&nbsp; <\/span>If all of them returned False, then there was nothing to save, and I\u2019ll abort the operation.<span>&nbsp; <\/span>But, since GenerateTrack() can also throw exceptions, I\u2019ll add a Try-Catch structure to catch those.<span>&nbsp; <\/span>The catch handler will display the error to the user, and then abort the save:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Private<\/span> <span>Sub<\/span> Save_Click(<span>ByVal<\/span> sender <span>As<\/span> System.Object, <span>ByVal<\/span> e <span>As<\/span> System.EventArgs) <span>Handles<\/span> Save.Click<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Try<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> GenerateTrack(Track1, 0) = <span>False<\/span> <span>And<\/span> _<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>GenerateTrack(Track2, 1) = <span>False<\/span> <span>And<\/span> _<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>GenerateTrack(Track3, 2) = <span>False<\/span> <span>And<\/span> _<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>GenerateTrack(Track4, 3) = <span>False<\/span> <span>Then<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>MsgBox(<span>&#8220;No tracks had valid data to write out&#8221;<\/span>)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Return<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Catch<\/span> ex <span>As<\/span> Exception<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>MsgBox(<span>&#8220;Error while saving: &#8220;<\/span> &amp; ex.Message.ToString)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Return<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>Try<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">It\u2019s important to use \u201cAnd\u201d for that instead of \u201cAndAlso,\u201d because otherwise you\u2019ll short-circuit tracks and not write them in.<\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Next, I need to pop up the save dialog box to determine where to save the file to.<span>&nbsp; <\/span>I\u2019ll pre-populate this to call the file \u201cUntitled.mid\u201d and have it point to \u201cMy Documents:\u201d<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Me<\/span>.SaveMIDIDialog.DefaultExt = <span>&#8220;MID&#8221;<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Me<\/span>.SaveMIDIDialog.FileName = _<\/span><\/p>\n<p class=\"MsoNormal\"><span>My<\/span><span>.Computer.FileSystem.CombinePath(<span>My<\/span>.Computer.FileSystem.SpecialDirectories.MyDocuments, _<\/span><\/p>\n<p class=\"MsoNormal\"><span>&#8220;Untitled.mid&#8221;<\/span><span>)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Me<\/span>.SaveMIDIDialog.InitialDirectory = _<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>My<\/span><span>.Computer.FileSystem.SpecialDirectories.MyDocuments<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Me<\/span>.SaveMIDIDialog.Filter = <span>&#8220;VB MIDI files (*.MID)|*.MID&#8221;<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Then, assuming that the user didn\u2019t cancel, I just call Save on the MIDI song:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> result <span>As<\/span> DialogResult = <span>Me<\/span>.SaveMIDIDialog.ShowDialog<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> result = DialogResult.OK <span>Then<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>Song.Save(SaveMIDIDialog.FileName)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>Sub<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">And that\u2019s it!<span>&nbsp; <\/span>Launch the app, and you can start inserting music, save it, and then double-click on the resulting file to play it in Windows Media Player (or whatever you\u2019ve got MIDI file set to).<span>&nbsp; <\/span>Here\u2019s a couple samples from Howard Shore\u2019s \u201cThe Fellowship of the Ring\u201d score:<\/font><\/p>\n<p class=\"MsoNoSpacing\"><b><u><font size=\"3\"><font face=\"Calibri\">Sample 1:<\/font><\/font><\/u><\/b><\/p>\n<p class=\"MsoNoSpacing\"><font size=\"3\"><font face=\"Calibri\"><b>First track, channel 0<\/b>: 1B&#8217;60,1.75C&#8221;65, 0.25B&#8217;70, 0.25C&#8221;75, 0.25B&#8217;80, 0.25A&#8217;85, 0.25C&#8221;90, 1.0B&#8217;95, 3.0E&#8217;80<\/font><\/font><\/p>\n<p class=\"MsoNoSpacing\"><font size=\"3\"><font face=\"Calibri\"><b>Second track, channel 1<\/b>: 4.0G60, 4.0A60<\/font><\/font><\/p>\n<p class=\"MsoNoSpacing\"><font size=\"3\"><font face=\"Calibri\"><b>Third track, channel 2<\/b>: 4.0&#8217;D#60, 4.0&#8217;F#60<\/font><\/font><\/p>\n<p class=\"MsoNoSpacing\"><font size=\"3\"><font face=\"Calibri\"><b>Fourth track, channel 3<\/b>: 4.0&#8221;C60, 2.0&#8221;&#8217;B80, 1.0&#8221;C#60, 1.0&#8221;D60<\/font><\/font><\/p>\n<p class=\"MsoNoSpacing\"><font size=\"3\" face=\"Calibri\">&nbsp;<\/font><\/p>\n<p class=\"MsoNoSpacing\"><b><u><font size=\"3\"><font face=\"Calibri\">Sample 2:<\/font><\/font><\/u><\/b><\/p>\n<p class=\"MsoNoSpacing\"><font size=\"3\"><font face=\"Calibri\"><b>First track, channel 0<\/b>: 2.0D60, 1.5C70, 0.25C70,0.25C70, 3.5D70, 0.25G60, 0.25A60, 1.5Bb60, 0.25A70, 0.25G70, 1.5F70, 0.25G70, 0.25A70, 2.0G70, 1.0F65, 1.0E60<\/font><\/font><\/p>\n<p class=\"MsoNoSpacing\"><font size=\"3\"><font face=\"Calibri\"><b>Second track, channel 1<\/b>2.0&#8217;A60, 1.5&#8217;A60, 0.25&#8217;A70,0.25&#8217;A70, 3.5&#8217;A70,0.25&#8217;G60, 0.25&#8217;A60, 2.0&#8217;F60, 2.0&#8217;A70, 2.0&#8217;G70, 1.0&#8217;F65, 1.0&#8217;G60<\/font><\/font><\/p>\n<p class=\"MsoNoSpacing\"><font size=\"3\"><font face=\"Calibri\"><b>Third track, channel 2<\/b>: 2.0&#8217;F#60, 1.5&#8217;F60, 0.25&#8217;F70,0.25&#8217;F70, 4.0&#8217;F#70, 2.0&#8217;D60, 2.0&#8217;F70, 2.0&#8217;D70, 1.0&#8217;D65, 1.0&#8217;E60<\/font><\/font><\/p>\n<p class=\"MsoNoSpacing\"><font size=\"3\"><font face=\"Calibri\"><b>Fourth track, channel 3<\/b>: 2.0&#8221;D60, 1.5&#8221;F60, 0.25&#8221;F70,0.25&#8221;F70,4.0&#8221;D70, 2.0&#8221;Bb60, 2.0&#8221;A70, 2.0&#8221;D70, 1.0&#8221;Bb65, 1.0&#8217;C60<\/font><\/font><\/p>\n<p class=\"MsoNoSpacing\"><font size=\"3\" face=\"Calibri\">&nbsp;<\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\">This is obviously just the tip of the iceberg \u2013 by adding to this application, I could assign different instruments to different tracks (via the \u201cProgramChange\u201d event), persist metadata containing lyrics, and so on.<span>&nbsp; <\/span>(I don\u2019t plan on doing that myself, since for me I simply wanted to learn more about MIDI, but who knows?<span>&nbsp; <\/span>Maybe later&#8230;.)<span>&nbsp; <\/span>The completed code (along with another method which describes setting metadata) is attached to this blog<span> post, and will also be posted on my <\/span><\/font><\/font><a href=\"http:\/\/code.msdn.microsoft.com\/templeofvb\"><span><font size=\"3\" face=\"Calibri\">Temple of VB<\/font><\/span><\/a><span><font size=\"3\" face=\"Calibri\"> site.<\/font><\/span><\/p>\n<p class=\"MsoNormal\"><span><font size=\"3\" face=\"Calibri\">\u2018Til next time,<\/font><\/span><\/p>\n<p class=\"MsoNormal\"><span><font size=\"3\"><font face=\"Calibri\"><span>&nbsp; <\/span>&#8211;Matt&#8211;*<\/font><\/font><\/span><\/p>\n<p><a href=\"https:\/\/msdnshared.blob.core.windows.net\/media\/MSDNBlogsFS\/prod.evol.blogs.msdn.com\/CommunityServer.Components.PostAttachments\/00\/09\/44\/93\/04\/VBMidiEditor.zip\">VBMidiEditor.zip<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>In part 1 of this series, I constructed a pair of classes to supporting persisting MIDI data to files.&nbsp; In this entry, I\u2019ll now leverage that code to support an (admittedly limited) music editor experience. Caveat:&nbsp; As I mentioned in the first post, I\u2019m sure many readers will be far more knowledgeable about MIDI than [&hellip;]<\/p>\n","protected":false},"author":258,"featured_media":8818,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[22,195],"tags":[101,165,166],"class_list":["post-3043","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-matt-gertz","category-visual-basic","tag-matt-gertz","tag-vb2005","tag-vb2008"],"acf":[],"blog_post_summary":"<p>In part 1 of this series, I constructed a pair of classes to supporting persisting MIDI data to files.&nbsp; In this entry, I\u2019ll now leverage that code to support an (admittedly limited) music editor experience. Caveat:&nbsp; As I mentioned in the first post, I\u2019m sure many readers will be far more knowledgeable about MIDI than [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/posts\/3043","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/users\/258"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/comments?post=3043"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/posts\/3043\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/media\/8818"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/media?parent=3043"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/categories?post=3043"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/tags?post=3043"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}