Jump to content

Recommended Posts

czardas, you dawg. This is very cool! Outstanding work, glad to see this come to fruition.

 

Yeah, it's been on the back of my mind for quite a while. Your comments and those of others helped me a lot back then, when I first mentioned it in dev chat. Thanks James.

Link to comment
Share on other sites

I'm a complete idiot when it comes to music theory so I cannot really experiment with this, but I can recognize the great work.

As someone who is very interesting in computer language theory and interpreter/compiler construction I have some feedback:

You should definitely write a grammar for the language. EBNF is simple and the advantages of having your language in an abstract form is really useful and makes stuff a lot easier. Plus anyone else wanting to implement a interpreter/compiler knows exactly what to target.

Split your interpreter into different parts. For something like this I think the following would be nice:

 - Lexer: Converts program into tokens.

 - Parser: Converts the tokens into an abstract syntax tree. This is basically the grammar represented in a tree data structure. If you have written in a grammar, it's easy to write a small recursive descent parser to parse the programs or use a parser generator such as ANTLR to generate a parser for you.

 - Evaluator: Simply takes your syntax tree and executes it.

Having this separation is great because it's easy to debug, maintain and extend. It also allows you to do cool stuff like compile your programs without redoing all the work. 

Otherwise, it's a really cool project and hearing music from my speakers was a joy! :)

Broken link? PM me and I'll send you the file!

Link to comment
Share on other sites

monoceres, your expertize in programming languages is second to none and your input is much appreciated. I had already planned to write the instructions for making an interpreter in the near future. This week I'm busy with commitments, but after that I'll make a start. You have given me a lot of new information to absorb. :)

I still need to look at where to draw the line between various errors and warnings, and how the interpreter should behave in all case scenarios. At the moment it throws stuff out that it doesn't like and tries to interpret the rest. The justification for this approach is that the ear also gives you  feedback information and a warning system may sometimes be preferable to throwing an error.

Edited by czardas
Link to comment
Share on other sites

I advice you to read up on grammars (EBNF), regex (for the lexer) and recursive descent parsers. With these three tools you will come a long way in making a good language.

Error handling is massively simplified in a recursive descent parser. Since the parser is more or less a direct reflection of the grammar, everything that doesn't fit the grammar is directly identified as errors.

Lets take a simple language that can add two digits together. The grammar in EBNF looks like this:

<adder program> = <digit>,{"+",<digit>}
<digit> = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"

 

Some valid inputs for this grammar is:

1
1+2
1+2+1+5

 

While some invalid inputs are:

[empty string]
1+ 
+1
2+4+

 

For fun, I wrote a little parser that evaluates the grammar:

#include <array.au3>
 
; Grammar:
; <adder program> = <digit>,{"+",<digit>}
; <digit> = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
;
 
; Displays error and terminates program.
Func error($message)
   MsgBox(0,"Error",$message)
   Exit 1
EndFunc
 
; Token types
Global Const $DIGIT = "digit"
Global Const $PLUS = "plus"
Global Const $END = "end"
 
; Returns a token!
Func Token($type,$value=0)
   Return $type&"|"&$value
EndFunc
 
Func GetTokenValue($token)
   $arr = StringSplit($token,"|")
   return $arr[2]
EndFunc
 
Func GetTokenType($token)
   $arr = StringSplit($token,"|")
   return $arr[1]
EndFunc
 
; Turns a string into an array of tokens.
Func Lex($input)
   Local $res[1]
   ; Regex that matches tokens.
   Local $patterns[2][2] = [[$DIGIT,"^\d"],[$PLUS,"^\+"]]
   
   ; Repeat while there is characters left in input.
   while $input <> ""
 For $i=0 To UBound($patterns)-1
$value = StringRegExp($input,$patterns[$i][1],3)
; Did the pattern match!
if not @error Then
$value = $value[0]
$input = StringTrimLeft($input,StringLen($value))
_ArrayAdd($res,Token($patterns[$i][0],$value))
; Restart pattern loop.
ContinueLoop 2
EndIf
 Next
 ; No pattern matched? Throw!!
 error("Could not scan string: "&$input)
   WEnd
   ; Insert special token for parser!
   _ArrayAdd($res,Token($END))
   Return $res
EndFunc
 
 
Global $tokens
Global $pos
Global $current
 
; Throws error if current token is not of type.
Func Expect($type)
   If GetTokenType($current)<>$type Then error("Expected token with type: "&$type&", got: "&GetTokenType($current))
EndFunc
 
; Returns true if current token is of type, else false.
Func Accept($type)
    Return GetTokenType($current) == $type
EndFunc
 
; Moves on to next token.
Func Advance()
   $pos += 1
   $current = $tokens[$pos]
EndFunc
 
; Returns the value of digit token.
Func Digit()
   Expect($DIGIT)
   Return Int(GetTokenValue($current))
EndFunc
 
; Parse/Executes an AdderProgram.
Func AdderProgram()
   ; Setup parser
   $pos = 0
   Advance()
   
   ; We know that each program starts with DIGIT.
   $sum = Digit()
   
   ; There can be infinite many repeating plus operation
   Advance()
   While Accept($PLUS)
 Advance()
 ; After accepting a PLUS we know there is a DIGIT following.
 $sum += Digit()
 Advance()
   WEnd
   ; We should have now reached the end of the program.
   ; If we're not at the END token, the program did not match grammar!
   Expect($END)
   Return $sum
EndFunc
 
$program = InputBox("Adder","Enter adder program:")
 
$tokens = Lex($program)
MsgBox(0,"Answer",$program&" = "&AdderProgram())
 
 

Broken link? PM me and I'll send you the file!

Link to comment
Share on other sites

I understand the logic of your model and I thank you for taking the time to post it. Like other languages, mus++ has good and bad writing practice rules, most of which should be acceptable. The syntax is mainly based on existing rules governed by music theory, although in some cases interpretation can be specific to a style of music or, in others, a matter of opinion. For this reason there are still one or two details need ironing out.

I was so happy when music came out of my speakers that I had to share my work even though It's far from complete. At least all the important features are already in place. Writing the tutorials has also helped me to clarify exactly how the language works. I will include some discussion of grammar and rules relating to syntax when I cover the remaining commands.

Edited by czardas
Link to comment
Share on other sites

I've finally had the sense to have a look at what you are on about and have done here, and I'm almost speechless ... which is something, coming from me. :blink:

Truly amazing and mind-blowing, and I will certainly investigate this all fully when I get the time. :D

Words cannot thank you enough for sharing this with us. :dance::guitar:

P.S. I'd heard rumors it was good, but I'm still somewhat stunned. You're the man! :thumbsup:

Belated HAPPY BIRTHDAY! :party:

Make sure brain is in gear before opening mouth!
Remember, what is not said, can be just as important as what is said.

Spoiler

What is the Secret Key? Life is like a Donut

If I put effort into communication, I expect you to read properly & fully, or just not comment.
Ignoring those who try to divert conversation with irrelevancies.
If I'm intent on insulting you or being rude, I will be obvious, not ambiguous about it.
I'm only big and bad, to those who have an over-active imagination.

I may have the Artistic Liesense ;) to disagree with you. TheSaint's Toolbox (be advised many downloads are not working due to ISP screwup with my storage)

userbar.png

Link to comment
Share on other sites

@TheSaint I'm absolutely delighted that you like this, I know how much music means to you from our previous discussions. At the end of the day I don't believe you will ever be able to replace the human element in the process of creating music. This language is a tool, nothing more and nothing less. Thanks for taking a look at it and for your positive feedback. :)

Edited by czardas
Link to comment
Share on other sites

The least I can do after being tardy, and all the fine effort you have put into it.

I also remember when you first started airing elements of this some time back, and I'm impressed big time at what you've made of it. Well done! :thumbsup:

Make sure brain is in gear before opening mouth!
Remember, what is not said, can be just as important as what is said.

Spoiler

What is the Secret Key? Life is like a Donut

If I put effort into communication, I expect you to read properly & fully, or just not comment.
Ignoring those who try to divert conversation with irrelevancies.
If I'm intent on insulting you or being rude, I will be obvious, not ambiguous about it.
I'm only big and bad, to those who have an over-active imagination.

I may have the Artistic Liesense ;) to disagree with you. TheSaint's Toolbox (be advised many downloads are not working due to ISP screwup with my storage)

userbar.png

Link to comment
Share on other sites

mus++ for non-musicians (Part 4)

This and the next tutorial will introduce some more advanced features available in mus++. Many professional musicians will find some of this tough going, but it is necessary to cover everything the language is capable of. You may wish to use this material as more of a reference. It is better to try out one or two features until you get used to what they each do. I will also provide more examples in the future. The more theoretical elements at the end of the tutorial are not covered in great detail and leave some questions unanswered. I will try to cover some of these omissions in the next instalment. Many of these concepts will take time to assimilate.


The keyword DC stands for Da Capo (Italian) and tells the interpreter to repeat from the start. The following plays the notes  A B  |  C  |  A B  |  C

2/4 A B | +C' DC

With DC, bar repeats using colon occuring within the repeated section are ignored. The next example produces the sequence  A B  |  C D  |  C D  |  E  |  A B  |  C D  |  E

2/4 A B |: C' D :| +E DC

With separate repeat endings the music goes immediately to the final ending and skips all other separate endings. The following example produces  A  B |  A C  |  D  |  A  C  |  D

2/4 |: A 1, B :|2, C' | +D DC

Instead of repeating from the start you can repeat from any earlier point using DS (Dal Segno => from the sign) in conjunction with a special symbol called segno represented by $ (dollar). The following example produces  A  |  B C  |  D E  |  F C  |  D E  |  F

2/4 A | B $ C' | D E | F DS

The rules about ignoring repeated bar sections with DC also apply with DS. If the music continues after DC or DS, depending on which section you are in, repeat marks become active again.

There are two ways to exit from within a section repeat. The first is to end the somewhere in the middle using the keyword fin (fine in Italian). Here we get  A B  |  C  |  A  B

2/4 A B fin | +C' DC

The second method to exit a repeated section is to use a coda represented by the capital letter Q. The coda is a tail section added on to the end of a piece of music. The coda symbol Q should appear twice - first at the escape point within the repeated section and then preceeded by a bar line after either DC or DS.

2/4 A B Q | +C' DC |Q +E'

The above plays the sequence  A B  |  C  |  A B  | E

and the next example plays  A B  |  C D  |  E F  |  C D  |  G

2/4 A B | $ C' D | Q E F DS |Q +G

In written music, the general rule with segno, coda and fine (in mus++ $ , Q & fin) are that these instructions should be ignored on first sight. When used in conjunction with DC (or DS) there is never any ambiguity in interpretation.

However, you sometimes encounter coda or fine being used to escape a standard repeat section when no DC or DS instruction has been given. The performer is sometimes expected to decide (or know) how many times to repeat a section depending on context. Such ambiguity is not acceptable for a scripting language and separate endings can be used to achieve the same result. For this reason the 'ignore on first sight' rule is not entirely accurate, and in mus++ these symbols only apply when used together with DC or DS.

