News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests
NB: Posting URL's See here: Posted URL Change

Main Menu

Request for MIDI programming info

Started by NoCforMe, July 02, 2012, 05:24:20 AM

Previous topic - Next topic

NoCforMe

I'm doing a little project using MIDI and could use a couple of things:

  • A tutorial on using MIDI commands through Win32
  • A decent MIDI reference manual
My project involves sending individual MIDI commands directly, rather than playing a MIDI stream.  (The one MIDI example I could find in the MASM32 package, in the bcraven folder, simply opens a MIDI file and plays it using INVOKE mciSendCommand, MidDeviceID, MCI_PLAY, blah, blah, blah.) I'd like a little guidance on how to use the "low-level" MIDI functions (I'm using midiOutGetDevCaps(), midiOutOpen(), midiOutShortMsg(), etc.). I've gotten them to work, but I need a little help with some issues, particularly timing (how to easily implement a timing scheme for determining note length).

Don't worry--I'm not trying to create a full-blown MIDI command interpreter. The protocol is far too complicated for that. I'm just trying to do some simple things,  mainly just playing single notes.

The MIDI reference manual I downloaded as PDF isn't very good; it spends far too much time going into details for each command and is badly organized.  Anyone know of a good comprehensive MIDI reference manual out there in Web-land?

Many thanks for useful information here.

dedndave

i would like to learn a little more about the MIDI interface, as well
it seems like it might be a nice way to create morse code tones

however, i can help a little with the timer issues
you can set up a high-resolution multi-media timer with timeSetEvent
it uses a call-back proc
http://msdn.microsoft.com/en-us/library/windows/desktop/dd757631%28v=vs.85%29.aspx
it also has a parameter for resolution
when you are done - timeKillEvent

you can also use timeBeginPeriod and timeEndPeriod to control resolution
setting higher resolution increases system overhead - so use it sparingly

NoCforMe

Quote from: dedndave on July 02, 2012, 06:07:47 AM
however, i can help a little with the timer issues
you can set up a high-resolution multi-media timer with timeSetEvent
it uses a call-back proc
http://msdn.microsoft.com/en-us/library/windows/desktop/dd757631%28v=vs.85%29.aspx
it also has a parameter for resolution
when you are done - timeKillEvent

you can also use timeBeginPeriod and timeEndPeriod to control resolution
setting higher resolution increases system overhead - so use it sparingly

I wonder how much this is different from the (seemingly much simpler) technique I'm using now,  which is simply calling SetTimer() for a particular window, then handling WM_TIMER messages in that window's proc (rather than setting a separate callback function, which you can also do with SetTimer() ).

From previous experience with using timers this way, I find the lower limit to resolution is about 10-20 msec; anything finer than that and the system tends to get bogged down, as you point out. (MIDI only seems to need the equivalent time resolution of a SMPTE frame, something on the order of 1/30 sec., or about 30 msec.)

It just occurred to me that one way to handle timing of MIDI events would be something like this (in pseudocode):

Start playing MIDI note ("note on")
Store note's data (note value) in global var.
Start timer with note's duration in msec.
In timer proc, wait for WM_TIMER event
On receipt of WM_TIMER msg,
   Stop note (send MIDI "note off" cmd), using previous note's value


Of course, this only works for monophonic (single note-at-a-time) playing, which is fine for now.

Ah, I see--from MSDN:
Quote
These timer services are useful for applications that demand high-resolution timing. For example, a MIDI sequencer requires a high-resolution timer because it must maintain the pace of MIDI events within a resolution of 1 millisecond.

