Types/Wave/get_NoteGrammar.ps1

<#
.SYNOPSIS
    Gets Wave Note Grammar
.DESCRIPTION
    Wave notes are a musical domain specific language.

    Defining a Grammar streamlines how we can work with them.
.NOTES
    ### Grammar Format

    The Grammar is defined in a simple primal format:

    It is an `[Ordered]` dictionary.

    The order defines the order of precedence.
    
    Each key is the name of an instruction.

    Each value is one or more literal strings or `[Regex]`

    The grammar is combined into a single regular expression,
    which is available in `.NotePattern`

    ### Grammar Maturity

    This Grammar is in currently in flux,
    and should not be viewed as final or fully matured.

    Certain elements specified in the grammar are not yet supported:

    * `<>` (Open/Close Phrase) support is not yet implemented
    * `[]` (Open/Close Divide) only supports some partial syntax
    * `𝆱𝆲` (GlissandoUp/Down) are not yet implemented
    * `𝆒𝆓` (Crescendo/Decrescendo) are not yet implemented
#>


[Ordered]@{
    OpenPhrase = '<'
    ClosePhrase = '>'
    OpenDivide = '['
    CloseDivide = ']'    
    UpTempo = '++'
    DownTempo = '--'
    Time1_1 = '𝅝'
    Time1_2 = '𝅗𝅥'
    Time1_4 = '𝅘𝅥'
    Time1_8 = '𝅘𝅥𝅮'
    Time1_16 = '𝅘𝅥𝅯'
    Time1_32 = '𝅘𝅥𝅰'
    Time1_64 = '𝅘𝅥𝅲'
    Time1_128 = '𝅘𝅥𝅲'
    GlissandoUp = '𝆱'
    GlissandoDown = '𝆲'
    Crescendo = '𝆒'
    Decrescendo = '𝆓'
    Rest1_1 = '𝄻'
    Rest1_2 = '𝄼'
    Rest1_4 = '𝄽'
    Rest1_8 = '𝄾'
    Rest1_16 = '𝄿'
    Rest1_32 = '𝅀'
    Rest1_64 = '𝅁'
    Rest1_128 = '𝅂'    
    Multiply = [Regex]::new('\*(?<scale>[\d\.]+)')
    Divide = [Regex]::New('/(?<scale>[\d\.]+)')
    RatioTime = [Regex]::New('\:(?<scale>[\d\.]+)')
    Repeat = [Regex]::New('!(?<scale>[\d+])')
    DECPS = [regex]::new('\e\[(?<decVolume>[0-7]);(?<decDuration>\d+);(?:(?<decNote>\d+);?){1,},~')
    Emoji = [Regex]::new("[\p{IsHighSurrogates}\p{IsLowSurrogates}\p{IsVariationSelectors}\p{IsCombiningHalfMarks}]+")    
    AtTime = [Regex]::new('\@(?<scale>[\d\.]+)')
    Rest = [Regex]::new('
        (?>
            ~| # Strudel notation
            rest|silence| # Common english
            [xz]| # ABC notation
            ø # Notemoji notation
        )
    '
,'IgnoreCase,IgnorePatternWhitespace')
    BeepCommand = [Regex]::new('
        Beep # beep
        (?<beepSequence>
            (?:-n)? # Followed by an option -n (new beat)
            \s{1,} # and at least one space
            (?>
                -f\s{0,}(?<beepFrequency>[\d\.]+)\s{1,}
                |
                -l\s{0,}(?<beepDuration>[\d\.]+)\s{1,}
                |
                -d\s{0,}(?<beepDelay>[\d\.]+)\s{1,}
                |
                -r\s{0,}(?<beepRepeat>\d+)\s{1,}
            ){1,}
        ){1,}
    '
,'IgnoreCase,IgnorePatternWhitespace')
    Beep = [Regex]::new(
        '
        Beep # Beep
        \s{0,} # Followed by optional whitespace
        \p{Ps}? # Optional starting punctuation
        (?<frequency>[\d\.]+) # Frequency
        \s{0,} # Optional whitespace
        \p{P} # Intermedia punctuation
        \s{0,} # Optional whitespace
        (?<duration>[\d\.]+) # Duration
        \s{0,} # Optional whitespace
        \p{Pe} # Ending punctuation
        '
,
        'IgnoreCase,IgnorePatternWhitespace'
    )
    MidiNote = [Regex]::New('m(?:idi)?\s{0,}(?<midiNumber>[\d\.]+)','IgnoreCase')
    <#ExactFrequency = [Regex]::new(
        '(?<frequency>[\d\.]+)(?<scale>(?>hz|㎐|㎑|㎒|㎓))'
    )#>

    Sleep = [Regex]::new(
        '
        Sleep # Sleep
        \s{0,} # Followed by optional whitespace
        \p{Ps}? # Optional starting punctuation
        \s{0,} # Optional whitespace
        (?<duration>[\d\.]+) # Duration
        \s{0,} # Optional whitespace
        \p{Pe}? # Optional ending punctuation'
,
        'IgnoreCase,IgnorePatternWhitespace'
    )
    # Letter notes are one of the more challenging parts of the grammar
    # We only want to match letters within words that are all notes.
    LetterNote = [Regex]::new('
    # Do not match if we preceed a letter
    # or punctuation that is not a-g
    # followed by any number of a-g letters
    (?<![\p{L}\p{P}-[abcdefg\|]][abcdefg\|]{0,})
    (?<pitch>
        (?>
            (?<flat>[\u266d_])
            |
            (?<sharp>[\#\u266f\^])
        )
    )?
    (?<letter>[a-g]) # a letter between a and g
    # as long as the next character is not a letter and not a-g.
    (?![\p{L}-[abcdefg]])
    (?:
        # Then an optional flat/sharp alter
        # and a number
        (?>
            # If it is a `b` flat,
            # it has to be followed by an octave.
            (?<flat>b)
                (?<octave>[1-8])
            |
            # If it is a notemoji flat or underscore
            (?<flat>[\u266d_])
                # the octave is optional
                (?<octave>[1-8])?
            |
            # If it is a notemoji sharp, pound sign, or caret
            (?<sharp>[\#\u266f\^])
                # the octave is optional
                (?<octave>[1-8])?
            |
            # If there was no sharp or flat, the octave can still be present
            (?<octave>[1-8])
        )
    )?
     # an optional octave
    '
, 'IgnoreCase, IgnorePatternWhitespace'        
    )
    Decimal = [Regex]::new(
        '
(?<IsNegative>\-)? # It might be start with a -
(?:(?> # Then it can be either:
    (?<Characteristic>\d+) # One or more digits (the Characteristic)
    (?:\.(?<Mantissa>\d+)){0,1} # followed by a period and one or more digits (the Mantissa)
    | # Or it can be
    (?:\.(?<Mantissa>\d+)) # just a Mantissa
))
(?:
    E
    (?<Exponent>
        [+-]\d+
    )
)?
    '
        
    )
}