The order and placement of segno, coda and fine are important. Here are some bad continuations - can you see why?

fin $ DS
fin Q DC |Q
Q $ DS |Q
1, Q :| 2, DC |Q


The following standard four continuations are recommended sequences, but other combinations are also possible.

fin DC
Q DC |Q
$ fin DS
$ Q DS |Q


When a change in speed occurs, the new speed is maintained even after DC or DS. Besides manually setting the tempo using the standard syntax mentioned earlier, you can also use the macro @Tempo which returns the current speed to the speed at the start of the piece (strictly speaking the speed at which the first note, or rest, was played).

5/4 @Tempo C D ;=190 E F G DC | _+-G

@Tempo can be used at any point in the music.

If you find this next section difficult, I advise you refer to the layout of the piano keyboard to help you. Remember the following rule: if a note is sharpened then it inherits the pitch of the next note on the right whether it be a white or a black key. If the note is flattened it aquires the pitch of the neighboring note on the left.

A very useful instruction exists in music called a key signature. Key signatures make certain notes either sharp or flat by default. There are 15 possible key signatures each with a different selection of sharps or flats. In mus++ key signatures are notated by the number of notes affected. 4# means four sharps are included in the musical key. Likewise flat keys use lower case b. 1b means one note is flattened within the key. The default key is 0# (which is the same as 0b) and only includes the white notes on the piano keyboard.

The following table gives the key signatures for all keys. Each note sequence is a major scale in a different key.

Key Signatures with sharps
0# = C  major / A  minor ... C  D  E  F  G  A  B  C
1# = G  major / E  minor ... G  A  B  C  D  E  F# G
2# = D  major / B  minor ... D  E  F# G  A  B  C# D
3# = A  major / F# minor ... A  B  C# D  E  F# G# A
4# = E  major / C# minor ... E  F# G# A  B  C# D# E
5# = B  major / G# minor ... B  C# D# E  F# G# A# B
6# = F# major / D# minor ... F# G# A# B  C# D# E# F#
7# = C# major / A# minor ... C# D# E# F# G# A# B# C#


Key Signatures with flats
0b = C  major / A  minor ... C  D  E  F  G  A  B  C
1b = F  major / D  minor ... F  G  A  Bb C  D  E  F
2b = Bb major / G  minor ... Bb C  D  Eb F  G  A  Bb
3b = Eb major / C  minor ... Eb F  G  Ab Bb C  D  Eb
4b = Ab major / F  minor ... Ab Bb C  Db Eb F  G  Ab
5b = Db major / Bb minor ... Db Eb F  Gb Ab Bb C  Db
6b = Gb major / Eb minor ... Gb Ab Bb C  Db Eb Fb Gb
7b = Cb major / Ab minor ... Cb Db Eb Fb Gb Ab Bb Cb


Learning all the above combinations seems a daunting task and many musicians are uncomfortable with certain keys because of the nature of the instrument they play. Some instruments are more suited to certain keys for example the guitar - due to way the instrument is tuned. The length of the air pipe in wind instruments is another influencing factor.

In the following example all notes written as B sound as Bb.

1b 4/4 F G A B | A G +F | ;B B A G | oF

The order in which sharps and flats are introduced in key signatures is fixed.

key ....... 1# 2# 3# 4# 5# 6# 7#
New Note .. F# C# G# D# A# E# B#


The order that flats are introduced turns out to be the reverse.

key ....... 1b 2b 3b 4b 5b 6b 7b
New Note .. Bb Eb Ab Db Gb Cb Fb


Personally I use a mneumonic, which also works in reverse, to remember this sequence:

Father Charles Goes Down And Ends Battle.
Battle Ends And Down Goes Charle's Father.

We end this tutorial with Tuplets. We have already discussed dividing a note into three equal durations using triplets, but what about other more complicated divisions. This is very easy in mus++. To do this you simply write the number you wish to divide by before brackets. The following would divide a half note + into 5 equal parts (quintuplet).

5(- - - - -)

There are limits to how many equal divisions you can have in mus++. In music you normally don't come across divisions of more than 11 because most musicians find such divisions difficult to perform accurately. Furthermore numbers with prime factors can be simplified and numbers which are a power of 2 (duplets) are interpreted differently. More about duplets later.

The following guide shows which notes to use when dividing different note durations. The first column shows the total duration.

Duration  Divisions .....................................
          3       5to7    9to15  17to31   33to63  65to127

         ?
~               ?
-         ~              ?
;         -       ~              ?
+         ;       -       ~              ?
o         +       ;       -       ~              ?


The general rule is that the duration of the tuplets would be longer if not compressed to fit within the associated note value in the first column.

4/4 - - (- - -) ~ ~ ~ ~ 7(~ ~ ~ ~ ~ ~ ~) | o

The system covers most musically meaningful divisions. The following musical example (taken from wiki) shows five beats divided into six equal parts. This is difficult to perform and uses a ratio (6:5) to indicate the division.

320px-Sextuplet_against_five_rhythm.png

Using ratios to denote rhythmic divisions is not currently planned as a feature in mus++. The wiki example can also be notated in other ways including the following:

5/4 6( ;~ ~; ;~ ~; ;~ ~; )

Examples using the features from the 4th tutorial

_MusPlusPlus( _ ; Irregular Jazz Rhythm - mus++ Example by czardas
"<tpt> 2b 4/4 ;=109 $ C' |: _7(+.C -'F#') 7(G B 'Bz' C' E Fb D) | 1, +'G' ;E' C :| 2, Q +'G' _7(~G ;=133 -.E' Ez) DS |Q @Tempo +'G' (-G G G) ;G | _+G ;")
Edited by czardas
Link to comment
Share on other sites

Time to start the dreaded documentation for this, this would be great as a project for a help file or wiki site. I think that the http://www.wikia.com site might be a place to start to think about something like this.

If I posted any code, assume that code was written using the latest release version unless stated otherwise. Also, if it doesn't work on XP I can't help with that because I don't have access to XP, and I'm not going to.
Give a programmer the correct code and he can do his work for a day. Teach a programmer to debug and he can do his work for a lifetime - by Chirag Gude
How to ask questions the smart way!

I hereby grant any person the right to use any code I post, that I am the original author of, on the autoitscript.com forums, unless I've specifically stated otherwise in the code or the thread post. If you do use my code all I ask, as a courtesy, is to make note of where you got it from.

Back up and restore Windows user files _Array.au3 - Modified array functions that include support for 2D arrays.  -  ColorChooser - An add-on for SciTE that pops up a color dialog so you can select and paste a color code into a script.  -  Customizable Splashscreen GUI w/Progress Bar - Create a custom "splash screen" GUI with a progress bar and custom label.  -  _FileGetProperty - Retrieve the properties of a file  -  SciTE Toolbar - A toolbar demo for use with the SciTE editor  -  GUIRegisterMsg demo - Demo script to show how to use the Windows messages to interact with controls and your GUI.  -   Latin Square password generator

Link to comment
Share on other sites

Time to start the dreaded documentation for this, this would be great as a project for a help file or wiki site. I think that the http://www.wikia.com site might be a place to start to think about something like this.

 

Yes, after I have cleaned up one or two things. I thought I might be able to finish describing all the current features in my previous post, but I decided it would be too much in one go. There's not so much left to describe, but descriptions can be complicated. That's what I'm experiencing with this, and the reason why examples are so useful. I would like to be able to dedicate more time to my projects. I have to plod along in the time available. :mellow:

Edited by czardas
Link to comment
Share on other sites

New secrets revealed: Compair the two examples from >post #34 with the following versions which use features introduced in the 4th tutorial. The sections repeat in a typical fashion. Notice how all the sharps (#) have disappeared from the code for 'The Oysterwives Rant'. They are no longer needed because of the key signature. This is much better organised code.

;

_MusPlusPlus( _ ; The Oysterwives Rant
"<fiddle> 2# 4/4 ;=125 |: $ -A | ;B -B A B E E F | A B A F D E F A | ;B -B A B C' D 'B' | A F D' 'F' Q ;E -E ;=128 :|" & _
";=131 |: A | B A B C' D E D 'B' | -.A ~B -A F -.A ~B ;=75 ~.A \ ;=131 -F | B A B C' D E D 'B' | A F D' 'F' ;E -E ;=134 :|"  & _
";=137 DS |Q @Tempo ;E -;.E")
Sleep(2000)

_MusPlusPlus( _ ; O'er The Muir Amang The Heather
"<bagp> 4/4 ;=90 -E' |: $ ;'G' -.G ~B ;G -.G ~B | ;A -.A ~B -D' 'G' B G | -.D' ~E -.D ~'B'  -.D' ~E -.G ~A| G F -E -.G ~'B' -.A ~B -.A ~G :|" & _
"|: -.G ~B -.G' ~A G F -E | -.G ~D E -.A A ~G ;A | -.A ~E -.G ~A G F -E -.D ~E -.G ~A | G F -E -.G ~'B' Q -.A ~B -.A ~G :| DS |Q ;.A")



















;

;

And now an example of use with AutoIt - Random Greek Mode generator :D

;

; An example of using mus++ with AutoIt

; Random Greek Mode generator
Local $sKey, $aSequence[7] = [ _
"-C D E F G A B C' 'B' A G F E D -oC", _
"-D E F G A B C' D C 'B' A G F E -oD", _
"-E F G A B C' D E D C 'B' A G F -oE", _
"-F G A B C' D E F E D C 'B' A G -oF", _
"-G A B C' D E F G F E D C 'B' A -oG", _
"-A B C' D E F G A G F E D C 'B' -oA", _
"-B C' D E F G A B A G F E D C -o'B'"]

For $i = 1 To 21
    If Random(0, 1, 1) Then
        $sKey = "#"
    Else
        $sKey = "b"
    EndIf
    _MusPlusPlus(Random(0, 7, 1) & $sKey & " ;=200 " & $aSequence[Random(0, 6, 1)])
Next

;
Edited by czardas
Link to comment
Share on other sites

mus++ for non-musicians (Part 5)

This final installment brings together all the elements of the previous tutorials and introduces the remaining features of mus++.

Firstly lets deal with some syntax. The order you should place performance instructions is as follows: instrument, key signature, time signature and finally tempo markings followed by the music. You may also miss out any of the above.

<fl> 2# 6/8 ;=120 ;'B' -C' ;'B' -A | ;B -C' ;.D

So far we have only discussed notes appearing within the mid pitch range, and those appearing one octave above or below that. You can go up or down more octaves by adding more quotes to the right or the left of the note name. The lowest note (in midi) is ''''A , and the highest note is C'''' .

It is possible to use a double quote in place of two single quotes, so the full range of notes can also be expressed as ""A to C"" .

Duplets are special tuplets which are used to divide a dotted note into two or four equal divisions.

