Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations gkittelson on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Winmm Midiin translate from VB to VFP 2

Status
Not open for further replies.

Jan Flikweert

Programmer
Mar 20, 2022
85
NL
Hi all,

I am working on a midiin solution using WINAPI winmm.dll. I mange to open,start and close midiinput. I do not manage to get response on midiinput messages, except that VFP crashes. Here I have the ost important parts of a VB example, hoping this will help.

I can provide the not fully working solution. For those who are interested I have a solution using MIDI-OX. The goal of my project is creating a Virtual Pipe Organ using REAPER (a digital audio workstation) and Garritan Aria audio player. Using MIDI-OX this works. Now with winmm.dll

Who can give me a help, a hint?

Code:
Public Declare Function midiInOpen Lib "winmm.dll" (ByRef hMidiIn As Integer, ByVal uDeviceID As Integer, ByVal dwCallback As MidiInCallback, ByVal dwInstance As Integer, ByVal dwFlags As Integer) As Integer
Public Delegate Function MidiInCallback(ByVal hMidiIn As Integer, ByVal wMsg As UInteger, ByVal dwInstance As Integer, ByVal dwParam1 As Integer, ByVal dwParam2 As Integer) As Integer
Public ptrCallback As New MidiInCallback(AddressOf MidiInProc)
Public Const CALLBACK_FUNCTION As Integer = &H30000
Public Const MIDI_IO_STATUS = &H20

Function MidiInProc(ByVal hMidiIn As Integer, ByVal wMsg As UInteger, ByVal dwInstance As Integer, ByVal dwParam1 As Integer, ByVal dwParam2 As Integer) As Integer
	If MonitorActive = True Then
		TextBox1.Invoke(New DisplayDataDelegate(AddressOf DisplayData), New Object() {dwParam1})
	End If
End Function

Private Sub ComboBox1_SelectedIndexChanged(sender As System.Object, e As System.EventArgs) Handles ComboBox1.SelectedIndexChanged
	ComboBox1.Enabled = False
	Dim DeviceID As Integer = ComboBox1.SelectedIndex
	midiInOpen(hMidiIn, DeviceID, ptrCallback, 0, CALLBACK_FUNCTION Or MIDI_IO_STATUS)
	midiInStart(hMidiIn)
	MonitorActive = True
	Button2.Text = "Stop monitor"
End Sub

And here the VFP code:
Code:
CLOSE ALL
CLEAR ALL

#DEFINE MAXPNAMELEN 32
#DEFINE MIDI_STATUS_PLAYNOTE 9
#DEFINE MIDI_STATUS_PATCH 12
#DEFINE CALLBACK_NULL 0
#DEFINE  CALLBACK_FUNCTION    0x30000        &&  dwCallback is a FARPROC
#DEFINE  CALLBACK_NULL    0x0                &&  no callback
#DEFINE  CALLBACK_TASK    0x20000            &&  dwCallback is a HTASK
#DEFINE  CALLBACK_TYPEMASK    0x70000        &&  callback type mask
#DEFINE  CALLBACK_WINDOW    0x10000          &&  dwCallback is a HWND
#DEFINE  MIM_CLOSE 962
#DEFINE  MIM_OPEN 961
#DEFINE  MIM_DATA 963
#DEFINE MIM_MOREDATA 972
#DEFINE MIM_LONGDATA 964
#DEFINE MIDI_IO_STATUS 32
#DEFINE MIDI_ERROR 965
#DEFINE MIDI_LONGERROR 966
#DEFINE MMSYSERR_ERROR 1
#DEFINE MMSYSERR_BADDEVICEID 2
#DEFINE MMSYSERR_NOTENABLED 3
#DEFINE MMSYSERR_ALLOCATED 4
#DEFINE MMSYSERR_INVALHANDLE 5
#DEFINE MMSYSERR_NODRIVER 6
#DEFINE MMSYSERR_NOMEM 7
#DEFINE MMSYSERR_NOTSUPPORTED 8
#DEFINE MMSYSERR_BADERRNUM 9
#DEFINE MMSYSERR_INVALFLAG 10
#DEFINE MMSYSERR_INVALPARAM 11
#DEFINE MMSYSERR_HANDLEBUSY 12
#DEFINE MMSYSERR_INVALIDALIAS 13
#DEFINE MMSYSERR_BADDB 14
#DEFINE MMSYSERR_KEYNOTFOUND 15
#DEFINE MMSYSERR_READERROR 16
#DEFINE MMSYSERR_WRITEERROR 17
#DEFINE MMSYSERR_DELETEERROR 18
#DEFINE MMSYSERR_VALNOTFOUND 19
#DEFINE MMSYSERR_NODRIVERCB 20
#DEFINE MMSYSERR_BASE  0
PUBLIC result AS LONG,HMIDIOUT AS LONG ,outHandle AS LONG, inHandle AS LONG, hDevice AS Long,Vrsltn,Hrsltn
Vrsltn=Sysmetric(2)
Hrsltn=Sysmetric(1)
_SCREEN.WIDTH=SYSMETRIC(1)
_SCREEN.HEIGHT=SYSMETRIC(2)
#INCLUDE vfp2c.h
SET LIBRARY TO vfp2c32.fll ADDITIVE 
DO declare
*PUBLIC nloopmidia,nCME

PUBLIC Cmeout
LOCAL nCount, nIndex, nBufsize, cBuffer
nCount = midiOutGetNumDevs()
FOR nIndex=0 TO nCount-1
	nBufsize = 1024
	cBuffer = REPLICATE(CHR(0), nBufsize)
	IF midiOutGetDevCaps(nIndex, @cBuffer, nBufsize) = 0
		LOCAL oMidiOutCaps As MIDIOUTCAPS
		oMidiOutCaps = CREATEOBJECT("MIDIOUTCAPS",@cBuffer)
		DO CASE
			CASE oMidiOutCaps.szPname=="CME U2MIDI" && Please replace with your midi input device
			nCmeout=nIndex+1
		ENDCASE
	ENDIF
NEXT

PUBLIC nCmein, HDMIIN
nCount = midiInGetNumDevs()
FOR nIndex=0 TO nCount-1
	nBufsize = 1024
	cBuffer = REPLICATE(CHR(0), nBufsize)
	IF midiInGetDevCaps(nIndex, @cBuffer, nBufsize) = 0
		LOCAL oMidiInCaps As MIDIINCAPS
		oMidiInCaps = CREATEOBJECT("MIDIINCAPS",@cBuffer)
		DO CASE
			CASE oMidiInCaps.szPname=="CME U2MIDI"
			nCmein=nIndex+1
		ENDCASE
	ENDIF
