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
 
Chris,

After starting midiinopen and midistart the callback function is used for MIM_OPEN. That is a good sign of life.

I have a Destroy function using DestroyCallback. That is not fired. That is only fired at the end of the program.

After succesfully midiinopen and start the program continuous in a do while loop. I also tried read events. Doing so in the debugger thee yellow sign stays on midiinopen. Staying there it does not react on midiinput. The do while loop or read events continue at the same time.

After pressing a few keys on the midiinput device VFP crashes without using the callback. The callback is not processed any way.

I fully agree with you the return value of the CreateCallback function is 'VOID'. The callbackfunction does not return anything.
The callback function using MidiInOpen seems to use 5 parameters. Using the debugger no parameters shows .f.. That says nothing about MIM_OPEN. I uses 'void','INTEGER,INTEGER,INTEGER,INTEGER,INTEGER'. I tried void long as you stated. That does not work. The paramaters are long, unsigned long,long,long,long. In the declare procedure and all winapi vfp examples you see the use of integer.

Looking at VB it is a matter how to use a callback from midiinopen WINAPI. There are not headers, structures in use. It also use 5 parameters.

It is possible f.e. BINDEVENTS is the solution.

The strange thing is CALLBACK_WINDOW works perfect. So what is the difference between MIDI_OPEN and MIM_DATA and CALLBACK_FUNCTION AND CALLBACK_WINDOW.

An other idea is that MIDI_IO_STATUS is only used for the question what went wrong.

An other idea is how to combine multiple flags: CALLBACK_FUNCTION+MIDI_IO_STATUS, BITAND(CALLBACK_FUNCTION,MIDI_IO_STATUS) or BITOR(CALLBACK_FUNCTION,MIDI_IO_STATUS).

An other remark is that WINAPI works zero based. VFP 1 bsed. Now you send a zero based Deviceid to the WINAPI. Can vfp2c32 handle this. I suppose it does, because in all other function opening midi in+out, connecting midi in + out en sending midiout it works fine.

Or do whe overlook the success of CALLBACK_WINDOW should we do it the same way buth then callback_function?

The first two parameters regarding the device work good, also. dwCallback is the instance of the callback and not used, can be zero. dwParam1 is the most import one. This contains the real message. In my case dwParam2 is not needed.

I hop this helps you tomorrow.

Kind regards,

Jan Flikweert
 
Chris,

Regarding the VB example it is all about the first five lines and the way they call the function MidiInProc starting in line 7. No more, no less! Private Sub ComboBox1 shows the use of midiInOpen and its parameters.
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
 
Jan Flikweert said:
they call the function MidiInPro

Well, they call the VB MidiInProc, not the Windows API MidiInPro, that's what's important.

I'm not sure how to do what's in the VB MidiInProc function, Invoke as in PInvoke is simply calling a Windows API function. So lets see if DisplayDataDelegate exists...
No, it does not, I findit mentioned in Is that the VB project you're trying to translate to VFP?

I see what Delegate methods are in VB, VFP has nothing like that, but it also doesn't matter that much. It would take a bit longer to explain but in short the FLLs CreateCallbackFunc creates those delegate functions and all they do in turn is call normal VFP functions or methods.

So here's a callback that in turn calls another callback, I think. I'll be on this later, perhaps only tomorrow.

Bye, Olaf.

Chriss
 
Chriss,

Yes that is the VB code. I posted this project as an attachment on 22-3. I attached it again.
VB MidiInMonitor

I do not mean to hurry you, I am thankfull you wish to help me and hope to inform you complete.

Kind regards,

Jan Flikweert

Code:
Imports System.Threading
Imports System.Runtime.InteropServices