<fl> 2# 6/8 ;=120 |: -C' 'B' C' 2('A' A) | A B C' 1, D C 'B' :|2, ;.D

Duplets are different from other tuplets because the sum of the note durations would be less than the associated dotted note (shown in the first column below) if the notes were played normally. Any tuplet division which is a power of 2 is interpreted as a duplet.

Duration  Divisions ································
          2       4       8       16      32      64
.        ?
~.               ?
-.        ~              ?
;.        -       ~              ?
+.        ;       -       ~              ?
o.        +       ;       -       ~              ?

If you wish to make a line break in a single voice, you must leave an empty line between each break. If you do not do this, the interpreter will see the new line as a new voice or instrument.

2/4 ~'''C D E F | G A B ''C D E F G |

A B 'C D E F G A | B 'C' D E  F G A B | +C'

When writing for more than one voice, it is important to write all performance instructions out in full in each voice - that is if you wish to keep the voices synchronized - this is not forced.

<pno> 4/4 ;=140  -C D  E F G F E D |   oC
<pno> 4/4 ;=140 ''C '''B   G   F   | o''C

The current interpreter allows you to use up to eight voices. If you insert an empty line as a break, you must maintain the same number of voices (consecutive lines) after the break.

This final section covers some more advanced theory related to key signatures and accidentals. Consider the following scale - Cb major - in which all the notes have been flattened.

Cb Db Eb Fb Gb Ab Bb Cb'

This can be written using the key signature 7b:

7b C D E F G A B C'

To sharpen a note in this scale, you must use a natural sign z.

7b C D E Fz G A B C'

To flatten a note, you must use another accidental called a double flat bb.

7b C D E F G A Bbb C'

Similar rules apply when a note is already sharpened in the key signature. In this case you use a natural sign z to flatten, and a double sharp x to sharpen the note.

4# C D E Fx G A B C'

Double sharps and double flats are generally quite rare, but their existance is a consequence of the major/minor key system used in Western harmonic theory. From personal observation, I would have to say that some note names are difficult to justify - for example Cbb, because such a name appears to have no harmonic significance in any musical key. Instead of Cbb, it is more meaningful to call this particular note Bb. Of course you can use the symbols in mus++ any way you like.

Another important fact about the key system is that some keys are actually duplicates. The key F# major contains the same pitch values as Gb major, but the notes are named differently. The following two scales are identical.

6# F G A B  C' D E F

6b G A B C' D  E F G


Also 7# contains the same set of pitch values as 5b, and likewise 5# is the same as 7b.

This covers all the features included in this first release of mus++. There will not be any changes to the use of the symbols included here. Additional features may be added at a later date.

Edited by czardas
Link to comment
Share on other sites

It's remarkable how much the visual appearance of mus++ reminds me of the old pianola rolls. I'm sure you'll like this one - it's a big favourite of mine:

;

#include-once
#include <Array.au3>

_MusPlusPlus( _ ; An der schonen blauen Donau
"<br pno> 2# 3/4 ;=156 +.    | +.     | +.    | +.     | +.      | +.    | +.    | +.     | +.    | +.      |" & @CR & _
"<br pno> 2# 3/4 ;=156 +.    | ;  ; A'| A ; F | F +    | +.      | ; ; A | A ; G | G +    | +.    | ;  ;  B |" & @CR & _
"<br pno> 2# 3/4 ;=156 +.    | ;  ; F'| F ; D | D +    | +.      | ; ; G | G ; C | C +    | +.    | ;  ;  G |" & @CR & _
"<br pno> 2# 3/4 ;=156 D F G | +.A    |_+;A   |_A  ; D | D  G  A | +.A   |_+;A   |_A  ; C | C E B | +.B     |" & @CR & _
"<br pno> 2# 3/4 ;=156 +.    | ;  F F | ; F F | ;  D ; | ;  ;  F | G G G |_G G G |_G  C ; | ; C C | ;  G  G |" & @CR & _
"<br pno> 2# 3/4 ;=156 +.    | ;  D D | ; D D | ; 'A A | ; 'D' D | ; C C | ; C C | ; 'A A | ; A A | ; 'C' C |" & @CR & _
"<br pno> 2# 3/4 ;=156 +;    |'D  A A | D A A | D  F F | D  A  A | E A A | E A A | E  G G | E G G |''A 'A A |" & @CR & _
@CR & _
" +.      | +.      | +.       | ;  ;  B'| B +   | +.     | +.      | +.     | +.    | +.      | +.      |" & @CR & _
" B   ; G | G   +   | +.       | ;  ;  F | F ; F | F  +   | +.      | ; ; D''| D ; A'| A +     | +.      |" & @CR & _
" G   ; C | C   +   | +.       | ;  ;  D | D ; D | D  +   | +.      | ; ; A  | A ; F | F +     | +.      |" & @CR & _
"_+;B     |_B   ; C | C    E B | +.B     |_+;B   | A  ; D | D  F  A | +.D'   |_+;D   |_D ;  'D'| D  F  A |" & @CR & _
" ;   G G | ;   C ; | ;    C C | ;  F  F | ; F F | ;  D ; | ;  ;  F | +.     | +.    | ; D   ; | +.      |" & @CR & _
" ;   C C | ;  'A A | ;    A A | ; 'D' D | ; D D | ; 'A A | ; 'D' D | ; D D  | ; D D | ; 'A  A | ; 'D' D |" & @CR & _
"''A 'A A |''A 'G G |''A ;'G G | D  A  A | D A A | D  F F | D  A  A | F A A  | F A A | F  ;  F | F  A  A |" & @CR & _
@CR & _
" +.     | +;    | B  +    | +.       | +.      | +.      | +.    | +.    | +.     | +.    | +.         ;=60 |" & @CR & _
" ; ; D''| D ; B'| E  ;  E | E G -B - | +;B     |_B  G# A | +;F'' |_F D ; | +F' ;E | +B ;A | D -  D  ;D ;=60 |" & @CR & _
" ; ; B  | B ; E |'E' +    | +.       | +.      | +.      | +.    | +.    | E      | +.    | ; -  A  ;A ;=60 |" & @CR & _
" ;+D'   |_D  ;D |_D  ; 'E'| E G -B - | +;B     |_B  G# A | +;F   |_F D F | +.'B'  | C'    | ; - 'F' ;F ;=60 |" & @CR & _
" ; E E  | ; E E | D  +    | +.       | ;  G  G | ;  Gz G | ; F F | +.    | D      |'G     | +.         ;=60 |" & @CR & _
" ; D D  | ; D D |'B  +    | +.       | ; 'C' C | ;  C  C | ; D D | ; D D | +.'B   | +;E   | F    ;   A ;=60 |" & @CR & _
" G B B  | G B B | G  ;  E | +;       | E  A  A |''A 'A A | D A A | F A A | +.G    | +;''A | 'D   ;   D ;=60 |" & @CR & _
@CR & _
" 3# ; @Tempo +          |: +.           | +.          | +.           | +.          | +.      | +.          |" & @CR & _
" 3# ; @Tempo -D'' - C - |: C - B' - B - | ; -B - A# - | A# - B - B - | ;  -E - E - | +F   ;E | ;  -E - E - |" & @CR & _
" 3# ; @Tempo ;      ;   |: +.           | +.          | +.           | +.          | +.      | +.          |" & @CR & _
" 3# ; @Tempo -F'  - E - |: E - D  - D - | ; -D - C  - | C  - D - D - | +.          | +.      | +.          |" & @CR & _
" 3# ; @Tempo +          |: ;  'E'   E   | ;  E   E    | ;    E   E   | ;   D   D   | ;   C C | ;   C   C   |" & @CR & _
" 3# ; @Tempo +          |: ;  'D'   D   | ;  D   C    | ;    D   D   | ;  'G   G   | ;   A A | ;   A   A   |" & @CR & _
" 3# ; @Tempo ;      ;   |: E   G    G   | E  G   Gz   | E    G   G   |''E 'E   E   |''A 'E E |''A 'E   E   |" & @CR & _
@CR & _
" +.       | +.           | +.           | +.            | +.             | +.           | +.    | +     -  - |" & @CR & _
" +B    ;A | ; -D'' - C - | C - B' - B - | ;  -B - C'' - | E   - D  - D - | ;  -G' - B - | +B ;A | ;-G F D 'B'|" & @CR & _
" +.       | +.           | +.           | +      -A#' - | C'' - B' - B - | ;  'D'   D   | +.    | +     -  - |" & @CR & _
" +'B'  ;A | ;  -F' - E - | E - D  - D - | ;  -D - C   - | E   - D  - D - | ; -'G' - B - | +B ;A | ;.G ;-     |" & @CR & _
" ;    C C | ;   D    C   | ;   D    D   | ;   D   C     | ;     D    D   | ;  'B    B   | ;+'C' |  B'   ;    |" & @CR & _
" ;    A A | ;   A    A   | ;   G    G   | ;   G   Gz    | ;     G    G   | ;   ;    G   | +B ;A | +F    ;    |" & @CR & _
" ''A 'E E |''A 'F    E   |''E 'E    E   |''E 'E   E     |''E   'E    E   |''E  E'   E#  | +.F   | +D    ;    |" & @CR & _
@CR & _
" B  B ;B B |1, +.             :|2,  ;  fin + | +.      | +.      | +.    DC" & @CR & _
" F' F ;F E |1, 'A' -D'' - C - :|2, 'A' fin + | +.A'    |_+.A'    |_;A  + DC" & @CR & _
" D' D ;D D |1, 'E'  +         :|2, 'E' fin + | +.      | +.      | +.    DC" & @CR & _
" G  G ;G G |1, C   -F'  - E - :|2,  C  fin + | +.      | +.      | +.    DC" & @CR & _
" E  G  E   |1, A    ;     ;   :|2,  A  fin + | +.'Gz'  | +;E     | Gz  + DC" & @CR & _
" +.        |1, +.             :|2,  ;  fin + | +.'E'   | +;'A    | 'E' + DC" & @CR & _
"''E G  E   |1, A    ;     ;   :|2,  A  fin + | +.      | +.      | +.    DC")


