Task: Change system volume via a Midi controller (I use the BCF2000 with physical sliders)
Edited: 10/27/2011: Added Gui that starts if there is an error or via tray menu
Requires:
Audio control functions from
http://www.autohotkey.com/forum/topic23792.html
com from
http://www.autohotkey.com/forum/viewtopic.php?t=22923 (not needed for AHK_L)
Use
Make sure you have VA.ahk and com.ahk present to include (s.a.)
Use a Midi controller to set the system volume. On startup, checks whether device is present. If not, opens GUI to set device. Once device is set, you can set Channel, and CC number. The GUI has a "listen" mode - it sets the channel and CC to a control that's moved on the device (once the device is selected)
To-do:
- explore how to set other specifics, like Mike sensitivity etc
Input welcome
Code:
/*
setting system volume via Midi
by dorfl68
for AHK basic
should work with AHK_L if certain lines below are removed as marked (not tested)
uses Audio control functions from http://www.autohotkey.com/forum/topic23792.html
uses com from http://www.autohotkey.com/forum/viewtopic.php?t=22923
*/
#singleinstance force
#include com.ahk ; delete this for AHK_l (I think) ; download from http://www.autohotkey.com/forum/viewtopic.php?t=22923
com_init() ; delete this for AHK_l (I think)
#include va.ahk ; download from http://www.autohotkey.com/forum/topic23792.html
menu, tray, add, Select Midi Device and Channel,gui
Iniread,watched_channel,midi_system_volume.ini,settings,watched_channel,1
Iniread,watched_cc,midi_system_volume.ini,settings,watched_cc,1
Iniread,watched_device,midi_system_volume.ini,settings,watched_device,0
Iniread,start_gui,midi_system_volume.ini,settings,start_gui,0
in_midi_gui := 0 ; set to 1 if in the GUI so we can detect this in the Midi in routine
start_midi()
if start_gui
open_gui(2)
return
start_midi()
{
global watched_channel, watched_cc, watched_device
midi_error = 0 ; midi error count
DeviceID := watched_device ; hardcoded for POC
;replace with a read from ini, set by a GUI, all that good stuff
MidiDLL := DllCall("LoadLibrary",Str,"winmm.dll") ; load midi DLL
; create the Midi Gui to track what's coming in
Gui, 99:Destroy ; better safe than error
Gui, 99: +LastFound +AlwaysOnTop +Caption +ToolWindow ; +ToolWindow avoids a taskbar button and an alt-tab menu item.
hWnd := WinExist()
CALLBACK_WINDOW := 0x10000 ; from orbiks code for midi input
_DBMessage("Starting Midi in for controller, device ID#" . DeviceID,a_thisfunc)
h_MidiIn =VarSetCapacity(h_MidiIn, 4, 0)
result := DllCall("winmm.dll\midiInOpen", UInt,&h_MidiIn, UInt,watched_device, UInt,hWnd, UInt,0, UInt,CALLBACK_WINDOW, "UInt")
If result
{
_DBMessage("Error in MidiInOpen DLL:" . result,a_thisfunc)
midi_error++
}
h_MidiIn := NumGet(h_MidiIn) ; because midiInOpen writes the value in 32 bit binary Number, AHK stores it as a string
result := DllCall("winmm.dll\midiInStart", UInt,h_MidiIn)
If result
{
_DBMessage("Error in MidiInStart DLL:" . result,a_thisfunc)
midi_error++
}
If Midi_error ; some error occured
Open_gui(1)
; check here for error
OnMessage(0x3C1, "MidiMsgDetect")
OnMessage(0x3C2, "MidiMsgDetect")
OnMessage(0x3C3, "MidiMsgDetect")
OnMessage(0x3C5, "MidiMsgDetect")
OnMessage(0x3C6, "MidiMsgDetect")
Return
} ; start Midi function
/*
Gui to set an detect device/channel/cc
*/
gui:
{
open_gui(2)
return
}
open_gui(m)
{
global watched_channel, watched_cc, watched_device,midi_in_selected_device, in_midi_gui , channel_handle,cc_handle
Iniwrite,0 ,midi_system_volume.ini,settings,start_gui ; only once allowed
if m= 1
message:="Error Opening Device"
if m=2
message="Select Device, Channel, and CC"
Midi_in_list := MidiInsList(numPorts) ; get list of Midi Device names
midi_in_selected_device:= watched_device +1 ; increment by one for pull-down
Gui, Font, S12 CDefault, Verdana
if numports = 0
{
Gui, Add, Text, section, No Midi Devices found
gui, add,button, xs section gByeBYe, Exit
gui, add,button, Ys gGuiClose, Try Again
}
Else
{
; we actually have a device present
Gui, Add, Text, section, %message%
Gui, Add, Text, xs section, Device:
Gui, Add, ComboBox, ys x100 Choose%Midi_in_selected_device% vMidi_in_selected_device AltSubmit, %Midi_in_list% ; --- midi in listing of ports
if m=2
{
Gui, Add, Text, xs section, Channel:
Gui, Add, edit, ys x100 w75 edit r1 readonly hwndchannel_handle,%watched_channel%
gui, add, updown, vwatched_channel range0-127 , %watched_channel%
in_midi_gui:=1 ; for CC detection
gui, add, checkbox, ys checked%in_midi_gui% vin_midi_gui gcheckChange, Listen for Control
Gui, Add, Text, xs section, CC:
Gui, Add, edit, ys x100 w75 edit r1 readonly hwndcc_handle,%watched_cc%
gui, add, updown, vwatched_cc range0-127 , %watched_cc%
gui, add,button, xs section gset_device, Use these settings
}
gui, add,button, xs section grestart_with_listen, Use Device and listen for channel
}
Gui, show
return
}
GuiClose:
guiEscape:
gui, destroy
reload
return
ByeBye:
ExitApp
restart_with_listen:
Set_device:
gui,submit
gui, destroy
watched_device := midi_in_selected_device-1 ; decrement by one for pull-down
Iniwrite,%watched_channel%,midi_system_volume.ini,settings,watched_channel
Iniwrite,%watched_cc%,midi_system_volume.ini,settings,watched_cc
Iniwrite,%watched_device% ,midi_system_volume.ini,settings,watched_device
if (a_thislabel = "Set_device")
Iniwrite,0 ,midi_system_volume.ini,settings,start_gui
Else
Iniwrite,1 ,midi_system_volume.ini,settings,start_gui
reload
return
checkChange:
gui, submit,nohide
return
MidiMsgDetect(hInput, midiMsg, wMsg)
{
global watched_channel, watched_cc, watched_device,channel_handle,cc_handle, in_midi_gui
_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?
_cc := (midiMsg >> 8) & 0xFF ; THIS IS DATA1 VALUE = NOTE NUMBER OR CC NUMBER
_midi_value := (midiMsg >> 16) & 0xFF ; DATA2 VALUE IS NOTE VELOCITY OR CC VALUE
_DBMessage("Midi received- channel: " . _chan . ", statusbyte: 0x" . convert_base(_statusbyte,10,16) . ", data1: " . _cc . ", data2: " . _midi_value,A_ThisFunc)
_DBMessage("Midi handle:" . hinput,a_thisfunc)
/* proof of concept
*/
If ((_statusbyte&240) = 176) ; check status byte for cc
{
_DBmessage("->CC identified - channel:" . _chan . ", control: " . _cc . ", value: " . _midi_value ,A_ThisFunc)
if (in_midi_gui)
{
; we are in the Gui. Instead of setting the volume, set the variables in the Gui to the channel and CC detected
controlsettext,,%_chan%, ahk_id %channel_handle%
controlsettext,,%_cc%, ahk_id %cc_handle%
}
Else
{
; test here what where why how
; test: check for channel 1 control 1
if (_chan = watched_channel) and (_cc = watched_cc)
{
_volume := _midi_value * 100/127 ; scale from 7 bit midi to 0-100%
VA_SetMasterVolume(_volume)
_dbmessage("Volume set to " . _volume, a_thisfunc)
}
}
}
Return
}
_DBmessage(message,func)
{
outputdebug,[%func%] %message%
return
}
convert_base(num,inputbase,outputbase)
; from the AHK boards
/*
base("100",2,10) ->100 binary to 4 decimal
base("10",10,2) ->10 decimal to 1010 binary
base("15",10,16) ->15 decimal to f hexadecimal
base("f",16,2) ->fdecimal to 1111 binary
*/
{
VarSetCapacity(S,65,0)
toDec := DllCall("msvcrt\_strtoui64", Str,num, Uint,0, Int,inputbase, "CDECL Int64")
DllCall("msvcrt\_i64toa", Int64,toDec, Str,S, Int,outputbase)
return, S
}
; Midi core functions
;========================================================================
;=========== core Midi functions from AHK boards =====
;============from orbik and lazslo and a few others ====
;=========== but adapted and edited by Dorfl =====
;========================================================================
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)
{
m = Error %result% (ErrorLevel = %ErrorLevel%) in retrieving the name of midi Input %uDeviceID%
msgbox, %m%
_DBmessage(m, a_thisfunc)
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
}
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)
}