Public Class Form1

    Public Declare Function midiInGetNumDevs Lib "winmm.dll" () As Integer
    Public Declare Function midiInGetDevCaps Lib "winmm.dll" _
           Alias "midiInGetDevCapsA" (ByVal uDeviceID As Integer, _
           ByRef lpCaps As MIDIINCAPS, ByVal uSize As Integer) As Integer
    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 Declare Function midiInStart Lib "winmm.dll" (ByVal hMidiIn As Integer) As Integer
    Public Declare Function midiInStop Lib "winmm.dll" (ByVal hMidiIn As Integer) As Integer
    Public Declare Function midiInReset Lib "winmm.dll" (ByVal hMidiIn As Integer) As Integer
    Public Declare Function midiInClose Lib "winmm.dll" (ByVal hMidiIn 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

    Public Delegate Sub DisplayDataDelegate(dwParam1)

    Public Structure MIDIINCAPS
        Dim wMid As Int16 ' Manufacturer ID
        Dim wPid As Int16 ' Product ID
        Dim vDriverVersion As Integer ' Driver version
        <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=32)> _
        Dim szPname As String ' Product Name
        Dim dwSupport As Integer ' Reserved
    End Structure

    Dim hMidiIn As Integer
    Dim StatusByte As Byte
    Dim DataByte1 As Byte
    Dim DataByte2 As Byte
    Dim MonitorActive As Boolean = False
    Dim HideMidiSysMessages As Boolean = False

    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 DisplayData(dwParam1)
        If ((HideMidiSysMessages = True) And ((dwParam1 And &HF0) = &HF0)) Then
            Exit Sub
        Else
            StatusByte = (dwParam1 And &HFF)
            DataByte1 = (dwParam1 And &HFF00) >> 8
            DataByte2 = (dwParam1 And &HFF0000) >> 16
            TextBox1.AppendText(String.Format("{0:X2} {1:X2} {2:X2}{3}", _
                                StatusByte, DataByte1, DataByte2, vbCrLf))
        End If
    End Sub

    Private Sub Form1_Load(ByVal sender As Object, _
            ByVal e As System.EventArgs) Handles Me.Load
        Me.Show()
        If midiInGetNumDevs() = 0 Then
            MsgBox("No MIDI devices connected")
            Application.Exit()
        End If

        Dim InCaps As New MIDIINCAPS
        Dim DevCnt As Integer

        For DevCnt = 0 To (midiInGetNumDevs - 1)
            midiInGetDevCaps(DevCnt, InCaps, Len(InCaps))
            ComboBox1.Items.Add(InCaps.szPname)
        Next DevCnt
    End Sub

    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

    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) _
            Handles Button1.Click
        TextBox1.Clear()
    End Sub

    Private Sub Button2_Click(sender As System.Object, e As System.EventArgs) _
            Handles Button2.Click
        If MonitorActive = False Then
            midiInStart(hMidiIn)
            MonitorActive = True
            Button2.Text = "Stop monitor"
        Else
            midiInStop(hMidiIn)
            MonitorActive = False
            Button2.Text = "Start monitor"
        End If
    End Sub

    Private Sub Button3_Click(sender As System.Object, e As System.EventArgs) _
            Handles Button3.Click
        If HideMidiSysMessages = False Then
            HideMidiSysMessages = True
            Button3.Text = "Show System messages"
        Else
            HideMidiSysMessages = False
            Button3.Text = "Hide System messages"
        End If
    End Sub

    Private Sub Form1_FormClosed(ByVal sender As Object, _
            ByVal e As System.Windows.Forms.FormClosedEventArgs) Handles Me.FormClosed
        MonitorActive = False
        midiInStop(hMidiIn)
        midiInReset(hMidiIn)
        'midiInClose(hMidiIn)
        Application.Exit()
    End Sub

End Class
 
 https://files.engineering.com/getfile.aspx?folder=4d60c645-ed67-498b-a570-94670e68dcc4&file=MIDI_monitor.zip
Chris,

I found a wowing good program in vfp2c32: vfp2cfromt. It converts C typedef struct to vfp. In this case we use a callback procedure, but the technique is interesting.

An C example:

Code:
typedef struct 
{
    int i;
    char k;
} elem;

Results in:

Code:
DEFINE CLASS elem AS Exception

	Address = 0
	SizeOf = 6
	Name = "elem"
	&& structure fields
	_MemberData = '<VFPData>' + ;
		'<memberdata name="i" type="property" display="i"/>' + ;
		'<memberdata name="k" type="property" display="k"/>' + ;
		'</VFPData>'

	i = .F.
	k = .F.

	PROCEDURE Init()
		THIS.Address = AllocMem(THIS.SizeOf)
	ENDPROC

	PROCEDURE Destroy()
		FreeMem(THIS.Address)
	ENDPROC

	PROCEDURE i_Access()
		RETURN ReadInt(THIS.Address)
	ENDPROC

	PROCEDURE i_Assign(lnNewVal)
		WriteInt(THIS.Address, m.lnNewVal)
	ENDPROC

	PROCEDURE k_Access()
		RETURN ReadChar(THIS.Address + 4)
	ENDPROC

	PROCEDURE k_Assign(lnNewVal)
		WriteChar(THIS.Address + 4, m.lnNewVal)
	ENDPROC

