#include <GUIConstantsEx.au3>
#include <WinAPI.au3>
#include <MIDI.au3>

Opt("MustDeclareVars", 1)
Opt("GUIOnEventMode", 1)

Global $hGui = GUICreate("Midi In Example", 1000, 420)
GUISetOnEvent($GUI_EVENT_CLOSE, "_Exit")
Global $cCombo = GUICtrlCreateCombo("", 10, 10, 400, 20)
Global $cButton_Start = GUICtrlCreateButton("Start", 420, 10, 80, 20)
GUICtrlSetOnEvent(-1, "_Midi_Start")
Global $cButton_Stop = GUICtrlCreateButton("Stop", 510, 10, 80, 20)
GUICtrlSetOnEvent(-1, "_Midi_Stop")
GUICtrlCreateLabel("MIM_DATA", 10, 40, 100, 20)
Global $cEdit_Data = GUICtrlCreateEdit("", 10, 60, 485, 350)
GUICtrlCreateLabel("MIM_LONGDATA", 505, 40, 100, 20)
Global $cEdit_LongData = GUICtrlCreateEdit("", 505, 60, 485, 350)
GUISetState()


_Midi_Startup()
Global $tBuffer1 = _CreateBuffer()
Global $tBuffer2 = _CreateBuffer()
_GetDevices()

Global $hMidi = 0
Global $bRelease = False
Global $iDeviceCurrent = -1


GUIRegisterMsg($MM_MIM_CLOSE, "_MidiWinProc")
GUIRegisterMsg($MM_MIM_DATA, "_MidiWinProc")
GUIRegisterMsg($MM_MIM_ERROR, "_MidiWinProc")
GUIRegisterMsg($MM_MIM_LONGDATA, "_MidiWinProc")
GUIRegisterMsg($MM_MIM_LONGERROR, "_MidiWinProc")
GUIRegisterMsg($MM_MIM_MOREDATA, "_MidiWinProc")
GUIRegisterMsg($MM_MIM_OPEN, "_MidiWinProc")

While Sleep(100)
WEnd



Func _ProcessLongData($pData, $iBytes)
    Local $tGetData = DllStructCreate("byte[" & $iBytes & "];", $pData)
    GUICtrlSetData($cEdit_LongData, DllStructGetData($tGetData, 1) & @CRLF, 1)
EndFunc   ;==>_ProcessLongData


Func _ProcessData($iMidiMessage)
    Local $iLoWord = _WinAPI_LoWord($iMidiMessage)
    Local $iHiWord = _WinAPI_HiWord($iMidiMessage)

    Local $bStatus = BitAND($iLoWord, 0xFF) ;LoByte
    Local $bData1 = BitShift($iLoWord, 8) ;HiByte
    Local $bData2 = BitAND($iHiWord, 0xFF) ;LoByte

    Local $Chan = BitAND($bStatus, 0xF)
    Local $MidiMsg = BitShift($bStatus, 4)

    GUICtrlSetData($cEdit_Data, "StatusByte: " & $bStatus & @TAB & "DataByte1: " & $bData1 & @TAB & "DataByte2: " & $bData2 & @CRLF, 1)

    Switch $MidiMsg

        Case 0x8 ; Note Off
            GUICtrlSetData($cEdit_Data, @TAB & "NoteOff: MidiChannel: " & $Chan & @TAB & "NoteNumber: " & $bData1 & @TAB & "Velocity: " & $bData2 & @CRLF, 1)

        Case 0x9 ; Note On
            GUICtrlSetData($cEdit_Data, @TAB & "NoteOn: MidiChannel: " & $Chan & @TAB & "NoteNumber: " & $bData1 & @TAB & "Velocity: " & $bData2 & @CRLF, 1)

        Case 0xA ; Polyphonic aftertouch
            GUICtrlSetData($cEdit_Data, @TAB & "Polyphonic aftertouch: MidiChannel: " & $Chan & @TAB & "NoteNumber: " & $bData1 & @TAB & "Poly Pressure: " & $bData2 & @CRLF, 1)

        Case 0xB ; Control Change
            GUICtrlSetData($cEdit_Data, @TAB & "Control Change: MidiChannel: " & $Chan & @TAB & "Controller: " & $bData1 & @TAB & "Value: " & $bData2 & @CRLF, 1)

        Case 0xC ; Program Change
            GUICtrlSetData($cEdit_Data, @TAB & "Program Change: MidiChannel: " & $Chan & @TAB & "Programm: " & $bData1 & @CRLF, 1)

        Case 0xD ; Channel aftertouch
            GUICtrlSetData($cEdit_Data, @TAB & "Channel aftertouch: MidiChannel: " & $Chan & @TAB & "Aftertouch : " & $bData1 & @CRLF, 1)

        Case 0xE ; Pitch Bend
            GUICtrlSetData($cEdit_Data, @TAB & "Pitch Bend: MidiChannel: " & $Chan & @TAB & "LSB: " & $bData1 & @TAB & "MSB: " & $bData2 & @CRLF, 1)


    EndSwitch

EndFunc   ;==>_ProcessData



