Announcement

Collapse
No announcement yet.

Midi In

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • Midi In

    Hello,

    In one of my projects I need to retrieve the Midi input for regular Midi messages such as “Note on” (up to 3 bytes) and
    also system exclusive messages (more than 3 bytes).
    I am using the example code below to do so.

    I have 2 Midi interfaces available, Emagic’s Unitor 8 and M-Audio Firewire Audiophile.

    The code works very well with Unitor 8. No problems at all.
    Only if I use the M-Audio interface, I got crashes as soon as I feed long system exclusive messages (267 bytes) and short regular messages
    into the input simultaneously.

    It would be very nice if somebody could check the code to point me to the culprit.

    Thank you very much in advance!

    Joerg


    Code:
    <script type="text/javascript">
    <!--
    #COMPILE EXE
    #DIM ALL
    
    %USEMACROS = 1
    #INCLUDE "Win32API.inc"
    
    
    '==============================================================================
    FUNCTION WINMAIN (BYVAL hInstance     AS DWORD, _
                      BYVAL hPrevInstance AS DWORD, _
                      BYVAL lpCmdLine     AS ASCIIZ PTR, _
                      BYVAL iCmdShow      AS LONG) AS LONG
    '------------------------------------------------------------------------------
        ' Program entry point
        '--------------------------------------------------------------------------
    
        LOCAL Msg       AS tagMsg
        LOCAL wce       AS WndClassEx
        LOCAL szAppName AS ASCIIZ * 80
        LOCAL hWnd      AS DWORD
    
        ' Setup and register a window class for the main window
        ' CODEPTR is used to pass the address of the function that will
        ' receive all messages sent to any window created with this class
        szAppName         = "Midi In Test"
        wce.cbSize        = SIZEOF(wce)
        wce.STYLE         = %CS_HREDRAW OR %CS_VREDRAW
        wce.lpfnWndProc   = CODEPTR(WndProc)
        wce.cbClsExtra    = 0
        wce.cbWndExtra    = 0
        wce.hInstance     = hInstance
        wce.hIcon         = %NULL
        wce.hCursor       = LoadCursor(%NULL, BYVAL %IDC_ARROW)
        wce.hbrBackground = %COLOR_BTNFACE + 1
        wce.lpszMenuName  = %NULL
        wce.lpszClassName = VARPTR(szAppName)
        wce.hIconSm       = LoadIcon(hInstance, BYVAL %IDI_APPLICATION)
    
        RegisterClassEx wce
    
        ' Create a window using the registered class
        hWnd = CreateWindow(szAppName, _               ' window class name
                            "Midi In Test", _          ' window caption
                            %WS_OVERLAPPEDWINDOW, _    ' window style
                            %CW_USEDEFAULT, _          ' initial x position
                            %CW_USEDEFAULT, _          ' initial y position
                            400, _                     ' initial x size
                            200, _                     ' initial y size
                            %NULL, _                   ' parent window handle
                            %NULL, _                   ' window menu handle
                            hInstance, _               ' program instance handle
                            BYVAL %NULL)               ' creation parameters
    
        IF hWnd = 0 THEN  ' exit on failure
            MSGBOX "Unable to create window"
            EXIT FUNCTION
        END IF
    
        ' Display the window on the screen
        ShowWindow hWnd, iCmdShow
        UpdateWindow hWnd
    
        ' Main message loop:
        ' Messages sent to HELLOWIN while it has the focus are received by
        ' GetMessage().  This loop translates each message and dispatches it
        ' to the appropriate handler.  When PostQuitMessage() is called, the
        ' loop terminates which ends the application.
        DO WHILE GetMessage(Msg, %NULL, 0, 0)
            TranslateMessage Msg
            DispatchMessage Msg
        LOOP
    
        FUNCTION = msg.wParam
    
    END FUNCTION
    
    '==============================================================================
    FUNCTION WndProc (BYVAL hWnd AS DWORD, BYVAL wMsg AS DWORD, _
                      BYVAL wParam AS DWORD, BYVAL lParam AS LONG) EXPORT AS LONG
    '------------------------------------------------------------------------------
        ' WndProc is the message handler for all windows creating using the HelloWin
        ' class name.  A single WndProc procedure can handle multiple windows by
        ' testing the hWnd variable passed to it.
        '--------------------------------------------------------------------------
    
        LOCAL  midiErr        AS LONG
        LOCAL  ptMidiInHdr1   AS MIDIHDR PTR
        LOCAL  ptMidiInHdr2   AS MIDIHDR PTR
        LOCAL  ptMidiInHdrRet AS MIDIHDR PTR
        LOCAL  hdrDummy       AS MIDIHDR
        LOCAL  lMidiIn        AS LONG
        STATIC hWndIn         AS DWORD
        STATIC hWndOut        AS DWORD
        LOCAL  lBuffSize      AS LONG
        LOCAL  sStatus        AS STRING * 2
        LOCAL  sNote          AS STRING * 2
        LOCAL  sVelo          AS STRING * 2
        STATIC lBuffRet       AS LONG
        STATIC lInreset       AS LONG
        LOCAL  lcount         AS LONG
    
        ' The SELECT CASE is used to catch only those messages which the message
        ' handler needs to process.  All other messages are passed through the
        ' tests to the default handler.
        SELECT CASE wMsg
    
        CASE %WM_CREATE
    
            lMidiIn   = 8
            lBuffSize = 384
    
            midiErr = midiInOpen(hWndIn, BYVAL lMidiIn, BYVAL hWnd, 0, %CALLBACK_WINDOW OR %MIDI_IO_STATUS)
    
            'no input available
            IF ISTRUE midiErr OR ISFALSE hWndIn THEN
    
               lBuffRet = 3
    
               SetWindowText hWnd, "No valid input available!"
    
               EXIT FUNCTION
    
            END IF
    
            ptMidiInHdr1 = HeapAlloc(GetProcessHeap(), %HEAP_ZERO_MEMORY, LEN(hdrDummy))
            ptMidiInHdr2 = HeapAlloc(GetProcessHeap(), %HEAP_ZERO_MEMORY, LEN(hdrDummy))
    
            'prepare Midi header1
            @ptMidiInHdr1.lpdata          = HeapAlloc(GetProcessHeap(), %HEAP_ZERO_MEMORY, lBuffSize)
            @ptMidiInHdr1.dwbufferlength  = lBuffSize
            @ptMidiInHdr1.dwbytesrecorded = 0
            @ptMidiInHdr1.dwUser          = 1 'indicates buffer number
            @ptMidiInHdr1.dwFlags         = 0
    
            'prepare Midi header2
            @ptMidiInHdr2.lpdata          = HeapAlloc(GetProcessHeap(), %HEAP_ZERO_MEMORY, lBuffSize)
            @ptMidiInHdr2.dwbufferlength  = lBuffSize
            @ptMidiInHdr2.dwbytesrecorded = 0
            @ptMidiInHdr2.dwUser          = 2 'indicates buffer number
            @ptMidiInHdr2.dwFlags         = 0
    
            midiErr  = midiInPrepareHeader(BYVAL hWndIn,  BYREF @ptMidiInHdr1, LEN(hdrDummy))
            midiErr  = midiInPrepareHeader(BYVAL hWndIn,  BYREF @ptMidiInHdr2, LEN(hdrDummy))
    
            midiErr  = midiInAddBuffer(BYVAL hWndIn, BYREF @ptMidiInHdr1, LEN(hdrDummy))
            midiErr  = midiInAddBuffer(BYVAL hWndIn, BYREF @ptMidiInHdr2, LEN(hdrDummy))
    
            midiErr  = midiInStart(BYVAL hWndIn)
    
        CASE %MM_MIM_DATA
    
            sStatus = MID$(HEX$(lParam, 6), 5, 2)
            sNote   = MID$(HEX$(lParam, 6), 3, 2)
            sVelo   = MID$(HEX$(lParam, 6), 1, 2)
    
            'display regular midi events
            SetWindowText hWnd, sStatus & "," & sNote & "," & sVelo
    
            EXIT FUNCTION
    
        CASE %MM_MIM_LONGERROR
    
             ptMidiInHdrRet = lParam
    
             midiErr = midiInAddBuffer(BYVAL hWndIn, BYREF @ptMidiInHdrRet, BYVAL LEN(hdrDummy))
    
        CASE %MM_MIM_LONGDATA
    
            ptMidiInHdrRet = lParam
    
            IF ISFALSE(lInReset) THEN
    
               'display SysEx midi events
               SetWindowText hWnd, CHR$(@ptMidiInHdrRet.lpdata)
    
               midiErr = midiInAddBuffer(BYVAL hWndIn, BYREF @ptMidiInHdrRet, BYVAL LEN(hdrDummy))
    
            ELSE
    
    
               lBuffRet = lBuffRet + @ptMidiInHdrRet.dwUser
    
               midiErr  = midiInUnprepareHeader(BYVAL hWndIn,  BYREF @ptMidiInHdrRet, LEN(hdrDummy))
    
               midiErr  = HeapFree(GetProcessHeap(), %HEAP_NO_SERIALIZE, @ptMidiInHdrRet.lpdata)
               midiErr  = HeapFree(GetProcessHeap(), %HEAP_NO_SERIALIZE, ptMidiInHdrRet)
    
            END IF
    
           EXIT FUNCTION
    
        CASE %WM_CLOSE
    
            lInReset = %TRUE
    
            midiErr = midiInReset(BYVAL hWndIn)
    
            WHILE lBuffRet <> 3
               DIALOG DOEVENTS 1 TO lCount
            WEND
    
            midiErr = midiInClose(BYVAL hWndIn)
    
        CASE %WM_DESTROY
            PostQuitMessage 0
            EXIT FUNCTION
    
        END SELECT
    
        ' Any message which is not handled in the above SELECT CASE reaches this
        ' point and is processed by the Windows default message handler.
        FUNCTION = DefWindowProc(hWnd, wMsg, wParam, lParam)
    
    END FUNCTION
    //-->
    </script>
    Joerg Koehler

  • #2
    I don't know if this is your problem but...
    Code:
    WHILE lBuffRet <> 3
               DIALOG DOEVENTS 1 TO lCount
    WEND
    Don't use "DDT" syntax when using SDK-style coding; and I would avoid embedding message loops in the window procedure, since you already have a message loop.

    Also, while I know nothing specfic about these 'midi' functions...

    Code:
       midiErr = midiInAddBuffer
       ...
      midiErr  = midiInUnprepareHeader 
     ...
    You are not checking for the success or failure of these functions; e.g. according to the doc for the "unprepareheader" function...
    Returns MMSYSERR_NOERROR if successful or an error otherwise. Possible error values include the following.

    Value Description
    MIDIERR_STILLPLAYING The buffer pointed to by lpMidiInHdr is still in the queue.
    MMSYSERR_INVALPARAM The specified pointer or structure is invalid.
    MMSYSERR_INVALHANDLE
    For sure I'd be checking those returns and if not the "NOERROR" value I'd be doing "something."

    MCM
    Michael Mattias
    Tal Systems (retired)
    Port Washington WI USA
    [email protected]
    http://www.talsystems.com

    Comment


    • #3
      Message loop

      Thank you Michael for your suggestions.

      About the DoEvents stuff...

      The question for me is how to wait within WM_CLOSE for the
      return of the pending buffers? If I send midiInReset, then the driver
      marks all pending buffers as done and sends the pointer to it
      back to the callback by using %MM_MIM_LONGDATA. This could take time
      and is async. Since I don't want to close the midi devices by hand before
      closing the app, i have to do that in WM_CLOSE.
      I suspect, WaitForSingleObject would block the whole thread and
      the message loop would never react to %MM_MIM_LONGDATA, wouldn't it?

      About checking midiInUnprepareHeader...
      The code above is only a sample code. The real code does of course check
      for success or failure. But it is to big to post in here. I could reduce
      the important things to make it simpler to describe.

      Joerg
      Joerg Koehler

      Comment


      • #4
        >The code above is only a sample code. The real code...

        Golly, I love this job....
        Michael Mattias
        Tal Systems (retired)
        Port Washington WI USA
        [email protected]
        http://www.talsystems.com

        Comment


        • #5
          Why should I post hundreds of lines of code if this example code has exactly
          the same problem??? It crashes in the same way!!!
          Joerg Koehler

          Comment


          • #6
            First of all, it's because I would not have bothered to look up those "midi" calls and what they returned had I seen that you were, in fact, doing this 'Programming 101' thing, that's why posting 'sample' code which is not the same as the 'code of interest' is such a crummy thing to do.

            But on to your problem..

            If the idea is you have a user interface, and want to make sure that you close down stuff, and closing down requires you receive and process notifcation messages...

            I would probably run the whole "midi" interface to call back to an invisible, separate window, a window which runs in its own thread of execution.

            But you might be able to get away with 'what you have' by....

            1. When you get WM_CLOSE, disable the main window so no user input can get in
            2. Eat the WM_CLOSE; do not allow the default processing (which is DestroyWindow)
            3. Post a private message to the window which means "effect shutdown" and exit the window procedure.
            4. Process the shutdown message. If you need to set up a PeekMessage() loop to do so, you can do that.
            5. When shutdown processing is complete, post yourself a "shutdown complete" message
            6. When processing the "shutdown complete" message, just call DestroyWindow yourself.

            But I prefer my initial idea... run all the midi* stuff in a separate window which executes in the context of another thread of execution. That will have the "fringe benefit" of keeping the midi* code physically separate from the "hundreds of [other] lines of code" and a lot easier to debug and maintain.

            MCM
            Michael Mattias
            Tal Systems (retired)
            Port Washington WI USA
            [email protected]
            http://www.talsystems.com

            Comment


            • #7
              Okay, but also if the midi stuff runs in a different thread, what he now does,
              still have to wait for the execution of this thread. Can I do this within the WM_CLOSE by using WaitforSingleObject?
              Joerg Koehler

              Comment


              • #8
                >Can I do this within the WM_CLOSE by using WaitforSingleObject

                No. WFSO suspends calling thread until the wait condition is satisfied. Wel, I suppose you could do this if you run all the midi stuff in another thread, but now your user interface won't update in response to moving and you'll get those blank rectangles on the screen.

                Basically I am saying you are to use WM_CLOSE ONLY to initiate your 'shutdown' sequence, and NOT allow WM_CLOSE to destroy the window.

                But since you say you are now using the multi-thread approach....

                You can wait for your shutdown function to end back in WinMain... and there you could use WFSO, waiting on the thread handle.

                eg
                Code:
                FUCNTION WinMain...
                
                  CreateWindow  (User interface)
                  ShowWindow
                
                  THREAD CREATE (midi window create and message loop) ... to hThreadMidi
                
                  ' message loop to process user interface window
                  WHILE GETMESSAGE (...
                
                  WEND
                 ' ------------------------------------------------------------
                '  Program gets here after UI window posts quit message. 
                ' ------------------------------------------------------------
                
                  ' Wait for shutdown function to end before ending program
                   WFSO   hthreadMidi, timeout
                   THREAD CLOSE hThreadMidi TO retvar 
                END FUNCTION  ' winmain
                MCM
                Michael Mattias
                Tal Systems (retired)
                Port Washington WI USA
                [email protected]
                http://www.talsystems.com

                Comment


                • #9
                  Okay, I have to run a shutdown sequence while the main windows is still existing because
                  the threads I am waiting for to determine are dealing with quasi global variables which are stored in a structure and
                  the pointer to this structure is stored in the extra bytes of the main window. This global structure would not exist anymore
                  if I wait for the threads after the main window is destroyed. So now the threads are sending application defined messages to the
                  disabled Main window when they have finished and this works great and reliable.

                  Thank you for your suggestions,

                  Regards,
                  Joerg
                  Joerg Koehler

                  Comment

                  Working...
                  X