NEXT

PUBLIC loCallback,dwInstance,ptrCallback
PUBLIC iMsg,lParam1,lParam2
dwInstance=0
loCallBack = CREATEOBJECT('cls_callback')
ptrCallback=loCallBack.Address
nResult1 = midiInOpen(@hDevice,0, ptrCallback, 0, CALLBACK_FUNCTION + MIDI_IO_STATUS  )
MESSAGEBOX("Midi In Open: "+STR(nResult1))
nResult2 = midiInStart(hDevice)
MESSAGEBOX("Midi In Start:"+STR(nResult2))
nResult2 = midiInStart(hDevice)
*!*	test=.t.
*!*	DO WHILE test=.t.
*!*		ON KEY test=.f.
*!*		sleep(10)
*!*	ENDDO
READ EVENTS
MidiInStop(hDevice)
MidiInReset(hDevice)
midiInClose(hDevice)
loCallBack.Destroy
CLEAR EVENTS
*DEFINE CLASS cls_callback AS Exception
DEFINE CLASS cls_callback AS Session
	Address = 0
	Datasession=2
	FUNCTION Init
		THIS.Address = CreateCallbackFunc('Test_Callback_function','VOID','INTEGER,INTEGER,INTEGER,INTEGER,INTEGER',THIS)		
	ENDFUNC

	FUNCTION Destroy
		IF THIS.Address != 0
			DestroyCallbackFunc(THIS.Address)
		ENDIF
	ENDFUNC
	
	FUNCTION Test_Callback_function(hDevice,iMsg,dwInstance,lParam1,lParam2)
		DO CASE
			CASE hDevice>0
				_VFP.AutoYield = .F.
				MESSAGEBOX(STR(hDevice))
				_VFP.AutoYield = .T.
			CASE lParam1>0
				_VFP.AutoYield = .F.
				MESSAGEBOX(STR(lParam1))
				_VFP.AutoYield = .T.
			CASE lParam2>0
				_VFP.AutoYield = .F.
				MESSAGEBOX(STR(lParam2))				
				_VFP.AutoYield = .T.
			CASE iMsg=MIM_DATA
				_VFP.AutoYield = .F.
				MESSAGEBOX("Data: De callback functie werd gebruikt voor data input."+CHR(13)+STR(lParam1))
				_VFP.AutoYield = .T.
			CASE iMsg=MIM_OPEN
				_VFP.AutoYield = .F.
				MESSAGEBOX("Open: De callback functie werd gebruikt voor open input.")
				_VFP.AutoYield = .T.
			CASE iMsg=MIM_CLOSE
				_VFP.AutoYield = .F.
				MESSAGEBOX("Open: De callback functie werd gebruikt voor close input.")
				_VFP.AutoYield = .T.
			CASE iMsg=MIM_MOREDATA
				_VFP.AutoYield = .F.
				MESSAGEBOX("More: De callback functie werd gebruikt voor more input."+CHR(13)+STR(lParam1))
				_VFP.AutoYield = .F.
			CASE iMsg= MIM_LONGDATA				
				_VFP.AutoYield = .F.
				MESSAGEBOX("Data: De callback functie werd gebruikt voor long data input."+CHR(13)+STR(lParam1))
				_VFP.AutoYield = .T.
			CASE iMsg= MIDI_ERROR			
				_VFP.AutoYield = .F.
				MESSAGEBOX("Error")
				_VFP.AutoYield = .T.
			CASE iMsg= MIDI_LONGERROR			
				_VFP.AutoYield = .F.
				MESSAGEBOX("Error")
				_VFP.AutoYield = .T.
			OTHERWISE
				_VFP.AutoYield = .F.
				MESSAGEBOX("Other: "+STR(lParam1))
				_VFP.AutoYield = .T.
		ENDCASE
	ENDFUNC
ENDDEFINE

DEFINE CLASS MIDIINCAPS As Session
#DEFINE MAXPNAMELEN 32
	wMid=0
	wPid=0
	vDriverVersion=0
	szPname=""
	wTechnology=0
	wVoices=0
	wNotes=0
	wChannelMask=0
	dwSupport=0

PROCEDURE Init(cBuffer)
	THIS.wMid = buf2word(SUBSTR(cBuffer, 1, 2))
	THIS.wPid = buf2word(SUBSTR(cBuffer, 3, 2))
	THIS.vDriverVersion = buf2dword(SUBSTR(cBuffer, 5, 4))

	THIS.szPname = SUBSTR(cBuffer, 9, MAXPNAMELEN) + CHR(0)
	THIS.szPname = SUBSTR(THIS.szPname, 1, AT(CHR(0),THIS.szPname)-1)

	THIS.wTechnology = buf2word(SUBSTR(cBuffer, MAXPNAMELEN+9, 2))
	THIS.wVoices = buf2word(SUBSTR(cBuffer, MAXPNAMELEN+11, 2))
	THIS.wNotes = buf2word(SUBSTR(cBuffer, MAXPNAMELEN+13, 2))
	THIS.wChannelMask = buf2word(SUBSTR(cBuffer, MAXPNAMELEN+15, 2))
	THIS.dwSupport = buf2dword(SUBSTR(cBuffer, MAXPNAMELEN+17, 4))

ENDDEFINE

*
FUNCTION buf2dword(cBuffer)
RETURN Asc(SUBSTR(cBuffer, 1,1)) + ;
	BitLShift(Asc(SUBSTR(cBuffer, 2,1)),  8) +;
	BitLShift(Asc(SUBSTR(cBuffer, 3,1)), 16) +;
	BitLShift(Asc(SUBSTR(cBuffer, 4,1)), 24)

FUNCTION buf2word(lcBuffer)
RETURN Asc(SUBSTR(lcBuffer, 1,1)) + ;
       Asc(SUBSTR(lcBuffer, 2,1)) * 256  
       
PROCEDURE declare
Declare INTEGER midiInStart In Winmm integer hmi
Declare INTEGER midiInStop In Winmm integer hmi
Declare Integer midiOutGetNumDevs In Winmm
Declare INTEGER midiInGetNumDevs In Winmm
Declare Integer midiOutClose In Winmm Integer hmo
Declare INTEGER midiInClose In Winmm Integer hmi
Declare Integer midiOutReset In Winmm Integer hmo
Declare INTEGER midiInReset In Winmm integer hmi 
Declare Sleep In kernel32 Integer dwMilliseconds
DECLARE INTEGER midiOutGetDevCaps IN Winmm;
	INTEGER uDeviceID, STRING @lpMidiOutCaps,;
	INTEGER cbMidiOutCaps