Func _MusPlusPlus($sSource) ; Parse mus++
    $sSource = _InitialTidySource($sSource)
    If @error Then Return SetError(1, 0, 0)

    Local $iParts = 8, $aSystems = StringSplit($sSource, @LF & @LF, 1)
    Local $aVoices[$iParts] = ["","","","","","","",""], $aStaves
    For $i = 1 To $aSystems[0]
        $aStaves = StringSplit($aSystems[$i], @LF)
        If $i = 1 And $aStaves[0] <= 8 Then
            $iParts = $aStaves[0]
            ReDim $aVoices [$iParts]
        ElseIf $iParts <> $aStaves[0] Then
            Return SetError (2, 0, 0) ; Inconsistant number of voices
        EndIf
        For $j = 0 To $iParts -1
            $aVoices[$j] &= " " & $aStaves[$j +1]
        Next
    Next

    Local $aTimeLine, $aRepeat[1][11], $iInstance, $vCurrInstrument, $vCurrKey, $sCurrNote, $iCurrOctave, $sLookFor, $sSegnoInstrument, _
    $sSegnoKey, $iSegnoOctave, $sSegnoNote, $iLatestInstrument, $iLatestKey, $iCurrRepeat, $iEndings, $iSegno, $iDalSegno, $iDaCapo, _
    $aAccidentals[7], $sSegnoAccidentals, $sAtTempo, $vCurrTempo, $aNotePart, $sSkipIndex, $sWarnings = "", $sErrors = "", $iTuplet, _
    $bTie, $bRest, $iPitch, $iBound =1, $aInstrument[$iParts], $iHangCheck, $iRepeats, $iCurrTime, $aMessage[$iParts][1][2] ;==> voice|instance|params=time/msg

    Local Enum $iEntry = 0, $iEntryInstrument, $iEntryKey, $iEntryOctave, $iEntryNote, $iFirstExit, $iFirstExitOctave, _
    $iFirstExitNote, $iFinalExit, $iFinalExitInstrument, $iFinalExitKey

    For $i = 0 To $iParts -1
        $aInstrument[$i] = 0xC0
    Next

    ; Parsing begins here
    For $i = 0 To $iParts -1
        _ClearAccidentals($aAccidentals)
        $aVoices[$i] = StringStripWS($aVoices[$i], 3)
        StringReplace($aVoices[$i], "|:", "") ; Get loop entry points
        If @extended Then ReDim $aRepeat[@extended +1][11]

        $iInstance = 0 ; bar repeat sections encountered
        $vCurrInstrument = "<pno>" ; piano
        $vCurrKey = "0#" ; C major / A minor
        $sCurrNote = ";" ; quarter note
        $iCurrOctave = 4
        $sLookFor = "" ; Look for Q or fin within a DC or DS section repeat

        ; Bar repeat data
        $aRepeat[$iInstance][$iEntry] = $iInstance
        $aRepeat[$iInstance][$iEntryInstrument] = $vCurrInstrument
        $aRepeat[$iInstance][$iEntryKey] = $vCurrKey
        $aRepeat[$iInstance][$iEntryOctave] = $iCurrOctave
        $aRepeat[$iInstance][$iEntryNote] = $sCurrNote
        $aRepeat[$iInstance][$iFirstExit] = $iInstance
        $aRepeat[$iInstance][$iFirstExitOctave] = $iCurrOctave
        $aRepeat[$iInstance][$iFirstExitNote] = $sCurrNote
        $aRepeat[$iInstance][$iFinalExit] = ""
        $aRepeat[$iInstance][$iFinalExitInstrument] = $vCurrInstrument
        $aRepeat[$iInstance][$iFinalExitKey] = $vCurrKey

        $iCurrRepeat = 1 ; First iteration of a bar repeat sequence
        $iEndings = 1 ; no separate bar repeat endings encountered yet

        $aTimeLine = StringRegExp($aVoices[$i], "[^\h]+", 3)
        $aVoices[$i] = ""

        $sSegnoInstrument = $vCurrInstrument ; Section repeat data
        $sSegnoKey = $vCurrKey ; Section repeat data =>
        $iSegnoOctave = $iCurrOctave
        $sSegnoNote = $sCurrNote
        $sSegnoAccidentals = ",,,,,," ; array is needed when $ occurs after an accidental
        $iLatestInstrument = 0 ; position in timeline
        $iLatestKey = 0 ; position in timeline
        $iSegno = -1 ; position of $
        $iDalSegno = -1 ; position of DS
        $iDaCapo = -1 ; position of DC
        $sAtTempo = -1
        $vCurrTempo = ";=100"
        $sSkipIndex = "|"

        For $j = 0 To UBound($aTimeLine) -1
            If StringInStr($sSkipIndex, "|" & $j & "|") Then ContinueLoop
            $iHangCheck = $j -1
            Select
                Case _IsInstrument($aTimeLine[$j])
                    If $j > $iLatestInstrument Then $iLatestInstrument = $j
                    _UpdateCurrentAttrib($vCurrInstrument, $aVoices, $aTimeLine[$j], $i)
                    $aRepeat[$iInstance][$iEntryInstrument] = $vCurrInstrument ; Temporary Patch

                Case _IsKeySig($aTimeLine[$j])
                    If $j > $iLatestKey Then $iLatestKey = $j
                    _UpdateCurrentAttrib($vCurrKey, $aVoices, $aTimeLine[$j], $i)

                Case _IsTempoMark($aTimeLine[$j])
                    If Not StringRegExp($aVoices[$i], "[A-Go\+;\-~\\\?]") Then
                        $sAtTempo = $aTimeLine[$j]
                    EndIf
                    _UpdateCurrentAttrib($vCurrTempo, $aVoices, $aTimeLine[$j], $i)

                Case _IsAtTempo($aTimeLine[$j])
                    _UpdateCurrentAttrib($vCurrTempo, $aVoices, $sAtTempo, $i)

                Case _IsSegno($aTimeLine[$j])
                    $sSegnoKey = $vCurrKey
                    $sSegnoInstrument = $vCurrInstrument
                    $iSegnoOctave = $iCurrOctave
                    $sSegnoNote = $sCurrNote
                    $iSegno = $j
                    $sSegnoAccidentals = ""
                    For $k = 0 To 5
                        $sSegnoAccidentals &= $aAccidentals[$k] & ","
                    Next
                    $sSegnoAccidentals &= $aAccidentals[6]

                Case _IsBarLine($aTimeLine[$j])
                    _ClearAccidentals($aAccidentals)

                Case _IsStartRepeat($aTimeLine[$j])
                    _ClearAccidentals($aAccidentals)
                    $iInstance += 1
                    If $j > $iDaCapo And $j > $iDalSegno Then
                        $aRepeat[$iInstance][$iEntry] = $j
                        $aRepeat[$iInstance][$iEntryInstrument] = $vCurrInstrument
                        $aRepeat[$iInstance][$iEntryKey] = $vCurrKey
                        $aRepeat[$iInstance][$iEntryOctave] = $iCurrOctave
                        $aRepeat[$iInstance][$iEntryNote] = $sCurrNote
                        $iCurrRepeat = 1
                        $iEndings = 1
                    EndIf

                Case _Is2ndTimeRound($aTimeLine[$j])
                    If $j > $iDaCapo And $j > $iDalSegno Then
                        If $aRepeat[$iInstance][$iFinalExit] = "" Then
                            $aRepeat[$iInstance][$iFirstExit] = $j
                            $aRepeat[$iInstance][$iFirstExitOctave] = $iCurrOctave
                            $aRepeat[$iInstance][$iFirstExitNote] = $sCurrNote
                        EndIf

                        $iRepeats = StringTrimRight($aTimeLine[$j], 1) ; string to number
                        If $iEndings < $iRepeats Then $iEndings = $iRepeats
                        If $iCurrRepeat > $iRepeats Then
                            If $vCurrKey <> $aTimeLine[$iLatestKey] Then
                                If _IsKeySig($aTimeLine[$iLatestKey]) Then
                                    $vCurrKey = $aTimeLine[$iLatestKey]
                                    $aVoices[$i] &= $vCurrKey & " "
                                EndIf
                            EndIf
                            _UpdateCurrentAttrib($vCurrInstrument, $aVoices, $aTimeLine[$iLatestInstrument], $i)
                            _ClearAccidentals($aAccidentals)
                            $j = $aRepeat[$iInstance][$iFinalExit] ; Go to the next end repeat mark
                        EndIf
                    Else
                        $j = $aRepeat[$iInstance][$iFinalExit] ; Go directly to the last end section
                        _ClearAccidentals($aAccidentals)
                        _UpdateCurrentAttrib($vCurrKey, $aVoices, $aRepeat[$iInstance][$iFinalExitKey], $i)
                        _UpdateCurrentAttrib($vCurrInstrument, $aVoices, $aRepeat[$iInstance][$iFinalExitInstrument], $i)
                    EndIf

                Case _IsTie($aTimeLine[$j]) Or _IsStartTuplet($aTimeLine[$j]) Or _IsEndTuplet($aTimeLine[$j])
                    $aVoices[$i] &= $aTimeLine[$j] & " "

                Case _IsNote($aTimeLine[$j])
                    $aNotePart = _NoteSplit($aTimeLine[$j])
                    If $aNotePart[0] <> "" Then
                        $sCurrNote = _GetCurrNote($aNotePart[0])
                    EndIf
                    If $aNotePart[1] <> "" Then
                        If StringInStr($aNotePart[1], "'") Then
                            $iCurrOctave = _GetOctave($aNotePart[1])
                        EndIf
                        If StringRegExp($aNotePart[1], "[#bxz]") Then
                            _UpdateAccidentals($aAccidentals, $aNotePart[1])
                        Else
                            _GetAccidental($aAccidentals, $aNotePart[1])
                        EndIf
                    EndIf
                    $aVoices[$i] &= $aNotePart[0] & $aNotePart[1] & " "
                    If $sAtTempo = -1 Then $sAtTempo = $vCurrTempo

                Case _IsEndRepeat($aTimeLine[$j])
                    If $j > $iDaCapo And $j > $iDalSegno Then
                        _ClearAccidentals($aAccidentals)
                        If $iCurrRepeat = $iEndings Then
                            $aRepeat[$iInstance][$iFinalExit] = $j
                            $aRepeat[$iInstance][$iFinalExitInstrument] = $vCurrInstrument
                            $aRepeat[$iInstance][$iFinalExitKey] = $vCurrKey
                        EndIf
                        If $iCurrRepeat <= $iEndings Then ; Go back to the start of the loop
                            $j = $aRepeat[$iInstance][$iEntry] ; $j will be incremented on the next pass ^^
                            $iCurrRepeat += 1
                            _UpdateCurrentAttrib($vCurrKey, $aVoices, $aRepeat[$iInstance][$iEntryKey], $i)
                            _UpdateCurrentAttrib($vCurrInstrument, $aVoices, $aRepeat[$iInstance][$iEntryInstrument], $i)
                            _UpdateCurrentAttrib($iCurrOctave, $aVoices, $aRepeat[$iInstance][$iEntryOctave], $i, "{", "} ")
                            _UpdateCurrentAttrib($sCurrNote, $aVoices, $aRepeat[$iInstance][$iEntryNote], $i, "{", "} ")
                        EndIf
                    EndIf

                Case _IsDalSegno($aTimeLine[$j])
                    If $iDalSegno < $j Then
                        If $iSegno = -1 Then
                            $sWarnings &= "Voice " & $i +1 & " Warning => Expected $ sign before DS " & @CRLF
                            ConsoleWrite($sWarnings) ; deal with this later
                            ExitLoop ; further parsing of this voice is meaningless
                        EndIf
                        $iDalSegno = $j
                        $j = $iSegno
                        $sLookFor = "DS"
                        _UpdateCurrentAttrib($vCurrKey, $aVoices, $sSegnoKey, $i)
                        _UpdateCurrentAttrib($vCurrInstrument, $aVoices, $sSegnoInstrument, $i)
                        _UpdateCurrentAttrib($iCurrOctave, $aVoices, $iSegnoOctave, $i, "{", "} ")
                        _UpdateCurrentAttrib($sCurrNote, $aVoices, $sSegnoNote, $i, "{", "} ")
                        $aAccidentals = StringSplit($sSegnoAccidentals, ",", 2)
                        For $iInstance = $iInstance To 0 Step -1
                            If $iSegno > $aRepeat[$iInstance][$iEntry] Then ExitLoop
                        Next
                    EndIf

                Case _IsDaCapo($aTimeLine[$j])
                    If $iDaCapo < $j Then
                        _ClearAccidentals($aAccidentals)
                        $iDaCapo = $j
                        $j = -1
                        $sLookFor = "DC"
                        $iInstance = 0
                        _UpdateCurrentAttrib($vCurrKey, $aVoices, $aRepeat[$iInstance][$iEntryKey], $i)
                        _UpdateCurrentAttrib($vCurrInstrument, $aVoices, $aRepeat[$iInstance][$iEntryInstrument], $i)
                        _UpdateCurrentAttrib($iCurrOctave, $aVoices, $aRepeat[$iInstance][$iEntryOctave], $i, "{", "} ")
                        _UpdateCurrentAttrib($sCurrNote, $aVoices, $aRepeat[$iInstance][$iEntryNote], $i, "{", "} ")
                    EndIf

                Case _IsCoda($aTimeLine[$j])
                    If ($sLookFor = "DS" And $j < $iDalSegno) Or ($sLookFor = "DC" And $j < $iDaCapo) Then
                        If $iDalSegno > $iDaCapo Then
                            $j = $iDalSegno
                        Else
                            $j = $iDaCapo
                        EndIf
                        _UpdateCurrentAttrib($vCurrKey, $aVoices, $aTimeLine[$iLatestKey], $i)
                        _UpdateCurrentAttrib($vCurrInstrument, $aVoices, $aTimeLine[$iLatestInstrument], $i)
                    EndIf

                Case _IsFin($aTimeLine[$j])
                    If ($sLookFor = "DS" And $j < $iDalSegno) Or ($sLookFor = "DC" And $j < $iDaCapo) Then ExitLoop

                Case Else
                    If _IsTimeSig($aTimeLine[$j]) Or _IsTechnique($aTimeLine[$j]) Or _IsBarRepeat($aTimeLine[$j]) Or _IsAccel($aTimeLine[$j]) Or _IsDynamicMark($aTimeLine[$j]) Then
                        ContinueLoop ; Currently unsupported performance instructions or features
                    Else
                        $sErrors &= "Voice " & $i +1 & " Syntax Error => " & $aTimeLine[$j] & @CRLF
                        $sSkipIndex &= $j & "|"
                        ConsoleWrite("Voice " & $i +1 & " Syntax Error => " & $aTimeLine[$j] & @LF)
                    EndIf
            EndSelect
            If $j = $iHangCheck Then $j += 1 ; Recursion correction
        Next
        ;ConsoleWrite($aVoices[$i] & @LF)

        $aTimeLine = StringRegExp($aVoices[$i], "[^\h]+", 3)
        $iInstance = UBound($aTimeLine)*2 ; upper limit... variable re-usage
        If $iInstance > UBound($aMessage, 2) Then ReDim $aMessage [$iParts][$iInstance][2]

        $vCurrInstrument = 0
        $iLatestInstrument = 0
        $vCurrKey = 0 ; C major / A minor
        $iLatestKey = 0 ; C major / A minor
        $vCurrTempo = 100
        $iCurrOctave = 4
        $sCurrNote = ";"
        $sLookFor = -1 ; variable re-usage
        $bTie = False ; variable re-usage
        $bRest = True
        $iInstance = 0 ; message count
        $iCurrTime = 0
        $iTuplet = 1
        Local $iDuration

        For $j = 0 To UBound($aTimeLine) -1
            Select
                Case _IsInstrument($aTimeLine[$j])
                    $aTimeLine[$j] = StringRegExpReplace($aTimeLine[$j], "(_)(pno|box|org|acc|gtr|bass|tpt|sax|hn|drum|bl)", " " & "$2")
                    $iLatestInstrument = _GetInstrument($aTimeLine[$j])
                    If @error And Not StringInStr($sErrors, $aTimeLine[$j]) Then
                        $sErrors &= "Voice " & $i +1 & " Instrument not recognized => " & $aTimeLine[$j] & @CRLF
                        ConsoleWrite("Voice " & $i +1 & " Instrument not recognized => " & $aTimeLine[$j] & @LF)
                    ElseIf $vCurrInstrument <> $iLatestInstrument Then
                        $vCurrInstrument = $iLatestInstrument
                        $aMessage[$i][$iInstance][0] = $iCurrTime
                        $aMessage[$i][$iInstance][1] = $vCurrInstrument*256 + 0xC0
                        $iInstance += 1
                    EndIf
                    $aInstrument[$i] = $vCurrInstrument*256 + 0xC0

                Case _IsKeySig($aTimeLine[$j])
                    $vCurrKey = _GetKey($aTimeLine[$j])

                Case _IsTempoMark($aTimeLine[$j])
                    $vCurrTempo = _GetQuartNoteBPM($aTimeLine[$j])

                Case _IsTie($aTimeLine[$j])
                    $bTie = True

                Case _IsStartTuplet($aTimeLine[$j])
                    $iTuplet = StringReplace($aTimeLine[$j], "(", "")
                    If $iTuplet = "" Then $iTuplet = 3

                Case _IsOctave($aTimeLine[$j])
                    $iCurrOctave = StringRegExpReplace($aTimeLine[$j], "[\{\}]", "")

                Case _IsCurrentNote($aTimeLine[$j])
                    $sCurrNote = StringRegExpReplace($aTimeLine[$j], "[\{\}]", "")

                Case _IsNote($aTimeLine[$j])
                    $aNotePart = _NoteSplit($aTimeLine[$j])
                    If $aNotePart[0] <> "" Then
                        $sCurrNote = _GetCurrNote($aNotePart[0])
                    Else
                        $aNotePart[0] = $sCurrNote
                    EndIf
                    $iDuration = _GetDuration($aNotePart[0], $vCurrTempo, $iTuplet)

                    If $aNotePart[1] <> "" Then
                        If StringInStr($aNotePart[1], "'") Then
                            $iCurrOctave = _GetOctave($aNotePart[1])
                        EndIf
                        If Not $bTie Then
                            $aMessage[$i][$iInstance][0] = $iCurrTime
                            $iPitch = _GetPitch($aNotePart[1], $vCurrKey, $iCurrOctave)
                            $aMessage[$i][$iInstance][1] = BitOr(($iPitch+0x15)*256, 0x90, 0x400000) ; Midi note on
                            $iInstance += 1
                        Else
                            If Not $bRest And $sLookFor > -1 Then
                                $aMessage[$i][$sLookFor][0] += $iDuration
                            EndIf
                        EndIf
                        $bRest = False
                    Else
                        $bRest = True
                    EndIf

                    $iCurrTime += $iDuration
                    If Not $bRest And Not $bTie Then ; Now turn note off
                        $aMessage[$i][$iInstance][0] = $iCurrTime
                        $aMessage[$i][$iInstance][1] = BitXOR($aMessage[$i][$iInstance -1][1], 0x400000) ; Midi note off
                        $sLookFor = $iInstance
                        $iInstance += 1
                    EndIf
                    $bTie = False

                Case _IsEndTuplet($aTimeLine[$j])
                    $iTuplet = 1
            EndSelect
        Next
        If $iInstance > $iBound Then $iBound = $iInstance
    Next

    ReDim $aMessage[$iParts][$iBound][2]

    ;_WalkThrough3D($aMessage) ; Debugging - Requires arratf.au3

    $sLookFor = ""
    For $i = 0 To $iParts -1
        For $j = 0 To UBound($aMessage, 2) -1
            If $aMessage[$i][$j][0] == "" Then ExitLoop
            If Not StringInStr($sLookFor, "," & $aMessage[$i][$j][0] & ",") Then
                $sLookFor &= $aMessage[$i][$j][0] & ","
            EndIf
        Next
    Next
    $aTimeLine = StringSplit(StringTrimRight($sLookFor, 1), ",", 2)
    _ArraySortByNumber($aTimeLine)

    If $aTimeLine[1] = 0 Then _ArrayDelete($aTimeLine, 0)

    Local $aGrid[UBound($aTimeLine)][$iParts*8 +3]
    For $i = 0 To UBound($aTimeLine) -1
        $aGrid[$i][0] = 1
        $aGrid[$i][1] = $aTimeLine[$i]
    Next

    For $i = $iParts -1 To 0 Step -1
        $iInstance = 0
        For $j = 0 To UBound($aMessage, 2) -1
            If $aMessage[$i][$j][0] == "" Then ExitLoop
            If $aMessage[$i][$j][0] == $aTimeLine[$iInstance] Then

                If BitAND($aMessage[$i][$j][1], 0x400000) = 0x400000 Then
                    If $aGrid[$iInstance][$aGrid[$iInstance][0]] <> $aInstrument[$i] Then
                        $aGrid[$iInstance][0] += 1
                        $aGrid[$iInstance][$aGrid[$iInstance][0]] = $aInstrument[$i]
                    EndIf
                EndIf

                $aGrid[$iInstance][0] += 1
                $aGrid[$iInstance][$aGrid[$iInstance][0]] = $aMessage[$i][$j][1]

            ElseIf $aMessage[$i][$j][0] > $aTimeLine[$iInstance] Then
                $iInstance += 1
                If $iInstance > UBound($aTimeLine) -1 Then ExitLoop
                $j -= 1
            EndIf
        Next

    Next
    ;_ArrayDisplay($aGrid)

    Local $iTimer, $dOpen = _MidiOutOpen()
    $iInstance = 0
    $iTimer = TimerInit()
    While 1
        If TimerDiff($iTimer) >= $aGrid[$iInstance][1] Then
            For $i = 2 To $aGrid[$iInstance][0]
                _MidiOutShortMsg($dOpen, $aGrid[$iInstance][$i])
            Next
            $iInstance += 1
        Else
            Sleep(10)
        EndIf
        If $iInstance > UBound($aTimeLine) -1 Then ExitLoop
    WEnd
    Sleep(600)
    _MidiOutClose($dOpen)
