通过Midi控制器控制Excel
我有一个通过Midi控制器控制Excel,excel,vba,midi,winmm,Excel,Vba,Midi,Winmm,我有一个 (来源:) 想用它上面的滑块来控制Excel,就像Excel表单控制滚动条一样 我已经设法修改了VBA,但它非常不稳定。 有人能帮我稳定一下吗?我认为函数midin_事件如果返回速度不够快,可能会崩溃,但我可能错了 提前谢谢 Public Const CALLBACK_FUNCTION = &H30000 Public Declare Function midiInOpen Lib "winmm.dll" (lphMidiIn As Long,
(来源:) 想用它上面的滑块来控制Excel,就像Excel表单控制滚动条一样 我已经设法修改了VBA,但它非常不稳定。 有人能帮我稳定一下吗?我认为函数midin_事件如果返回速度不够快,可能会崩溃,但我可能错了 提前谢谢
Public Const CALLBACK_FUNCTION = &H30000
Public Declare Function midiInOpen Lib "winmm.dll"
(lphMidiIn As Long,
ByVal uDeviceID As Long, ByVal dwCallback As Any,
ByVal dwInstance As Long, ByVal dwFlags As Long) As Long
Public Declare Function midiInClose Lib "winmm.dll"
(ByVal hMidiIn As Long) As Long
Public Declare Function midiInStart Lib "winmm.dll"
(ByVal hMidiIn As Long) As Long
Public Declare Function midiInStop Lib "winmm.dll"
(ByVal hMidiIn As Long) As Long
Public Declare Function midiInReset Lib "winmm.dll"
(ByVal hMidiIn As Long) As Long
Private ri As Long
Public Sub StartMidiFunction()
Dim lngInputIndex As Long
lngInputIndex=0
Call midiInOpen(ri, lngInputIndex, AddressOf MidiIn_Event,
0, CALLBACK_FUNCTION)
Call midiInStart(ri)
End Function
Public Sub EndMidiRecieve()
Call midiInReset(ri)
Call midiInStop(ri)
Call midiInClose(ri)
End Sub
Public Function MidiIn_Event(ByVal MidiInHandle As Long,
ByVal Message As Long, ByVal Instance As Long,
ByVal dw1 As Long, ByVal dw2 As Long) As Long
'dw1 contains the midi code
If dw1 > 255 Then 'Ignore time codes
Call MsgBox(dw1) 'This part is unstable
End If
End Function
问题可能是
MsgBox
:
- 由于MIDI事件使用回调,它们很可能从另一个线程运行。VBA本质上是单线程的(请参见示例),因此尝试从另一个线程显示模式对话框可能会导致问题(未定义的行为、崩溃、任何其他问题…)
- MIDI通常会触发大量的事件(滑块或旋钮的最小移动都会触发事件),因此移动明显数量的东西可能会导致数百个事件。在每个事件中显示对话框(需要单击“确定”)可能是一个问题
调用MsgBox(dw1)
替换为Debug.Print dw1
,以便将值直接打印在即时窗口中,这应该更稳定。如果您试图执行一些简单的操作(例如,更新单元格中的值,滚动窗口),只要对midin\u Event
的每次调用在下一个事件之前完成,您就可以不受影响
一个更复杂但更稳定的解决方案是将数据点推送到事件处理程序中的队列上,并在VBA中使用一个重复计时器,从队列中弹出项目并在VBA线程上执行一些操作。这太酷了:D 但是上面提到的messagebox会杀死它,但是删除messagebox可能不会有多大帮助。您也希望最小化excel的流量,因为vba->excel不会是即时的 所以解决办法是 关于工作簿启动宏
Public lngMessage As String
Private Sub Workbook_Open()
alertTime = Now + TimeValue("00:00:01")
Application.OnTime alertTime, "EventMacro"
End Sub
Sub EventMacro()
ActiveSheet.Cells(1, 1).Value = lngMessage
alertTime = Now + TimeValue("00:00:01")
End Sub
Public Function MidiIn_Event(ByVal MidiInHandle As Long, ByVal Message As Long, ByVal Instance As Long, ByVal dw1 As Long, ByVal dw2 As Long) As Long
'dw1 contains the midi code
If dw1 > 255 Then 'Ignore time codes
lngMessage = dw1 'This part is now happy
End If
End Function
您需要一个通用函数来处理midin_事件1给出的数据,在下面的示例中,该函数是runClock()函数 我这样做是为了能够使用状态栏来计算按键和时钟类型的消息
Option Explicit
Private Const CALLBACK_FUNCTION = &H30000
'MIDI Functions here: https://docs.microsoft.com/en-us/windows/win32/multimedia/midi-functions
#If Win64 Then
Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As LongPtr)
Private Declare PtrSafe Function timeGetTime Lib "winmm.dll" () As Long
'For MIDI device INPUT
Private Declare PtrSafe Function midiInOpen Lib "winmm.dll" (lphMidiIn As LongPtr, ByVal uDeviceID As LongPtr, ByVal dwCallback As LongPtr, ByVal dwInstance As LongPtr, ByVal dwFlags As LongPtr) As Long
Private Declare PtrSafe Function midiInClose Lib "winmm.dll" (ByVal hMidiIn As LongPtr) As Long
Private Declare PtrSafe Function midiInStart Lib "winmm.dll" (ByVal hMidiIn As LongPtr) As Long
Private Declare PtrSafe Function midiInStop Lib "winmm.dll" (ByVal hMidiIn As LongPtr) As Long
Private Declare PtrSafe Function midiInReset Lib "winmm.dll" (ByVal hMidiIn As LongPtr) As Long
#Else
Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Private Declare Function timeGetTime Lib "winmm.dll" () As Long
'For MIDI device INPUT
Private Declare Function midiInOpen Lib "winmm.dll" (lphMidiIn As Long, ByVal uDeviceID As Long, ByVal dwCallback As Long, ByVal dwInstance As Long, ByVal dwFlags As Long) As Long
Private Declare Function midiInClose Lib "winmm.dll" (ByVal hMidiIn As Long) As Long
Private Declare Function midiInStart Lib "winmm.dll" (ByVal hMidiIn As Long) As Long
Private Declare Function midiInStop Lib "winmm.dll" (ByVal hMidiIn As Long) As Long
Private Declare Function midiInReset Lib "winmm.dll" (ByVal hMidiIn As Long) As Long
#End If
#If Win64 Then
Private mlngCurDevice As Long
Private mlngHmidi As LongPtr
#Else
Private mlngCurDevice As Long
Private mlngHmidi As Long
#End If
Private ClockTicks As Integer
Private Notes As Integer
Private Looper As Long
Private LongMessage As Long
Private actualTime As Long
Public Sub runClock()
'When canceled become able to close opened Input devices (For ESC press)
On Error GoTo handleCancel
Application.EnableCancelKey = xlErrorHandler
With Application
.Calculation = xlCalculationManual
.ScreenUpdating = False
'.DisplayStatusBar = False
'.EnableEvents = False
End With
mlngCurDevice = 8 'My Device is 8 but yours is 0
Notes = 0
Looper = 0
'Open Input Device
Call midiInOpen(mlngHmidi, mlngCurDevice, AddressOf MidiIn_Event, 0, CALLBACK_FUNCTION)
'Ends only when Status is different from 0
Do While Notes < 10
'Reset Status count
ClockTicks = 0
'Begins lissinting the MIDI input
Call midiInStart(mlngHmidi)
'Loops until the right message is given <= 255 and > 0
Do While ClockTicks < 1000 And Notes < 10
'Sleep if needed
Sleep 10
Application.StatusBar = "Looper=" & Looper & " | Notes=" & Notes & " | ClockTicks=" & ClockTicks & " | Message=" & LongMessage
Looper = Looper + 1
'DoEvents enables ESC key
If Abs(timeGetTime - actualTime) > 3000 Then
DoEvents
actualTime = timeGetTime
End If
Loop
'Ends lisingting the MIDI input
Call midiInReset(mlngHmidi)
Call midiInStop(mlngHmidi)
Loop
'Closes Input device
Do While midiInClose(mlngHmidi) <> 0
Loop
With Application
.Calculation = xlCalculationAutomatic
.ScreenUpdating = True
.DisplayStatusBar = True
.EnableEvents = True
End With
MsgBox "ENDED WITH SUCCESS", , "Message:"
'Close all opened MIDI Inputs when canceled (ESC key pressed)
handleCancel:
If Err.Number = 18 Then
'Ends lisingting the MIDI input
Call midiInReset(mlngHmidi)
Call midiInStop(mlngHmidi)
Do While midiInClose(mlngHmidi) <> 0
Loop
With Application
.Calculation = xlCalculationAutomatic
.ScreenUpdating = True
.DisplayStatusBar = True
.EnableEvents = True
End With
MsgBox "ENDED WITH SUCCESS", , "Message:"
End If
End Sub
Private Function MidiIn_Event(ByVal mlngHmidi As Long, ByVal Message As Long, ByVal Instance As Long, ByVal dw1 As Long, ByVal dw2 As Long) As Long
'The value 963 is the MIM_DATA concerning regular MIDI messages
If Message = 963 Then
LongMessage = Message
If dw1 > 255 Then
Notes = Notes + 1
Else
ClockTicks = ClockTicks + 1
End If
End If
End Function
选项显式
私有常量回调函数=&H30000
'此处的MIDI功能:https://docs.microsoft.com/en-us/windows/win32/multimedia/midi-functions
#如果是Win64,那么
私有声明PtrSafe子睡眠库“kernel32”(ByVal dwr作为LongPtr)
私有声明PtrSafe函数timeGetTime Lib“winmm.dll”()的长度为
'用于MIDI设备输入
私有声明PtrSafe函数midinopen Lib“winmm.dll”(lphMidiIn作为LongPtr,ByVal uDeviceID作为LongPtr,ByVal dwCallback作为LongPtr,ByVal dwstance作为LongPtr,ByVal dwFlags作为LongPtr)作为LongPtr
私有声明PtrSafe函数midiInClose Lib“winmm.dll”(ByVal hmidii作为LongPtr)为Long
私有声明PtrSafe函数midistart Lib“winmm.dll”(ByVal hmidii作为LongPtr)为Long
私有声明PtrSafe函数midinstop Lib“winmm.dll”(ByVal hmidii作为LongPtr)为Long
私有声明PtrSafe函数midinreset Lib“winmm.dll”(ByVal hmidii作为LongPtr)为Long
#否则
私有声明子睡眠库“kernel32”(ByVal-dwms长度)
私有声明函数timeGetTime库“winmm.dll”()的长度为
'用于MIDI设备输入
私有声明函数MIDINOPEN Lib“winmm.dll”(lphMidiIn为Long,ByVal uDeviceID为Long,ByVal dwCallback为Long,ByVal dwstance为Long,ByVal dwFlags为Long)为Long
私有声明函数midiInClose Lib“winmm.dll”(ByVal hMidiIn作为Long)作为Long
私有声明函数midistart Lib“winmm.dll”(ByVal hMidiIn As Long)尽可能长
私有声明函数midinstop Lib“winmm.dll”(ByVal hMidiIn为Long)为Long
私有声明函数midinreset Lib“winmm.dll”(ByVal-hMidiIn为Long)为Long
#如果结束
#如果是Win64,那么
专用MLNGCUR设备,只要
作为长PTR的私有mlngHmidi
#否则
专用MLNGCUR设备,只要
私人mlngHmidi长
#如果结束
作为整数的专用时钟信号
作为整数的私有注释
专用活套
私人长消息
私人实际时间尽可能长
公共子运行时钟()
'取消时,可以关闭打开的输入设备(对于ESC按键)
关于错误转到handleCancel
Application.EnableCancelKey=xlErrorHandler
应用
.Calculation=xlCalculationManual
.ScreenUpdate=False
'.DisplayStatusBar=False
'.EnableEvents=False
以
mlngCurDevice=8'我的设备是8,但你的设备是0
注释=0
活套=0
“开放式输入设备
调用MIDINOPEN(mlngHmidi、MLNGCURDEVENT、MIDINU事件的地址、0、回调函数)
'仅在状态不同于0时结束
当笔记<10时做
'重置状态计数
时钟信号=0
'开始LISSINT MIDI输入
呼叫midiInStart(mlngHmidi)
'循环,直到给出正确的消息0
当时钟信号<1000且音符<10时执行此操作
“需要的话就睡觉
睡眠10
Application.StatusBar=“Looper=“&Looper&”| Notes=“&Notes&”| ClockTicks=“&ClockTicks&”| Message=“&LongMessage
活套=活套+1
'启用ESC键
如果Abs(timeGetTime-actualTime)>3000,则
多芬特
actualTime=timeGetTime
如果结束
环
'结束对MIDI输入的LISING
呼叫midinreset(mlngHmidi)
呼叫MIDINSTOP(mlngHmidi)
环
'关闭输入设备
在MidInclose(mlngHmidi)0时执行
环
应用
.Calculation=xlcalculation自动
.ScreenUpdate=True
.DisplayStatusBar=True
.EnableEvents=True
以
MsgBox“以成功结束”,“消息:”