Declare INTEGER midiInGetDevCaps In Winmm;
	INTEGER uDeviceID, STRING @lpMidiInCaps,;
	INTEGER cbMidiInCaps
Declare Integer midiOutOpen In Winmm;
	INTEGER @lphmo, Integer uDeviceID, Integer dwCallback,;
	INTEGER dwCallbackInstance, Integer dwFlags
Declare INTEGER midiInOpen In Winmm;
	INTEGER @hDevice, INTEGER nDevice,;
	INTEGER ptrCallback , INTEGER dwInstance, INTEGER dwFlags
Declare Integer midiOutShortMsg In Winmm;
	INTEGER hmo, Long dwMsg
Declare INTEGER midiInMessage In Winmm;
	INTEGER hmi, Long midiInMsg , Integer para1 , integer para2
RETURN

Kind regards,

Jan Flikweert
 
Chriss,

MIDI_IO_STATUS+CALLBACK_WINDOW+CALLBACK_FUNCTION are flags from WINDAPI midiinopen.

Kind regards,

Jan Flikweert
 
Yes, I know, I linked to its definition early on.


Value Meaning
CALLBACK_FUNCTION The dwCallback parameter is a callback procedure address.
...
CALLBACK_WINDOW The dwCallback parameter is a window handle.

When you specify the flag CALLBACK_FUNCTION, the parameter DWORD_PTR dwCallback has to be a callback address and the mechanism used is C function callbacks.
When you specify the flag CALLBACK_WINDOW - as I did - the parameter DWORD_PTR dwCallback has to be a HWND and the mechanism used is windows messages.

Hasn't that become clear before already?

Note in that case the type of the parameter isn't a DWORD_PTR, it's a HWND. This only works in the world of strongly typed languages as C because both are an INTEGER in the end.
Also note once more that telling midiInOpen a HWND doesn't mean all messages come to the hwnd, in VFP you have to use a BINDEVENT per message you want to receive. Messages that in fact get sent to the hwnd do not trigger the eventhandler when you don't subscribe to them.

If you want to get a call to a windows event handler for any message coming to it, you'd have to make thousands of BINDEVENT() calls, one for each message constant.

Now simply wait. I think when I know how to use it you'll be able to adapt it to your needs and many questions you have right now likely will be answered in one go. Right now, as I see it, you don't have really understood what we did so far and how that's only windows messages and not callbacks. It's quite a similar mechanism, but windows messages don't work by a pointer to an address as C callbacks.

Chriss
 
Chriss,

Chris miller said:
Hasn't that become clear before already?

Yes that is clear.

Here a minimal piece of code which gives a message when there is no midiin device on your system. When there is a midi in device it opens the first device,prints 961 it waits for input. Pressing a key will close the mididin and give 962.

Code:
#Define  CALLBACK_FUNCTION    196608        &&  dwCallback is a FARPROC
#Define MIDI_IO_STATUS  32
#INCLUDE vfp2c.h
#INCLUDE VFP2CCALLBACK.h
Set Library To vfp2c32.fll Additive && Library with callback funtion
Do Declare
***Main***
If midiInGetNumDevs()>0
	Local loCallBack
	Public nDevice,hDevice
	nDevice=0
	loCallBack = Createobject('cls_callback')
	dwCallback=loCallBack.Address
	nResult_midi_open = midiInOpen(@hDevice,nDevice,dwCallback, 0,CALLBACK_FUNCTION+MIDI_IO_STATUS)
	nResult_midi_start = midiInStart(hDevice)
	testkeypress=.T.
	?"Press a key to close the program."
	Do While testkeypress=.T.
		On Key testkeypress=.F.
		sleep(1)
	Enddo
*READ EVENTS
	nResult_midi_in_stop = midiInStop(hDevice)
	nResult_midi_in_close = midiInClose(hDevice)
ELSE
	MESSAGEBOX("You have no MIDI in device connected to your system.")
Endif
***Class definition***
Define Class cls_callback As Exception
	Address = 0

	Function Init
	This.Address = CreateCallbackFunc('MidiInProc','VOID','LONG,LONG,LONG,LONG,LONG',This)
	Endfunc

	Function Destroy
	If This.Address != 0
		DestroyCallbackFunc(This.Address)
	Endif
	Endfunc

	Function MidiInProc(hDevice,uMsg,dwInstance,DataByte1,DataByte2)
	If uMsg<>0
		?uMsg
	Endif
	Endfunc
Enddefine

***Declare winapi***
Procedure Declare
Declare Integer midiInStart In Winmm Integer hmi
Declare Integer midiInStop In Winmm Integer hmi
Declare Integer midiInClose In Winmm Integer hmi
Declare Sleep In kernel32 Integer dwMilliseconds
Declare Integer midiInOpen In "winmm.dll";
	INTEGER @lphMidiIn ,  Integer uDeviceID , Integer dwCallback , Integer dwInstance , Integer dwFlags
Declare Integer midiInGetNumDevs In Winmm

Kind regards,

Jan Flikweert
 
Good, but I orient myself on the VB code.

I don't yet have a midi device per your code. Now just please wait.

Chriss
 
Just btw, you have it!

That's using midiInOpen and midiStart with a callback address.

That's the way to use callbacks, but there are more details, like that VB mechanism of
Code:
TextBox1.Invoke(New DisplayDataDelegate(AddressOf DisplayData), New Object() {dwParam1})
And what to do in VFP at that point. Right?

If that has become unimportant you have all you need, it seems, but don't get note on/off working. I'll take that into account.

By the way, rereading a lot here I get to questions I didn't answer yet:
Jan Flikweert said:
Where is the message stored in the _SCREEN.HWnd?
Messages are sent and received. When they are received they are processed, not stored. There is a message queue, you could see that as a storage of messages and their data, while they are not yet processed, but in a typical case this isn't a queue of a busy shop, events are processed when they arrive.

The sender can be windows, a device like a midi device or mouse, the filesystem, anything. Messages are not stored in a HWND, HWND is the address they are sent to and with the BINDEVENT call you tell which method of which object should be called when a certain message constant is received by the HWND, That method would be responsible for storing things that arrive, not BINDEVENTS. BINDEVENT specifies which message (really just one message constant) to a hwnd should be forwarded by making a handler call. You specify to which object and which method of it is called. And that will then happen until UNBINDEVENT is called or the handler object is released. I also think if the window with the HWND is released.

Anyway, that's a kind of callback this way, turning a message received of a HWND (window) to a call is done in VFP, Windows only acts on the level of the messages system. C Callbacks in contrast mean no intermediate thing like the message queue. When C uses the callback address this means a direct call of whatever is at that address in RAM, hopefully code that can run directly, not the object code of VFP that needs to be interpreted by the VFP runtime. Therefore the need of the FLL for creating such "pure" cdecl callbacks and telling you at which RAM address they are for passing it into whatever needs such a callback address.
 