EndFunc ;==> _MusPlusPlus

#region ; conditionals
Func _IsInstrument($sInstruction)
    Return StringRegExp($sInstruction, "(?i)(\A<[a-z \-_0-9]+>\z)")
EndFunc ;==> _IsInstrument

Func _IsKeySig($sInstruction)
    Return StringRegExp($sInstruction, "(\A[0-7][#b]\z)")
EndFunc ;==> _IsKeySig

Func _IsTimeSig($sInstruction) ; Requires accentuation / dynamics
    Return StringRegExp($sInstruction, "(\A\d+/(1|2|4|8|16|32|64)\z)")
EndFunc ;==> _IsTimeSig

Func _IsTempoMark($sInstruction)
    Return StringRegExp($sInstruction, "(\A[o\+;\-~\\\?]\.?=\d*\.?\d+\z)")
EndFunc ;==> _IsTempoMark

Func _IsAtTempo($sInstruction)
    Return $sInstruction = "@Tempo" ; not case sensitive
EndFunc ;==> _IsAtTempo

Func _IsSegno($sInstruction)
    Return $sInstruction = "$"
EndFunc ;==> _IsSegno

Func _IsBarLine($sInstruction)
    Return $sInstruction = "|"
EndFunc ;==> _IsBarLine

Func _IsStartRepeat($sInstruction)
    Return $sInstruction = "|:"