Func _MidiWinProc($hWnd, $iMsg, $wParam, $lParam)
    Switch $iMsg
        Case $MIM_OPEN
            ConsoleWrite("> MIM_OPEN" & @CRLF)

        Case $MIM_CLOSE ;hMidi Handle ab jetzt ungültig
            ConsoleWrite("> MIM_CLOSE" & @CRLF)
            $hMidi = 0

        Case $MIM_DATA ; Normale Midi MSG
            ConsoleWrite("> MIM_DATA" & @CRLF)
            _ProcessData($lParam)

        Case $MIM_MOREDATA
            ConsoleWrite("> MIM_MOREDATA" & @CRLF)

        Case $MIM_LONGDATA
            ConsoleWrite("> MIM_LONGDATA" & @CRLF)

            Switch $bRelease
                Case False ; SysEx Daten kommen rein
                    Local $tMidiHDR = DllStructCreate($MIDIHDR, $lParam)
                    Local $iBytesRecorded = DllStructGetData($tMidiHDR, "dwBytesRecorded")
                    ConsoleWrite("+ LONGDATA IN: " & $iBytesRecorded & " bytes" & @CRLF)

                    _ProcessLongData(DllStructGetData($tMidiHDR, "lpData"), $iBytesRecorded)

                    _MidiIn_AddBuffer($hMidi, $lParam, 48)

                Case Else ; Druch _MidiIn_Reset wird hier der Buffer zurückgegeben und muss hier "unprepared" werden
                    ConsoleWrite("! Unprepare Buffer" & @CRLF)
                    _MidiIn_UnprepareHeader($hMidi, $lParam, 48)

            EndSwitch

        Case $MIM_ERROR
            ConsoleWrite("> MIM_ERROR" & @CRLF)

        Case $MIM_LONGERROR
            ConsoleWrite("> MIM_LONGERROR" & @CRLF)
            _MidiIn_AddBuffer($hMidi, $lParam, 48)

    EndSwitch
EndFunc   ;==>_MidiWinProc




Func _Midi_Start()
    ConsoleWrite(@CRLF & "!=============================" & @CRLF)
    ConsoleWrite("! starting..." & @CRLF & @CRLF)
    Local $aRegExp = StringRegExp(GUICtrlRead($cCombo), "<(\d+)>", 3)
    If @error Or Not IsArray($aRegExp) Then Return
    Local $iDevice = $aRegExp[0]

    If $iDevice = $iDeviceCurrent Then
        ConsoleWrite("- already running" & @CRLF)
        Return
    EndIf

    $bRelease = False

    Local $iRet


    If $hMidi <> 0 Then _Midi_Stop()
    $hMidi = _MidiIn_Open($iDevice, $hGui, 0, BitOR($CALLBACK_WINDOW, $MIDI_IO_STATUS))
    ConsoleWrite("! MidiIn Open: " & $hMidi & " error: " & @error & @CRLF)
    $iDeviceCurrent = $iDevice

    $iRet = _MidiIn_PrepareHeader($hMidi, DllStructGetPtr($tBuffer1, "lpData"), 48)
    ConsoleWrite("! Prepare Header: " & $iRet & " error: " & @error & @CRLF)
    $iRet = _MidiIn_PrepareHeader($hMidi, DllStructGetPtr($tBuffer2, "lpData"), 48)
    ConsoleWrite("! Prepare Header: " & $iRet & " error: " & @error & @CRLF)

    $iRet = _MidiIn_AddBuffer($hMidi, DllStructGetPtr($tBuffer2, "lpData"), 48)
    ConsoleWrite("! Add Buffer: " & $iRet & " error: " & @error & @CRLF)
    $iRet = _MidiIn_AddBuffer($hMidi, DllStructGetPtr($tBuffer1, "lpData"), 48)
    ConsoleWrite("! Add Buffer: " & $iRet & " error: " & @error & @CRLF)

    $iRet = _MidiIn_Start($hMidi)
    ConsoleWrite("! MidiIn Start: " & $iRet & " error: " & @error & @CRLF)


EndFunc   ;==>_Midi_Start



Func _Midi_Stop()
    ConsoleWrite(@CRLF & "!=============================" & @CRLF)
    ConsoleWrite("! stopping..." & @CRLF & @CRLF)

    $bRelease = True

    Local $iRet
    $iRet = _MidiIn_Reset($hMidi)
    ConsoleWrite("! MidiIn Reset: " & $iRet & " error: " & @error & @CRLF)
    $iRet = _MidiIn_Stop($hMidi)
    ConsoleWrite("! MidiIn Stop: " & $iRet & " error: " & @error & @CRLF)
    $iRet = _MidiIn_Close($hMidi)
    ConsoleWrite("! MidiIn Close: " & $iRet & " error: " & @error & @CRLF)

    $iDeviceCurrent = -1
EndFunc   ;==>_Midi_Stop




Func _GetDevices()
    Local $tMidiInCaps = DllStructCreate($MIDIINCAPS)
    Local $pMidiInCaps = DllStructGetPtr($tMidiInCaps)
    Local $iMidiInCaps = DllStructGetSize($tMidiInCaps)

    Local $iDeviceCnt = _MidiIn_GetNumDevs()
    Local $sDevice = ""
    Local $sDefault = ""

    For $i = 0 To ($iDeviceCnt - 1)
        _MidiIn_GetDevCaps($i, $pMidiInCaps, $iMidiInCaps)
        $sDevice &= "<" & $i & "> " & DllStructGetData($tMidiInCaps, "szPname") & "|"
        If $i = 0 Then $sDefault = "<" & $i & "> " & DllStructGetData($tMidiInCaps, "szPname")
    Next

    GUICtrlSetData($cCombo, $sDevice, $sDefault)
EndFunc   ;==>_GetDevices




Func _CreateBuffer($iSize = 256, $iID = 0)
    Local $tBuffer = DllStructCreate("uint BufferSize; byte BufferData[" & $iSize & "];" & $MIDIHDR)

    DllStructSetData($tBuffer, "BufferSize", $iSize)
    DllStructSetData($tBuffer, "lpData", DllStructGetPtr($tBuffer, "BufferData"))
    DllStructSetData($tBuffer, "dwBufferLength", $iSize)
    DllStructSetData($tBuffer, "dwUser", $iID)

    Return $tBuffer
EndFunc   ;==>_CreateBuffer



Func _Exit()
    Exit
EndFunc   ;==>_Exit