Chriss,

Chris Miller said:
And what to do in VFP at that point. Right?

Normally i should say get the pointer of the parameters.

But there is nothing to retrieve, because the callback is not fired.

Kind regards,

Jan Flikweert
 
I have a start in a reaction from VFP, it's just that it crashes.

I did install several things, first the mid file was taken by VLC player, which uses fluidsynth, by the way. So that already is installed with VLC.a
I don't think it matters.

The major component I needed to even get a count of midi devices from midiInGetNumDevs() was loopMIDI to create virtual Midi ports.
I also installed a virtual MIDI instrument: VMPK (Virtual Midi Piano Keyboard) from
So far I see at least one problem in your code to not inintialize hDevice to 0, midiInOpen() sets it from .F. to .T. but not a number that's necessary for midiInOpen.
What you didn't do is output the result of the API calls, how do you know they work, if you don't check that they are as expected.
For example midiInOpen() should return 0 to nResult_midi_open.

Besides that I changed from a while loop to READ EVENTS. ON KEYBOARD ALT+F4 CLEAR EVENTS exits, it's also printed on screen as instruction.

Code:
#Define  CALLBACK_FUNCTION    196608        &&  dwCallback is a FARPROC
#Define MIDI_IO_STATUS  32
#INCLUDE vfp2c.h
#INCLUDE VFP2CCALLBACK.h

Set Library To vfp2c32.fll Additive && Library with callback funtion
Do Declare
***Main***
CLEAR 
LOCAL lnDevices
lnDevices= midiInGetNumDevs()
? 'number of devices',lnDevices

If lnDevices>0 
	Local loCallBack
	Public nDevice,hDevice
	nDevice=0 && unsure if 0 is okay if you only have 1 device.
	hDevice=0 && necessary to also set this 0
	loCallBack = Createobject('cls_callback')
	dwCallback = loCallBack.Address
	nResult_midi_open = midiInOpen(@hDevice,nDevice,dwCallback,0,CALLBACK_FUNCTION)
	? 'devicehandle',hDevice
	nResult_midi_start = midiInStart(hDevice)
	? 'midi start result:',nResult_midi_start
	ON KEY LABEL ALT+F4 CLEAR EVENTS
	? 'Press ALT+F4 to exit'
	READ EVENTS
	nResult_midi_in_stop = midiInStop(hDevice)
	nResult_midi_in_close = midiInClose(hDevice)
	? 'stop result:',nResult_midi_in_stop
	? 'close result:',nResult_midi_in_close
	? 'Done'
ELSE
	MESSAGEBOX("You have no MIDI in device connected to your system.")
ENDIF
***Class definition***
Define Class cls_callback As Exception
	Address = 0

	Function Init
	   This.Address = CreateCallbackFunc('MidiInProc','VOID','LONG,LONG,LONG,LONG,LONG',This)
	Endfunc

	Function Destroy
	   If This.Address != 0
		  DestroyCallbackFunc(This.Address)
	   Endif
	Endfunc

	Function MidiInProc(hDevice,uMsg,dwInstance,DataByte1,DataByte2)
             ? 'midiin',hDevice,uMsg,dwInstance,DataByte1,DataByte2	
	Endfunc
Enddefine

***Declare winapi***
Procedure Declare
Declare Integer midiInStart In Winmm Integer hmi
Declare Integer midiInStop In Winmm Integer hmi
Declare Integer midiInClose In Winmm Integer hmi
Declare Sleep In kernel32 Integer dwMilliseconds
Declare Integer midiInOpen In "winmm.dll";
	INTEGER @lphMidiIn ,  Integer uDeviceID , Integer dwCallback , Integer dwInstance , Integer dwFlags
Declare Integer midiInGetNumDevs In Winmm

I changed MidiInProc to just output the parameters to screen. So far the only output I got was from midiInStart.

I'm not sure how to configure the VMPK keyboard. I guess VFP crashing may not be because of an error in this code but how I configure the VMPK midi in/out etc.:
VMPK_Setup_frpwgm.png

loopMidi is the virtual midi port created by
As far as I understand this I have configured the keyboard to output to this port so VFP gets callbacks. And that it works with the message 961:
midicallbacks_l5ajzo.png


When I now just play a single note, loopMidi tells me there are some data bytes on the virtual port and VFP crashes after a few seconds.
If I don't play anything and just end the VFP code with ALT+F4 I get the message 962, as you.

Not sure what's wrong with the callback. That it works with midiInOpen() and midiInStop() already shows that it's defined okay and the callback works.
I really wonder whether the callbacks that come through midi port activity should get their own other address and definition of parameters to work correctly or whether it's a problem with the configuration or even worse with the drivers.

You mentioned vfp2c32.fll having a bug fixed in 2.0.0.19. Well, I downloaded it from the link given on the github homepage: The fll contained in it is still version 2.0.0.18 (as can be seen when you chage extension from fll to dll).

I don't guess that's the problem, because the callbacks caused by midiInOpen and midiInClose don't crash.

Chriss
 
Chriss,

Windows API counts from 0. So if there are midi devices connected, my code processes the first midi in device by sending 0.

I provided you a most simple code to keep things easy.

So nDevice = 0 means the first device.
hDevice needs no setting. midiinopen assigns a handle to hDevice in the first paramter of MidiInOpen.

You configured midi VMPK good.

You reproduced the same thing as I did. Midi in open 961 and midi close 962.