EndFunc ;==> _IsStartRepeat

Func _Is2ndTimeRound($sInstruction)
    Return StringRegExp($sInstruction, "(\A\d+,\z)")
EndFunc ;==> _Is2ndTimeRound

Func _IsTie($sInstruction)
    Return $sInstruction = "_"
EndFunc ;==> _IsTie

Func _IsStartTuplet($sInstruction)
    Return StringRegExp($sInstruction, "(\A\d*\(\z)")
EndFunc ;==> _IsStartTuplet

Func _IsNote($sNote)
    If StringRegExp($sNote, "(\A'*[A-G](#|b|bb|x|z)?'*\z)") Then Return 1
    Return StringRegExp($sNote, "(\A[o\+;\-~\\\?\.]+('*[A-G](#|b|bb|x|z)?'*)?\z)")
EndFunc ;==> _IsNote

Func _IsEndTuplet($sInstruction)
    Return $sInstruction == ")"
EndFunc ;==> _IsEndTuplet

Func _IsEndRepeat($sInstruction)
    Return $sInstruction = ":|"
EndFunc ;==> _IsEndRepeat

Func _IsCoda($sInstruction)
    Return $sInstruction == "Q"
EndFunc ;==> _IsCoda

Func _IsDalSegno($sInstruction)
    Return $sInstruction == "DS"
EndFunc ;==> _IsDalSegno

Func _IsDaCapo($sInstruction)
    Return $sInstruction == "DC"
EndFunc ;==> _IsDaCapo

Func _IsFin($sInstruction)
    Return $sInstruction == "fin"
EndFunc ;==> _IsFin

Func _IsOctave($sInstruction)
    Return StringRegExp($sInstruction, "\A\{[0-8]\}\z")
EndFunc ;==> _IsOctave

Func _IsCurrentNote($sInstruction)
    Return StringRegExp($sInstruction, "\A\{[o+;=~\?]\.*\}\z")
EndFunc ;==> _IsCurrentNote
#endregion ;==> conditionals

#region ; currently unsupported features
Func _IsTechnique($sInstruction) ; Returns an integer - gliss is currently not supported
    Return StringInStr(" gliss pizz ", " " & $sInstruction & " ", 1)
EndFunc ;==> _IsTechnique

Func _IsBarRepeat($sInstruction) ; currently unsupported
    Return StringRegExp($sInstruction, "(\A%\d*\z)")
EndFunc ;==> _IsBarRepeat

Func _IsAccel($sInstruction) ; currently unsupported
    Return  StringInStr(" accel rit ", " " & $sInstruction & " ", 1)
EndFunc ;==> _IsAccel

Func _IsDynamicMark($sInstruction) ; Returns an integer - currently unsupported
    Return StringInStr(" cres dim ppp pp /p mp mf /f ff fff ", " " & $sInstruction & " ", 1)
EndFunc ;==> _IsDynamicMark
#endregion ;==> currently unsupported features

#region ; data requests
Func _GetAccidental($aAccidentals, ByRef $sNote)
    Local $sAlpha = StringReplace($sNote, "'", "")
    $sNote = StringReplace($sNote, $sAlpha, $sAlpha & $aAccidentals[Asc($sAlpha) - 65], 0, 1)
EndFunc ;==> _GetAccidental

Func _GetKey($sKey)
    Local $iSign = 1
    If StringInStr($sKey, "b", 1) Then $iSign = -1
    Return StringRegExpReplace($sKey, "[#b]", "")*$iSign
EndFunc ;==> _GetKey

Func _GetQuartNoteBPM($sTempo)
    Local $aTempo = StringSplit($sTempo, "=", 2)
    Local $iNote = StringInStr("?\~-;+o", $aTempo[0]) -5
    Return $aTempo[1]*2^$iNote
EndFunc ;==> _GetQuartNoteBPM

Func _GetCurrNote($sNote)
    Local $sCurrChr, $sRhythm = ""
    For $i = StringLen($sNote) To 1 Step -1
        $sCurrChr = StringMid($sNote, $i, 1)
        $sRhythm = $sCurrChr & $sRhythm
        If StringInStr("o+;-~\?", $sCurrChr, 1) Then ExitLoop
    Next
    Return $sRhythm
EndFunc ;==> _GetCurrNote

Func _GetOctave($sNote)
    Local $iPos, $iOctave = 4
    For $i = 65 To 71
        $iPos = StringInStr($sNote, Chr($i), 1)
        If $iPos Then
            ExitLoop
        EndIf
    Next
    For $i = 1 To $iPos -1
        If StringMid($sNote, $i, 1) = "'" Then $iOctave -= 1
    Next
    For $i = $iPos +1 To StringLen($sNote)
        If StringMid($sNote, $i, 1) = "'" Then $iOctave += 1
    Next
    Return $iOctave
EndFunc ;==> _GetOctave

Func _GetPitch($sName, $iKey = 0, $iOctave = 4)
    Local $iPitch, $sAlpha
    Select
        Case StringInStr($sName, "C", 1)
            $sAlpha = "C"
            $iPitch = 39
        Case StringInStr($sName, "D", 1)
            $sAlpha = "D"
            $iPitch = 41
        Case StringInStr($sName, "E", 1)
            $sAlpha = "E"
            $iPitch = 43
        Case StringInStr($sName, "F", 1)
            $sAlpha = "F"
            $iPitch = 44
        Case StringInStr($sName, "G", 1)
            $sAlpha = "G"
            $iPitch = 46
        Case StringInStr($sName, "A", 1)
            $sAlpha = "A"
            $iPitch = 48
        Case StringInStr($sName, "B", 1)
            $sAlpha = "B"
            $iPitch = 50
    EndSelect

    Select
        Case StringInStr($sName, "bb", 1)
            $iPitch -= 2
        Case StringInStr($sName, "b", 1)
            $iPitch -= 1
        Case StringInStr($sName, "z", 1)
            $iPitch += 0
        Case StringInStr($sName, "#", 1)
            $iPitch += 1
        Case StringInStr($sName, "x", 1)
            $iPitch += 2
        Case $iKey
            If $iKey > 0 Then
                If StringInStr(StringLeft("FCGDAEB", $iKey), $sAlpha) Then $iPitch += 1
            Else
                If StringInStr(StringRight("FCGDAEB", -$iKey), $sAlpha) Then $iPitch -= 1
            EndIf
    EndSelect

    $iOctave -= 4
    $iPitch += $iOctave*12
    If $iPitch < 0 Or $iPitch > 87 Then Return SetError (2, 0, "") ; Out of range pitch
    Return $iPitch ; values range from 0 to 87
EndFunc ;==> _GetPitch

Func _GetDuration($sNote, $iTempo = 100, $iTuplet = 1)
    Local $sLen = StringLen($sNote)
    If Not $sLen Then Return Default
    If StringLeft($sNote, 1) = "." Then Return SetError(1, 0, 0) ; Syntax error - Dot not preceeded by a note value
    Local $iDuration = 0, $iDots = 0, $sCurrChr = "", $iID, $iNote = 0
    For $i = 1 To $sLen
        $sCurrChr = StringMid($sNote, $i, 1)
        $iID = StringInStr("?\~-;+o.", $sCurrChr) -1
        Switch $iID
            Case 0 To 6
                $iDuration += $iNote
                $iNote = 6930 * 2^$iID
            Case 7
                While $sCurrChr = "."
                    $iDots += 1
                    $i += 1
                    $sCurrChr = StringMid($sNote, $i, 1)
                WEnd
                $iNote *= (2^($iDots +1) -1)/2^$iDots
                $i -= 1
                $iDots = 0
        EndSwitch
    Next
    $iDuration += $iNote
    If $iTuplet > 1 Then
        Switch $iTuplet
            Case 2, 4, 8, 16, 32, 64 ; In mus - only 2 and 4 appear, and then only in compound time
                $iDuration *= 3/2 ; it's the same result in all cases
            Case 3 ; triplets are the most common tuplet division
                $iDuration *= 2/3
            Case 5 To 7
                $iDuration *= 4/$iTuplet
            Case 9 To 15 ; In mus - tuplets greater than 12 almost never appear
                $iDuration *= 8/$iTuplet
            Case 17 To 31
                $iDuration *= 16/$iTuplet
            Case 33 To 63
                $iDuration *= 32/$iTuplet
            Case 65 To 127
                $iDuration *= 64/$iTuplet
            Case Else
                Return SetError (2, 0, 0) ; Unsupported out of range tuplet value
        EndSwitch
    EndIf
    Return $iDuration*125/(231*$iTempo)
EndFunc ;==> _GetDuration

