From 7d8d8449dc7ac07cb1435c4fd1d0bfa411ad1113 Mon Sep 17 00:00:00 2001 From: Laurence Dougal Myers Date: Sat, 18 Jan 2014 20:51:46 +1100 Subject: [PATCH] First commit for midi_to_joy. Setup for mapping any CC controller (up to values of 112) to an X axis on a VJoy joystick. --- .hgignore | 0 MidiRules.ahk | 126 ++++++ Midi_In_and_GuiMonitor.ahk | 95 +++++ Midi_under_the_hood.ahk | 557 ++++++++++++++++++++++++ VJoy_Test.ahk | 460 ++++++++++++++++++++ VJoy_lib.ahk | 175 ++++++++ VJoy_lib_1.ahk | 839 +++++++++++++++++++++++++++++++++++++ joystick/joy_info.ahk | 84 ++++ joystuff.ahk | 107 +++++ midi_to_joy_1.ahk | 172 ++++++++ midi_to_joy_1.ini | Bin 0 -> 92 bytes midi_to_joy_1io.ini | 0 vJoyInterface.dll | Bin 0 -> 19456 bytes x32/vJoyInterface.dll | Bin 0 -> 16896 bytes x64/vJoyInterface.dll | Bin 0 -> 19456 bytes 15 files changed, 2615 insertions(+) create mode 100644 .hgignore create mode 100644 MidiRules.ahk create mode 100644 Midi_In_and_GuiMonitor.ahk create mode 100644 Midi_under_the_hood.ahk create mode 100644 VJoy_Test.ahk create mode 100644 VJoy_lib.ahk create mode 100644 VJoy_lib_1.ahk create mode 100644 joystick/joy_info.ahk create mode 100644 joystuff.ahk create mode 100644 midi_to_joy_1.ahk create mode 100644 midi_to_joy_1.ini create mode 100644 midi_to_joy_1io.ini create mode 100644 vJoyInterface.dll create mode 100644 x32/vJoyInterface.dll create mode 100644 x64/vJoyInterface.dll diff --git a/.hgignore b/.hgignore new file mode 100644 index 0000000..e69de29 diff --git a/MidiRules.ahk b/MidiRules.ahk new file mode 100644 index 0000000..9aa6d79 --- /dev/null +++ b/MidiRules.ahk @@ -0,0 +1,126 @@ + +;************************************************* +;* RULES - MIDI FILTERS +;************************************************* + +/* + The MidiRules section is for modifying midi input from some other source. + *See hotkeys below if you wish to generate midi messages from hotkeys. + + Write your own MidiRules and put them in this section. + Keep rules together under proper section, notes, cc, program change etc. + Keep them after the statusbyte has been determined. + Examples for each type of rule will be shown. + The example below is for note type message. + + Remember byte1 for a noteon/off is the note number, byte2 is the velocity of that note. + example + ifequal, byte1, 20 ; if the note number coming in is note # 20 + { + byte1 := (do something in here) ; could be do something to the velocity(byte2) + gosub, SendNote ; send the note out. + } + */ + +MidiRules: ; write your own rules in here, look for : ++++++ for where you might want to add + ; stay away from !!!!!!!!!! + + ; =============== Is midi input a Note On or Note off message? =============== + if statusbyte between 128 and 159 ; see range of values for notemsg var defined in autoexec section. "in" used because ranges of note on and note off + { ; beginning of note block + + ; !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! above end of no edit + + ; =============== add your note MidiRules here ==; =============== + + /* + Write your own note filters and put them in this section. + Remember byte1 for a noteon/off is the note number, byte2 is the velocity of that note. + example + ifequal, byte1, 20 ; if the note number coming in is note # 20 + { + byte1 := (do something in here) ; could be do something to the velocity(byte2) + gosub, SendNote ; send the note out. + } + */ + ; ++++++++++++++++++++++++++++++++ examples of note rules ++++++++++ feel free to add more. + + ; ++++++++++++++++++++++++++++++++ End of examples of note rules ++++++++++ + } ; end of note block + +; =============== all cc detection ---- + ; is input cc? + + if statusbyte between 176 and 191 ; check status byte for cc 176-191 is the range for CC messages ; !!!!!!!! no edit this line, uykwyad + ;gosub, sendcc + + { + ; ++++++++++++++++++++++++++++++++ examples of CC rules ++++++++++ feel free to add more. + if byte1 not in %cc_msg% ; if the byte1 value is one of these... + { + tmp_axis_val := Floor((byte2 / 112) * AxisMax_X) + VJoy_SetAxis(tmp_axis_val, iInterface, HID_USAGE_X) + + cc := byte1 ; pass them as is, no change. + gosub, ShowMidiInMessage + GuiControl,12:, MidiMsOut, CC %statusbyte% %chan% %cc% %byte2% + gosub, ShowMidiOutMessage + ;gosub, sendCC + } + ; ++++++++++++++++++++++++++++++++ examples of cc rules ends ++++++++++++ + } + + ; Is midi input a Program Change? + if statusbyte between 192 and 208 ; check if message is in range of program change messages for byte1 values. ; !!!!!!!!!!!! no edit + { + ; ++++++++++++++++++++++++++++++++ examples of program change rules ++++++++++ + ; Sorry I have not created anything for here nor for pitchbends.... + + ;GuiControl,12:, MidiMsOut, ProgC:%statusbyte% %chan% %byte1% %byte2% + ;gosub, ShowMidiInMessage + gosub, sendPC + ; need something for it to do here, could be converting to a cc or a note or changing the value of the pc + ; however, at this point the only thing that happens is the gui change, not midi is output here. + ; you may want to make a SendPc: label below + ; ++++++++++++++++++++++++++++++++ examples of program change rules ++++++++++ + } + ;msgbox filter triggered +; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ end of edit section +Return + +;************************************************* +;* MIDI OUTPUT LABELS TO CALL +;************************************************* + +SendNote: ;(h_midiout,Note) ; send out note messages ; this should probably be a funciton but... eh. + ;{ + + + ;GuiControl,12:, MidiMsOutSend, NoteOut:%statusbyte% %chan% %byte1% %byte2% + ;global chan, EventType, NoteVel + ;MidiStatus := 143 + chan + note = %byte1% ; this var is added to allow transpostion of a note + midiOutShortMsg(h_midiout, statusbyte, note, byte2) ; call the midi funcitons with these params. + gosub, ShowMidiOutMessage +Return + +SendCC: ; not sure i actually did anything changing cc's here but it is possible. + + + ;GuiControl,12:, MidiMsOutSend, CCOut:%statusbyte% %chan% %cc% %byte2% + midiOutShortMsg(h_midiout, statusbyte, cc, byte2) + + ;MsgBox, 0, ,sendcc triggered , 1 + Return + +SendPC: + gosub, ShowMidiOutMessage + ;GuiControl,12:, MidiMsOutSend, ProgChOut:%statusbyte% %chan% %byte1% %byte2% + midiOutShortMsg(h_midiout, statusbyte, pc, byte2) + /* + COULD BE TRANSLATED TO SOME OTHER MIDI MESSAGE IF NEEDED. + */ +Return + + + diff --git a/Midi_In_and_GuiMonitor.ahk b/Midi_In_and_GuiMonitor.ahk new file mode 100644 index 0000000..5f92866 --- /dev/null +++ b/Midi_In_and_GuiMonitor.ahk @@ -0,0 +1,95 @@ +/* + PARSE - LAST MIDI MESSAGE RECEIVED - + Midi monitor. +*/ + +;************************************************* +;* MIDI INPUT PARSE FUNCTION +;* +;************************************************* + +; !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! no edit below here .... + +MidiMsgDetect(hInput, midiMsg, wMsg) ; Midi input section in "under the hood" calls this function each time a midi message is received. Then the midi message is broken up into parts for manipulation. See http://www.midi.org/techspecs/midimessages.php (decimal values). + { + global statusbyte, chan, note, cc, byte1, byte2, stb + + statusbyte := midiMsg & 0xFF ; EXTRACT THE STATUS BYTE (WHAT KIND OF MIDI MESSAGE IS IT?) + chan := (statusbyte & 0x0f) + 1 ; WHAT MIDI CHANNEL IS THE MESSAGE ON? + byte1 := (midiMsg >> 8) & 0xFF ; THIS IS DATA1 VALUE = NOTE NUMBER OR CC NUMBER + byte2 := (midiMsg >> 16) & 0xFF ; DATA2 VALUE IS NOTE VELEOCITY OR CC VALUE + pitchb := (byte2 << 7) | byte1 ;(midiMsg >> 8) & 0x7F7F masking to extract the pbs + + + if statusbyte between 176 and 191 ; test for cc + stb := "CC" ; if so then set stp to cc + if statusbyte between 144 and 159 + stb := "NoteOn" + if statusbyte between 128 and 143 + stb := "NoteOff" + if statusbyte between 224 and 239 + stb := "PitchB" + gosub, ShowMidiInMessage ; show updated midi input message on midi monitor gui. + gosub, MidiRules ; run the label in file MidiRules.ahk Edit that file. + + } +; end of MidiMsgDetect funciton + +Return + +;************************************************* +;* SHOW MIDI INPUT ON GUI MONITOR +;************************************************* + +ShowMidiInMessage: ; update the midimonitor gui + +Gui,14:default +Gui,14:ListView, In1 ; see the first listview midi in monitor + LV_Add("",stb,statusbyte,chan,byte1,byte2) + LV_ModifyCol(1,"center") + LV_ModifyCol(2,"center") + LV_ModifyCol(3,"center") + LV_ModifyCol(4,"center") + LV_ModifyCol(5,"center") + If (LV_GetCount() > 10) + { + LV_Delete(1) + } +return + +;************************************************* +;* SHOW MIDI OUTPUT ON GUI MONITOR +;************************************************* + +ShowMidiOutMessage: ; update the midimonitor gui + +Gui,14:default +Gui,14:ListView, Out1 ; see the second listview midi out monitor + LV_Add("",stb,statusbyte,chan,byte1,byte2) + LV_ModifyCol(1,"center") + LV_ModifyCol(2,"center") + LV_ModifyCol(3,"center") + LV_ModifyCol(4,"center") + LV_ModifyCol(5,"center") + If (LV_GetCount() > 10) + { + LV_Delete(1) + } +return + +;************************************************* +;* MIDI MONITOR GUI CODE +;************************************************* + +midiMon: ; midi monitor gui with listviews +gui,14:destroy +gui,14:default +gui,14:add,text, x80 y5, Midi Input ; %TheChoice% + Gui,14:Add, DropDownList, x40 y20 w140 Choose%TheChoice% vMidiInPort gDoneInChange altsubmit, %MiList% ; ( +gui,14:add,text, x305 y5, Midi Ouput ; %TheChoice2% + Gui,14:Add, DropDownList, x270 y20 w140 Choose%TheChoice2% vMidiOutPort gDoneOutChange altsubmit , %MoList% +Gui,14:Add, ListView, x5 r11 w220 Backgroundblack caqua Count10 vIn1, EventType|StatB|Ch|Byte1|Byte2| +gui,14:Add, ListView, x+5 r11 w220 Backgroundblack cyellow Count10 vOut1, EventType|StatB|Ch|Byte1|Byte2| +gui,14:Show, autosize xcenter y5, MidiMonitor + +Return \ No newline at end of file diff --git a/Midi_under_the_hood.ahk b/Midi_under_the_hood.ahk new file mode 100644 index 0000000..23a7de4 --- /dev/null +++ b/Midi_under_the_hood.ahk @@ -0,0 +1,557 @@ + +; !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! no edit below here, unless you know what you are doing. + +;************************************************* +;* MIDI UNDER THE HOOD +;* DO NOT EDIT IN HERE, unless you know what +;* what you are doing! +;************************************************* + +;**************************************************************************************************************** +;******************************************** midi "under the hood" ********************************************* +/* + This part is meant to take care of the "under the hood" midi input and output selection and save selection to an ini file. + Hopefully it simplifies usage for others out here trying to do things with midi and ahk. + + * use it as an include. + + The code here was taken/modified from the work by TomB/Lazslo on Midi Output + http://www.autohotkey.com/forum/viewtopic.php?t=18711&highlight=midi+output + + Orbik's Midi input thread + http://www.autohotkey.com/forum/topic30715.html + This method does NOT use the midi_in.dll, it makes direct calls to the winmm.dll + + Many different people took part in the creation of this file. + + ; Last edited 6/17/2010 11:30 AM by genmce + +*/ + +;************************************************* +;* GET PORTS LIST PARSE +;************************************************* +MidiPortRefresh: ; get the list of ports + + MIlist := MidiInsList(NumPorts) + Loop Parse, MIlist, | + { + } + TheChoice := MidiInDevice + 1 + +MOlist := MidiOutsList(NumPorts2) + Loop Parse, MOlist, | + { + } + TheChoice2 := MidiOutDevice + 1 + +return + +;************************************************* +;* LOAD UP SOME STUFF. +;************************************************* +;----------------------------------------------------------------- + +ReadIni() ; also set up the tray Menu + { + Menu, tray, add, MidiSet ; set midi ports tray item + Menu, tray, add, ResetAll ; Delete the ini file for testing -------------------------------- + menu, tray, add, MidiMon + global MidiInDevice, MidiOutDevice, version ; version var is set at the beginning. + IfExist, %version%.ini + { + IniRead, MidiInDevice, %version%.ini, Settings, MidiInDevice , %MidiInDevice% ; read the midi In port from ini file + IniRead, MidiOutDevice, %version%.ini, Settings, MidiOutDevice , %MidiOutDevice% ; read the midi out port from ini file + } + Else ; no ini exists and this is either the first run or reset settings. + { + MsgBox, 1, No ini file found, Select midi ports? + IfMsgBox, Cancel + ExitApp + IfMsgBox, yes + gosub, midiset + ;WriteIni() + } + } +;************************************************* +;* WRITE TO INI FILE FUNCTION +;************************************************* + +;CALLED TO UPDATE INI WHENEVER SAVED PARAMETERS CHANGE +WriteIni() + { + global MidiInDevice, MidiOutDevice, version + + IfNotExist, %version%io.ini ; if no ini + FileAppend,, %version%io.ini ; make one with the following entries. + IniWrite, %MidiInDevice%, %version%.ini, Settings, MidiInDevice + IniWrite, %MidiOutDevice%, %version%.ini, Settings, MidiOutDevice + } + +;************************************************* +;* PORT TESTING +;************************************************* +;------------ port testing to make sure selected midi port is valid -------------------------------- + +port_test(numports,numports2) ; confirm selected ports exist ; CLEAN THIS UP STILL + + { + global midiInDevice, midiOutDevice, midiok + + ; ----- In port selection test based on numports + If MidiInDevice not Between 0 and %numports% + { + MidiIn := 0 ; this var is just to show if there is an error - set if the ports are valid = 1, invalid = 0 + ;MsgBox, 0, , midi in port Error ; (this is left only for testing) + If (MidiInDevice = "") ; if there is no midi in device + MidiInerr = Midi In Port EMPTY. ; set this var = error message + ;MsgBox, 0, , midi in port EMPTY + If (midiInDevice > %numports%) ; if greater than the number of ports on the system. + MidiInnerr = Midi In Port Invalid. ; set this error message + ;MsgBox, 0, , midi in port out of range + } + Else + { + MidiIn := 1 ; setting var to non-error state or valid + } + ; ----- out port selection test based on numports2 + If MidiOutDevice not Between 0 and %numports2% + { + MidiOut := 0 ; set var to 0 as Error state. + If (MidiOutDevice = "") ; if blank + MidiOuterr = Midi Out Port EMPTY. ; set this error message + ;MsgBox, 0, , midi o port EMPTY + If (midiOutDevice > %numports2%) ; if greater than number of availble ports + MidiOuterr = Midi Out Port Out Invalid. ; set this error message + ;MsgBox, 0, , midi out port out of range + } + Else + { + MidiOut := 1 ;set var to 1 as valid state. + } + ; ---- test to see if ports valid, if either invalid load the gui to select. + ;midicheck(MCUin,MCUout) + If (%MidiIn% = 0) Or (%MidiOut% = 0) + { + MsgBox, 49, Midi Port Error!,%MidiInerr%`n%MidiOuterr%`n`nLaunch Midi Port Selection! + IfMsgBox, Cancel + ExitApp + midiok = 0 ; Not sure if this is really needed now.... + Gosub, MidiSet ;Gui, show Midi Port Selection + } + Else + { + midiok = 1 + Return ; DO NOTHING - PERHAPS DO THE NOT TEST INSTEAD ABOVE. + } + } +Return + +;************************************************* +;* MIDI SET GUI +;************************************************* +; ------------------ end of port testing --------------------------- + + +MidiSet: ; midi port selection gui + + ; ------------- MIDI INPUT SELECTION ----------------------- + Gui, 6: Destroy + Gui, 2: Destroy + Gui, 3: Destroy + Gui, 4: Destroy + Gui, 4: +LastFound +AlwaysOnTop +Caption +ToolWindow ;-SysMenu + Gui, 4: Font, s12 + Gui, 4: add, text, x10 y10 w300 cmaroon, Select Midi Ports. ; Text title + Gui, 4: Font, s8 + Gui, 4: Add, Text, x10 y+10 w175 Center , Midi In Port ;Just text label + Gui, 4: font, s8 + ; midi ins list box + Gui, 4: Add, ListBox, x10 w200 h100 Choose%TheChoice% vMidiInPort gDoneInChange AltSubmit, %MiList% ; --- midi in listing of ports + ;Gui, Add, DropDownList, x10 w200 h120 Choose%TheChoice% vMidiInPort gDoneInChange altsubmit, %MiList% ; ( you may prefer this style, may need tweak) + + ; --------------- MidiOutSet --------------------- + Gui, 4: Add, TEXT, x220 y40 w175 Center, Midi Out Port ; gDoneOutChange + ; midi outlist box + Gui, 4: Add, ListBox, x220 y62 w200 h100 Choose%TheChoice2% vMidiOutPort gDoneOutChange AltSubmit, %MoList% ; --- midi out listing + ;Gui, Add, DropDownList, x220 y97 w200 h120 Choose%TheChoice2% vMidiOutPort gDoneOutChange altsubmit , %MoList% + Gui, 4: add, Button, x10 w205 gSet_Done, Done - Reload script. + Gui, 4: add, Button, xp+205 w205 gCancel, Cancel + ;gui, 4: add, checkbox, x10 y+10 vNotShown gDontShow, Do Not Show at startup. + ;IfEqual, NotShown, 1 + ;guicontrol, 4:, NotShown, 1 + Gui, 4: show , , %version% Midi Port Selection ; main window title and command to show it. + +Return + +;-----------------gui done change stuff - see label in both gui listbox line + + + + +;44444444444444444444444444 NEED TO EDIT THIS TO REFLECT CHANGES IN GENMCE PRIOR TO SEND OUT + +DoneInChange: + gui +lastfound + Gui, Submit, NoHide + Gui, Flash + Gui, 4: Submit, NoHide + Gui, 4: Flash + If %MidiInPort% + UDPort:= MidiInPort - 1, MidiInDevice:= UDPort ; probably a much better way do this, I took this from JimF's qwmidi without out editing much.... it does work same with doneoutchange below. + GuiControl, 4:, UDPort, %MidiIndevice% + WriteIni() + ;MsgBox, 32, , midi in device = %MidiInDevice%`nmidiinport = %MidiInPort%`nport = %port%`ndevice= %device% `n UDPort = %UDport% ; only for testing +Return + +DoneOutChange: + gui +lastfound + Gui, Submit, NoHide + Gui, Flash + + Gui, 4: Submit, NoHide + Gui, 4: Flash + If %MidiOutPort% + UDPort2:= MidiOutPort - 1 , MidiOutDevice:= UDPort2 + GuiControl, 4: , UDPort2, %MidiOutdevice% + WriteIni() + ;Gui, Destroy +Return + +;------------------------ end of the doneout change stuff. + +Set_Done: ; aka reload program, called from midi selection gui + Gui, 3: Destroy + Gui, 4: Destroy + sleep, 100 + Reload +Return + +Cancel: + Gui, Destroy + Gui, 2: Destroy + Gui, 3: Destroy + Gui, 4: Destroy + Gui, 5: Destroy +Return + +;************************************************* +;* MIDI OUTPUT - UNDER THE HOOD +;************************************************* +; ********************** Midi output detection + +MidiOut: ; Function to load new settings from midi out menu item + OpenCloseMidiAPI() + h_midiout := midiOutOpen(MidiOutDevice) ; OUTPUT PORT 1 SEE BELOW FOR PORT 2 +return + +ResetAll: ; for development only, leaving this in for a program reset if needed by user + MsgBox, 33, %version% - Reset All?, This will delete ALL settings`, and restart this program! + IfMsgBox, OK + { + FileDelete, %version%.ini ; delete the ini file to reset ports, probably a better way to do this ... + Reload ; restart the app. + } + IfMsgBox, Cancel +Return + +GuiClose: ; on x exit app + Suspend, Permit ; allow Exit to work Paused. I just added this yesterday 3.16.09 Can now quit when Paused. + + MsgBox, 4, Exit %version%, Exit %version% %ver%? ; + IfMsgBox No + Return + Else IfMsgBox Yes + midiOutClose(h_midiout) + + Gui, 6: Destroy + Gui, 2: Destroy + Gui, 3: Destroy + Gui, 4: Destroy + Gui, 5: Destroy + gui, 7: destroy + ;gui, + Sleep 100 + ;winclose, Midi_in_2 ;close the midi in 2 ahk file + ExitApp + +;************************************************* +;* MIDI INPUT / OUTPUT UNDER THE HOOD +;************************************************* + +;############################################## MIDI LIB from orbik and lazslo############# +;-------- orbiks midi input code -------------- +; Set up midi input and callback_window based on the ini file above. +; This code copied from ahk forum Orbik's post on midi input + +; nothing below here to edit. + +; =============== midi in ===================== + +Midiin_go: +DeviceID := MidiInDevice ; midiindevice from IniRead above assigned to deviceid +CALLBACK_WINDOW := 0x10000 ; from orbiks code for midi input + +Gui, +LastFound ; set up the window for midi data to arrive. +hWnd := WinExist() ;MsgBox, 32, , line 176 - mcu-input is := %MidiInDevice% , 3 ; this is just a test to show midi device selection + +hMidiIn = +VarSetCapacity(hMidiIn, 4, 0) + result := DllCall("winmm.dll\midiInOpen", UInt,&hMidiIn, UInt,DeviceID, UInt,hWnd, UInt,0, UInt,CALLBACK_WINDOW, "UInt") + If result + { + MsgBox, Error, midiInOpen Returned %result%`n + ;GoSub, sub_exit + } + +hMidiIn := NumGet(hMidiIn) ; because midiInOpen writes the value in 32 bit binary Number, AHK stores it as a string + result := DllCall("winmm.dll\midiInStart", UInt,hMidiIn) + If result + { + MsgBox, Error, midiInStart Returned %result%`nRight Click on the Tray Icon - Left click on MidiSet to select valid midi_in port. + ;GoSub, sub_exit + } + +OpenCloseMidiAPI() + + ; ----- the OnMessage listeners ---- + + ; #define MM_MIM_OPEN 0x3C1 /* MIDI input */ + ; #define MM_MIM_CLOSE 0x3C2 + ; #define MM_MIM_DATA 0x3C3 + ; #define MM_MIM_LONGDATA 0x3C4 + ; #define MM_MIM_ERROR 0x3C5 + ; #define MM_MIM_LONGERROR 0x3C6 + + OnMessage(0x3C1, "MidiMsgDetect") ; calling the function MidiMsgDetect in get_midi_in.ahk + OnMessage(0x3C2, "MidiMsgDetect") + OnMessage(0x3C3, "MidiMsgDetect") + OnMessage(0x3C4, "MidiMsgDetect") + OnMessage(0x3C5, "MidiMsgDetect") + OnMessage(0x3C6, "MidiMsgDetect") + +Return + +;************************************************* +;* MIDI IN PORT HANDLING +;************************************************* + +;--- MIDI INS LIST FUNCTIONS - port handling ----- + +MidiInsList(ByRef NumPorts) + { ; Returns a "|"-separated list of midi output devices + local List, MidiInCaps, PortName, result + VarSetCapacity(MidiInCaps, 50, 0) + VarSetCapacity(PortName, 32) ; PortNameSize 32 + + NumPorts := DllCall("winmm.dll\midiInGetNumDevs") ; #midi output devices on system, First device ID = 0 + + Loop %NumPorts% + { + result := DllCall("winmm.dll\midiInGetDevCapsA", UInt,A_Index-1, UInt,&MidiInCaps, UInt,50, UInt) + If (result OR ErrorLevel) { + List .= "|-Error-" + Continue + } + DllCall("RtlMoveMemory", Str,PortName, UInt,&MidiInCaps+8, UInt,32) ; PortNameOffset 8, PortNameSize 32 + List .= "|" PortName + } + Return SubStr(List,2) + } + +MidiInGetNumDevs() { ; Get number of midi output devices on system, first device has an ID of 0 + Return DllCall("winmm.dll\midiInGetNumDevs") + } +MidiInNameGet(uDeviceID = 0) { ; Get name of a midiOut device for a given ID + + ;MIDIOUTCAPS struct + ; WORD wMid; + ; WORD wPid; + ; MMVERSION vDriverVersion; + ; CHAR szPname[MAXPNAMELEN]; + ; WORD wTechnology; + ; WORD wVoices; + ; WORD wNotes; + ; WORD wChannelMask; + ; DWORD dwSupport; + + VarSetCapacity(MidiInCaps, 50, 0) ; allows for szPname to be 32 bytes + OffsettoPortName := 8, PortNameSize := 32 + result := DllCall("winmm.dll\midiInGetDevCapsA", UInt,uDeviceID, UInt,&MidiInCaps, UInt,50, UInt) + + If (result OR ErrorLevel) { + MsgBox Error %result% (ErrorLevel = %ErrorLevel%) in retrieving the name of midi Input %uDeviceID% + Return -1 + } + + VarSetCapacity(PortName, PortNameSize) + DllCall("RtlMoveMemory", Str,PortName, Uint,&MidiInCaps+OffsettoPortName, Uint,PortNameSize) + Return PortName + } + +MidiInsEnumerate() { ; Returns number of midi output devices, creates global array MidiOutPortName with their names + local NumPorts, PortID + MidiInPortName = + NumPorts := MidiInGetNumDevs() + + Loop %NumPorts% { + PortID := A_Index -1 + MidiInPortName%PortID% := MidiInNameGet(PortID) + } + Return NumPorts + } + +;************************************************* +;* MIDI OUT LIBRARY FROM lASZLO/TOMB +; Modified by JimF - removed long message +; handling as well as combining status byte with ch +; see commented out section below if you want to change it back +;************************************************* +; =============== end of midi selection stuff + + +MidiOutsList(ByRef NumPorts) + { ; Returns a "|"-separated list of midi output devices + local List, MidiOutCaps, PortName, result + VarSetCapacity(MidiOutCaps, 50, 0) + VarSetCapacity(PortName, 32) ; PortNameSize 32 + + NumPorts := DllCall("winmm.dll\midiOutGetNumDevs") ; #midi output devices on system, First device ID = 0 + + Loop %NumPorts% + { + result := DllCall("winmm.dll\midiOutGetDevCapsA", UInt,A_Index-1, UInt,&MidiOutCaps, UInt,50, UInt) + If (result OR ErrorLevel) + { + List .= "|-Error-" + Continue + } + DllCall("RtlMoveMemory", Str,PortName, UInt,&MidiOutCaps+8, UInt,32) ; PortNameOffset 8, PortNameSize 32 + List .= "|" PortName + } + Return SubStr(List,2) + } +;---------------------midiOut from TomB and Lazslo and JimF -------------------------------- + +;THATS THE END OF MY STUFF (JimF) THE REST ID WHAT LASZLo AND PAXOPHONE WERE USING ALREADY +;AHK FUNCTIONS FOR MIDI OUTPUT - calling winmm.dll +;http://msdn.microsoft.com/library/default.asp?url=/library/en-us/multimed/htm/_win32_multimedia_functions.asp +;Derived from Midi.ahk dated 29 August 2008 - streaming support removed - (JimF) + + +OpenCloseMidiAPI() { ; at the beginning to load, at the end to unload winmm.dll + static hModule + If hModule + DllCall("FreeLibrary", UInt,hModule), hModule := "" + If (0 = hModule := DllCall("LoadLibrary",Str,"winmm.dll")) { + MsgBox Cannot load libray winmm.dll + Exit + } + } + +;FUNCTIONS FOR SENDING SHORT MESSAGES + +midiOutOpen(uDeviceID = 0) { ; Open midi port for sending individual midi messages --> handle + strh_midiout = 0000 + + result := DllCall("winmm.dll\midiOutOpen", UInt,&strh_midiout, UInt,uDeviceID, UInt,0, UInt,0, UInt,0, UInt) + If (result or ErrorLevel) { + MsgBox There was an Error opening the midi port.`nError code %result%`nErrorLevel = %ErrorLevel% + Return -1 + } + Return UInt@(&strh_midiout) + } + +midiOutShortMsg(h_midiout, MidiStatus, Param1, Param2) { ;Channel, + ;h_midiout: handle to midi output device returned by midiOutOpen + ;EventType, Channel combined -> MidiStatus byte: http://www.harmony-central.com/MIDI/Doc/table1.html + ;Param3 should be 0 for PChange, ChanAT, or Wheel + ;Wheel events: entire Wheel value in Param2 - the function splits it into two bytes +/* + If (EventType = "NoteOn" OR EventType = "N1") + MidiStatus := 143 + Channel + Else If (EventType = "NoteOff" OR EventType = "N0") + MidiStatus := 127 + Channel + Else If (EventType = "CC") + MidiStatus := 175 + Channel + Else If (EventType = "PolyAT" OR EventType = "PA") + MidiStatus := 159 + Channel + Else If (EventType = "ChanAT" OR EventType = "AT") + MidiStatus := 207 + Channel + Else If (EventType = "PChange" OR EventType = "PC") + MidiStatus := 191 + Channel + Else If (EventType = "Wheel" OR EventType = "W") { + MidiStatus := 223 + Channel + Param2 := Param1 >> 8 ; MSB of wheel value + Param1 := Param1 & 0x00FF ; strip MSB + } +*/ + result := DllCall("winmm.dll\midiOutShortMsg", UInt,h_midiout, UInt, MidiStatus|(Param1<<8)|(Param2<<16), UInt) + If (result or ErrorLevel) { + MsgBox There was an Error Sending the midi event: (%result%`, %ErrorLevel%) + Return -1 + } + } + +midiOutClose(h_midiout) { ; Close MidiOutput + Loop 9 { + result := DllCall("winmm.dll\midiOutClose", UInt,h_midiout) + If !(result or ErrorLevel) + Return + Sleep 250 + } + MsgBox Error in closing the midi output port. There may still be midi events being Processed. + Return -1 + } + +;UTILITY FUNCTIONS +MidiOutGetNumDevs() { ; Get number of midi output devices on system, first device has an ID of 0 + Return DllCall("winmm.dll\midiOutGetNumDevs") + } + +MidiOutNameGet(uDeviceID = 0) { ; Get name of a midiOut device for a given ID + + ;MIDIOUTCAPS struct + ; WORD wMid; + ; WORD wPid; + ; MMVERSION vDriverVersion; + ; CHAR szPname[MAXPNAMELEN]; + ; WORD wTechnology; + ; WORD wVoices; + ; WORD wNotes; + ; WORD wChannelMask; + ; DWORD dwSupport; + + VarSetCapacity(MidiOutCaps, 50, 0) ; allows for szPname to be 32 bytes + OffsettoPortName := 8, PortNameSize := 32 + result := DllCall("winmm.dll\midiOutGetDevCapsA", UInt,uDeviceID, UInt,&MidiOutCaps, UInt,50, UInt) + + If (result OR ErrorLevel) { + MsgBox Error %result% (ErrorLevel = %ErrorLevel%) in retrieving the name of midi output %uDeviceID% + Return -1 + } + + VarSetCapacity(PortName, PortNameSize) + DllCall("RtlMoveMemory", Str,PortName, Uint,&MidiOutCaps+OffsettoPortName, Uint,PortNameSize) + Return PortName + } + +MidiOutsEnumerate() { ; Returns number of midi output devices, creates global array MidiOutPortName with their names + local NumPorts, PortID + MidiOutPortName = + NumPorts := MidiOutGetNumDevs() + + Loop %NumPorts% { + PortID := A_Index -1 + MidiOutPortName%PortID% := MidiOutNameGet(PortID) + } + Return NumPorts + } + +UInt@(ptr) { +Return *ptr | *(ptr+1) << 8 | *(ptr+2) << 16 | *(ptr+3) << 24 +} + +PokeInt(p_value, p_address) { ; Windows 2000 and later + DllCall("ntdll\RtlFillMemoryUlong", UInt,p_address, UInt,4, UInt,p_value) +} diff --git a/VJoy_Test.ahk b/VJoy_Test.ahk new file mode 100644 index 0000000..ae40d70 --- /dev/null +++ b/VJoy_Test.ahk @@ -0,0 +1,460 @@ +; VJoy_Test.ahk + +#include %A_ScriptDir%\VJoy_lib.ahk + + VJoy_Init() + nButtons := VJoy_GetVJDButtonNumber(iInterface) + + cbtn := 1 + + StatStr := (status = VJD_STAT_OWN) ? "OWN" : "FREE" ; only FREE state required. + + AxisMax_X := VJoy_GetVJDAxisMax(iInterface, HID_USAGE_X) + AxisMax_Y := VJoy_GetVJDAxisMax(iInterface, HID_USAGE_Y) + AxisMax_Z := VJoy_GetVJDAxisMax(iInterface, HID_USAGE_Z) + AxisMax_RX := VJoy_GetVJDAxisMax(iInterface, HID_USAGE_RX) + AxisMax_RY := VJoy_GetVJDAxisMax(iInterface, HID_USAGE_RY) + AxisMax_RZ := VJoy_GetVJDAxisMax(iInterface, HID_USAGE_RZ) + AxisMax_SL0 := VJoy_GetVJDAxisMax(iInterface, HID_USAGE_SL0) + AxisMax_SL1 := VJoy_GetVJDAxisMax(iInterface, HID_USAGE_SL1) + AxisMax_WHL := VJoy_GetVJDAxisMax(iInterface, HID_USAGE_WHL) + + ; build gui + Gui, Add, Text, w80 , Status: %StatStr% + Gui, add, Button, x100 y5 Default gBtnReset, Re&set + Gui, add, Button, x150 y5 gBtnReload, &Reload + Gui, add, Button, x200 y5 gBtnjoycpl, Open &cpl + Gui, Add, Text, x10, %nButtons% Buttons supported + Gui, Add, Button, x140 y30 gBtnTestAllON, Test all On + Gui, Add, Button, x210 y30 gBtnTestAllOFF, Off + + Loop, %nButtons% + { + bX := ((Mod((A_Index-1), 8)) * 30 ) + 10 + bY := FLOOR((A_Index-1) / 8) * 24 + 70 + Gui, add, Button, h5 x%bx% y%by% gBtn%A_Index%, %A_Index% + } + + nexty := 180 + if (VJoy_GetVJDAxisExist(iInterface, HID_USAGE_X)) { + Gui, Add, Text, x10 w130 y%nexty%, Axis X: 0 / %AxisMax_X% + Gui, Add, Slider, x140 y%nexty% vAxisX gSliderXChanged + nexty += 40 + } else { + Gui, Add, Text, x0 y0 + } + + if (VJoy_GetVJDAxisExist(iInterface, HID_USAGE_Y)) { + Gui, Add, Text, x10 w130 y%nexty%, Axis Y: 0 / %AxisMax_Y% + Gui, Add, Slider, x140 y%nexty% vAxisY gSliderYChanged + nexty += 40 + } else { + Gui, Add, Text, x0 y0 + } + + if (VJoy_GetVJDAxisExist(iInterface, HID_USAGE_Z)) { + Gui, Add, Text, x10 w130 y%nexty%, Axis Z: 0 / %AxisMax_Z% + Gui, Add, Slider, x140 y%nexty% vAxisZ gSliderZChanged + nexty += 40 + } else { + Gui, Add, Text, x0 y0 + } + + if (VJoy_GetVJDAxisExist(iInterface, HID_USAGE_RX)) { + Gui, Add, Text, x10 w130 y%nexty%, Axis RX: 0 / %AxisMax_RX% + Gui, Add, Slider, x140 y%nexty% vAxisRX gSliderRXChanged + nexty += 40 + } else { + Gui, Add, Text, x0 y0 + } + + if (VJoy_GetVJDAxisExist(iInterface, HID_USAGE_RY)) { + Gui, Add, Text, x10 w130 y%nexty%, Axis RY: 0 / %AxisMax_RY% + Gui, Add, Slider, x140 y%nexty% vAxisRY gSliderRYChanged + nexty += 40 + } else { + Gui, Add, Text, x0 y0 + } + + if (VJoy_GetVJDAxisExist(iInterface, HID_USAGE_RZ)) { + Gui, Add, Text, x10 w130 y%nexty%, Axis RZ: 0 / %AxisMax_RZ% + Gui, Add, Slider, x140 y%nexty% vAxisRZ gSliderRZChanged + nexty += 40 + } else { + Gui, Add, Text, x0 y0 + } + + if (VJoy_GetVJDAxisExist(iInterface, HID_USAGE_SL0)) { + Gui, Add, Text, x10 w130 y%nexty%, Slider0: 0 / %AxisMax_SL0% + Gui, Add, Slider, x140 y%nexty% vAxisSL0 gSliderSL0Changed + nexty += 40 + } else { + Gui, Add, Text, x0 y0 + } + + if (VJoy_GetVJDAxisExist(iInterface, HID_USAGE_SL1)) { + Gui, Add, Text, x10 w130 y%nexty%, Slider1: 0 / %AxisMax_SL1% + Gui, Add, Slider, x140 y%nexty% vAxisSL1 gSliderSL1Changed + nexty += 40 + } else { + Gui, Add, Text, x0 y0 + } + + if (VJoy_GetVJDAxisExist(iInterface, HID_USAGE_WHL)) { + Gui, Add, Text, x10 w130 y%nexty%, Wheel: 0 / %AxisMax_WHL% + Gui, Add, Slider, x140 y%nexty% vAxisWHL gSliderWHLChanged + nexty += 40 + } else { + Gui, Add, Text, x0 y0 + } + + if (ContPovNumber) { + Gui, Add, Text, x10 y%nexty%,Number of Continuous POVs: %ContPovNumber% + nexty += 20 + Gui, Add, Text, x10 y%nexty%, Continuous Pov test + Gui, Add, Slider, x140 y%nexty% vPovValSlider gSliderContPov + nexty+=40 + Gui, Add, Edit, x10 w80 y%nexty% vPovValDirect gEditContPov, -1 + Loop, %ContPovNumber% + { + _contpov_listing := % _contpov_listing . A_Index + if (A_Index < ContPovNumber) { + _contpov_listing := % _contpov_listing . "|" + } + } + Gui, Add, ListBox, x140 w40 y%nexty% vContPovChoice gContPovChoose, %_contpov_listing% + nexty += 30 + } + + if (DiscPovNumber) { + Gui, Add, Text, x10 y%nexty%,Number of Descrete POVs: %DiscPovNumber% + nexty += 20 + + tmpy := nexty + Gui, Add, Button, x160 y%tmpy% gBtnPovN, N + tmpy := nexty + 30 + Gui, Add, Button, x155 y%tmpy% gBtnPovNeu, Neu + tmpy := nexty + 60 + Gui, Add, Button, x160 y%tmpy% gBtnPovS, S + tmpy := nexty + 30 + Gui, Add, Button, x120 y%tmpy% gBtnPovW, W + Gui, Add, Button, x200 y%tmpy% gBtnPovE, E + + Loop, %DiscPovNumber% + { + _contpov_listing := % _contpov_listing . A_Index + if (A_Index < DiscPovNumber) { + _contpov_listing := % _contpov_listing . "|" + } + } + Gui, Add, ListBox, x10 w40 y%nexty% vDiscPovChoice gDiscPovChoose, %_contpov_listing% + + nexty += 100 + } + GetKeyState, _JoyStat, JoyInfo + Gui, Add, Text, x10 y%nexty%, JoyInfo: %_JoyStat% + Gui, Show + +return + +SliderXChanged: + Gui, Submit, NoHide + tmp_axis_val := Floor(AxisMax_X * AxisX / 100) + VJoy_SetAxis(tmp_axis_val, iInterface, HID_USAGE_X) + ControlSetText, Static3, Axis X %tmp_axis_val% / %AxisMax_X% +return + +SliderYChanged: + Gui, Submit, NoHide + tmp_axis_val := Floor(AxisMax_Y * AxisY / 100) + VJoy_SetAxis(tmp_axis_val, iInterface, HID_USAGE_Y) + ControlSetText, Static4, Axis Y %tmp_axis_val% / %AxisMax_Y% +return + +SliderZChanged: + Gui, Submit, NoHide + tmp_axis_val := Floor(AxisMax_Z * AxisZ / 100) + VJoy_SetAxis(tmp_axis_val, iInterface, HID_USAGE_Z) + ControlSetText, Static5, Axis Z %tmp_axis_val% / %AxisMax_Z% +return + +SliderRXChanged: + Gui, Submit, NoHide + tmp_axis_val := Floor(AxisMax_RX * AxisRX / 100) + VJoy_SetAxis(tmp_axis_val, iInterface, HID_USAGE_RX) + ControlSetText, Static6, Axis RX %tmp_axis_val% / %AxisMax_RX% +return + +SliderRYChanged: + Gui, Submit, NoHide + tmp_axis_val := Floor(AxisMax_RY * AxisRY / 100) + VJoy_SetAxis(tmp_axis_val, iInterface, HID_USAGE_RY) + ControlSetText, Static7, Axis RY %tmp_axis_val% / %AxisMax_RY% +return + +SliderRZChanged: + Gui, Submit, NoHide + tmp_axis_val := Floor(AxisMax_RZ * AxisRZ / 100) + VJoy_SetAxis(tmp_axis_val, iInterface, HID_USAGE_RZ) + ControlSetText, Static8, Axis RZ %tmp_axis_val% / %AxisMax_RZ% +return + +SliderSL0Changed: + Gui, Submit, NoHide + tmp_axis_val := Floor(AxisMax_SL0 * AxisSL0 / 100) + VJoy_SetAxis(tmp_axis_val, iInterface, HID_USAGE_SL0) + ControlSetText, Static9, Slider0 %tmp_axis_val% / %AxisMax_SL0% +return + +SliderSL1Changed: + Gui, Submit, NoHide + tmp_axis_val := Floor(AxisMax_SL1 * AxisSL1 / 100) + VJoy_SetAxis(tmp_axis_val, iInterface, HID_USAGE_SL1) + ControlSetText, Static10, Slider1 %tmp_axis_val% / %AxisMax_SL1% +return + +SliderWHLChanged: + Gui, Submit, NoHide + tmp_axis_val := Floor(AxisMax_WHL * AxisWHL / 100) + VJoy_SetAxis(tmp_axis_val, iInterface, HID_USAGE_WHL) + ControlSetText, Static11, Wheel %tmp_axis_val% / %AxisMax_WHL% +return + +BtnReload: +F12:: + VJoy_Close(iInterface) + Reload +return + +;GuiClose: +; ExitApp +;return + +OnExit: + VJoy_Close(iInterface) +return + +; GUIBtn test all buttons +BtnTestAllON: + Loop, %nButtons% + { + VJoy_SetBtn(1, iInterface, A_Index) + } +return +BtnTestAllOFF: + Loop, %nButtons% + { + VJoy_SetBtn(0, iInterface, A_Index) + } +return + +BtnTest(id, btn) { + VJoy_SetBtn(1, id, btn) + Sleep, 100 + VJoy_SetBtn(0, id, btn) ; Release button 1 +} + +; GUIBtn1 for test button1 +Btn1: + BtnTest(iInterface, 1) +return +Btn2: + BtnTest(iInterface, 2) +return +Btn3: + BtnTest(iInterface, 3) +return +Btn4: + BtnTest(iInterface, 4) +return +Btn5: + BtnTest(iInterface, 5) +return +Btn6: + BtnTest(iInterface, 6) +return +Btn7: + BtnTest(iInterface, 7) +return +Btn8: + BtnTest(iInterface, 8) +return +Btn9: + BtnTest(iInterface, 9) +return +Btn10: + BtnTest(iInterface, 10) +return +Btn11: + BtnTest(iInterface, 11) +return +Btn12: + BtnTest(iInterface, 12) +return +Btn13: + BtnTest(iInterface, 13) +return +Btn14: + BtnTest(iInterface, 14) +return +Btn15: + BtnTest(iInterface, 15) +return +Btn16: + BtnTest(iInterface, 16) +return +Btn17: + BtnTest(iInterface, 17) +return +Btn18: + BtnTest(iInterface, 18) +return +Btn19: + BtnTest(iInterface, 19) +return +Btn20: + BtnTest(iInterface, 20) +return +Btn21: + BtnTest(iInterface, 21) +return +Btn22: + BtnTest(iInterface, 22) +return +Btn23: + BtnTest(iInterface, 23) +return +Btn24: + BtnTest(iInterface, 24) +return +Btn25: + BtnTest(iInterface, 25) +return +Btn26: + BtnTest(iInterface, 26) +return +Btn27: + BtnTest(iInterface, 27) +return +Btn28: + BtnTest(iInterface, 28) +return +Btn29: + BtnTest(iInterface, 29) +return +Btn30: + BtnTest(iInterface, 30) +return +Btn31: + BtnTest(iInterface, 31) +return +Btn32: + BtnTest(iInterface, 32) +return + +; Open Game Control Panel +Btnjoycpl: + RunWait %ComSpec% /C start joy.cpl,, Hide +return + +BtnReset: + AxisX := AxisY := AxisZ := AxisRX := AxisRY := AxisRZ := Slider0 := Slider1 := 0 + Gui, Submit, NoHide + + VJoy_Close(iInterface) + VJoy_Init() +return + +EditContPov: + Gui, Submit, NoHide + + GuiControlGet, ContPovChoice + if (ContPovChoice < 1 or ContPovChoice > ContPovNumber) { + MsgBox, Please select a pov + return + } + + if (PovValDirect < -1) { + PovValDirect := -1 + Gui, Submit, NoHide + return + } + if (PovValDirect > 35999) { + PovValDirect := 35999 + Gui, Submit, NoHide + return + } + VJoy_SetContPov(PovValDirect, iInterface, ContPovChoice) +return + +SliderContPov: + Gui, Submit, NoHide + GuiControlGet, ContPovChoice + if (ContPovChoice < 1 or ContPovChoice > ContPovNumber) { + MsgBox, Please select a pov + return + } + PovValDirect := Floor(35999 * PovValSlider / 100) + ControlSetText, Edit1, %PovValDirect% + VJoy_SetContPov(PovValDirect, iInterface, ContPovChoice) +return + +ContPovChoose: + Gui, Submit, NoHide + GuiControlGet, ContPovChoice +return + +BtnPovNeu: + Gui, Submit, NoHide + GuiControlGet, DiscPovChoice + if (DiscPovChoice < 1 or DiscPovChoice > DiscPovNumber) { + MsgBox, Please select a pov + return + } + VJoy_SetDiscPov(-1, iInterface, DiscPovChoice) +return + +BtnPovN: + Gui, Submit, NoHide + GuiControlGet, DiscPovChoice + if (DiscPovChoice < 1 or DiscPovChoice > DiscPovNumber) { + MsgBox, Please select a pov + return + } + VJoy_SetDiscPov(0, iInterface, DiscPovChoice) +return + +BtnPovE: + Gui, Submit, NoHide + GuiControlGet, DiscPovChoice + if (DiscPovChoice < 1 or DiscPovChoice > DiscPovNumber) { + MsgBox, Please select a pov + return + } + VJoy_SetDiscPov(1, iInterface, DiscPovChoice) +return + +BtnPovS: + Gui, Submit, NoHide + GuiControlGet, DiscPovChoice + if (DiscPovChoice < 1 or DiscPovChoice > DiscPovNumber) { + MsgBox, Please select a pov + return + } + VJoy_SetDiscPov(2, iInterface, DiscPovChoice) +return + +BtnPovW: + Gui, Submit, NoHide + GuiControlGet, DiscPovChoice + if (DiscPovChoice < 1 or DiscPovChoice > DiscPovNumber) { + MsgBox, Please select a pov + return + } + VJoy_SetDiscPov(3, iInterface, DiscPovChoice) +return + +DiscPovChoose: + Gui, Submit, NoHide + GuiControlGet, DiscPovChoice +return \ No newline at end of file diff --git a/VJoy_lib.ahk b/VJoy_lib.ahk new file mode 100644 index 0000000..508d8dc --- /dev/null +++ b/VJoy_lib.ahk @@ -0,0 +1,175 @@ +;VJoy_lib.ahk + + iInterface = 2 ; Default target vJoy device + + ; ported from VjdStat in vjoyinterface.h + VJD_STAT_OWN := 0 ; The vJoy Device is owned by this application. + VJD_STAT_FREE := 1 ; The vJoy Device is NOT owned by any application (including this one). + VJD_STAT_BUSY := 2 ; The vJoy Device is owned by another application. It cannot be acquired by this application. + VJD_STAT_MISS := 3 ; The vJoy Device is missing. It either does not exist or the driver is down. + VJD_STAT_UNKN := 4 ; Unknown + + ;HID Descriptor definitions(ported from public.h + HID_USAGE_X := 0x30 + HID_USAGE_Y := 0x31 + HID_USAGE_Z := 0x32 + HID_USAGE_RX := 0x33 + HID_USAGE_RY := 0x34 + HID_USAGE_RZ := 0x35 + HID_USAGE_SL0 := 0x36 + HID_USAGE_SL1 := 0x37 + HID_USAGE_WHL := 0x38 + HID_USAGE_POV := 0x39 + +VJoy_init() { + Global iInterface, VJD_STAT_OWN, VJD_STAT_FREE, VJD_STAT_BUSY, VJD_STAT_MISS, VJD_STAT_UNKN, ContPovNumber, DiscPovNumber, hDLL + + SetWorkingDir, %A_ScriptDir% + curdir:=A_WorkingDir + + if (!hDLL) { + dllpath = %A_ScriptDir%\vJoyInterface.dll + hDLL := DLLCall("LoadLibrary", "Str", dllpath) + if (!hDLL) { + MsgBox, LoadLibrary %dllpath% fail + } + } + + result := DllCall("vJoyInterface.dll\vJoyEnabled", "Int") + if (ErrorLevel = 4) { + MsgBox, Error! VJoy library "vJoyInterface.dll" is not found!`nErrorLevel:%ErrorLevel% + ExitApp + } + if (!result) { + MsgBox, Error! VJoy interface is not installed!`nErrorLevel:%ErrorLevel% + ExitApp + } + + status := DllCall("vJoyInterface\GetVJDStatus", "Int", iInterface) + if (status = VJD_STAT_OWN) { + ToolTip, vJoy Device %iInterface% is already owned by this feeder + } else if (status = VJD_STAT_FREE) { + ToolTip, vJoy Device %iInterface% is free + } else if (status = VJD_STAT_BUSY) { + MsgBox vJoy Device %iInterface% is already owned by another feeder`nCannot continue`n + ExitApp + } else if (status = VJD_STAT_MISS) { + MsgBox vJoy Device %iInterface% is not installed or disabled`nCannot continue`n + ExitApp + } else { + MsgBox vJoy Device %iInterface% general error`nCannot continue`n + ExitApp + } + Sleep, 50 + ToolTip + + ; Get the number of buttons and POV Hat switchessupported by this vJoy device + ContPovNumber := DllCall("vJoyInterface\GetVJDContPovNumber", "UInt", iInterface, "Int") + DiscPovNumber := DllCall("vJoyInterface\GetVJDDiscPovNumber", "UInt", iInterface, "Int") + + ; Acquire the target device + if (status = VJD_STAT_FREE) { + ac_jvd := VJoy_AcquireVJD(iInterface) + } + if ((status = VJD_STAT_OWN) || ((status = VJD_STAT_FREE) && (!ac_jvd))) { + MsgBox % "Failed to acquire vJoy device number % iInterface " + ExitApp + } else { + ToolTip, Acquired: vJoy device number %iInterface% + } + Sleep, 50 + ToolTip + + VJoy_ResetVJD(iInterface) + +; VJoy_RelinquishVJD(iInterface) + + return +} + +VJoy_AcquireVJD(id) { + return DllCall("vJoyInterface\AcquireVJD", "UInt", id) +} + + +VJoy_GetVJDStatus(id) { + status := DllCall("vJoyInterface\GetVJDStatus", "UInt", id) + return status +} + +VJoy_GetVJDButtonNumber(id) { + res := DllCall("vJoyInterface\GetVJDButtonNumber", "Int", id) + return res +} + +VJoy_SetBtn(sw, id, btn_id) { + res := DllCall("vJoyInterface\SetBtn", "Int", sw, "UInt", id, "UChar", btn_id) + if (!res) { + MsgBox, SetBtn(%sw%, %id%, %btn_id%) err: %ErrorLevel%`nnLastError: %A_LastError% + } + return res +} + +VJoy_ResetAll() { + res := DllCall("vJoyInterface\ResetAll") + return res +} + +VJoy_ResetVJD(id) { + res := DllCall("vJoyInterface\ResetVJD", "UInt", id) + return res +} + +VJoy_GetVJDAxisMax(id, usage) { + res := DllCall("vJoyInterface\GetVJDAxisMax", "Int", id, "Int", usage, "IntP", Max_Axis) + return Max_Axis +} + +VJoy_GetVJDAxisExist(id, usage) { + Axis_t := DllCall("vJoyInterface\GetVJDAxisExist", "UInt", id, "UInt", usage) + if (!Axis_t) { +; MsgBox, GetVJDAxisExist Error!`nErrorLevel:%ErrorLevel% + } + if (ErrorLevel) { + ToolTip, GetVJDAxisExist Warning!`nErrorLevel:%ErrorLevel% + ToolTip + } + return Axis_t +} + +VJoy_SetAxis(axis_val, id, usage) { + res := DllCall("vJoyInterface\SetAxis", "Int", axis_val, "UInt", id, "UInt", usage) + if (!res) { + MsgBox, SetAxis Error!`nErrorLevel:%ErrorLevel% + } + return res +} + +VJoy_RelinquishVJD(id) { + DllCall("vJoyInterface\RelinquishVJD", "UInt", id) +} + +VJoy_Close(id) { + VJoy_ResetAll() + VJoy_RelinquishVJD(id) + if (hDLL) { + DLLCall("FreeLibraly", "Ptr", hDLL) + hDLL:= + } +} + +VJoy_SetDiscPov(Value, id, nPov) { + _res := DllCall("vJoyInterface\SetDiscPov", "Int", Value, "UInt", id, "UChar", nPov) + if (!_res) { + MsgBox, SetDiscPov err: %ErrorLevel% + } + return _ef_res +} + +VJoy_SetContPov(Value, id, nPov) { + _res := DllCall("vJoyInterface\SetContPov", "Int", Value, "UInt", id, "UChar", nPov) + if (!_res) { + MsgBox, SetContPov err: %ErrorLevel% + } + return _ef_res +} \ No newline at end of file diff --git a/VJoy_lib_1.ahk b/VJoy_lib_1.ahk new file mode 100644 index 0000000..c4af3c0 --- /dev/null +++ b/VJoy_lib_1.ahk @@ -0,0 +1,839 @@ +; VJoy_lib.ahk Ver1.1 +; Original code by Axlar - http://www.autohotkey.com/board/topic/87690- +; modded by evilC - VJoy_SetAxis fix (Bad ternary operator) + + VJD_MAXDEV := 16 + + ; ported from VjdStat in vjoyinterface.h + VJD_STAT_OWN := 0 ; The vJoy Device is owned by this application. + VJD_STAT_FREE := 1 ; The vJoy Device is NOT owned by any application (including this one). + VJD_STAT_BUSY := 2 ; The vJoy Device is owned by another application. It cannot be acquired by this application. + VJD_STAT_MISS := 3 ; The vJoy Device is missing. It either does not exist or the driver is down. + VJD_STAT_UNKN := 4 ; Unknown + + ; HID Descriptor definitions(ported from public.h + HID_USAGE_X := 0x30 + HID_USAGE_Y := 0x31 + HID_USAGE_Z := 0x32 + HID_USAGE_RX:= 0x33 + HID_USAGE_RY:= 0x34 + HID_USAGE_RZ:= 0x35 + HID_USAGE_SL0:= 0x36 + HID_USAGE_SL1:= 0x37 + + VJDev := Object() + +; Load lib from already load or current/system directory +VJoy_LoadLibrary() { + Global hVJDLL + if (hVJDLL) { + return hVJDLL + } + + ; Load dll from any path or get handle of already loaded + hVJDLL := DLLCall("LoadLibrary", "Str", "vJoyInterface") + if (hVJDLL) { + return hVJDLL + } + + ; If dll deployed into current and it was wrong, warn + dllpath = %A_ScriptDir%\vJoyInterface.dll + if (FileExist(dllpath)) { + if (A_Is64bitOS) { + is64bit = 64-bitOS + } + AHKEd := (A_PtrSize = 4) ? "32-bit" : "64-bit" + RequiredDLL := (A_PtrSize = 4) ? "x86" : "x64" + dll_info := GetFileVersion(dllpath) + if (dll_info and !InStr(dll_info, RequiredDLL)) { + isWrong = =wrong! + } + MsgBox, + ( +LoadLibrary %dllpath% failed! +Exiting. +Make sure %RequiredDLL% vJoyInterface.dll is in %A_ScriptDir% + (%dll_info%%isWrong%) + AutoHotkey: %AHKEd% + OSVersion:%A_OSVersion% %is64bit% + ) + } + return 0 +} + +GetFileVersion(pszFilePath) { + dwSize := DLLCall("Version\GetFileVersionInfoSize", "Str", pszFilePath) + if (!dwSize) { + return + } + VarSetCapacity(pvData, dwSize) + if (!DLLCall("Version\GetFileVersionInfo", "Str", pszFilePath + , "Int", 0, "Int", dwSize, "Ptr", &pvData)) { + return + } + ; Get British product version string + if (!DLLCall("Version\VerQueryValue", "UInt", &pvData, "Str" + , "\\StringFileInfo\\040904b0\\ProductVersion", "UIntP" + , lpTranslate, "UInt", 0)) { + return + } + return StrGet(lpTranslate) +} + +Class VJoyDev { + __New(dev_id) { + + Global NoticeDone, hVJDLL, VJD_STAT_OWN, VJD_STAT_FREE, VJD_STAT_BUSY, VJD_STAT_MISS, VJD_STAT_UNKN,HID_USAGE_X,HID_USAGE_Y,HID_USAGE_Z,HID_USAGE_RX,HID_USAGE_RY,HID_USAGE_RZ,HID_USAGE_SL0,HID_USAGE_SL1 + + if (!hVJDLL) { + hVJDLL := VJoy_LoadLibrary() + } + if (!hVJDLL) { + if (!NoticeDone) { + NoticeDone := True + MsgBox, [VJoy Constructer] LoadLibrary vJoyInterface.dll failed! + } + return + } + + this.DeviceEnabled := DllCall("vJoyInterface.dll\vJoyEnabled") + if (ErrorLevel = 4) { + MsgBox, Error! VJoy library "vJoyInterface.dll" is not found!`nErrorLevel:%ErrorLevel% + return + } + if (!this.DeviceEnabled) { + ;MsgBox, Error! VJoy interface is not installed!`nErrorLevel:%ErrorLevel% + return + } + + DeviceStatus := DllCall("vJoyInterface\GetVJDStatus", "UInt", dev_id) + if (DeviceStatus = VJD_STAT_OWN) { + stat_str = VJD_STAT_OWN + ;ToolTip, vJoy Device %dev_id% is already owned by this feeder + } else if (DeviceStatus = VJD_STAT_FREE) { + ;ToolTip, vJoy Device %dev_id% is free + stat_str = VJD_STAT_FREE + } else if (DeviceStatus = VJD_STAT_BUSY) { + MsgBox vJoy Device %dev_id% is already owned by another feeder`nCannot continue`n + stat_str = VJD_STAT_BUSY + return + } else if (DeviceStatus = VJD_STAT_MISS) { + ;MsgBox vJoy Device %dev_id% is not installed or disabled`nCannot continue`n + stat_str = VJD_STAT_MISS + return + } else { + stat_str = VJD_STAT_UNKN + MsgBox vJoy Device %dev_id% general error`nCannot continue`n + return + } + ;ToolTip + + ; Get the number of buttons and POV Hat switchessupported by this vJoy device + this.ContPovNumber := DllCall("vJoyInterface\GetVJDContPovNumber", "UInt", dev_id) + this.ContPov := Object() + Loop, % this.ContPovNumber ; insert dummy + this.ContPov.Insert(A_Index, 0) + + this.DiscPovNumber := DllCall("vJoyInterface\GetVJDDiscPovNumber", "UInt", dev_id) + this.DiscPov := Object() + Loop, % this.DiscPovNumber ; insert dummy + this.DiscPov.Insert(A_Index, 0) + + this.NumberOfButtons := DllCall("vJoyInterface\GetVJDButtonNumber", "Int", dev_id) + this.Btn := Object() + Loop, % this.NumberOfButtons ; insert dummy + this.Btn.Insert(A_Index, 0) + + this.AxisExist_X := DllCall("vJoyInterface\GetVJDAxisExist", "Int", dev_id, "Int", HID_USAGE_X ) + this.AxisExist_Y := DllCall("vJoyInterface\GetVJDAxisExist", "Int", dev_id, "Int", HID_USAGE_Y ) + this.AxisExist_Z := DllCall("vJoyInterface\GetVJDAxisExist", "Int", dev_id, "Int", HID_USAGE_Z ) + this.AxisExist_RX := DllCall("vJoyInterface\GetVJDAxisExist", "Int", dev_id, "Int", HID_USAGE_RX ) + this.AxisExist_RY := DllCall("vJoyInterface\GetVJDAxisExist", "Int", dev_id, "Int", HID_USAGE_RY ) + this.AxisExist_RZ := DllCall("vJoyInterface\GetVJDAxisExist", "Int", dev_id, "Int", HID_USAGE_RZ ) + this.AxisExist_SL0 := DllCall("vJoyInterface\GetVJDAxisExist", "Int", dev_id, "Int", HID_USAGE_SL0) + this.AxisExist_SL1 := DllCall("vJoyInterface\GetVJDAxisExist", "Int", dev_id, "Int", HID_USAGE_SL1) + + if (DllCall("vJoyInterface\GetVJDAxisMax", "Int", dev_id, "Int", HID_USAGE_X, "IntP", nResult)) { + this.AxisMax_X := nResult + } + if (DllCall("vJoyInterface\GetVJDAxisMax", "Int", dev_id, "Int", HID_USAGE_Y, "IntP", nResult)) { + this.AxisMax_Y := nResult + } + if (DllCall("vJoyInterface\GetVJDAxisMax", "Int", dev_id, "Int", HID_USAGE_Z, "IntP", nResult)) { + this.AxisMax_Z := nResult + } + if (DllCall("vJoyInterface\GetVJDAxisMax", "Int", dev_id, "Int", HID_USAGE_RX, "IntP", nResult)) { + this.AxisMax_RX := nResult + } + if (DllCall("vJoyInterface\GetVJDAxisMax", "Int", dev_id, "Int", HID_USAGE_RY, "IntP", nResult)) { + this.AxisMax_RY := nResult + } + if (DllCall("vJoyInterface\GetVJDAxisMax", "Int", dev_id, "Int", HID_USAGE_RZ, "IntP", nResult)) { + this.AxisMax_RZ := nResult + } + if (DllCall("vJoyInterface\GetVJDAxisMax", "Int", dev_id, "Int", HID_USAGE_SL0, "IntP", nResult)) { + this.Slider0_Max := nResult + } + if (DllCall("vJoyInterface\GetVJDAxisMax", "Int", dev_id, "Int", HID_USAGE_SL1, "IntP", nResult)) { + this.Slider1_Max := nResult + } + + ; Acquire the target device + if (DeviceStatus = VJD_STAT_FREE) { + ac_jvd := DllCall("vJoyInterface\AcquireVJD", "UInt", dev_id) + if (!ac_jvd) { + MsgBox, Dev:%dev_id% aquire fail ErrorLevel: %ErrorLevel% + } + } + + if (DeviceStatus = VJD_STAT_OWN) { + MsgBox % "Failed to acquire vJoy device number: " dev_id "`n(Other process owned device)" + return + } else if (DeviceStatus = VJD_STAT_FREE and !ac_jvd ) { + MsgBox % "Failed to acquire vJoy device number: " dev_id "`nAcquired: " ac_jvd + return + } else { + ;ToolTip, % "Acquired: vJoy device number: " dev_id + } + ;ToolTip + + this.DeviceID := dev_id + this.DeviceStatus := DeviceStatus + this.Reset() + this.DeviceReady := True + + return this + } + + __Delete() { + this.Relinquish() + } + + SetAxis(axis_val, usage) { + res := DllCall("vJoyInterface\SetAxis", "Int", axis_val, "UInt", this.DeviceID, "UInt", usage) + if (!res) { + MsgBox, SetAxis(%axis_val%`,%usage%) Error!`nErrorLevel:%ErrorLevel% + } + return res + } + + SetAxis_X(axis_val) { + Global HID_USAGE_X + new_val := parse_rel_val(axis_val, this.Axis_X, this.AxisMax_X) + res := this.SetAxis(new_val, HID_USAGE_X) + if (res) { + this.Axis_X := new_val + } + return res + } + SetAxis_Y(axis_val) { + Global HID_USAGE_Y + new_val := parse_rel_val(axis_val, this.Axis_Y, this.AxisMax_Y) + res := this.SetAxis(new_val, HID_USAGE_Y) + if (res) { + this.Axis_Y := new_val + } + return res + } + SetAxis_Z(axis_val) { + Global HID_USAGE_Z + new_val := parse_rel_val(axis_val, this.Axis_Z, this.AxisMax_Z) + res := this.SetAxis(new_val, HID_USAGE_Z) + if (res) { + this.Axis_Z := new_val + } + return res + } + SetAxis_RX(axis_val) { + Global HID_USAGE_RX + new_val := parse_rel_val(axis_val, this.Axis_RX, this.AxisMax_RX) + res := this.SetAxis(new_val, HID_USAGE_RX) + if (res) { + this.Axis_RX := new_val + } + return res + } + SetAxis_RY(axis_val) { + Global HID_USAGE_RY + new_val := parse_rel_val(axis_val, this.Axis_RY, this.AxisMax_RY) + res := this.SetAxis(new_val, HID_USAGE_RY) + if (res) { + this.Axis_RY := new_val + } + return res + } + SetAxis_RZ(axis_val) { + Global HID_USAGE_RZ + new_val := parse_rel_val(axis_val, this.Axis_RZ, this.AxisMax_RZ) + res := this.SetAxis(new_val, HID_USAGE_RZ) + if (res) { + this.Axis_RZ := new_val + } + return res + } + SetAxis_SL0(axis_val) { + Global HID_USAGE_SL0 + new_val := parse_rel_val(axis_val, this.Axis_SL0, this.AxisMax_SL0) + res := this.SetAxis(new_val, HID_USAGE_SL0) + if (res) { + this.Slider0 := new_val + } + return res + } + SetAxis_SL1(axis_val) { + Global HID_USAGE_SL1 + new_val := parse_rel_val(axis_val, this.Axis_SL1, this.AxisMax_SL1) + res := this.SetAxis(new_val, HID_USAGE_SL1) + if (res) { + this.Slider1 := new_val + } + return res + } + + GetBtn(bid) { + if (bid < 1 or bid > this.NumberOfButtons) { + return 0 + } + return this.Btn[bid] + } + + SetBtn(sw, btn_id) { + if (btn_id < 1 or btn_id > this.NumberOfButtons) { + MsgBox, SetBtn: range check error! + return 0 + } + res := DllCall("vJoyInterface\SetBtn", "Int", sw, "UInt", this.DeviceID, "UChar", btn_id) + if (res) { + this.Btn[btn_id] := sw + } + return res + } + + SetDiscPov(Value, nPov) { + _res := DllCall("vJoyInterface\SetDiscPov", "Int", Value, "UInt", this.DeviceID, "UChar", nPov) + if (!_res) { + MsgBox, SetDiscPov err: %ErrorLevel% + } else { + this.DiscPov[nPov] := Value + } + return _res + } + + SetContPov(Value, nPov) { + _res := DllCall("vJoyInterface\SetContPov", "Int", Value, "UInt", this.DeviceID, "UChar", nPov) + if (!_res) { + MsgBox, SetContPov err: %ErrorLevel% + } else { + this.ContPov[nPov] := Value + } + return _res + } + + Reset() { + ; Reset local state values + this.Axis_X := 0 + this.Axis_Y := 0 + this.Axis_Z := 0 + this.Axis_RX := 0 + this.Axis_RY := 0 + this.Axis_RZ := 0 + this.Slider0 := 0 + this.Slider1 := 0 + + for i in this.ContPov + this.ContPov[i] := 0 + for i in this.DiscPov + this.DiscPov[i] := 0 + for i in this.Btn + this.Btn[i] := 0 + return DllCall("vJoyInterface\ResetVJD", "UInt", this.DeviceID) + } + + Relinquish() { + return DllCall("vJoyInterface\RelinquishVJD", "UInt", this.DeviceID) + } +} + +VJoy_init(id := 1) { + Global VJDev, VJD_MAXDEV + if (id < 1 || id > VJD_MAXDEV) { + MsgBox, [%A_ThisFunc%] Device %id% is invalid. Please specify 1-%VJD_MAXDEV%. + return + } + VJDev[id] := new VJoyDev(id) + return VJDev[id] +} + + +VJoy_DeviceErr(id) { + Global VJD_MAXDEV, VJDev + if (id < 1 or id > VJD_MAXDEV) { + MsgBox, [%A_ThisFunc%] Device %id% is invalid. Please specify 1-%VJD_MAXDEV%. + return True + } + if (!VJDev[id].DeviceReady) { + MsgBox, [%A_ThisFunc%] Device %id% is not ready. + return True + } + return False +} +VJoy_Ready(id) { + Global VJD_MAXDEV, VJDev + if (id < 1 || id > VJD_MAXDEV) { + return False + } + return VJDev[id].DeviceReady +} + +VJoy_ResetVJD(id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].Reset() +} + +VJoy_ResetAll() { + return DllCall("vJoyInterface\ResetAll") +} + +; Release device +VJoy_RelinquishVJD(id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].Relinquish() +} + +; Acquire device - added by evilC +VJoy_AcquireVJD(id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return DllCall("vJoyInterface\AcquireVJD", "UInt", id) +} + +; destructor +VJoy_Close() { + Global VJDev + + VJoy_ResetAll() + + for idx, dev in VJDev + dev.delete + if (hVJDLL) { + DLLCall("FreeLibraly", "Ptr", hVJDLL) + hVJDLL:= + } +} + +VJoy_GetContPovNumber(id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].ContPovNumber +} + +VJoy_GetDiscPovNumber(id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].DiscPovNumber +} + +VJoy_GetVJDButtonNumber(id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].NumberOfButtons +} + +VJoy_GetAxisExist_X(id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].AxisExist_X +} +VJoy_GetAxisExist_Y(id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].AxisExist_Y +} +VJoy_GetAxisExist_Z(id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].AxisExist_Z +} +VJoy_GetAxisExist_RX(id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].AxisExist_RX +} +VJoy_GetAxisExist_RY(id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].AxisExist_RY +} +VJoy_GetAxisExist_RZ(id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].AxisExist_RZ +} +VJoy_GetAxisExist_SL0(id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].AxisExist_SL0 +} +VJoy_GetAxisExist_SL1(id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].AxisExist_SL1 +} + + +VJoy_GetAxisMax_X(id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].AxisMax_X +} +VJoy_GetAxisMax_Y(id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].AxisMax_Y +} +VJoy_GetAxisMax_Z(id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].AxisMax_Z +} +VJoy_GetAxisMax_RX(id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].AxisMax_RX +} +VJoy_GetAxisMax_RY(id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].AxisMax_RY +} +VJoy_GetAxisMax_RZ(id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].AxisMax_RZ +} +VJoy_GetAxisMax_SL0(id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].Slider0_Max +} +VJoy_GetAxisMax_SL1(id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].Slider1_Max +} + +; for compatibility +VJoy_GetVJDAxisMax(id, usage) { + + Global VJDev, HID_USAGE_X,HID_USAGE_Y,HID_USAGE_Z,HID_USAGE_RX,HID_USAGE_RY,HID_USAGE_RZ,HID_USAGE_SL0,HID_USAGE_SL1 + if (VJoy_DeviceErr(id)) + return False + + return (usage = HID_USAGE_X) ? VJDev[id].AxisMax_X : + (usage = HID_USAGE_Y) ? VJDev[id].AxisMax_Y : + (usage = HID_USAGE_Z) ? VJDev[id].AxisMax_Z : + (usage = HID_USAGE_RX) ? VJDev[id].AxisMax_RX : + (usage = HID_USAGE_RY) ? VJDev[id].AxisMax_RY : + (usage = HID_USAGE_RZ) ? VJDev[id].AxisMax_RZ : + (usage = HID_USAGE_SL0) ? VJDev[id].AxisMax_Y : + VJDev[id].AxisMax_SL1 +} + +VJoy_GetVJDAxisExist(id, usage) { + + Global VJDev, HID_USAGE_X,HID_USAGE_Y,HID_USAGE_Z,HID_USAGE_RX,HID_USAGE_RY,HID_USAGE_RZ,HID_USAGE_SL0,HID_USAGE_SL1 + if (VJoy_DeviceErr(id)) + return False + + return (usage = HID_USAGE_X) ? VJDev[id].AxisExist_X : + (usage = HID_USAGE_Y) ? VJDev[id].AxisExist_Y : + (usage = HID_USAGE_Z) ? VJDev[id].AxisExist_Z : + (usage = HID_USAGE_RX) ? VJDev[id].AxisExist_RX : + (usage = HID_USAGE_RY) ? VJDev[id].AxisExist_RY : + (usage = HID_USAGE_RZ) ? VJDev[id].AxisExist_RZ : + (usage = HID_USAGE_SL0) ? VJDev[id].AxisExist_Y : + VJDev[id].AxisExist_SL1 +} + +VJoy_GetBtn(id, btn_id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].GetBtn(btn_id) +} + +VJoy_SetBtn(sw, id, btn_id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + res := VJDev[id].SetBtn(sw, btn_id) + if (!res) { + MsgBox, SetBtn(%sw%, %id%, %btn_id%) err: %ErrorLevel%`nnLastError: %A_LastError% + } + return res +} + +VJoy_GetAxis_X(id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].Axis_X +} +VJoy_GetAxis_Y(id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].Axis_Y +} +VJoy_GetAxis_Z(id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].Axis_Z +} +VJoy_GetAxis_RX(id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].Axis_RX +} +VJoy_GetAxis_RY(id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].Axis_RY +} +VJoy_GetAxis_RZ(id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].Axis_RZ +} +VJoy_GetAxis_SL0(id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].Slider0 +} +VJoy_GetAxis_SL1(id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].Slider1 +} + +VJoy_SetAxis_X(axis_val, id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].SetAxis_X(axis_val) +} +VJoy_SetAxis_Y(axis_val, id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].SetAxis_Y(axis_val) +} +VJoy_SetAxis_Z(axis_val, id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].SetAxis_Z(axis_val) +} +VJoy_SetAxis_RX(axis_val, id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].SetAxis_RX(axis_val) +} +VJoy_SetAxis_RY(axis_val, id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].SetAxis_RY(axis_val) +} +VJoy_SetAxis_RZ(axis_val, id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].SetAxis_RZ(axis_val) +} +VJoy_SetAxis_SL0(axis_val, id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].SetAxis_SL0(axis_val) +} +VJoy_SetAxis_SL1(axis_val, id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].SetAxis_SL1(axis_val) +} + +; for compatibility +VJoy_SetAxis(axis_val, id, usage) { + Global VJDev, HID_USAGE_X,HID_USAGE_Y,HID_USAGE_Z,HID_USAGE_RX,HID_USAGE_RY,HID_USAGE_RZ,HID_USAGE_SL0,HID_USAGE_SL1 + if (VJoy_DeviceErr(id)) + return False + + if (usage == HID_USAGE_X){ + return VJDev[id].SetAxis_X(axis_val) + } else if (usage == HID_USAGE_Y){ + return VJDev[id].SetAxis_Y(axis_val) + } else if (usage == HID_USAGE_Z){ + return VJDev[id].SetAxis_Z(axis_val) + } else if (usage == HID_USAGE_RX){ + return VJDev[id].SetAxis_RX(axis_val) + } else if (usage == HID_USAGE_RY){ + return VJDev[id].SetAxis_RY(axis_val) + } else if (usage == HID_USAGE_RZ){ + return VJDev[id].SetAxis_RZ(axis_val) + } else if (usage == HID_USAGE_SL0){ + return VJDev[id].SetAxis_SL0(axis_val) + } else if (usage == HID_USAGE_SL1){ + return VJDev[id].SetAxis_SL1(axis_val) + } else { + MsgBox, Unknown Axis: %usage% + } +} + +VJoy_GetDiscPov(id, nPov) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].DiscPov[nPov] +} +VJoy_SetDiscPov(Value, id, nPov) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].SetDiscPov(Value, nPov) +} + +VJoy_GetContPov(id, nPov) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].ContPov[nPov] +} +VJoy_SetContPov(Value, id, nPov) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + return VJDev[id].SetContPov(Value, nPov) +} + +; for debug: dump value of structure +VJoy_Dump(id) { + Global VJDev + if (VJoy_DeviceErr(id)) + return False + + num := VJoy_GetVJDButtonNumber(id) + for idx, btn in VJDev[id].Btn + { + if (idx<10) + buf1 .= "_" + buf1 .= idx . "|" + buf2 .= "_" . btn . "|" + } + str_btn = Button(%num%):`n %buf1%`n %buf2%`n + + if (VJoy_GetAxisMax_X(id)) { + str_btn .= "Axis_X: " . VJoy_GetAxis_X(id) . "`n" + } + if (VJoy_GetAxisMax_Y(id)) { + str_btn .= "Axis_Y: " . VJoy_GetAxis_Y(id) . "`n" + } + if (VJoy_GetAxisMax_Z(id)) { + str_btn .= "Axis_Z: " . VJoy_GetAxis_Z(id) . "`n" + } + if (VJoy_GetAxisMax_RX(id)) { + str_btn .= "Axis_RX: " . VJoy_GetAxis_RX(id) . "`n" + } + if (VJoy_GetAxisMax_RY(id)) { + str_btn .= "Axis_RY: " . VJoy_GetAxis_RY(id) . "`n" + } + if (VJoy_GetAxisMax_RZ(id)) { + str_btn .= "Axis_RZ: " . VJoy_GetAxis_RZ(id) . "`n" + } + if (VJoy_GetAxisMax_SL0(id)) { + str_btn .= "Axis_SL0: " . VJoy_GetAxis_SL0(id) . "`n" + } + if (VJoy_GetAxisMax_SL1(id)) { + str_btn .= "Axis_SL1: " . VJoy_GetAxis_SL1(id) . "`n" + } + + num := VJoy_GetContPovNumber(id) + if (num) { + for idx, btn in VJDev[id].ContPov + { + Loop, % (StrLen(btn) - 1) + buf3 .= "_" + buf3 .= idx . "|" + buf4 .= btn . "|" + } + str_cont = ContPov(%num%):`n %buf3%`n %buf4%`n + } else { + str_cont = No Continuous Button.`n + } + str_btn .= str_cont + + num := VJoy_GetDiscPovNumber(id) + if (num) { + for idx, btn in VJDev[id].DiscPov + { + Loop, % (StrLen(btn) - 1) + buf5 .= "_" + buf5 .= idx . "|" + buf6 .= btn . "|" + } + str_Disc = DiscPov(%num%):`n %buf5%`n %buf6%`n + } else { + str_Disc = No Discrete Button.`n + } + str_btn .= str_Disc + ToolTip, %str_btn% +} + +parse_rel_val(invar, curval, max) { + if (InStr(invar, "+")) { + StringReplace, _buffer, invar, + + res := curval + _buffer + if (res > max) + return max + return res + } else if (InStr(invar, "-")) { + StringReplace, _buffer, invar, - + res := curval - _buffer + if (res < 0) + return 0 + return res + } + return invar +} \ No newline at end of file diff --git a/joystick/joy_info.ahk b/joystick/joy_info.ahk new file mode 100644 index 0000000..1c43fa2 --- /dev/null +++ b/joystick/joy_info.ahk @@ -0,0 +1,84 @@ +; none of this is written by genmce - just lifted from ahk forum +; I think Chris wrote it - good for joystick info. + +; July 6, 2005: Added auto-detection of joystick number. +; May 8, 2005 : Fixed: JoyAxes is no longer queried as a means of +; detecting whether the joystick is connected. Some joysticks are +; gamepads and don't have even a single axis. + +; If you want to unconditionally use a specific joystick number, change +; the following value from 0 to the number of the joystick (1-16). +; A value of 0 causes the joystick number to be auto-detected: +JoystickNumber = 2 + +; END OF CONFIG SECTION. Do not make changes below this point unless +; you wish to alter the basic functionality of the script. + +; Auto-detect the joystick number if called for: +if JoystickNumber <= 0 +{ + Loop 16 ; Query each joystick number to find out which ones exist. + { + GetKeyState, JoyName, %A_Index%JoyName + if JoyName <> + { + JoystickNumber = %A_Index% + break + } + } + if JoystickNumber <= 0 + { + MsgBox The system does not appear to have any joysticks. + ExitApp + } +} + +#SingleInstance +SetFormat, float, 03 ; Omit decimal point from axis position percentages. +GetKeyState, joy_buttons, %JoystickNumber%JoyButtons +GetKeyState, joy_name, %JoystickNumber%JoyName +GetKeyState, joy_info, %JoystickNumber%JoyInfo +Loop +{ + buttons_down = + Loop, %joy_buttons% + { + GetKeyState, joy%a_index%, %JoystickNumber%joy%a_index% + if joy%a_index% = D + buttons_down = %buttons_down%%a_space%%a_index% + } + GetKeyState, joyx, %JoystickNumber%JoyX + axis_info = X%joyx% + GetKeyState, joyy, %JoystickNumber%JoyY + axis_info = %axis_info%%a_space%%a_space%Y%joyy% + IfInString, joy_info, Z + { + GetKeyState, joyz, %JoystickNumber%JoyZ + axis_info = %axis_info%%a_space%%a_space%Z%joyz% + } + IfInString, joy_info, R + { + GetKeyState, joyr, %JoystickNumber%JoyR + axis_info = %axis_info%%a_space%%a_space%R%joyr% + } + IfInString, joy_info, U + { + GetKeyState, joyu, %JoystickNumber%JoyU + axis_info = %axis_info%%a_space%%a_space%U%joyu% + } + IfInString, joy_info, V + { + GetKeyState, joyv, %JoystickNumber%JoyV + axis_info = %axis_info%%a_space%%a_space%V%joyv% + } + IfInString, joy_info, P + { + GetKeyState, joyp, %JoystickNumber%JoyPOV + axis_info = %axis_info%%a_space%%a_space%POV%joyp% + } + ToolTip, %joy_name% (#%JoystickNumber%):`n%axis_info%`nButtons Down: %buttons_down%`n`n(right-click the tray icon to exit) + Sleep, 100 +} +return + +esc::ExitApp diff --git a/joystuff.ahk b/joystuff.ahk new file mode 100644 index 0000000..ebf4a24 --- /dev/null +++ b/joystuff.ahk @@ -0,0 +1,107 @@ + +go_joystuff: + +gosub,joydisplay + +;************************************************* +;* add this to autoexec section of main generic midi program this section all about joystick +;************************************************* +;gosub,joydisplay ; only if you want the joydisplay to show. +SetTimer, stick, 75 ; run the loop every 50 ms - much smoother than 100 +joyX_last := -1 ; initialize +joyY_last := -1 +joyZ_last := -1 +joyR_last := -1 + IniRead, joyXnum, %version%io.ini, joystick, xaxis , %xaxis% ; read the midi In port from ini file + IniRead, joyYnum, %version%io.ini, joystick, yaxis , %yaxis% ; read the midi out port from ini file + IniRead, joyZnum, %version%io.ini, joystick, zaxis , %zaxis% ; read the midi In port from ini file + IniRead, joyRnum, %version%io.ini, joystick, raxis , %raxis% ; read the midi out port from ini file + guicontrol,2:,joyXnum, %JoyXnum% +;joyXnum := 7 ; midi output CC number for this axis +;JoyYnum := 1 +;joyZnum := 10 +;joyRnum := 11 +;************************************************* +;* end section to add to autoexec +;************************************************* +;************************************************* +;* add #include joystuff.ahk to generic midi program file +;************************************************* +;************************************************* +;* save below as joystuff.ahk put it in the same folder as generic midi +;************************************************* +return +joydisplay: ; for testging only. + Gui,2: +LastFound +AlwaysOnTop +Caption +ToolWindow ; +ToolWindow avoids a taskbar button and an alt-tab menu item. + Gui,2: Color, %CustomColor% + Gui,2: Font, s9 ; Set a large font size (32-point). + Gui,2: Add, Text, vMyText w300, ; XX & YY serve to auto-size the window. + gui,2:add,edit, vjoyXnum + Gui,2: Show, AutoSize xcenter ycenter NoActivate ; NoActivate avoids deactivating the currently active window. + +return + +stick: ; label for running joystick detection + { + GetKeyState, JoyX, JoyX ; Get position of X axis. + GetKeyState, JoyY, JoyY ; Get position of Y axis. + getkeystate, joyz, joyz ; lever 3rd axis on my joystick - ms force feedback2 (picked it up at a yardsale for $1. + getkeystate, joyr, joyr ; twist stick rotation axis on my stick + + joyXval := round(joyx*1.27) ; assumes Joyx 0-100 conversion to 0 -127 + joyYval := round((joyy/(-100)+1)*127) ; inverted output joyY 100 - 0 conversion to 127 - 0 (thanks skylord5816 ahk chat) + joyZval := round((joyZ/(-100)+1)*127) ; + joyrval := round(joyr*1.27) ;joy rotation + + GuiControl,2:, MyText, X%joyX% = ccX%joyXval% | Y%joyY% = ccY%joyYval% | R%joyR% = ccR%joyRval% | Z%joyz% = ccZ%joyzval% ; updates the joydisplay, active. + + If (joyXVal != joyx_last) ; if it has moved then send another message. + { + stb = CC + statusbyte = 176 ; chan 1 + 175 + byte1 = %joyXnum% + byte2 = %joyXval% + midiOutShortMsg(h_midiout, statusbyte, byte1, byte2) ; commented out just to run stand alone - showing the settimer is working. + gosub, ShowMidiOutMessage + joyx_last := joyXval + ;MsgBox joylast + + } + if (joyYval != joyY_last) + { + stb = CC + statusbyte = 176 ; chan 1 + 175 + byte1 = %joyYnum% + byte2 = %joyYval% + midiOutShortMsg(h_midiout, statusbyte, byte1, byte2) + gosub, ShowMidiOutMessage + joyY_last := joyYval + + } + If (joyRVal != joyR_last) + { + stb = CC + statusbyte = 176 ; chan 1 + 175 + byte1 = %joyRnum% + byte2 = %joyRval% + midiOutShortMsg(h_midiout, statusbyte, byte1, byte2) ; commented out just to run stand alone - showing the settimer is working. + gosub, ShowMidiOutMessage + joyR_last := joyRval + ;MsgBox joylast + + } + if (joyzval != joyZ_last) + { + stb = CC + statusbyte = 176 ; chan 1 + 175 + byte1 = %joyZnum% + byte2 = %joyZval% + midiOutShortMsg(h_midiout, statusbyte, byte1, byte2) ; commented out just to run stand alone - showing the settimer is working. + gosub, ShowMidiOutMessage + joyZ_last := joyZval + + } + Else + Return ; if none of them have moved, nothing to do. + } +return diff --git a/midi_to_joy_1.ahk b/midi_to_joy_1.ahk new file mode 100644 index 0000000..952d326 --- /dev/null +++ b/midi_to_joy_1.ahk @@ -0,0 +1,172 @@ +/* + ;************************************************* + ;* GENERIC MIDI APP V.0.6 + ;************************************************* +; Last edited 12/16/2010 10:46 PM by genmce + + + Spliting this program into multiple files for readability. + + + + I fear that adding so many different examples my make this more difficult to use. + I may have to pull different parts, MidiRules into an include file the same goes for the hotkeys midi generation... + Ah well... + + **************************************************************** + Please post your revisions back to this forum post. + Please post your derivative projects back to this page, so that others can learn from what you do. + Please share, as I am sharing, so that others may learn and grow! + **************************************************************** + +Generic Midi Program + Basic structural framework for a midi program in ahk. + The description of what this is for is contained in the first post on the topic Midi Input/Output Combined at the ahk forum. + Please read it, if you want to know more. + I have added a few more examples for different midi data types as well as more, meaningful (I hope), documentation to the script. + You are strongly encouraged to visit http://www.midi.org/techspecs/midimessages.php (decimal values), to learn more + about midi data. It will help you create your own midi rules. + + I have combined much of the work of others here. + It is a working script, most of the heavy lifing has been done for you. + You will need to create your own midi rules. + By creating or modifying if statements in the section of the MidiRules.ahk file. + By creating hotkeys that generate midi messages in the hotkeyTOmidi.ahk file. + + I don't claim to be an expert on this, just a guy who pulled disparate things together. + I really need help from someone to add sysex functionality. + +****** Sections with !!!!!!!!!!!!!!!!!!!!!! - don't edit between these, unless you know what you are doing (uykwyad) ! + +****** Sections with ++++++++++++++++++++++ Edit between these marks. You won't break it, I don't think??? + + v. 0.6 + + Joystick to midi + + Mouse to midi + + v. 0.5 + + changing this to multiple files for better readability, hopefully, I did not make it more confusing?? + + will leave version 4 up on the site and add these versions with a download location for version 5 + + New midi monitor, little smaller, little cleaner, I hope. + + Does NOT detect when midi input = midi output so display will get stuck in midi loop - just change your midi ports. + + Auto detect ini file, if it does not exist, one will be generated. + + ini file name is generated from script name, so if you change you script name, it will generate a new ini file. Don't worry, it will do that every time to change the main program file name. + + + v. 0.4 + + added an example of hotkey generating midi (volume controller) + + added a second example for you create your own hotkey generated midi message. + + + v. 0.3 changes + + Adding text for how to add new rules. + + Midi Rules, name used instead of filters, seems more appropriate. + + Moved all rules outside the detector function, hoping to make it easier to understand and use. + + abandoned the idea of dynamic code and a gui to create rules, omg, someone else do that, please! + + Adding more examples for rules. + + more to come, maybe... + + removed that notemsg var - did not need it, not sure why i used it... nevermind + + + TODO - + + add a way to echo all midi input that is not filtered or modified to the output. Like a gui switch (sometimes you want it all sometimes you don't) + + make the midi monitor easier to use and read, also a toggle for it to be on or off. + - midi monitor columnar with data columns to show statusbyte, byte1 and byte2 as well as midi chan. + So need a heading grid for each .... + + bring back sendnote() funtion instead of a gosub, same for each type of midi data. + + Figure out how to do SYSEX with it, PLEASE HELP ME ON THIS! + + Generic midi program v. - 0.2 changes + + - fixed bad note off detection... + - select input and output port + - open and close selected midi ports + - write ports to ini file + - send receive midi data + - modify received midi data, send it to output port + - uses contributions by so many different people. See midi input and midi output forum posts. Links in midi under the hood section below + - "under the hood" midi functions and subroutines at the end of this file + - post your creations back to this midi I/O thread - that way we can all build on it. + + Notes - All midi in/out lib stuff is included here, no external dlls, besides winmm.dll required. + Use of this - you should only need to edit the input part - great way to practice you if else logic and midi manipulation. + This does not do sysex. I really want to develop that, but not sure how to go about it. + Perhaps one day. + +*/ + +; !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! no edit here +#Include VJoy_lib.ahk + +#Persistent +#SingleInstance +SendMode Input ; Recommended for new scripts due to its superior speed and reliability. +SetWorkingDir %A_ScriptDir% ; Ensures a consistent starting directory. + +if A_OSVersion in WIN_NT4,WIN_95,WIN_98,WIN_ME ; if not xp or 2000 quit +{ + MsgBox This script requires Windows 2000/XP or later. + ExitApp +} + +;************************************************* +version = midi_to_joy_1 ; Change this to suit you. +;************************************************* +VJoy_Init() +AxisMax_X := VJoy_GetVJDAxisMax(iInterface, HID_USAGE_X) +readini() ; load values from the ini file, via the readini function - see Midi_under_the_hood.ahk file +gosub, MidiPortRefresh ; used to refresh the input and output port lists - see Midi_under_the_hood.ahk file +port_test(numports,numports2) ; test the ports - check for valid ports? - see Midi_under_the_hood.ahk file +gosub, midiin_go ; opens the midi input port listening routine see Midi_under_the_hood.ahk file +gosub, midiout ; opens the midi out port see Midi_under_the_hood.ahk file +gosub, midiMon ; see below - a monitor gui - see Midi_In_and_GuiMonitor.ahk + + +;!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! end edit here + +;************************************************* +;* VARIBLES TO SET @ STARTUP +;************************************************* + +cc_msg = 73,74 ; ++++++++++++++++ you might want to add other vars that load in auto execute section +; varibles below are for keyboarcc +channel = 1 ; default channel =1 +ccnum = 7 ; 7 is volume +volVal = 0 ; Default zero for volume +volDelta = 1 ; Amount to change volume +; end of vars for hotkey and keyboardcc + +/* + yourVar = 0 + yourVarDelta = 3 + yourVarCCnum = 1 ; modwheel +*/ + +;settimer, KeyboardCCs, 70 ; timer (loop of code) to run the KeyboardCCs at the 70 interval + +;gosub, go_xymouse + +return ; !!!! no edit here, need this line to end the auto exec section. + +;************************************************* +;* END OF AUTOEXEC SECTION +;************************************************* + +/* + The rest of the script has been broken out to other parts for readability, I hope! +*/ + +;************************************************* +;* INCLUDE FILES - +;* these files need to be in the same folder +;************************************************* +; include files below - you need each of these files in the same folder as this file for this to work. + +#Include Midi_In_and_GuiMonitor.ahk ; this file contains: the function to parse midi message into parts we can work with and a midi monitor. +#Include MidiRules.ahk ; this file contains: Rules for manipulating midi input then sending modified midi output. +#Include hotkeyTOmidi_1.ahk ; this file contains: examples of HOTKEY generated midi messages to be output - the easy way! +#Include hotkeyTOmidi_2.ahk ; this file contains: examples of HOTKEY generated midi messages to be output - the BEST way! +;#include joystuff.ahk ; this file contains: joystick stuff. +;#include xy_mouse.ahk + +#Include Midi_under_the_hood.ahk ; this file contains: (DO NOT EDIT THIS FILE) all the dialogs to set up midi ports and midi message handling. \ No newline at end of file diff --git a/midi_to_joy_1.ini b/midi_to_joy_1.ini new file mode 100644 index 0000000000000000000000000000000000000000..107a9c6965603b5840d78dc08347cbda5863f399 GIT binary patch literal 92 zcmezWFPb5kA(f#72s0V-7}6Pv8DbfD8MqjHf&3I8_5_N$09BL$*~viKmcbB3-k+fq Ls25d*0ayh9fZq_j literal 0 HcmV?d00001 diff --git a/midi_to_joy_1io.ini b/midi_to_joy_1io.ini new file mode 100644 index 0000000..e69de29 diff --git a/vJoyInterface.dll b/vJoyInterface.dll new file mode 100644 index 0000000000000000000000000000000000000000..e93a41b83f0b89d1b8653a578db028abbde93293 GIT binary patch literal 19456 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 Q=6BC`8u(5F|A7Yn8)CUiumAu6 literal 0 HcmV?d00001 diff --git a/x64/vJoyInterface.dll b/x64/vJoyInterface.dll new file mode 100644 index 0000000000000000000000000000000000000000..e93a41b83f0b89d1b8653a578db028abbde93293 GIT binary patch literal 19456 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