From ab70c607d6efe062e55e858fca999f4cc11501fa Mon Sep 17 00:00:00 2001 From: david Date: Mon, 11 May 2020 22:34:22 -0500 Subject: [PATCH] Add functionality for most buttons --- MIDI/MidiRules.ahk | 149 +++++++++ MIDI/MidiStart.ahk | 39 +++ MIDI/Midi_In_and_GuiMonitor.ahk | 95 ++++++ MIDI/Midi_under_the_hood.ahk | 558 ++++++++++++++++++++++++++++++++ README.md | 9 +- VB.ahk | 302 +++++++++++++---- settings.nktrl2_data | Bin 0 -> 403 bytes 7 files changed, 1089 insertions(+), 63 deletions(-) create mode 100644 MIDI/MidiRules.ahk create mode 100644 MIDI/MidiStart.ahk create mode 100644 MIDI/Midi_In_and_GuiMonitor.ahk create mode 100644 MIDI/Midi_under_the_hood.ahk create mode 100644 settings.nktrl2_data diff --git a/MIDI/MidiRules.ahk b/MIDI/MidiRules.ahk new file mode 100644 index 0000000..5874493 --- /dev/null +++ b/MIDI/MidiRules.ahk @@ -0,0 +1,149 @@ +;************************************************* +;* 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 + + if statusbyte between 144 and 159 ; detect if note message is "note on" + ;gosub, ShowMidiInMessage + ;GuiControl,14:, StatusByteIn,%statusbyte% + ;GuiControl,14:, ChanIn,%chan% + ;GuiControl,14:, Byte1In,%byte1% + ;GuiControl,14:, Byte2In,%byte2% ; display noteOn message in gui + + if statusbyte between 128 and 143 ; detect if note message is "note off" + ;gosub, ShowMidiInMessage + ;GuiControl,12:, MidiMsOut, noteOff:%statusbyte% %chan% %byte1% %byte2% ; display note off in gui + + ; !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 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. + + ifequal, byte1, 20 ; if the note number coming in is note # 20 + { + byte1 := (byte1 +1) ; transpose that note up 1 note number + gosub, SendNote ; send the note out. + } + + ifequal, byte1, 30 ; if the note number coming in is note # 30 + { + send , {NumLock} ; send a keypress when note number 20 is received. + } + + ; a little more complex filter two notes + if ((byte1 != 60) and (byte1 != 62)) ; if note message is not(!) 60 and not(!) 62 send the note out - ie - do nothing except statements above (note 20 and 30 have things to do) to it. + { + gosub, SendNote ; send it out the selected output midi port + ;msgbox, ,straight note, note %byte1% message, 1 ; this messagebox for testing only. + } + + IfEqual, byte1, 60 ; if the note number is middle C (60) (you can change this) + { + byte1 := (byte1 + 5) ;transpost up 5 steps + gosub, SendNote ;(h_midiout, note) ;send a note transposed up 5 notes. + ;msgbox, ,transpose up 5, note on %byte1% message, 1 ; for testing only - show msgbox for 1 sec + } + + IfEqual, byte1, 62 ; if note on is note number 62 (just another example of note detection) + { + byte1 := (byte1 -5) ;transpose down 5 steps + gosub, SendNote + ;msgbox, ,transpose down 5, note on %byte1% message, 1 ; for testing only, uncomment if you need it. + } + ; ++++++++++++++++++++++++++++++++ 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, midiCCin + } + + ; 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/MidiStart.ahk b/MIDI/MidiStart.ahk new file mode 100644 index 0000000..7538394 --- /dev/null +++ b/MIDI/MidiStart.ahk @@ -0,0 +1,39 @@ +#Persistent +#SingleInstance, force +SendMode Input ; Recommended for new scripts due to its superior speed and reliability. +SetWorkingDir %A_ScriptDir% ; Ensures a consistent starting directory. + +;************************************************* +version = 1 ; Change this to suit you. +;************************************************* + +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 + +return ; !!!! no edit here, need this line to end the auto exec section. + +;************************************************* +;* END OF AUTOEXEC SECTION +;************************************************* + +;************************************************* +;* 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 MidiRulesEdit.ahk ; this file contains: Rules for manipulating midi input then sending modified midi output. +#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. diff --git a/MIDI/Midi_In_and_GuiMonitor.ahk b/MIDI/Midi_In_and_GuiMonitor.ahk new file mode 100644 index 0000000..5fc548e --- /dev/null +++ b/MIDI/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 diff --git a/MIDI/Midi_under_the_hood.ahk b/MIDI/Midi_under_the_hood.ahk new file mode 100644 index 0000000..152639b --- /dev/null +++ b/MIDI/Midi_under_the_hood.ahk @@ -0,0 +1,558 @@ + +; !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 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/README.md b/README.md index 13b51b5..f237a3b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ -# Volume knob -> AHK -> Voicemeeter interface -This is a little thing I slapped together so that I can control all sorts of VoiceMeeter things with the volume knob on my K70. +# Korg NanoKontrol2 -> AHK -> VoiceMeeter -I also have things in there to mimic Discord's Muting and Deafening features. \ No newline at end of file +This is something I threw together from multiple different sources for the VoiceMeeter control with AHK to the MIDI interaction with AHK. + +This should be good to go, I also included the "Scene data file" (the config) for the Korg software to configure the channels and CCs for the sliders as I have it set up. I'll make a YouTube video of it sometime soon. + +After first startup if you would no longer like the dialog box that shows the MIDI output to show, you can change line 1 of `MidiStart.ahk` to read `ShowGUI := False`. You can edit AHK files in any plaintext editor, including notepad. diff --git a/VB.ahk b/VB.ahk index c2af767..9cdae29 100644 --- a/VB.ahk +++ b/VB.ahk @@ -4,6 +4,7 @@ #HotkeyInterval 99000000 #KeyHistory 0 #UseHook +#Persistent ListLines Off SendMode Input ; Recommended for new scripts due to its superior speed and reliability. SetTitleMatchMode RegEx @@ -56,9 +57,203 @@ if (login_result == 1) { Sleep 2000 } +; == MIDI == +; ========== +#Include, MIDI\MidiStart.ahk +#Include, MIDI\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, MIDI\MidiRules.ahk ; this file contains: Rules for manipulating midi input then sending modified midi output. +#Include, MIDI\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. + +midiCCin: + cc := byte1 ; The Control Channel + val := byte2 ; The value (0-127 for faders, 0 or 1 for buttons (that part is set with the software)) + + Switch chan + { + Case 1: ; The first fader: Work Laptop Out + Switch cc + { + Case 0: + Lvl := fader_to_fader(val) + adjustVolLvl("Strip[0]" . ".Gain", Lvl) + Return + Case 1: + Lvl := dial_to_pan(val) + adjustVolLvl("Strip[0]" . ".Pan_x", Lvl) + Case 2: + adjustToggle("Strip[0]" . ".Solo", val) + Return + Case 3: + adjustToggle("Strip[0]" . ".Mute", val) + Return + Case 4: + adjustToggle("Strip[0]" . ".B2", val) + Return + Default: + Return + } + Case 2: ; The second fader: Work Laptop In + Switch cc + { + Case 0: + Lvl := fader_to_fader(val) + adjustVolLvl("Bus[2]" . ".Gain", Lvl) + Return + Case 3: + adjustToggle("Bus[2]" . ".Mute", val) + Return + Default: + Return + } + Case 3: ; The third fader: Desktop Audio + Switch cc + { + Case 0: + Lvl := fader_to_fader(val) + adjustVolLvl("Strip[3]" . ".Gain", Lvl) + Return + Case 1: + Lvl := dial_to_pan(val) + adjustVolLvl("Strip[3]" . ".Pan_x", Lvl) + Case 2: + adjustToggle("Strip[3]" . ".Solo", val) + Return + Case 3: + adjustToggle("Strip[3]" . ".Mute", val) + Return + Case 4: + adjustToggle("Strip[3]" . ".B2", val) + Return + Default: + Return + } + Case 4: ; The third fader: Comms In + Switch cc + { + Case 0: + Lvl := fader_to_fader(val) + adjustVolLvl("Bus[3]" . ".Gain", Lvl) + Return + Case 3: + adjustToggle("Bus[3]" . ".Mute", val) + Return + Default: + Return + } + Case 5: ; The fifth fader: Comms Out + Switch cc + { + Case 0: + Lvl := fader_to_fader(val) + adjustVolLvl("Strip[4]" . ".Gain", Lvl) + Return + Case 1: + Lvl := dial_to_pan(val) + adjustVolLvl("Strip[4]" . ".Pan_x", Lvl) + Case 2: + adjustToggle("Strip[4]" . ".Solo", val) + Return + Case 3: + adjustToggle("Strip[4]" . ".Mute", val) + Return + Case 4: + adjustToggle("Strip[4]" . ".B2", val) + Return + Default: + Return + } + Case 6: ; The sixth fader: Speakers + Switch cc + { + Case 0: + Lvl := fader_to_fader(val) + adjustVolLvl("Bus[0]" . ".Gain", Lvl) + Return + Case 3: + adjustToggle("Bus[0]" . ".Mute", val) + Return + Default: + Return + } + Case 7: ; The seventh fader: Mic + Switch cc + { + Case 0: + Lvl := fader_to_fader(val) + adjustVolLvl("Bus[1]" . ".Gain", Lvl) + Return + Case 3: + adjustToggle("Bus[1]" . ".Mute", val) + Return + Default: + Return + } + Case 8: ; The eighth fader: Music + Switch cc + { + Case 0: + Lvl := fader_to_fader(val) + adjustVolLvl("Strip[2]" . ".Gain", Lvl) + Return + Case 1: + Lvl := dial_to_pan(val) + adjustVolLvl("Strip[2]" . ".Pan_x", Lvl) + Case 2: + adjustToggle("Strip[2]" . ".Solo", val) + Return + Case 3: + adjustToggle("Strip[2]" . ".Mute", val) + Return + Case 4: + adjustToggle("Strip[2]" . ".B1", val) + adjustToggle("Strip[2]" . ".B2", val) + Return + Default: + Return + } + Case 10: ; VoiceMeeter recorder controls + Switch cc + { + Case 7: ; Rewind + adjustToggle("Recorder" . ".REW", val) + Return + Case 8: ; Fast Forward + adjustToggle("Recorder" . ".FF", val) + Return + Case 9: + adjustToggle("Recorder" . ".Stop", val) + Return + Case 10: + adjustToggle("Recorder" . ".Play", val) + Return + Case 11: + adjustToggle("Recorder" . ".Record", val) + Return + Default: + Return + } + Default: + Return + } +Return + +fader_to_fader(val) ; Translates MIDI fader values to the VoiceMeeter software faders +{ + ; The upper limit for faders in VM is 12.0 + ; The lower limit for the faders in VM that I would like is -40.0, how low it should go when the physical fader is all the way at the bottom + nval := Round(((val / 127) * 52) - 40) + Return nval +} +; Formula: ((value / max value) * total range) - negative part of range +dial_to_pan(val) +{ + nval := Round((val / 127) - 0.5, 2) + Return nval +} ; == HOTKEYS == ; ============= +/* Volume_Up:: If GetKeyState("ScrollLock", "T") ; Control music volume if scroll lock is on { @@ -130,51 +325,37 @@ Volume_Down:: } } return - +*/ Volume_Mute:: - If (GetKeyState("Media_Stop")) ; Mutes all output. Basically a kill switch - { - b0M := Round(readParam("Bus[0]" . ".Mute")) ; Speakers - b1M := Round(readParam("Bus[1]" . ".Mute")) ; Headphones - cM := b0M + b1M + b0M := Round(readParam("Bus[0]" . ".Mute")) ; Speakers + b1M := Round(readParam("Bus[1]" . ".Mute")) ; Headphones + cM := b0M + b1M - if (cM = "2") - { ; Unmute the ones that were unmuted before - adjustMute("Bus[0]" . ".Mute", b0Ms) ; Speakers - adjustMute("Bus[1]" . ".Mute", b1Ms) ; Headphones - } else { - if !(b0M) ; Speakers - { - b0Ms := True - } else { - b0Ms := False - } - - if !(b1M) ; Headphones - { - b1Ms := True - } else { - b1Ms := False - } - ; Mute - adjustMute("Bus[0]" . ".Mute", "0") ; Speakers - adjustMute("Bus[1]" . ".Mute", "0") ; Headphones - } - Return + if (cM = "2") + { ; Unmute the ones that were unmuted before + adjustToggle("Bus[0]" . ".Mute", b0Ms) ; Speakers + adjustToggle("Bus[1]" . ".Mute", b1Ms) ; Headphones + } else { + if !(b0M) ; Speakers + { + b0Ms := True + } else { + b0Ms := False + } + + if !(b1M) ; Headphones + { + b1Ms := True + } else { + b1Ms := False + } + ; Mute + adjustToggle("Bus[0]" . ".Mute", "0") ; Speakers + adjustToggle("Bus[1]" . ".Mute", "0") ; Headphones } - cM := Round(readParam("Bus[1]" . ".Mute")) ; Toggles between the speakers and headphones being muted, normal operation - if (cM) - { - adjustMute("Bus[0]" . ".Mute", "0") ; Speakers - adjustMute("Bus[1]" . ".Mute", "1") ; Headphones - } - if !(cM) - { - adjustMute("Bus[0]" . ".Mute", "1") ; Speakers - adjustMute("Bus[1]" . ".Mute", "0") ; Headphones - } -return +Return +/* !m:: ; Mute: No audio out Send {F23} b3M := Round(readParam("Bus[3]" . ".Mute")) ; Comms IN @@ -182,16 +363,16 @@ return If (s4M) { - adjustMute("Bus[3]" . ".Mute", "1") ; Comms IN - adjustMute("Strip[4]" . ".Mute", "1") ; Comms OUT + adjustToggle("Bus[3]" . ".Mute", "1") ; Comms IN + adjustToggle("Strip[4]" . ".Mute", "1") ; Comms OUT return } If !(b3M) { - adjustMute("Bus[3]" . ".Mute", "0") + adjustToggle("Bus[3]" . ".Mute", "0") } else { - adjustMute("Bus[3]" . ".Mute", "1") + adjustToggle("Bus[3]" . ".Mute", "1") } Return @@ -201,12 +382,12 @@ Return If (s4M) { - adjustMute("Bus[3]" . ".Mute", "1") ; Comms IN - adjustMute("Strip[4]" . ".Mute", "1") ; Comms OUT + adjustToggle("Bus[3]" . ".Mute", "1") ; Comms IN + adjustToggle("Strip[4]" . ".Mute", "1") ; Comms OUT } else { - adjustMute("Bus[3]" . ".Mute", "0") ; Comms IN + adjustToggle("Bus[3]" . ".Mute", "0") ; Comms IN Sleep, 650 ; Delay so that the "deafened" sound from discord can play - adjustMute("Strip[4]" . ".Mute", "0") ; Comms OUT + adjustToggle("Strip[4]" . ".Mute", "0") ; Comms OUT } Return @@ -216,21 +397,21 @@ Return if (cM) { - adjustMute("Bus[2]" . ".Mute", "1") ; Work Laptop - adjustMute("Bus[3]" . ".Mute", "0") ; Comms IN - adjustMute("Strip[3]" . ".Mute", "0") ; Desktop + adjustToggle("Bus[2]" . ".Mute", "1") ; Work Laptop + adjustToggle("Bus[3]" . ".Mute", "0") ; Comms IN + adjustToggle("Strip[3]" . ".Mute", "0") ; Desktop Sleep, 650 ; Delay so that the "deafened" sound from discord can play - adjustMute("Strip[4]" . ".Mute", "0") ; Comms OUT + adjustToggle("Strip[4]" . ".Mute", "0") ; Comms OUT } if !(cM) { - adjustMute("Bus[2]" . ".Mute", "0") ; Work Laptop - adjustMute("Bus[3]" . ".Mute", "1") ; Comms IN - adjustMute("Strip[4]" . ".Mute", "1") ; Comms OUT - adjustMute("Strip[3]" . ".Mute", "1") ; Desktop + adjustToggle("Bus[2]" . ".Mute", "0") ; Work Laptop + adjustToggle("Bus[3]" . ".Mute", "1") ; Comms IN + adjustToggle("Strip[4]" . ".Mute", "1") ; Comms OUT + adjustToggle("Strip[3]" . ".Mute", "1") ; Desktop } Return - +*/ ; == Functions == ; =============== @@ -265,7 +446,7 @@ adjustVolLvl(loc, tVol) { DllCall(VMR_FUNCTIONS["SetParameterFloat"], "AStr", loc, "Float", tVol, "Int") } -adjustMute(loc, tM) { +adjustToggle(loc, tM) { if (tM = 0) tM := 1 else @@ -273,6 +454,8 @@ adjustMute(loc, tM) { DllCall(VMR_FUNCTIONS["SetParameterFloat"], "AStr", loc, "Float", tM, "Int") } + + add_vmr_function(func_name) { VMR_FUNCTIONS[func_name] := DllCall("GetProcAddress", "Ptr", VMR_MODULE, "AStr", "VBVMR_" . func_name, "Ptr") if (ErrorLevel || VMR_FUNCTIONS[func_name] == 0) @@ -289,4 +472,3 @@ die(die_string:="UNSPECIFIED FATAL ERROR.", exit_status:=254) { MsgBox 16, FATAL ERROR, %die_string% ExitApp exit_status } - diff --git a/settings.nktrl2_data b/settings.nktrl2_data new file mode 100644 index 0000000000000000000000000000000000000000..9859671be9e5f1f3d7745cb1d6c01e1358c577fd GIT binary patch literal 403 zcmaKnxe)>}6hkHMBcO#X!2tmU(1A-Jrv=lwk+cEq?2Lc##