Func _GetInstrument($vInstrument)
    $vInstrument = StringRegExpReplace($vInstrument, "[<>]", "")
    Local $aMIDIInst[72][4] _ ; A selection of available MIDI imstruments
    = _ ; Name    , ##,Mn,Mx
    [["pno"     ,  0, 0,87], _ ; Acoustic Grand Piano ... KEYBOARDS
    ["br pno"   ,  1, 0,87], _ ; Bright Piano
    ["e pno"    ,  2, 0,87], _ ; Electric Grand Piano
    ["h-t pno"  ,  3, 0,87], _ ; Honky-tonk piano
    ["hpsd"     ,  6, 0,87], _ ; Harpsichord
    ["clav"     ,  7, 0,87], _ ; Clavichord
    ["cel"      ,  8, 0,87], _ ; Celesta
    ["glock"    ,  9, 0,87], _ ; Glockenspiel ... PITCHED PERCUSSION
    ["mus box"  , 10, 0,87], _ ; Music Box
    ["vib"      , 11, 0,87], _ ; Vibraphone
    ["marim"    , 12, 0,87], _ ; Marimba
    ["xyl"      , 13, 0,87], _ ; Xylophone
    ["chimes"   , 14, 0,87], _ ; Tubular Bells
    ["dulc"     , 15, 0,87], _ ; Dulcimer
    ["ham org"  , 16, 0,87], _ ; Drawbar Organ ... ORGAN
    ["perc org" , 17, 0,87], _ ; Percussive Organ
    ["org"      , 19, 0,87], _ ; Church Organ
    ["harm"     , 20, 0,87], _ ; Harmonium Reed Organ
    ["accord"   , 21, 0,87], _ ; Accordion
    ["mouth org", 22, 0,87], _ ; Harmonica
    ["tango acc", 23, 0,87], _ ; Bandoneon
    ["gtr"      , 24, 0,87], _ ; Classical Guitar ... GUITAR
    ["a gtr"    , 25, 0,87], _ ; Accoustic Guitar
    ["jazz gtr" , 26, 0,87], _ ; Jazz Guitar
    ["e gtr"    , 27, 0,87], _ ; Electric Guitar
    ["mut gtr"  , 28, 0,87], _ ; Muted Electric Guitar
    ["fuzz gtr" , 30, 0,87], _ ; Distortion Guitar
    ["a bass"   , 32, 0,87], _ ; Acoustic Bass ... BASS
    ["e bass"   , 33, 0,87], _ ; Electric Bass
    ["bass"     , 34, 0,87], _ ; Upright Bass
    ["f bass"   , 35, 0,87], _ ; Fretless Bass
    ["sl bass"  , 36, 0,87], _ ; Slap Bass
    ["vln"      , 40, 0,87], _ ; Violin ... STRINGS
    ["vla"      , 41, 0,87], _ ; Viola
    ["vc"       , 42, 0,87], _ ; Cello
    ["db"       , 43, 0,87], _ ; Double Bass
    ["hp"       , 46, 0,87], _ ; Harp
    ["timp"     , 47, 0,87], _ ; Timpani (perc)
    ["tpt"      , 56, 0,87], _ ; Trumpet ... BRASS
    ["tbn"      , 57, 0,87], _ ; Trombone
    ["tba"      , 58, 0,87], _ ; Tuba
    ["mut tpt"  , 59, 0,87], _ ; Muted Trumpet
    ["hn"       , 60, 0,87], _ ; French Horn
    ["s sax"    , 64, 0,87], _ ; Soprano Sax ... REED
    ["a sax"    , 65, 0,87], _ ; Alto Sax
    ["sax"      , 66, 0,87], _ ; Tenor Sax
    ["b sax"    , 67, 0,87], _ ; Baritone Sax
    ["ob"       , 68, 0,87], _ ; Oboe
    ["eng hn"   , 69, 0,87], _ ; English Horn
    ["bsn"      , 70, 0,87], _ ; Bassoon
    ["cl"       , 71, 0,87], _ ; Clarinet
    ["picc"     , 72, 0,87], _ ; Piccolo ... PIPE
    ["fl"       , 73, 0,87], _ ; Flute
    ["rec"      , 74, 0,87], _ ; Recorder
    ["pan"      , 75, 0,87], _ ; Panpipes
    ["bottle"   , 76, 0,87], _ ; Bottle
    ["shaku"    , 77, 0,87], _ ; Shakuhachi
    ["whistle"  , 78, 0,87], _ ; Whistle
    ["oc"       , 79, 0,87], _ ; Ocarina
    ["sitar"    ,104, 0,87], _ ; Sitar ... OTHER
    ["banjo"    ,105, 0,87], _ ; Banjo
    ["shamisen" ,106, 0,87], _ ; Shamisen
    ["koto"     ,107, 0,87], _ ; Koto
    ["kalimba"  ,108, 0,87], _ ; Kalimba (perc)
    ["bagp"     ,109, 0,87], _ ; Bagpipe
    ["fiddle"   ,110, 0,87], _ ; Fiddle
    ["shanai"   ,111, 0,87], _ ; Shanai (woodwind)
    ["bell"     ,112, 0,87], _ ; Tinkle Bell
    ["st drum"  ,114, 0,87], _ ; Steel Drums
    ["w bl"     ,115, 0,87], _ ; Woodblock
    ["taiko"    ,116, 0,87], _ ; Taiko Drum
    ["tom-t"    ,117, 0,87]]   ; Tom-tom

    For $i = 0 To 71
        If $aMIDIInst[$i][0] = $vInstrument Then
            $vInstrument = $aMIDIInst[$i][1]
            ExitLoop
        EndIf
    Next

    $vInstrument = StringRegExpReplace($vInstrument, "[<>]", "")
    If StringRegExp($vInstrument, "[^\d]") Or $vInstrument < 0 Or $vInstrument > 127 Then Return SetError(1, 0, 0) ; returns Grand Piano
    Return $vInstrument ; values range from 0 to 117
EndFunc ;==> _GetInstrument
#endregion ;==> data requests

#region ; miscellaneous functions
Func _InitialTidySource($sSource)
    If _IllegalDotCheck($sSource) Then Return SetError(1, 0, 0)
    $sSource = StringReplace($sSource, '"', "''") ; Helmholtz-Wilkinson octaves
    $sSource = StringReplace($sSource, '(', "( ") ; Add spaces after (
    $sSource = StringReplace($sSource, ')', " ) ") ; Add spaces before and after )
    $sSource = StringRegExpReplace($sSource, "(<\h+)", " <") ; Remove spaces after <
    $sSource = StringRegExpReplace($sSource, "(\h+>)", "> ") ; Remove spaces before >
    $sSource = StringReplace($sSource, '_', " _ ") ; Add spaces before and after underscore
    $sSource = StringReplace($sSource, '|:', "|: ") ; Add spaces after start repeats
    $sSource = StringReplace($sSource, ':|', " :|") ; Add spaces before end repeats
    $sSource = StringReplace($sSource, ':|:', ":||:") ; Convert double repeat marks _
    $sSource = StringReplace($sSource, '|', " | ") ; Add spaces before and after bar lines
    $sSource = StringReplace($sSource, '| :', "|:") ; Restore start repeat marks
    $sSource = StringReplace($sSource, ': |', ":|") ; Restore end repeat marks
    $sSource = StringReplace($sSource, ' |  | ', " || ") ; Restore double bar lines
    $sSource = StringRegExpReplace($sSource, "(<[\-\w]+)(\h+)(pno|box|org|acc|gtr|bass|tpt|sax|hn|drum|bl)", "$1" & "_" & "$3")
    $sSource = StringReplace(StringReplace($sSource, @CRLF, @LF), @CR, @LF) ; Replace all breaks with @LF
    $sSource = StringRegExpReplace($sSource, "(\n\h*)", @LF) ; Remove spaces after breaks
    $sSource = StringRegExpReplace($sSource, "[\n]{2,}", @LF & @LF) ; Remove duplicate empty lines
    $sSource = StringRegExpReplace($sSource, "(\A\n*)", "") ; Remove Preceeding breaks
    $sSource = StringRegExpReplace($sSource, "(\n*\z)", "") ; Remove Trailing breaks
    Return $sSource
EndFunc ;==> _InitialTidySource

Func _UpdateCurrentAttrib(ByRef $vCurrAttrib, ByRef $aVoices, $vNewAttrib, $iIndex, $sPadLeft = "", $sPadRight = " ")
    If $vCurrAttrib <> $vNewAttrib Then
        $vCurrAttrib = $vNewAttrib
        $aVoices[$iIndex] &= $sPadLeft & $vCurrAttrib & $sPadRight
    EndIf
EndFunc ;==> _UpdateCurrentAttrib

Func _ClearAccidentals(ByRef $aAccidentals)
    For $i = 0 To 6
        $aAccidentals[$i] = ""
    Next
EndFunc ;==> _ClearAccidentals

Func _UpdateAccidentals(ByRef $aAccidentals, $sNote)
    $sNote = StringReplace($sNote, "'", "")
    Local $sAlpha = StringLeft($sNote, 1)
    $aAccidentals[Asc($sAlpha) - 65] = StringTrimLeft($sNote, 1)
EndFunc ;==> _UpdateAccidentals

Func _NoteSplit($sNote)
    Local $aNotePart[2] = ["", ""]
    $aNotePart[1] = StringRegExpReplace($sNote, "[o\+;\-~\\\?\.]+", "") ; Remove rhthmic values
    $aNotePart[0] = StringLeft($sNote, StringLen($sNote) - StringLen($aNotePart[1]))
    Return $aNotePart
EndFunc ;==> _NoteSplit

Func _IllegalDotCheck($sVoo)
    Return StringRegExp($sVoo, "(o\.{7}|\+\.{6}|;\.{5}|\-\.{4}|~\.{3}|\\\.\.|\?\.)")
        ; Warning - detected an unsupported number of dots after a note
EndFunc ;==> _IllegalDotCheck
#endregion ;==>  miscellaneous functions

#region ; MIDI functions
;=======================================================
;Retrieves a MIDI handle and Opens the Device
;Parameters(Optional) - Device ID, Window Callback,
; instance, flags
;Author : Eynstyne
;Library : Microsoft winmm.dll
;=======================================================
Func _MidiOutOpen($devid = 0, $callback = 0, $instance = 0, $flags = 0)
   Local $ret = DllCall("winmm.dll", "long", "midiOutOpen", "handle*", 0, "int", $devid, "dword_ptr", $callback, "dword_ptr", $instance, "long", $flags)
   If @error Then Return SetError(@error,0,0)
   If $ret[0] Then Return SetError(-1,$ret[0],0)
   Return $ret[1]
EndFunc   ;==>_MidiOutOpen

;=======================================================
;Closes Midi Output/Input devices
;Parameters - MidiHandle
;Author : Eynstyne
;Library : Microsoft winmm.dll
;=======================================================
Func _MidiOutClose ($hmidiout)
   Local $ret = DllCall("winmm.dll", "long", "midiOutClose", "handle", $hmidiout)
   If @error Then Return SetError(@error,0,0)
   If $ret[0] Then Return SetError(-1,$ret[0],0)
   Return $ret[0]
EndFunc   ;==> _MidiOutClose

;=======================================================
;Gets the Mixer Volume for MIDI
;Parameters - None
;Author : Eynstyne
;Library : Microsoft winmm.dll
;=======================================================
Func _MidiOutGetVolume ($devid = 0)
   Local $ret = DllCall("winmm.dll", "long", "midiOutGetVolume", "handle", $devid, "dword*",0)
   If @error Then Return SetError(@error,0,0)
   If $ret[0] Then Return SetError(-1,$ret[0],0)
   Return $ret[2]
EndFunc   ;==> _MidiOutGetVolume

;=======================================================
;Sets the Mixer Volume for MIDI
;Parameters - Volume (0 - 65535)
;Author : Eynstyne
;Library : Microsoft winmm.dll
;=======================================================
Func _MidiOutSetVolume($iVolume = 65535, $devid = 0)
    Local $iMixVolume=BitAND($iVolume,0xFFFF)+BitShift(BitAND($iVolume,0xFFFF),-16) ; From Ascend4nt
    Local $ret = DllCall("winmm.dll", "long", "midiOutSetVolume", "handle", $devid, "int", $iMixVolume)
    If @error Then Return SetError(@error,0,0)
    If $ret[0] Then Return SetError(-1,$ret[0],0)
    Return $ret[0]