ENDDEFINE

Which is interesting. I am going to look again. Very interesting is the use of AllocMem to return the Address.

Kind regards,

Jan Flikweert
 
Hi Chris and all interested,

Looking at the help file of vfp2c.fll I found the next text. I think this is important.

Looking in the debugger just open a midi in device results in multiple callback objects. F.e. VFP2C_CBO_51594432. This is a real object. With opening and closing there are 3 or 4 such objects VFP2C_CBO_51598552. I suppose the returnvalue of 8 digits is a memory address wich increases with 4120, I asume the size of the class in memory. Interesting to look at laVarname, returnvalue and _VFP2C_CBO_..

[highlight #F57900]Remarks

To emulate callbacks on an object the library has to make a copy of the object into a public variable.
The name of the variable is auto generated by the following scheme:


Copy code

lcVarName = "__VFP2C_CBO_" + ALLTRIM(STR(returnvalue))


This workaround is necessary cause the FoxPro LCK doesn't provide an API function to call methods of an object.
The public variable is automatically released when you unbind the message.
Although a copy of the object is made the internal object reference count is not incremented, the public copy doesn't affect your object's lifetime (scope).
The only thing you have to consider is that your own variables doesn't conflict with the above naming scheme which is very unlikely.
[/highlight]

Kind regards,

Jan Flikweert
 
This thread has become very long, time to open another one.

I'm unsure how that involves libsynth or the midi monitor, but I didn't get to playing with (virtual) midi devices last weekend.

Better luck this weekend.


The public variables are only necessary for the FLL, not for you/us and I also don't see them as turning a C object into a VFP object. The address returned by CreateCallbackAdress is for passing in to the C world so it has it's callback address. That doesn't mean this mechanism can be used to make VFP objects from an address of a C object.

What the text tells is the FLL creates a copy of the VFP object that is having the callback method you specify for the callbacks. The object that is called back is still not that copy of the VFP object, as far as I see it, and even if, the address you get is usable in the C world for making a call to a C function created at that address, which in turn calls the VFP object method, that's the connection the FLL makes.
But that doesn't mean it creates VFP variables for C objects just knowing the address of a C object.

I don't know whether the address of the C object new_fluid_settings is just the start address of a C struct. I think this doesn't just mean a struct in the sense of an object with just a few members with simple type values (int, float, string), but an object with properties and methods and there it stops being useful to only know the address.

VFP can read from an address given to it by a DLL with the help of Sys(2600), you may look into this.Read SIZEOF(struct) bytes from there and see if they could be decoded to values of the struct members. Which would first of all require to know what struct a fluid_setting is and in which order which members are which data type and therefore which number of bytes representing the type value in which manner. Like even some simple value as an integer can be stored in 1,2,4,8 bytes or arbitrary length with the first byte being the lowest significant value and the last byte the highest significant (big endian) or vice versa (little-endian).

Chriss
 
Chris,

The base of this post is still the same: how to translate that VB midi input monitor to VFP? That can be viewed from different sights, we can consider different solutions.

On one hand Fluidsynth has nothing to do with this post, that is why I created a new thread.

On the other hand Fluidsynth has something to do with midi.

Fluidsynth is a audio-backend a synthsizer and open-source. And Fluidsynth can process midi. Fluidsynth is in the jOrgan community very popular.

It should be possible to create a VPO(Virutal Pipe Organ) using VFP and Fluidsynth.

And I have made progession. It is possible to control Fluidsynth from VFP using Windows Script Shell. I should prefer the Libfluidsynth API, but that is not easy. To study the option of Libfluidsynth I opened the second thread.

The goal of the Fluidsynth thread is controlling the Libfluidsynth API from VFP.

The base of MIDI is you have midi in and out. Reading midi is done from the midi-out port. Using Loopmidi from Tobias Erischen you create several virtual ports and that ports have in and out. Playing a midi file is done with destination loopmidi in. Then you can route this port in to the out port using the program MIDI-OX. REAPER can do it to. Then you can use VB midi monitor from that output port.

The nice thing of VB monitor is you can run the exe file. Then you can choose the output port to read the input from.

Let me know if you need help.

One good program is the Digital Audio Workstation REAPER. It is not free, but can be tried for free.

I can post videos+sound from the dekstop of my computer.

Kind regards,

Jan Flikweert



 
Chris, and all interested,

I have made small progress using createcallback using vfp2c32. The object loCallback wich is created based on cls_callback is LOCAL[highlight #C17D11][/highlight]. The callback is named by vfp2c exists onetime and the address does not change. It processes Midi open (MIM_OPEN message 961) and Midi close (MIM_CLOSE message 962). As kwnown note on/off messages result crash of VFP without any use of the callback in the mean time. Here the most important part of the code.

Code:
***Main***
[highlight #CC0000]Local loCallBack[/highlight]
loCallBack = Createobject('cls_callback')
dwCallback=loCallBack.Address
nResult_midi_open = midiInOpen(@hDevice,nDevice,dwCallback, dwInstance,CALLBACK_FUNCTION+MIDI_IO_STATUS)

***Class definition***
Define Class cls_callback As Exception

	DataByte1=.F.
	uMsg=.F.
	hDevice=.F.
	dwInstance=.F.
	DataByte2=.F.
	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)
	This.DataByte1=DataByte1
		This.DataByte2=DataByte2
		This.uMsg=uMsg
		This.dwInstance=dwInstance
		This.hDevice=hDevice
		_vfp.AutoYield = .F.
		If This.uMsg<>0
			?This.uMsg
		Endif
			_vfp.AutoYield = .T.
		Return This.Address
	Endfunc
Enddefine

***Declare winapi***
Declare Integer midiInOpen In "winmm.dll";
	INTEGER @lphMidiIn ,  Integer uDeviceID , Integer dwCallback , Integer dwInstance , Integer dwFlags

Kind regards,

Jan Flikweert
 
Jan,

creating something as LOCAL when you need it to process callbacks that may come after it's gone is of course a problem you introduce.
The lifetime of your object should be as long as you receive callbacks, of course.

vfp2c32 said:
the library has to make a copy of the object into a public variable
and
vfp2c32 said:
Although a copy of the object is made the internal object reference count is not incremented, [highlight #FCE94F]the public copy doesn't affect your object's lifetime (scope)[/highlight].

Means the VFP object clone created becomes independent from your loCallback.
It stays in memory even if your local object is gone, but here comes what releases it anyway:

vfp2c32 said:
The public variable is automatically released when you unbind the message.
When loCallback is released at the end of its local scope its Destroy event calls DestroyCallbackFunc(This.Address) and that releases the public variable, too.

Which makes it seem the vfp2c32 variable is scoped LOCAL, too. Bur it isn't.

Jan Flikweert said:
As known note on/off messages result crash of VFP without any use of the callback in the mean time.
I don't know what you mean with this. But maybe you indeed have found the reason why, if you'd understood yourself what I just said.

Why wasn't I pointing out earlier that the callback to loCallback only can work while it exists? Because that should be clear when its Destroy method unbinds the callback. You can store a local variable into something that exists longer. For example by setting a property of a public goApp object, a collection or adding a property to _screen, for example.

What you should note here is that a public variable mainly only means a longer lifetime if nothing actively releases it. The automatic release of the local loCallback is tearing down the global vfp2c32 object, too. It won't explain why that crashes VFP, it would mainly only mean that callbacks are revoked, they should not happen. Unless the address given to midiInOpen still is used for callbacks, which would lead to calling into no man's land and crashes.

I think that explains it, while DestroyCallbackFunc is a clean process regarding the VFP object side, the FLL can't know where you have made the callback address known and how long the lifetime of that is, the FLL relies on you to only DestroyCallbackFunc when it's not needed anymore.

The class definition is meant to be used on a VFP object that lasts as long as callbacks are received. That the copy of the object made by the FLL is public doesn't save you, as the destruction of the your local callback object also releases the public variable by destruction of the callback function. And that destruction doesn't get forwarded to anything knowing and using the callback address.

Chriss
 
About scope/lifetime of objects, one usual example is how a LOCAL loForm vanishes if it's non-modal.

The solution used for letting a form live after loForm is released by say a click event creating a form into a local loForm variable is to add this loForm to a collection or other thing longer in scope than the click event is.

You have to use that on loCallback too, as the destruction of loCallback also destroys the public copy of it by the call of DestroyCallbackFunc(This.Address).

It's a clean definition of the cls_callback class, you should know that it's destroy should only come after you don't need it to process further callbacks, no matter if you know about the FLL mechanism to create a PUBLIC copy or not. PUBLIC doesn't mean scoped until the EXE quits. Variable scope is only indirectly about lifetime, local vars are released at the end of a procedure/function/method. Public variables not, but they can be released before quitting, too. Which is done per the descrition of what DestryCallbackFunc does.

When you use a callback for anything that could happen from the moment you start using it until your application quits, it should be preserved for longer than the local variable loCallback by adding it to something like an object that's also scoped to the whole lifetime of the exe. That's usually a goApp object or for sake of not having something like that the always existing _screen (even when you have SCREEN=OFF in a config.fpw, that only means _Screen.visible=.f.)

As you won't just have one callback address, what suits keeping the callback objects in scope is a collection, which is itself added to something with lifetime of the application.

The other solution is not using a method of an object as callback, but a PRG or procedure or function in a PRG. A major disadvantage then is there's no natural way to remember the address associated with the callback, though, like it is to remember that address in the object receiving the callbacks.

Chriss
 
Chris,

Chris Miller said:
I don't know what you mean with this. But maybe you indeed have found the reason why, if you'd understood yourself what I just said.

When you play f.e. a midi keyboard between the events open/start midi and stop/close midi openmidiin callback records the midi messages from your midi keyboard. The most important messages regards playng the notes. That events are called note on/note off. In order of appearance thsi happens and that are the actions of the callback:

MidiIn open: Callback reacts
Midiin start: Needs no callback
Playing a note on : Callback does not react
Playing note off: Callback does not react
MidiOut Stop: Needs no callback
MidiOut Close: Callback reacts

And of course there are other messageds possible.

The strange things is that the callback reacts both on midiinopen and midiinclose, but not note on/off messages which occurs between midiinopn+close.

I continue study your reply.

Kind regards,

Jan Flikweert
 
Chris,

The CreateCallBack from vfp2c32 works just like the example which is provided with vfp2c32. I ran the example, added a breakpoint, opened the debugger and checked the variables.

The callback the way I use it now works exact the same as in the example.

Something comes in mind: As most current computers, processors do, my system is 64 bit. My OS is also 64 bit. Does it requere a different definition of variables passed to the callback:

Code:
This.Address = CreateCallbackFunc('MidiInProc','VOID','LONG,LONG,LONG,[highlight #F57900]DOUBLE or INT64 OR UINT64,DOUBLE or INT64 OR UINT64[/highlight]',This)
Function MidiInProc(hDevice,uMsg,dwInstance,DataByte1,DataByte2)

Kind regards,

Jan Flikweert
 
Chris,

In C it is all about those two lines. There is a comment: To make this work on 64bit, change DWORD to DWORD_PTR on line 2.

C:
[highlight #EF2929]void CALLBACK MidiInProc(HMIDIIN hMidiIn, UINT wMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)[/highlight]
[highlight #EF2929]rv = midiInOpen(&hMidiDevice, nMidiPort, (DWORD)(void*)MidiInProc, 0, CALLBACK_FUNCTION);[/highlight]

And here an complete example with above code:

C:
#include <SDKDDKVer.h>
#include <Windows.h>

#include <stdio.h>
#include <conio.h>

#include <mmsystem.h>
#pragma comment(lib, "winmm.lib")

void PrintMidiDevices()
{
	UINT nMidiDeviceNum;
	MIDIINCAPS caps;

	nMidiDeviceNum = midiInGetNumDevs();
	if (nMidiDeviceNum == 0) {
		fprintf(stderr, "midiInGetNumDevs() return 0...");
		return;
	}

	printf("== PrintMidiDevices() == \n");
	for (unsigned int i = 0; i < nMidiDeviceNum; ++i) {
		midiInGetDevCaps(i, &caps, sizeof(MIDIINCAPS));
		printf("\t%d : name = %s\n", i, caps.szPname);
	}
	printf("=====\n");
}

[highlight #EF2929]void CALLBACK MidiInProc(HMIDIIN hMidiIn, UINT wMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)[/highlight]
{
	switch(wMsg) {
	case MIM_OPEN:
		printf("wMsg=MIM_OPEN\n");
		break;
	case MIM_CLOSE:
		printf("wMsg=MIM_CLOSE\n");
		break;
	case MIM_DATA:
		printf("wMsg=MIM_DATA, dwInstance=%08x, dwParam1=%08x, dwParam2=%08x\n", dwInstance, dwParam1, dwParam2);
		break;
	case MIM_LONGDATA:
		printf("wMsg=MIM_LONGDATA\n"); 
		break;
	case MIM_ERROR:
		printf("wMsg=MIM_ERROR\n");
		break;
	case MIM_LONGERROR:
		printf("wMsg=MIM_LONGERROR\n");
		break;
	case MIM_MOREDATA:
		printf("wMsg=MIM_MOREDATA\n");
		break;
	default:
		printf("wMsg = unknown\n");
		break;
	}
	return;
}

int main(int argc, char* argv[])
{
	HMIDIIN hMidiDevice = NULL;;
	DWORD nMidiPort = 0;
	UINT nMidiDeviceNum;
	MMRESULT rv;

	PrintMidiDevices();
	
	nMidiDeviceNum = midiInGetNumDevs();
	if (nMidiDeviceNum == 0) {
		fprintf(stderr, "midiInGetNumDevs() return 0...");
		return -1;
	}

	[highlight #EF2929]rv = midiInOpen(&hMidiDevice, nMidiPort, (DWORD)(void*)MidiInProc, 0, CALLBACK_FUNCTION);[/highlight]
	if (rv != MMSYSERR_NOERROR) {
		fprintf(stderr, "midiInOpen() failed...rv=%d", rv);
		return -1;
	}

	midiInStart(hMidiDevice);

	while(true) {
		if (!_kbhit()) {
			Sleep(100);
			continue;
		}
		int c = _getch();
		if (c == VK_ESCAPE) break;
		if (c == 'q') break;
	}

	midiInStop(hMidiDevice);
	midiInClose(hMidiDevice);
	hMidiDevice = NULL;

	return 0;
}

Kind regards,


Jan Flikweert
 
Chris,

I suppose I have found the problem. The site with the latest download of vfp2c32 reports:

[highlight #8AE234]just a small bugfix to CreateCallbackFunc (optional callback object parameter wasn't really optional)[/highlight]

This explains why the first two parameters work fine.

Here the link to the download.

Download VFP2C32 2.0.0.19

The next problem I face to is the download from that link concerns 2.0.0.15(help file and version history). And our issue should be solved with release 2.0.0.19.

I have reported the issue.

Kind regards,


Jan Flikweert
 
Jan Flikweert said:
The most important messages regards playing the notes. That events are called note on/note off.

Well, by passing in the callback address to midiInOpen, this is not becoming the callback function/method to all midi events.

Perhaps look at the callback mechanism once more in general: Instead of a function that does what it does and finally returns a value like number = VAL(string), setting a callback means, you continue while a function churns on something and give it a recipient address to turn to once it's ready to return the result. It's like making an shop order with a shipping address differing from yours.

So you perhaps simply expect too much from that one callback address.

If you go for the message approach, it depends on the number of BINDEVENTS() calls you make and the message constants you use which events are getting back to you. So maybe you would need to make other calls than midiInOpen and also pass the same callback address to such functions, to get callbacks for note on/off events, too. Or use additional callback objects for additional functions with a separate instance to handle note on/off callbacks.

I haven't thought through the bug you think you found, it may just be a misunderstanding. Not of lifetime scope but of message this is scoped to. I don't think what you start with midiInOpen covers all events that happen to/from/with a device until you close it. It only involves the callbacks that are related to the midiInOpen function itself.

Chriss
 
Hi Chriss,

Chris Miller said:
So you perhaps simply expect too much from that one callback address.

Naamloos_eelpes.png


Naamloos2_ltqet7.png


This is the result of the VFP debugger running the shipped example with VFP2C32 It shows the window handle of each open window.

In those two prints the objects name and the address do not change. Onle the variable hHwnd changes.

Kind regards,

Jan Flikweert
 
Hello Jan,

in this ccallback.prg example only one callback is used, that of EnumWindows. It's always the same, it's not even a windows message in that case, it's just a normal simple c callback. EnumWinodws is called once and it calls back multiple times, once for every window. That's already special for a c callback, but still not the case of multiple different callbacks. It's always the exact same callback that just passed back a hwnd as and the second lparam parameter is what you pass in to EnumWindows as second parameter after the callback address. Everything is triggered by the one call of EnumWindows.

It also only uses a local loCallback that's not persisted. In this case it works, as EnumWindows is not asynchronous. It does return only after all windows are enumerated and VFP will not continue before that finishes. Add LIST MEMORY LIKE loCallback into the EnumWindowsCallback method of the WNDENUMPROC class and see loCallback is existing all the time. It would not, if the call to EnumWindows would return before the first callback is done, then the VFP code ends and thus loCallback would be released and LIST MEMORY LIKE loCallback would not list LOCALLBACK. And while vfp2c32 creates a PUBLIC object copy, when loCallback is released it's Destroy event calls DestroyCallbackFunc(This.Address) and by the description that in turn releases the public variable. Everything working from there would only work on the basis that releasing anything in VFP doesn't immediately overwrite the memory used by it, it's just deallocated and ready to be used elsewhere by anything, even other processes.

So in the midi code you can't just use loCallback as a local variable, and you also don't do, you create goMessagehandler.


There's a much bigger difference, though. In the example of me using BINDEVENTS the mechanism is Windows Messages, not C callbacks. But even there the mechanism is the same as a callback, VFP handles the memory addressed internally and you can bind to an object you pass into BINDEVENT, also you pass the VFP object that handles messages (callbacks) by reference and a string with the method name to call back. In the C world that's not worth anything, as it couldn't cope with a VFP object reference/address just like VFP can't use the address of a C object to call that objects methods.

In your implementation you call:
Code:
BINDEVENT(_Screen.hwnd, MIM_DATA, goMessageHandler,'HandleEvent')

So even if note on/off events occur, but are not transported via MIM_DATA messages, you don't get them, you might need further BINDEVENT calls for other MIM_* messages. On top of that the call to midiInOpen is the only source of forwarding Midi messages to your hwnd. It may not cause every midi message to go to your hwnd. Even if all messages would come to you, you only listen to MIM_DATA messages, there are much more messages to receive. Which mean your problem can come from two sides: Too few BINDEVENTS or too few midi function calls that would add further callbacks and may need other parameters so other callback handlers or at least other callback methods and thus also other callback addresses. One callback address can always only take one specific parameter set.

Moving that to the C callback mechanism instead isn't yet done, right? Or did you already get there without me?

Chriss
 
Chriss,

Chris Miller said:
Moving that to the C callback mechanism instead isn't yet done, right? Or did you already get there without me?

In an previous post you provided an example using this and I reported back that it does work. But this way(BINDEVENT(_screen.hwnd) is CALLBACK_WINDOW+MIDI_IO_STATUS.

Now we are talking about CALLBACK_FUNCTION+MIDI_IO_STATUS. And CALLBACK_FUNCTION is the most used way. I suppose it is faster. For a heavy duty like midi I think significant.

I wait until the update of VFP2C32 to version 2.0.0.19. It has proven to work with two parameters and a known issue regarding optional paramters. There is no reason to doubt on the statement that there is an known issue with this.

Kind regards,


Jan Flikweert
 
Jan Flickweert said:
In an previous post you provided an example using this and I reported back that it does work.
Well, that just used ptrCallback=_Screen.hwnd for Windows Messages, not a callback address.

This isn't callbacks, this is message handling.

The flags used were CALLBACK_WINDOW and MIDI_IO_STATUS not callbacks via CALLBACK_FUNCTION and the callback address passed to C. You could have picked up from the EnumWindows example how this works, but I see you didn't. So, that's what I'll dive into now.

Chriss
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top