On one hand I am not shore if VFP2C32 causes the trouble, because the error which is solved in 2.0.0.19 concerns the last parameter in This.Address = CreateCallbackFunc('MidiInProc','VOID','LONG,LONG,LONG,LONG,LONG',[highlight #EF2929]This[/highlight])
When there is an optional parameter needed, it does not accept a null value.

On the other hand: could it be possible that midiinopen en midiinclose uses only the first two parameters. For the played notes it requires espesially the fourth parameter? In other words the functions does not process the fourth and fifth parameter. The callback example uses 2 parameters.

Kind regards,


Jan Flikweert

 
Chriss,

Using VMPK you can disable MidiInput and Omni mode. You play from the screen and therefore do not use midi input ports in the program.

This does not cause a problem.

You see in loopmidi traffic.

The fact that VFP crashes is the same behaviour as here.

It is adviced to check if vfp should receive midi. This can be done with the exe file which is also in the zip file of VB monitor. There you can choose a input device and see messages. These messages are when you choose decimal 3 digits for each note. f.e. 144 36 100.

Kind regards,

Jan Flikweert
 
Chriss,

I modified your last VFP code. I added a FOR loop to choose loopMIDI port as input port. Ofcourse you can change that name. This is need to ensure that the program listens really to loopMIDI port.

Code:
#Define  CALLBACK_FUNCTION    196608        &&  dwCallback is a FARPROC
#Define MIDI_IO_STATUS  32
#INCLUDE vfp2c.h
#INCLUDE VFP2CCALLBACK.h

Set Library To vfp2c32.fll Additive && Library with callback funtion
Do Declare
***Main***
Clear
Local lnDevices
Public nDevice
lnDevices= midiInGetNumDevs()
For nIndex=0 To lnDevices-1
	nBufsize = 1024
	cBuffer = Replicate(Chr(0), 1024)
	If midiInGetDevCaps(nIndex, @cBuffer,nBufsize ) = 0
		szPname = Substr(cBuffer, 9, 32)
		szPname = Substr(szPname , 1, At(Chr(0),szPname)-1)
		Do CASE
		Case szPname == "loopMIDI Port"
			nDevice=nIndex
		Otherwise
		Endcase
	Endif
Next
? 'number of devices',lnDevices
If lnDevices>0
	Local loCallBack
	Public hDevice
	hDevice=0 && necessary to also set this 0
	loCallBack = Createobject('cls_callback')
	dwCallback = loCallBack.Address
	nResult_midi_open = midiInOpen(@hDevice,nDevice,dwCallback,0,CALLBACK_FUNCTION)
	? 'devicehandle',hDevice
	nResult_midi_start = midiInStart(hDevice)
	? 'midi start result:',nResult_midi_start
testkeypress=.t.	
DO WHILE testkeypress=.t.
	ON KEY testkeypress=.f.
	sleep(1)
ENDDO
	nResult_midi_in_stop = midiInStop(hDevice)
	nResult_midi_in_close = midiInClose(hDevice)
	? 'stop result:',nResult_midi_in_stop
	? 'close result:',nResult_midi_in_close
	? 'Done'
Else
	Messagebox("You have no MIDI in device connected to your system.")
Endif
***Class definition***
Define Class cls_callback As Exception
	Address = 0

	Function Init
	This.Address = CreateCallbackFunc('MidiInProc','VOID','LONG,LONG,LONG,LONG,LONG',This)
	Endfunc

	Function Destroy
	If This.Address != 0
		DestroyCallbackFunc(This.Address)
	Endif
	Endfunc

	Function MidiInProc(hDevice,uMsg,dwInstance,DataByte1,DataByte2)
	? 'midiin',hDevice,uMsg,dwInstance,DataByte1,DataByte2
	Endfunc
Enddefine

***Declare winapi***
Procedure Declare
Declare Integer midiInStart In Winmm Integer hmi
Declare Integer midiInStop In Winmm Integer hmi
Declare Integer midiInClose In Winmm Integer hmi
Declare Sleep In kernel32 Integer dwMilliseconds
Declare Integer midiInOpen In "winmm.dll";
	INTEGER @lphMidiIn ,  Integer uDeviceID , Integer dwCallback , Integer dwInstance , Integer dwFlags
Declare Integer midiInGetNumDevs In Winmm 
Declare INTEGER midiInGetDevCaps In Winmm;
	INTEGER uDeviceID, STRING @lpMidiInCaps,;
	INTEGER cbMidiInCaps

Kind regards,


Jan Flikweert
 
As I only have one device and the hDevice handle is a sensible handle number and not 0 or .T. or anything that midiInOPen rejects, I think I'm fine.

But I would have looked for ways to enumerate devices and get their names, so thanks for posting that.

How's that doing on your PC?


Chriss
 
Chriss,

I ran VMPK and outputed to loopMIDI port. The VB Midimonitor works fine! See next print screen. Note that the valuas are HEX. So 90 is Decimal 144. 80 is Decimal 128. That are note on and off messages.

Naamloos_vfjjy1.jpg


I attached Midi monitor.exe:

Midi Monitor

Now I am going to try the code I posted to you.

Kind regards,

Jan Flikweert
 
 https://files.engineering.com/getfile.aspx?folder=6dcefe40-0595-409a-abbc-502a8249eb5d&file=MIDI_monitor.exe
Chriss,

I can conform that here VFP also crashes the same way using VMPK.

Due to errors I updated the last code I provided. I changed the on key statement because after pressing ALT+F4 the program keeps reading events.

Code:
#Define  CALLBACK_FUNCTION    196608        &&  dwCallback is a FARPROC
#Define MIDI_IO_STATUS  32
#INCLUDE vfp2c.h
#INCLUDE VFP2CCALLBACK.h

Set Library To vfp2c32.fll Additive && Library with callback funtion
Do Declare
***Main***
Clear
Local lnDevices
Public nDevice
lnDevices= midiInGetNumDevs()
For nIndex=0 To lnDevices-1
	nBufsize = 1024
	cBuffer = Replicate(Chr(0), 1024)
	If midiInGetDevCaps(nIndex, @cBuffer,nBufsize ) = 0
		szPname = Substr(cBuffer, 9, 32)
		szPname = Substr(szPname , 1, At(Chr(0),szPname)-1)
		Do Case
		Case szPname == "loopMIDI Port"
			nDevice=nIndex
		Otherwise
		Endcase
	Endif
Next
? 'number of devices',lnDevices
If lnDevices>0
	Local loCallBack
	Public hDevice
	hDevice=0 && necessary to also set this 0
	loCallBack = Createobject('cls_callback')
	dwCallback = loCallBack.Address
	nResult_midi_open = midiInOpen(@hDevice,nDevice,dwCallback,0,CALLBACK_FUNCTION)
	? 'devicehandle',hDevice
	nResult_fmidi_start = midiInStart(hDevice)
	? 'midi start result:',nResult_midi_start
	testkeypress=.T.
	?"Press any key to close."
	Do While testkeypress=.T.
		On Key testkeypress=.F.
		sleep(1)
	Enddo
	nResult_midi_in_stop = midiInStop(hDevice)
	nResult_midi_in_close = midiInClose(hDevice)
	? 'stop result:',nResult_midi_in_stop
	? 'close result:',nResult_midi_in_close
	? 'Done'
Else
	Messagebox("You have no MIDI in device connected to your system.")
Endif
***Class definition***
Define Class cls_callback As Exception
	Address = 0

	Function Init
	This.Address = CreateCallbackFunc('MidiInProc','VOID','LONG,LONG,LONG,LONG,LONG',This)
	Endfunc

	Function Destroy
	If This.Address != 0
		DestroyCallbackFunc(This.Address)
	Endif
	Endfunc

	Function MidiInProc(hDevice,uMsg,dwInstance,DataByte1,DataByte2)
	? 'midiin',hDevice,uMsg,dwInstance,DataByte1,DataByte2
	Endfunc
Enddefine

***Declare winapi***
Procedure Declare
Declare Integer midiInStart In Winmm Integer hmi
Declare Integer midiInStop In Winmm Integer hmi
Declare Integer midiInClose In Winmm Integer hmi
Declare Sleep In kernel32 Integer dwMilliseconds
Declare Integer midiInOpen In "winmm.dll";
	INTEGER @lphMidiIn ,  Integer uDeviceID , Integer dwCallback , Integer dwInstance , Integer dwFlags
Declare Integer midiInGetNumDevs In Winmm
Declare Integer midiInGetDevCaps In Winmm;
	INTEGER uDeviceID, String @lpMidiInCaps,;
	INTEGER cbMidiInCaps

On one hand I think not important on the other hand is onkey alt+f4 clear events a nice use.

In real life I load a form and that keeps reading events and waiting for callbacks.

Kind regards,

Jan Flikweert
 
Indeed nDevice=0 is the loopMidi port. Also the midi monitor EXE does display the midi data.

ALT+F4 works okay for me and it's a classic for ending Windows. If you'd like another hotkey for quitting you can also use just F4, for example. If it doesn't work, then maybe because of your modal form.
The keypress detecting loop you implemented isn't working fine, it's not reacting immediately. But that's simply subject of what works best for each of us.

No idea why VFP crashes, I noticed the callback of the VB midi monitor is not defined as VOID but as Integer, so it should return an Integer but I don't see the VB code do so. And doing that in VFP code in the MidiInProc doesn't change anything. The value might be important, just like the usual Windows Message processing should return a specific value and not just anything.

If the callback should return something to signal it has processed the data and that doesn't happen, I can imagine this leads to a crash as something overflows.

Time to dive into the VB project, I think. Tomorrow.

Chriss
 
Okay,

here's my conclusion. In the end vfp2c32 isn't capable of creating a callback function as necessary for the midi callbacks.
I got there by looking into VB code meaning. What strikes out is the Textbox.Invoke call. I looked into what this means for VB and in short it's a multi threading issue. Invoke is a threadsafe way of setting control properties, like the display value.

Next thought I had was there are several FLLs in the vfp2c32 project, and vfp2c32t.fll is a threadsafe FLL to use within multithreaded DLLs you can build with VFP.

But that FLL won't work for us, as it hasn't got CreateCallbackFunc, see
searchcode said:
623 /* windows message hooks */
624#ifndef _THREADSAFE
625 {"BindEventsEx", (FPFI) BindEventsEx, 6, "II?C.?.I"},
626 {"UnbindEventsEx", (FPFI) UnbindEventsEx, 3, "I.I.L"},
627 /* C callback function emulation */
628 {"CreateCallbackFunc", (FPFI) CreateCallbackFunc, 5, "CCC.O.I"},
629 {"DestroyCallbackFunc", (FPFI) DestroyCallbackFunc, 1, "I"},
630#endif

The ifndef _THREADSAFE means CreateCallbackFunc and DestroyCallbackFunc are only put into the FLL for use in VFP for the normal FLL, not the threadsafe.

There was some hope to still get it going as there is one more parameter of the CreateCallbackFunc function about what exact type of callback function it creates and that also mentions one flag means creating the callback in a separate thread and not blocking the "3rd party" (winmm.dll) C DLL.

vfp2c32 help said:
CALLBACK_SYNCRONOUS - The C callback function is called on the main Visual FoxPro thread.
For all Winapi functions e.g. EnumWindows.
CALLBACK_ASYNCRONOUS_POST - The C callback function is called on a separate thread, for 3rd party C DLL's that raise
events (call the C function). The callback itself is send asynchronously to the
main Visual FoxPro thread, it does not block the 3rd party thread, the event is processed when FoxPro is in a READ EVENTS state.
CALLBACK_ASYNCRONOUS_SEND - The C callback function is called on a separate thread, for 3rd party C DLL's that raise
events (call the C function). The callback is send synchronously to the main Visual FoxPro thread,
the thread resumes after the FoxPro function returns.
CALLBACK_CDECL - The C function uses the cdecl calling convention, by default the callback function is created with the stdcall calling convention

The flags can be combined, especially CALLBACK_CDECL and the asynchronous flags, it makes no sense to combine synchronous with asynchronous flag values.
I think VB manages the delegates/callbacks better then the vfp2c32 fll can so to use this you'd need to go for using the VB solution as the monitor with a bridge to VFP to get at the data it can see.

Tom mentioned a .net brigde from West Wind to use .NET code in VFP, but in this case I think it would be simpler if the VB.NET monitor writes out to a dbf or simpler txt file to read into VFP. What should be added to the events is the exact time they happened, so playing that back in the correct timing will replay it.

Vfp2c32 FLL is not the only FLL that adds a callback feature. You have one other in plain view: VB.NET. As I said earlier a project in VB can let object be COM visible and then you can access them in VFP in the style of ActiveX controls or COM servers.

I don't know how active Christian Ehlscheid is, but sending him a mail to look into this would be a possibility. In summary for him you could point out the software necessary to see this working with the VB Midi monitor. For me that worked with

1. VMPK (Vitual Midi Piano Keyboard) configured this way by Edit->Midi connections:
2. loopMIDI virtual Midi Port creating a port named "loopMIDI port" (which is it's the default).
3. The Midi Monitor at Code Projects.
4. The VFP code that almost works but fails with all flags of CreateCallbackFunc, also the non-blocking asynchronous ones:
Code:
#Define CALLBACK_FUNCTION 0x30000
#Define MIDI_IO_STATUS  32

#Define CALLBACK_SYNCRONOUS 1
#Define CALLBACK_ASYNCRONOUS_POST 2
#Define CALLBACK_ASYNCRONOUS_SEND 4
#Define CALLBACK_CDECL 8

Set Library To LOCFILE("vfp2c32.fll") Additive && Library with callback feature
Do Declare
***Main***
Clear
Local lnDevices, nDevice
lnDevices= midiInGetNumDevs()
? 'number of devices',lnDevices

For nIndex=0 To lnDevices-1
   nBufsize = 1024
   cBuffer = Replicate(Chr(0), 1024)
   If midiInGetDevCaps(nIndex, @cBuffer,nBufsize ) = 0
      szPname = Substr(cBuffer, 9, 32)
      szPname = Substr(szPname , 1, At(Chr(0),szPname)-1)
      ? szPname, nIndex
      If szPname == "loopMIDI Port"
         nDevice=nIndex
      Endif
   Endif
Next

If lnDevices>0
   Local loCallBack
   PUBLIC hDevice
   hDevice=0
   loCallBack = Createobject('cls_callback')
   dwCallback = loCallBack.Address
   nResult_midi_open = midiInOpen(@hDevice,nDevice,dwCallback,0,CALLBACK_FUNCTION+MIDI_IO_STATUS)
   ? 'midi IN open result:',nResult_midi_open
   IF  hDevice<>0
      ? 'devicehandle',hDevice
   ELSE
      ? 'hDevice will be set or was already set from callback'
   ENDIF 
   
   nResult_midi_start = midiInStart(hDevice)
   ? 'midi start result:',nResult_midi_start
   On Key Label F4 Clear Events
   ? 'Press F4 to exit'
   Read Events
   nResult_midi_in_stop = midiInStop(hDevice)
   nResult_midi_in_close = midiInClose(hDevice)
   ? 'stop result:',nResult_midi_in_stop
   ? 'close result:',nResult_midi_in_close
   ? 'Done'
Else
   Messagebox("You have no MIDI in device connected to your system.")
Endif
***Class definition***
Define Class cls_callback As Exception
   Address = 0

   Function Init
      * My guess, needs a new callback option from VFP2C32
      This.Address = CreateCallbackFunc('MidiInProc','VOID','LONG,LONG,LONG,LONG,LONG',This, CALLBACK_SYNCRONOUS)
   Endfunc

   Function Destroy
      If This.Address != 0
         DestroyCallbackFunc(This.Address)
      Endif
   Endfunc

   Function MidiInProc(tDevice,uMsg,dwInstance,DataByte1,DataByte2)
       IF hDevice<>tDevice
          hDevice = tDevice
          ? 'hdevice set to', tDevice
       ENDIF
       
       ? hDevice,uMsg,dwInstance,DataByte1,DataByte2
   Endfunc
Enddefine

***Declare winapi***
Procedure Declare
   Declare Integer midiInStart In Winmm Integer hmi
   Declare Integer midiInStop In Winmm Integer hmi
   Declare Integer midiInClose In Winmm Integer hmi
   Declare Sleep In kernel32 Integer dwMilliseconds
   Declare Integer midiInOpen In "winmm.dll";
      INTEGER @lphMidiIn ,  Integer uDeviceID , Integer dwCallback , Integer dwInstance , Integer dwFlags
   Declare Integer midiInGetNumDevs In Winmm
   Declare Integer midiInGetDevCaps In Winmm;
      INTEGER uDeviceID, String @lpMidiInCaps,;
      INTEGER cbMidiInCaps

some final notes on details I changed:
I made hDevice public (again) as with some call flags the hDevice variable isn't set from the midiInOpen call, but the first callback cmoing into the callback function has a device ID in it, therefore the new code in that function:
Code:
   Function MidiInProc(tDevice,uMsg,dwInstance,DataByte1,DataByte2)
       IF hDevice<>tDevice
          hDevice = tDevice
          ? 'hdevice set to', tDevice
       ENDIF
       
       ? hDevice,uMsg,dwInstance,DataByte1,DataByte2
   Endfunc
That change should not become a final solution, but also shows something is already weird when calling midiInOpen. And even when hDevice is set <>0, the midiInStart call returns errors 4 or 5 instead of 0 (OK) sometimes. It's important to know that a call to midiInOpen creates a mmidi event that means a callback with uMsg=961. There's where you can also get the hDevice from, even when the by reference @hDevice parameter to midiInStart is not set to that device handle.

Also important, perhaps, there are more sites related to midi programming where you might find out more about the necessary ingredients.

Last not least, the message binding should be able to also give you the midi events and their data. So as a next step we could try to do without the FLL again just with hwnd and windows messages.

Chriss
 
Chriss,

Wow!!!!!!! This works. You won't believe it. It is the combination of CALLBACK_ASYNCRONOUS_POST and _VFP.AutoYield
Code:
      This.Address = CreateCallbackFunc('MidiInProc','VOID','LONG,LONG,LONG,LONG,LONG',This, [highlight #73D216]CALLBACK_ASYNCRONOUS_POST)[/highlight]
   Endfunc

   Function Destroy
      If This.Address != 0
         DestroyCallbackFunc(This.Address)
      Endif
   Endfunc

   Function MidiInProc(tDevice,uMsg,dwInstance,DataByte1,DataByte2)
       IF hDevice<>tDevice
			[highlight #8AE234]_VFP.AutoYield = .F.[/highlight]
          hDevice = tDevice
          ? 'hdevice set to', tDevice
			[highlight #73D216]_VFP.AutoYield = .T.[/highlight]

I will look at it.

Kind regards,


Jan Flikweert
 
Chriss,

I can report that this solution results in a note on and of message: This are the results:

Naamloos_eor064.jpg


This is real amazing!

I most say I am very thankfull for the work you did tot get this solved. We almost thought it was not possible.

One remark which is usefull: When midiinopen returns 4 or 5 the midi input is not well closed. Best way is to restart vfp.

Kind regards,

Jan Flikweert

Code:
#DEFINE MIDI_STATUS_PLAYNOTE 9
#DEFINE MIDI_STATUS_PATCH 12
#DEFINE CALLBACK_NULL 0
#DEFINE  CALLBACK_FUNCTION    196608        &&  dwCallback is a FARPROC
#DEFINE  CALLBACK_NULL    0x0                &&  no callback
#DEFINE  CALLBACK_TASK    0x20000            &&  dwCallback is a HTASK
#DEFINE  CALLBACK_TYPEMASK    0x70000        &&  callback type mask
#DEFINE  CALLBACK_WINDOW    0x10000          &&  dwCallback is a HWND
#DEFINE  MIM_CLOSE 962
#DEFINE  MIM_OPEN 961
#DEFINE  MIM_DATA 963
#DEFINE MIM_MOREDATA 972
#DEFINE MIM_LONGDATA 964
#DEFINE MIDI_IO_STATUS  32
#DEFINE MIDI_ERROR 965
#DEFINE MIDI_LONGERROR 966
#DEFINE MMSYSERR_ALLOCATED 4
#DEFINE MMSYSERR_INVALHANDLE 5
#DEFINE MHDR_DONE  1
#DEFINE MHDR_PREPARED  2
#DEFINE MHDR_INQUEUE  4
#DEFINE MHDR_ISSTRM  8
#INCLUDE vfp2c.h
#INCLUDE VFP2CCALLBACK.h

#Define CALLBACK_SYNCRONOUS 1
#Define CALLBACK_ASYNCRONOUS_POST 2
#Define CALLBACK_ASYNCRONOUS_SEND 4
#Define CALLBACK_CDECL 8

Set Library To LOCFILE("vfp2c32.fll") Additive && Library with callback feature
Do Declare
***Main***
Clear
Local lnDevices, nDevice
lnDevices= midiInGetNumDevs()
? 'number of devices',lnDevices

For nIndex=0 To lnDevices-1
   nBufsize = 1024
   cBuffer = Replicate(Chr(0), 1024)
   If midiInGetDevCaps(nIndex, @cBuffer,nBufsize ) = 0
      szPname = Substr(cBuffer, 9, 32)
      szPname = Substr(szPname , 1, At(Chr(0),szPname)-1)
      ? szPname, nIndex
      If szPname == "CME U2MIDI"
         nDevice=nIndex
      Endif
   Endif
Next

If lnDevices>0
   Local loCallBack
   PUBLIC hDevice
   hDevice=0
   loCallBack = Createobject('cls_callback')
   dwCallback = loCallBack.Address
   nResult_midi_open = midiInOpen(@hDevice,nDevice,dwCallback,0,CALLBACK_FUNCTION+MIDI_IO_STATUS)
   ? 'midi IN open result:',nResult_midi_open
   IF  hDevice<>0
      ? 'devicehandle',hDevice
   ELSE
      ? 'hDevice will be set or was already set from callback'
   ENDIF 
   
   nResult_midi_start = midiInStart(hDevice)
   ? 'midi start result:',nResult_midi_start
   On Key Label F4 Clear Events
   ? 'Press F4 to exit'
   Read Events
   nResult_midi_in_stop = midiInStop(hDevice)
   nResult_midi_in_close = midiInClose(hDevice)
   ? 'stop result:',nResult_midi_in_stop
   ? 'close result:',nResult_midi_in_close
   ? 'Done'
Else
   Messagebox("You have no MIDI in device connected to your system.")
Endif
***Class definition***
Define Class cls_callback As Exception
   Address = 0

   Function Init
      * My guess, needs a new callback option from VFP2C32
      This.Address = CreateCallbackFunc('MidiInProc','VOID','LONG,LONG,LONG,LONG,LONG',This, CALLBACK_ASYNCRONOUS_POST)
   Endfunc

   Function Destroy
      If This.Address != 0
         DestroyCallbackFunc(This.Address)
      Endif
   Endfunc

   Function MidiInProc(tDevice,uMsg,dwInstance,DataByte1,DataByte2)
		DO CASE
			case uMsg=MIM_OPEN
				_VFP.AutoYield=.f.
				? hDevice,uMsg,dwInstance,DataByte1,DataByte2
				MESSAGEBOX("MIM_OPEN")
				_VFP.AutoYield=.t.				
			case uMsg=MIM_CLOSE
				_VFP.AutoYield=.f.
				? hDevice,uMsg,dwInstance,DataByte1,DataByte2
				MESSAGEBOX("MIM_CLOSE")
				_VFP.AutoYield=.t.				
			case uMsg=MIM_DATA
				_VFP.AutoYield=.f.
				? hDevice,uMsg,dwInstance,DataByte1,DataByte2
				stts=BITAND(DataByte1,255)
				dt1=BITRSHIFT(BITAND(DataByte1,65280),8)
				dt2=BITRSHIFT(BITAND(DataByte1,16711680),16)
				MESSAGEBOX("Data: "+STR(stts)+CHR(13)+STR(dt1)+CHR(13)+STR(dt2))
				_VFP.AutoYield=.t.				
			case uMsg=MIM_MOREDATA
				_VFP.AutoYield=.f.
				? hDevice,uMsg,dwInstance,DataByte1,DataByte2
				stts=BITAND(DataByte1,255)
				dt2=BITRSHIFT(BITAND(DataByte1,16711680),16)
				dt1=BITRSHIFT(BITAND(DataByte1,65280),8)
				MESSAGEBOX("Data"+STR(stts)+CHR(13)+STR(dt1)+CHR(13)+STR(dt2))
				_VFP.AutoYield=.t.
			OTHERWISE
				_VFP.AutoYield=.f.
				? hDevice,uMsg,dwInstance,DataByte1,DataByte2
				_VFP.AutoYield=.t.
			ENDCASE
   Endfunc
Enddefine

***Declare winapi***
Procedure Declare
   Declare Integer midiInStart In Winmm Integer hmi
   Declare Integer midiInStop In Winmm Integer hmi
   Declare Integer midiInClose In Winmm Integer hmi
   Declare Sleep In kernel32 Integer dwMilliseconds
   Declare Integer midiInOpen In "winmm.dll";
      INTEGER @lphMidiIn ,  Integer uDeviceID , Integer dwCallback , Integer dwInstance , Integer dwFlags
   Declare Integer midiInGetNumDevs In Winmm
   Declare Integer midiInGetDevCaps In Winmm;
      INTEGER uDeviceID, String @lpMidiInCaps,;
      INTEGER cbMidiInCaps
 
Jan Flikweert said:
When midiinopen returns 4 or 5 the midi input is not well closed.

Well, I did so and it doesn't work for me. The docs say to use it when using CALLBACK_ASYNCRONOUS_POST, that's true. More to the point would be to put _VFP.AutoYield = .F. right at the start and _VFP.AutoYield = .T. at the end and not within any IFs or cases. It's something to do always.

The problem I have is even before the first callback that usually still worked, from Open. I get error "Declare DLL call caused an exception" when calling midiInOpen.

I was trying your solution from 24 Mar 22 10:54 somewhere a third into this long thread, but no messaage arrived in the screenevents class HandleEvent method, though I bound to even more messages than just MIM_DATA.

I may look into this later or after a fresh restart next time, for now it's good to see you got it going. I'm really happy this was fruitful in the end.

Chriss
 
Chriss,

You are right autoyould is once needed.

Code:
If szPname == "CME U2MIDI"

I see the midi device is not correct should be something like loopMIDI port.

Kind regards,


Jan Flikweert

 
Chriss,

I noticed an issue.

Compiling a program file in VFP containing our callback -even the code which works- causes after compiling an error message:

Code:
Compiling d:\program files (x86)\midi_console_winmm2\program\startexe.prg
#pragma warning(disable : 4290) // disable warning 4290 - VC++ doesn't implement throw ...

Still our example works.

Documentation Microsoft

I think I can ignore this.

Kind regards,

Jan Flikweert
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top