EndFunc ;==> _MidiOutSetVolume

;=======================================================
;MIDI Message Send Function
;Parameters - Message as Hexcode or Constant
;Author : Eynstyne
;Library : Microsoft winmm.dll
;=======================================================
Func _MidiOutShortMsg($hmidiout, $msg)
   Local $ret = DllCall("winmm.dll", "long", "midiOutShortMsg", "handle", $hmidiout, "long", $msg)
   If @error Then Return SetError(@error,0,0)
   If $ret[0] Then Return SetError(-1,$ret[0],0)
   Return $ret[0]
EndFunc ;==> _MidiOutShortMsg
#endregion ;==> MIDI functions

#region ; functions ripped from arrayf.au3 and stringf.au3
; #FUNCTION# ====================================================================================================================
; Name...........: _ArraySortByNumber
; Description ...: Sorts a 1D array numerically ascending.
; Syntax.........: _ArraySortByNumber(ByRef $avArray [, $bVulgarFrac = False])
; Parameters ....: $avArray     - [ByRef] The array to sort
;                  $bVulgarFrac - [Optional] If set to True, vulgar fractions will be also be sorted numerically
; Return values .: Success   - Returns 1
;                  Failure   - Returns zero and sets @error to the following values:
;                  |@error = 1 : $avArray is not a one dimensional array
; Author ........: czardas
; Modified.......:
; Remarks .......: The array is sorted first by numbers and then by strings.
; Related .......: _ArraySort, _ArraySortByLen
; Link ..........:
; Example .......:
; ===============================================================================================================================

Func _ArraySortByNumber(ByRef $avArray, $bVulgarFrac = False)
    If Not IsArray($avArray) Or UBound($avArray, 0) > 1 Then Return SetError(1, 0, 0)

    Local $iBound = UBound($avArray) -1
    Local $aTemp[$iBound +1][2]
    For $i = 0 To $iBound
        $aTemp[$i][0] = $avArray[$i]
        If _StringIsNumber($avArray[$i], $bVulgarFrac) Then
            $aTemp[$i][1] = Execute($avArray[$i])
        Else
            $aTemp[$i][1] = "z" & $avArray[$i]
        EndIf
    Next
    _ArraySort($aTemp, 0, 0, 0, 1)
    For $i = 0 To $iBound
        $avArray[$i] = $aTemp[$i][0]
    Next
    Return 1
EndFunc ;==> _ArraySortByNumber

; #FUNCTION# ====================================================================================================================
; Name...........: _StringIsNumber
; Description ...: Checks whether a string is a number as recognised by the AutoIt interpreter
; Syntax.........: _StringIsNumber($sString [, $bVulgarFrac])
; Parameters ....: $sString     - The string to test
;                  $bVulgarFrac - [Optional] if set to True, vulgar fractions will also return True
; Return values .: True or False
; Author ........: czardas
; Remarks .......: Returns True for integers, floats, hexadecimal and scientific notation.
; Related .......: StringIsDigit, StringIsFloat, StringIsInt, StringIsXDigit
; Link ..........:
; Example .......: MsgBox(0, "1.2e-300 is a number", _StringIsNumber("1.2e-300"))
; ===============================================================================================================================

Func _StringIsNumber($sString, $bVulgarFrac = False)
    Local $bReturn = False
    If StringIsInt($sString) Or StringIsFloat($sString) Then
        $bReturn = True ; string is integer or float
    ElseIf StringRegExp($sString, "(?i)(\A[\+\-]?0x[A-F\d]+\z)") Then
        $bReturn = True ; string is hexadecimal integer
    ElseIf StringRegExp($sString, "(?i)(\A[\+\-]?\d*\.?\d+e[\+\-]?\d+\z)") Then
        $bReturn = True ; exponential (or scientific notation)
    ElseIf $bVulgarFrac And StringRegExp($sString, "(\A[\+\-]?\d+/\d+\z)") Then
        $bReturn = True ; string is a vulgar fraction
    EndIf
    Return $bReturn
EndFunc ; _StringIsNumber
#endregion ;==> functions ripped from arrayf.au3 and stringf.au3

;

I have made some small improvements to the current interpreter. I have tested all the features included in the language, and there are some small issues which still need addressing - things I haven't mentioned. Cleaning up the code for the interpreter is next on the agenda. I will also add more simple music examples for beginners to study. Enjoy. :)

Edited by czardas
Link to comment
Share on other sites

  • 1 month later...

In view of the recent updates to the midi thread, this project will undergo a major overhaul. Because I'm so busy with commitments, the changes will have to wait. I advise caution when using separate endings in repeat sections with the current version. Bad syntax with this feature can cause infinite looping. I need to fix this with error handling.

In the mean time here's one way the current version of mus++ can be compressed by removing as many symbols as possible and laying out each voice in a single line without breaks. Perhaps you will like it, although it becomes unreadable.

;

_MusPlusPlus( _ ; Fur Elise
" <pno> ;=132 |: -E' D# $ E D E 'B' Dz' C ;'A' - C E A ;B - E G# B ;C' - 'E' E' D# E D E 'B' Dz' C ;'A' - C E A ;B - E C' 'B' 1, +A :|2, ;A - B C' D |: ;-E 'Gz' F' E ;-D 'F' E' D ;-C 'E' D' C ;'B'" & _
" +;- D#' -E' D E D# E D E D E D E 'B' Dz' C ;'A' - C E A ;B - E G# B ;C' - 'E' E' D# E D E 'B' Dz' C ;'A' - C E A ;B - E C' 'B' Q 1, ;A - B C' D :|2, ;A 5(+ ~F A) +C' -~F E ;E D -~Bb A -A G F E D" & _
" C ;'Bb' 5(A ~B) A G A B +C' -D D# | ;-E E F 'A' ;.C \D C 'B' C' -~D 'B' |: ~C' ~ 'G' ~ A ~ B ~ C' ~ D ~ E G C' 'B' A G F E D G F D :| E F E D# E 'B' E' D E 'B' E' D ;-E 'B' E' D ;-E 'B' E' D E D" & _
" E D E D |: E D# E 'B' Dz' C ;'A' - C E A ;B - E G# B ;C' - 'E' E' D# E D E 'B' Dz' C ;'A' - C E A ;B - E C' 'B' 1, ;A - B C' D ;-E 'Gz' F' E ;-D 'F' E' D ;-C 'E' D' C ;'B' + ;- D#' -E D E D# E D" & _
" E D :|2, ;A + +.C#' +D -E F +F ;F +.E +D -Cz 'B' +A ;A ;A C' 'B' +.A +.C#' +D -E F +F ;F +.F +Eb -D Cz +'Bb' ;A +G# ;G | +A ; B + (-'A 'C' E A C' E D C 'B' A C' E A C'' E D C B' 'A' C' E A C'' E" & _
" D C B' Bb A Ab G Gb F E Eb D|Db'' C B Bb A Ab G Gb F) DS Q +A" & @CR & " <pno> ;=132 |: ; $ ooooo; 1, + :|2, +. |: oo-- ''E 'E 'E' E E' 'E' E' E E'' oooooo;. Q 1, +. :|2, ;- 'E' F E o.- E - E - E" & _
" ooo |: ~ G' ~ G ~ G ~ G ~ G ~ G +. :| ooo |: ooooo; 1, ooo -''E 'E 'E' E E' 'E' E' E E'' -o :|2, +. 'Bb' +A -C#' D +D ;D +.Cz +. +'F#' o+. B +A -C#' D +D ;D +.D +. +'Fz' ;F +F ;F +E ; E oooo; DS" & _
" Q +" & @CR & " <pno> ;=132 |: ; $ ooooo; 1, + :|2, +. |: ooooooooo.. Q 1, +. :|2, ;- 'C' C C o.. -'Bb - B oo- 'D' -- E - E - E |: ;- G G G ;G + :| ooo |: ooooo; 1, ooooo; :|2, +. G +F ; +G# ;G +.A" & _
" +F -E D +C ;C ;C E D +.C Gz +F o.. +G -F Eb +D ;D +D ;D +C ; D o ;Ez E ; E E ; E E o; DS Q +C"  & @CR & " <pno> ;=132 |: ; $ ooooo; 1, + :|2, +. |: ooooooooo.. Q 1, +. :|2, +- 'Bb o.. -G - G -ooo" & _
" |: ;'E' ;- E  ;E 'A 'B :| B oo.. |: ooooo; 1, ooooo; :|2, +. E' oo-- ''A |: 15, A :| G# G  ''A -+ +.'E' ooooo; 'G o ;'C' C ; C C ; C  C o; DS Q +''A"  & @CR & " <pno> ;=132 |: ; $ +-- ''A 'E A ;-" & _
" ''E 'E G# ;- ''A 'E A o- ''A 'E A ;- ''E 'E G# ;- 1, ''A 'E A - :|2, ''A 'E A ;- |: 'C G 'C' ;- ''G 'G B ;- ''A 'E A ooo;- ''A 'E A ;- ''E 'E G# ;- ''A 'E A o- ''A 'E A ;- ''E 'E G# ;- Q 1, ''A 'E" & _
" A ;- :|2, ''A 'E A Bb A G F A 'C' 'A 'C' 'A F Bb 'D' 'B 'D' 'B F - F - F - F A 'C' 'A 'C' 'A F A 'C' 'A 'C' 'A E A 'C' 'A D F G - G - G - |: ;'C' - F E D ;C 'F G :| ;G# oo+; |: +-- ''A 'E A ;- ''E" & _
" 'E G# ;- ''A 'E A o- ''A 'E A ;- ''E 'E G ;- 1, ''A 'E A ;- 'C Gz 'C' ;- ''G 'G B ;- ''A 'E A  oo+- :|: 30, ''A :|: 16, ''D :|''E E '''A|: 23, ''A :|: 18, ''Bb :| ''Bz B B B B B +'C ; E + ;'''A ;" & _
" 'A A ; A A ; A A o; DS Q +'''A")

;

I used a hack 5(A ~ B) to create grace notes. This is a neat little trick giving you a lot of control. There are so many things to consider and flexibility is paramount.

Haha the editor turned my grace note into a smiley. ^^

Edited by czardas
Link to comment
Share on other sites

czardas, did you invent this mus++ language?  If so, that's pretty impressive!  As is the interpreter.. very cool indeed

Link to comment
Share on other sites

Thanks Ascend4nt - your excellent examples in the midi thread have been very helpful to me, and yes I created this to simplify several tasks. I introduced some features which are perhaps more suitable to programming, but the majority of the syntax bears a direct relationship to features existing in written music - making it easy to edit (once you get used to it): because the script uses lots of abstraction compaired to raw midi. I'll soon be using it as an integral part of the code behind some programs to aid music students (including myself :) ).

Edited by czardas
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...