PSMidi.psm1

#region PreCode _PreModule_Requires

#Requires -Modules @{ ModuleName = 'WindowsMidiServices'; ModuleVersion = '0.0.1' }
#endregion PreCode _PreModule_Requires

#region Classes

enum NoteIndex {
    C = 0
    D = 2
    E = 4
    F = 5
    G = 7
    A = 9
    B = 11
    H = 11
}

Class Chord {
    [string] $BaseChord
    [int] $Octave
    [string] $Alt
    hidden [int] $AltMidi
    [int] $MidiNote

    Chord($Chord) {
        $null = $Chord -match '^(?<BaseNote>[a-gA-G])(?<Alt>[#b]?)(?<Octave>(-?[1-2]|[0-8])$)'

        $this.BaseChord = $Matches['BaseNote'].ToUpper()
        $this.Alt = $Matches['Alt'] ?? [string]::Empty
        $this.AltMidi = $Matches['Alt'] -eq 'b' ? [int]-1 : $Matches['Alt'] -eq '#' ? 1 : 0
        $this.Octave = $Matches['Octave']
        $this.MidiNote = (
                ([NoteIndex]$Matches['BaseNote'].ToUpper()).value__ + $this.AltMidi
            ) + (
                ([int]$Matches['Octave'] + 2) * 12)
    }
}
#endregion Classes

#region Get-PSNoteIndexFromNoteName

Function Get-PSNoteIndexFromNoteName {
    [CmdletBinding()]
    Param(
        [Parameter()]
        [ValidatePattern('^[a-gA-G][#b]?(-?[1-2]|[0-8])$', ErrorMessage = 'Input must match pattern <note><octave>')]
        [string]$NoteName
    )

    [chord]::new($NoteName)
}
#endregion Get-PSNoteIndexFromNoteName

#region New-PSMidiMessage



Function New-PSMidiMessage {
    [CmdletBinding(DefaultParameterSetName = 'Note')]
    Param(
        [Parameter(ParameterSetName = 'Note')]
        [Parameter(ParameterSetName = 'Pitch')]
        [ValidateSet('C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B')]
        [String]$Note = 'C',

        [Parameter(ParameterSetName = 'Note')]
        [Parameter(ParameterSetName = 'Pitch')]
        [ValidateRange(0, 9)]
        [int]$Octave = 3, 

        [Parameter(ParameterSetName = 'Note')]
        [Parameter(ParameterSetName = 'Pitch')]
        [ValidateRange(0, 15)]
        [int]$Group = 0,

        [Parameter(ParameterSetName = 'Note')]
        [Parameter(ParameterSetName = 'Pitch')]
        [Microsoft.Windows.Devices.Midi2.Messages.Midi2ChannelVoiceMessageStatus]$MessageStatus = 'NoteOn',

        [Parameter(ParameterSetName = 'Note')]
        [Parameter(ParameterSetName = 'Pitch')]
        [ValidateRange(0, 15)]
        [int]$MidiChannel = 0,

        [Parameter(ParameterSetName = 'Note')]
        [Parameter(ParameterSetName = 'Pitch')]
        [ValidateRange(0, 65535)]
        [uint]$Velocity = 65535,

        [Parameter(ParameterSetName = 'Pitch')]
        [switch]$Pitch,

        [Parameter(ParameterSetName = 'Pitch')]
        [ValidateRange(0, 65535)]
        [int]$AttributeData
    )

    if ($PSBoundParameters.Keys.Contains('Pitch')) {
        $attributeType = 3
    }
    else {
        $attributeType = 0
        $AttributeData = 0
    }

    $Chord = [Chord]::new("${Note}${Octave}")
    $playNote = ($Chord.MidiNote -shl 8) -bor $attributeType

    [uint]$messageData = ($Velocity -shl 16) -bor $AttributeData

    [Microsoft.Windows.Devices.Midi2.Messages.MidiMessageBuilder]::BuildMidi2ChannelVoiceMessage(
        ((Get-Date).ToFileTimeUtc()),
        [Microsoft.Windows.Devices.Midi2.MidiGroup]::new($Group),
        $MessageStatus,
        [Microsoft.Windows.Devices.Midi2.MidiChannel]::new($MidiChannel),
        $playNote,
        $messageData
    )
}
#endregion New-PSMidiMessage

#region Send-PSMidiMessage

Function Send-PSMidiMessage {
    Param(
        [Parameter(Mandatory)]
        [WindowsMidiServices.MidiEndpointConnection]$Connection,

        [Parameter(Mandatory, ValueFromPipeline)]
        [Microsoft.Windows.Devices.Midi2.MidiMessage64]$Message
    )
    
    Send-MidiMessage -Connection $Connection -Words $($Message.Word0, $Message.Word1)
}
#endregion Send-PSMidiMessage