Applications that do not use high-resolution timing should use the SetTimer function instead of multimedia timer services. The timer services provided by SetTimer post WM_TIMER messages to a message queue, while the multimedia timer services call a callback function.
(Although that's not entirely correct: one can use a timer callback proc with SetTimer() instead of handling WM_TIMER messages.)

dedndave

i was thinking of using the call-back function to control everything
create a "list" of notes to be played, with varying periods
then let the call-back step through the list
to start it off - timeSetEvent
when it gets to the end of the list timeKillEvent stops it
the multi-media timer runs in it's own thread, so your program can go off and do other things

there is one other issue, which you may have already encountered
that is the volume control
it seems that whenever you run MediaPlayer (or similar), it sets the MIDI level to 0
my thought there was...
get the wave volume level - set the MIDI level to the same percentage
when done - set the MIDI level back to 0 (like everybody else does - lol)

NoCforMe

Ackshooly, that should work for polyphonic playing as well, since the whole thing runs asynchronously. For instance, you could start two notes at once, assigning each a different timer ID, then simply turn each one off at the end of its period in the callback function. (Limited, of course, by total system overhead). Sweet.

Regarding the volume issue, I think this would be a "nicer" way to handle this:

  • Get the current MIDI volume
  • Set the MIDI volume to whatever the user chooses, or some initial default
  • Restore the original MIDI volume at program exit

(The WAV volume might be set to zero initially)

jj2007

Choose carefully the timer function... timeSetEvent:
QuoteThe multimedia timer runs in its own thread.
...
Note  This function is obsolete. New applications should use CreateTimerQueueTimer

It's the usual M$ tactics. They both do roughly the same, but the new one has two args more for various more or less useful flags.

By the way: Setting up the callback function is trivial:

invoke SetTimer, hWnd, id, ms, CbTimer
...
CbTimer proc hwnd:DWORD, uMsg:DWORD, PMidEvent:DWORD, dwTime:DWORD
  print "it's me", 13, 10
  ret
CbTimer endp

dedndave

the queue timer functions don't seem to be as reliable/stable as the older funtions

NoCforMe

Quote from: jj2007 on July 02, 2012, 07:18:22 AM
Choose carefully the timer function... timeSetEvent:
QuoteThe multimedia timer runs in its own thread.
...
Note  This function is obsolete. New applications should use CreateTimerQueueTimer

It's the usual M$ tactics. They both do roughly the same, but the new one has two args more for various more or less useful flags.

I think I'd need to use the newer function (CreateTimerQueueTimer() ), because the other one creates a "generic" (unidentified) timer, unless I've read the spec wrong. In other words, CreateTimerQueueTimer() returns a handle for each timer, in addition to passing a parameter to the callback function. This would allow me to use a single callback function, and to determine which MIDI note the timer event was for based on this parameter.

dedndave

oh - should have mentioned that
rather than you specifying a timer ID, the function supplies one (the return value)
that ID is passed to timeKillEvent

NoCforMe

#9
Got it (just read that part of the function description).

The advantage with using CreateTimerQueueTimer() is that I can specify the parameter to be something meaningful to me (like the ID of a MIDI note), rather than having to interpret a random unique value handed to me by Windoze.

The following comment left on the MSDN page for timeSetEvent() seems to sum up the business of this function being declared "obsolete" pretty well:
Quote
Rumors of the death of timeSetEvent() seem to be exaggerated
Although the timeSetEvent() API call has been officially deprecated for some time, it still seems to be the only reliable, high-resolution timer available in Windows. I (and lots of other folks: http://cboard.cprogramming.com/windows-programming/104677-createtimerqueuetimer.html) have tried using the recommended CreateTimerQueueTimer(), but the behavior is simply not the same, and does not seem to work as reliably. I've reluctantly returned to using timeSetEvent().
(This comment was left about 2 years ago.)

MichaelW

The timer queue timers seem to work essentially like the other timers, and have the same 10 or 15.6 ms resolution.

;==============================================================================
    include \masm32\include\masm32rt.inc
    include \masm32\include\winmm.inc
    includelib \masm32\lib\winmm.lib
;==============================================================================

;-----------------------------------------------------------------------------
; This macro more or less duplicates the functionality of the BASIC language
; TIMER function, returning the elapsed seconds since the performance counter
; started counting as a REAL8 value with an effective resolution of a few
; microseconds.
;-----------------------------------------------------------------------------

timer MACRO
    IFNDEF _timer_pc_frequency_
        .data
            align 8
            _timer_pc_frequency_      dq 0
            _timer_pc_count_          dq 0
            _timer_elapsed_seconds_   dq 0
            _timer_initialized_       dd 0
        .code
    ENDIF
    .IF _timer_initialized_ == 0
        invoke QueryPerformanceFrequency, ADDR _timer_pc_frequency_
        inc _timer_initialized_
    .ENDIF
    invoke QueryPerformanceCounter, ADDR _timer_pc_count_
    fild _timer_pc_count_
    fild _timer_pc_frequency_
    fdiv
    fstp _timer_elapsed_seconds_
    EXITM <_timer_elapsed_seconds_>
ENDM

;==============================================================================
    .data
        r8          REAL8 0.0
        t1          REAL8 0.0
        hTimerQueue dd    0
        phNewTimer  dd    0
        param       dd    123
    .code
;==============================================================================

TimerProc proc lpParameter:PVOID, junk:DWORD
    fld timer()
    fsub t1
    fstp r8
    printf("%1.4f s\n", r8)
    fld timer()
    fstp t1
    ret
TimerProc endp

;==============================================================================
start:
;==============================================================================

    invoke Sleep, 3000

    invoke CreateTimerQueue
    mov hTimerQueue, eax
    .IF eax == 0
        printf("CreateTimerQueue error %s\n",LastError$())
    .ENDIF

    ;invoke timeBeginPeriod, 1

    invoke CreateTimerQueueTimer, addr phNewTimer, hTimerQueue,
                                  TimerProc, addr param,
                                  1, 1, 0
    .IF eax == 0
        printf("CreateTimerQueueTimer error %s\n",LastError$())
    .ENDIF

    invoke Sleep, 2000

    ;invoke timeEndPeriod, 1

    invoke DeleteTimerQueueEx, hTimerQueue, INVALID_HANDLE_VALUE
    .IF eax == 0
        printf("DeleteTimerQueueEx error %s\n",LastError$())
    .ENDIF

    inkey
    exit

;==============================================================================
end start
Well Microsoft, here's another nice mess you've gotten us into.

Zen

NoCforMe,
About a year ago, I started into the whole MIDI universe, by downloading the DXi SDK from Cakewalk. This COM/C++ code demonstrates how to write a DXi Audio Plug-In, or, a Software Synthesizer. It's HUGE. You can read all about it and download the source code here: Cakewalk DirectX Developer SDK. Information about what Cakewalk calls its MIDI Effects Processor (MFX) can be found here: About MFX, Cakewalk Delvelopment. At the time I had one of Cakewalk's Home Studio Audio Editors (Sonar), which is designed for songwriters; it stores data in MIDI format, and exports music files in Wave, MP3, or Several MIDI file formats.
I had a difficult time locating a decent source of information about the MIDI formats themselves. Apparently, there is not a huge demand from programmers for technical MIDI specification information. But, I found this book in Borders: MIDI Power!: The Comprehensive Guide, by Robert Guerin. It's basic,...but, describes simple format used in most of the MIDI messages,...and then describes the necessary and most useful of the individual MIDI messages pretty well.
As I recall, MIDI has an internal method for synchronizing everything. It's very important to understand this. It's also important to understand the difference between the MIDI file format (used for storing song data), and, the format that is used for connecting MIDI compatible devices together.
Also, you should read up on software synthesizers, since the MIDI protocol is just a message protocol, and, will not actually produce any music. They are compiled as plug-in DLLs, and there are several different standards. The Windows operating system comes with: Microsoft GS Wavetable SW Synth. I think they are registered as a COM DirectShow Audio Filter. At least that was what existed on Windows XP. Newer versions of the Windows Operating System (and DirectX) have organized things differently.