Hey all,
I've been digging all night thru the forums and found a lot of hints but couldn't really find a solution.
I want to execute commands based on the activated window. As a simple example: let's say I have 3 windows: notepad, calc and firefox.
When I switch to notepad a message box appears "Let's write something", when I switch to calc "let's calculate!" and when switching to
firefox "Let's browse!".
What's the least CPU taxing method for the window polling, Shell Hook or WinWaitActive, or maybe even something else?
Any help appreciated!
Actions based on which window has focus Topic is solved
Re: Actions based on which window has focus
Hallo,
try:
My Task Manager shows CPU = 0%
try:
Code: Select all
SetTimer, Windows, 1000
Return
Windows:
If WinActive("ahk_class MozillaWindowClass") And (Msg <> "MozillaWindowClass")
{
Msg = MozillaWindowClass
MsgBox, Let's browse!
}
Else If WinActive("ahk_class Notepad") And (Msg <> "Notepad")
{
Msg = Notepad
MsgBox, Let's write something
}
Else If WinActive("ahk_class CalcFrame") And (Msg <> "CalcFrame")
{
Msg = CalcFrame
MsgBox, let's calculate!
}
Return
Re: Actions based on which window has focus
This was posted a while ago by SvenBent.
I consider this as a valid answer for your case, check it out. I have been following his advise and I am enthusiastic about the performance.
I consider this as a valid answer for your case, check it out. I have been following his advise and I am enthusiastic about the performance.
Code: Select all
SetBatchLines, -1
Return
Esc:: ExitApp
;-------------------------------------------------------------------------------
Watchdog(wParam, lParam := "") { ; monitor activating windows
;-------------------------------------------------------------------------------
static init := DllCall("RegisterShellHookWindow", Ptr, A_ScriptHwnd)
, MsgNum := DllCall("RegisterWindowMessage", Str, "SHELLHOOK")
, neglect := OnMessage(MsgNum, "Watchdog")
, CleanUp := {base: {__Delete: "Watchdog"}}
If Not IsObject(CleanUp) {
OnMessage(MsgNum, "")
DllCall("DeregisterShellHookWindow", Ptr, A_ScriptHwnd)
}
If (wParam = 1) ; HSHELL_WINDOWCREATED
Or (wParam = 4) ; HSHELL_WINDOWACTIVATED
Or (wParam = 32772) ; HSHELL_RUDEAPPACTIVATED
{
WinGetClass, Class, ahk_id %lParam%
; MsgBoxes interfere with monitoring activating windows
If (Class = "Notepad")
ToolTip("Let's write something!")
Else If (Class = "ApplicationFrameWindow")
ToolTip("Let's calculate!")
Else If (Class = "MozillaWindowClass")
ToolTip("Let's browse!")
}
}
;-------------------------------------------------------------------------------
ToolTip(Text, x := "", y := "", Delay := 2000) { ; custom wrapper
;-------------------------------------------------------------------------------
ToolTip, %Text%, x, y
SetTimer, ToolTipOff, -%Delay%
Return, Text
ToolTipOff:
ToolTip ; off
Return
}
Last edited by wolf_II on 01 Jun 2017, 07:24, edited 1 time in total.
Re: Actions based on which window has focus
Hey all,
gonna try that Rohwedder, thanks!
wolf_II: unfortunately the script doesn't work for me. I replaced the Title with "WinRAR" to check if it works, but no luck.
Running WIndows 8.1 x64 with AHK 1.1.24.04
EDIT: ah I saw you added the code, but this doesn't work either for me. Let me update AHK to the latest version.
EDIT2: seems to work now! Very cool!
gonna try that Rohwedder, thanks!
wolf_II: unfortunately the script doesn't work for me. I replaced the Title with "WinRAR" to check if it works, but no luck.
Running WIndows 8.1 x64 with AHK 1.1.24.04
EDIT: ah I saw you added the code, but this doesn't work either for me. Let me update AHK to the latest version.
EDIT2: seems to work now! Very cool!
Last edited by Stevie on 01 Jun 2017, 07:20, edited 1 time in total.
Re: Actions based on which window has focus
What is the ClassName for Win8 calculator? I used the Win10 name, maybe that's different in Win8.1?
Edit: I see you got it. Very good!
Code: Select all
;-------------------------------------------------------------------------------
Watchdog(wParam, lParam := "") { ; monitor activating windows
;-------------------------------------------------------------------------------
static init := DllCall("RegisterShellHookWindow", Ptr, A_ScriptHwnd)
, MsgNum := DllCall("RegisterWindowMessage", Str, "SHELLHOOK")
, neglect := OnMessage(MsgNum, "Watchdog")
, CleanUp := {base: {__Delete: "Watchdog"}}
If Not IsObject(CleanUp) {
OnMessage(MsgNum, "")
DllCall("DeregisterShellHookWindow", Ptr, A_ScriptHwnd)
}
If (wParam = 1) ; HSHELL_WINDOWCREATED
Or (wParam = 4) ; HSHELL_WINDOWACTIVATED
Or (wParam = 32772) ; HSHELL_RUDEAPPACTIVATED
{
WinGetClass, Class, ahk_id %lParam%
ToolTip(Class)
}
}
Re: Actions based on which window has focus
The 3 apps were only a simple example.
What I'm actually doing is sending MIDI messages based on the activated window of my MIDI Sequencer Cubase.
I'm now trying to extend this. I don't want to have the MIDI message sent everytime the window stays the same, I only want to have the message sent,
when a window change has happened. I would need to save the last window in a variable and compare it to to the activated window, right?
pseudo code:
Would that work or am I missing something?
What I'm actually doing is sending MIDI messages based on the activated window of my MIDI Sequencer Cubase.
I'm now trying to extend this. I don't want to have the MIDI message sent everytime the window stays the same, I only want to have the message sent,
when a window change has happened. I would need to save the last window in a variable and compare it to to the activated window, right?
pseudo code:
Code: Select all
if windowactivated = lastwindow {}
else if windowactivated = Cubase
{
send midi note C3
}
else if windowactivated = Key Editor
{
send midi note D3
}
else if windowactivated = Export
{
send midi note E3
}
Re: Actions based on which window has focus
Do you get multiple ToolTips? (or ToolTips that stay longer than 2 seconds?)Stevie wrote:... message sent everytime the window stays the same ...
I would not say it's necessary to store the last activated window, can you please post code to demonstrate the problem?
(If you are referring to Rohwedder's code with SetTimer, please ignore this post)
Re: Actions based on which window has focus
@wolf_II
I was trying it with your code, since a shell hook seems more appropriate. The script will run 24/7 in the background.
The ToolTip is only shown 2 seconds, so there will be no problem with sending the MIDI message over and over again, I think
But is it possible to use WinGetTitle instead of WinGetClass?
I was trying it with your code, since a shell hook seems more appropriate. The script will run 24/7 in the background.
The ToolTip is only shown 2 seconds, so there will be no problem with sending the MIDI message over and over again, I think
But is it possible to use WinGetTitle instead of WinGetClass?
Re: Actions based on which window has focus Topic is solved
Try this:
I hope that helps.
Code: Select all
SetBatchLines, -1
Return
Esc:: ExitApp
;-------------------------------------------------------------------------------
Watchdog(wParam, lParam := "") { ; monitor activating windows
;-------------------------------------------------------------------------------
static init := DllCall("RegisterShellHookWindow", Ptr, A_ScriptHwnd)
, MsgNum := DllCall("RegisterWindowMessage", Str, "SHELLHOOK")
, neglect := OnMessage(MsgNum, "Watchdog")
, CleanUp := {base: {__Delete: "Watchdog"}}
If Not IsObject(CleanUp) {
OnMessage(MsgNum, "")
DllCall("DeregisterShellHookWindow", Ptr, A_ScriptHwnd)
}
If (wParam = 1) ; HSHELL_WINDOWCREATED
Or (wParam = 4) ; HSHELL_WINDOWACTIVATED
Or (wParam = 32772) ; HSHELL_RUDEAPPACTIVATED
{
WinGetTitle, Title, ahk_id %lParam%
WinGetClass, Class, ahk_id %lParam%
ToolTip(Title "`n" Class)
}
}
Last edited by wolf_II on 01 Jun 2017, 08:25, edited 1 time in total.
Re: Actions based on which window has focus
Damn, I think I have a problem here. "Title" is always empty, there is no string saved in the variable.
I even put DetectHiddenWindows, On. But nothing, the string stays empty.
I even put DetectHiddenWindows, On. But nothing, the string stays empty.
Re: Actions based on which window has focus
Oops, my bad, sorry
I will edit the code above.
I will edit the code above.
Re: Actions based on which window has focus
Aaaaawesome, I really thought the problem is on my side. This worked! Thank you so much
I just noticed that the MIDI send function also uses a DLLCall. I incorporated the code with the script
and the MIDI port cannot be opened. Is it possible that 2 DllCalls will interfere?
I just noticed that the MIDI send function also uses a DLLCall. I incorporated the code with the script
and the MIDI port cannot be opened. Is it possible that 2 DllCalls will interfere?
Re: Actions based on which window has focus
Would you mind posting your MIDI code? I will have a look at it and test some examples.
Apart from that, I don't think, DllCalls interfere.
Apart from that, I don't think, DllCalls interfere.
Re: Actions based on which window has focus
I don't mind it at all, I'm completely lost to be honest
That's my script:
This is "MIDI Functions.ahk"
And these are the errors I get, in that order:
That's my script:
Code: Select all
#Include Midi Functions.ahk
SetBatchLines, -1
;Open the Windows midi API dll
hModule := OpenMidiAPI()
;pause
;Open the midi port
h_midiout := midiOutOpen(18)
;pause
;-------------------------------------------------------------------------------
Watchdog(wParam, lParam := "") { ; monitor activating windows
;-------------------------------------------------------------------------------
static init := DllCall("RegisterShellHookWindow", Ptr, A_ScriptHwnd)
, MsgNum := DllCall("RegisterWindowMessage", Str, "SHELLHOOK")
, neglect := OnMessage(MsgNum, "Watchdog")
, CleanUp := {base: {__Delete: "Watchdog"}}
If Not IsObject(CleanUp) {
OnMessage(MsgNum, "")
DllCall("DeregisterShellHookWindow", Ptr, A_ScriptHwnd)
}
If (wParam = 1) ; HSHELL_WINDOWCREATED
Or (wParam = 4) ; HSHELL_WINDOWACTIVATED
Or (wParam = 32772) ; HSHELL_RUDEAPPACTIVATED
{
;-------------Send middle C-----------------------------------------------------
; "N1" is shorthand for "NoteOn". See comments in midiOutShortMsg for a full list of allowable event types
midiOutShortMsg(h_midiout, "N1", 1, 60, 127)
;Send Note-Off command for middle C
midiOutShortMsg(h_midiout, "N0", 1, 60, 0)
}
}
Code: Select all
;+++++++++++++++++++++++++++++++++MIDI Functions++++++++++++++++++++++++++++++++++++++++
;AHK functions for performing various midi output operations by calling winmm.dll
;by Tom Boughner
;Last Modified 4/27/07
;
;
;
;-----------------------------Open the Windows midi API dll---------------------------------
OpenMidiAPI() ;this should be done at the beginning of every script that uses any of these functions to load winmm.dll into memory
{
;it is important that you call this function by assigning it to a variable, so you retain the handle to it for closing later
hModule := DllCall("LoadLibrary", "str", "winmm.dll")
return %hModule%
}
;*********************************************************************************************
;**********************Functions for Sending Individual Messages******************************
;*********************************************************************************************
;Keep in mind that ahk doesn't allow for precise timing control - sleep is always at least 10ms and can vary depending on processor load
;So, if you need to send several events with precise timing, use the Midi Stream functions instead.
;---------------------------------Open the midi port---------------------------------
;This is only used when opening the port for sending individual midi messages.
;To send a buffer of midi stream data, use midiStreamOpen
midiOutOpen(uDeviceID = 0)
{
;returns a handle for the device to be opened. This handle must be used in all other function calls that reference this device.
;uDeviceID is the midi output port to open. You can list these ports with the midiOutGetDevCaps function
strh_midiout = "0000" ;initialize as a 4 byte string
dwFlags := 0
result := DllCall("winmm.dll\midiOutOpen"
, UInt, &strh_midiout
, UInt, uDeviceID
, UInt, 0
, UInt, 0
, UInt, dwFlags
, "UInt")
if (result or errorlevel)
{
msgbox There was an error opening the midi port. The port may be in use. Try closing and reopening all midi-enabled applications.
return -1
}
;not sure why this is necessary, but handle is invalid without converting it:
VarSetCapacity(h_midiout,4,0)
h_midiout := ExtractInteger(strh_midiout, 0, False, 4)
return %h_midiout%
}
;---------------------------------Send 1 Midi message---------------------------------
midiOutShortMsg(h_midiout, EventType, Channel, Param1, Param2)
{
;h_midiout is handle to midi output device returned by midiOutOpen function
;EventType and Channel are combined to create the MidiStatus byte.
;MidiStatus message table can be found at http://www.harmony-central.com/MIDI/Doc/table1.html
;Possible values for EventTypes are NoteOn (N1), NoteOff (N0), CC, PolyAT (PA), ChanAT (AT), PChange (PC), Wheel (W) - vals in () are optional shorthand
;SysEx not supported by the midiOutShortMsg call
;Param3 should be 0 for PChange, ChanAT, or Wheel. When sending Wheel events, put the entire Wheel value
;in Param2 - the function will split it into it's two bytes
;returns 0 if successful, -1 if not.
;Calc MidiStatus byte
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, leave LSB only
}
;Midi message Dword is made up of Midi Status in lowest byte, then 1st parameter, then 2nd parameter. Highest byte is always 0
dwMidi := MidiStatus + (Param1 << 8) + (Param2 << 16)
;Call api function to send midi event
result := DllCall("winmm.dll\midiOutShortMsg"
, UInt, h_midiout
, UInt, dwMidi
, UInt)
if (result or errorlevel)
{
msgbox, There was an error sending the midi event
return -1
}
return
}
;---------------------------------Close MidiOutput---------------------------------
;This function should only be called when you are done using the midi output port, such as in a script's OnExit routine
midiOutClose(h_midiout)
{
result := DllCall("winmm.dll\midiOutClose", UInt, h_midiout)
if (result or errorlevel)
{
msgbox, There was an error closing the midi output port. There may still be midi events being processed through it.
return -1
}
return
}
;---------------------------------Free winmm.dll---------------------------------
FreeMidiAPI(hModule)
{
DllCall("FreeLibrary", "Uint", hModule)
if (result or errorlevel)
{
msgbox, There was an error freeing the MidiAPI file. Are you sure you assigned the OpenMidiAPI call to a variable and passed that variable (unchanged) to this function?
return -1
}
return
}
;*********************************************************************************************
;**********************************Functions for Stream Output********************************
;*********************************************************************************************
;Functions:
;midiStreamOpen
;
;-------------Open the midi port for streaming-------------------------------------------
midiStreamOpen(DeviceID)
;MMRESULT midiStreamOpen(
;LPHMIDISTRM lphStream, pointer to handle to identify stream - filled by call to midiStreamOpen
;LPUINT puDeviceID, pointer to DeviceID (for some reason, pointing to the DeviceID doesn't work for me, I had to just pass the deviceID itself.)
;DWORD cMidi, Always 1
;DWORD_PTR dwCallback, pointer to callback function, event, etc. (0 = none)
;DWORD_PTR dwInstance, number you can assign to this stream
;DWORD fdwOpen type of callback
;);
;Returns handle to midi stream, used by all other midi stream out functions
;Note this routine does not use any callbacks
{
strh_stream = "0000" ;init stream pointer
cMidi := 1 ;must be 1 per spec
dwCallback := 0
dwInstance := 0
CALLBACK_NULL := 0
VarSetCapacity(uDeviceID, 4, 0)
InsertInteger(DeviceID, uDeviceID, 0, 4)
result := DllCall("winmm.dll\midiStreamOpen"
, UInt, &strh_stream
, UInt, &uDeviceID
, UInt, cMidi
, UInt, dwCallback
, UInt, dwInstance
, UInt, CALLBACK_NULL
, "UInt")
if (result or errorlevel)
{
msgbox There was an error opening the midi port. The port may be in use. Try closing and reopening all midi-enabled applications.
return -1
}
;Not sure why, but the lines below are necessary to get AHK to treat the handle as a number instead of a string:
;the h_stream handle created by the above dll call is converted to an unsigned integer value - uih_stream,
;which is used as the handle for all of send midi commands that follow
VarSetCapacity(h_stream,4,0)
h_stream := ExtractInteger(strh_stream, 0, False, 4)
return h_stream
}
;-------------Create Single Event----------------------------------------------------------
;Assembles MIDIEVENT structure for a single event. This structure contains the event itself, plus it's timing.
;This MIDIEVENT is then placed into the MidiBuffer.
;--Event Structure
;typedef struct {
; DWORD dwDeltaTime; offset to time this event should be sent
; DWORD dwStreamID; streamID this should be sent to (assumed to always be 0 for our purposes)
; DWORD dwEvent; Event DWord (Highest byte is EventCode [shortMsg for us], followed by param2, param1, status)
; DWORD dwParms[]; not needed for short messages
;} MIDIEVENT;
;NOTE: MidiBuffer needs to have already been given the correct size using VarSetCapacity.
;This means you must determine how many events to send in the buffer before calling this routine
;BufferSize = 12 * number of events
;The function automatically places events in the buffer in the order they are received.
AddEventToBuffer(ByRef MidiBuffer, DeltaTime, EventType, Channel, Param1, Param2, NewBuffer = 0)
;NewBuffer is optional parameter - it signals the function to reset BufOffset to 0, meaning we are starting a new buffer
{
Static BufOffset := 0 ;variable to keep track of where in the buffer the next event goes, set to global so that it can be reset by calling script when starting a new buffer
if (NewBuffer)
{
BufOffset := 0
}
;Check to make sure we haven't reached end of buffer already
If (BufOffset + 12 > VarSetCapacity(MidiBuffer))
{
msgbox, Midi Buffer is already full.`nEvent %EventType% %Channel% %Param1% %Param2%`n could not be added.
return -1
}
;Calc MidiStatus byte (same as in midiOutShortMsg Function)
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, leave LSB only
}
Else
{
msgbox, Invalid EventType.
pause
}
;Midi message Dword is made up of Midi Status in lowest byte, then 1st parameter, then 2nd parameter. Highest byte is always 0
dwEvent := MidiStatus + (Param1 << 8) + (Param2 << 16)
VarSetCapacity(MIDIEVENT, 12) ;12 is size of a single midievent
;create MIDIEVENT
InsertInteger(DeltaTime, MIDIEVENT, 0, 4)
InsertInteger(StreamID, MIDIEVENT, 4, 4)
InsertInteger(dwEvent, MIDIEVENT, 8, 4)
;MEEvent := ExtractInteger(MIDIEVENT, 8, False, 4) ;should be midi event
;Add Event to Buffer
DllCall("RtlMoveMemory", "UInt", &MidiBuffer + BufOffset, "UInt", &MIDIEVENT, "UInt", 12)
;msgbox % errorlevel . " " . dwEvent . " " . dwEventTest . " " . &MIDIEVENT . " " . &MidiBuffer
;MBDeltaTime := ExtractInteger(MidiBuffer, 0, False, 4) ;should equal deltatime
;MBStreamID := ExtractInteger(MidiBuffer, 4, False, 4) ;should equal 0
;MBEvent := ExtractInteger(MidiBuffer, 8, False, 4) ;should be midi event
;msgbox, DT = %MBDeltaTime% ID = %MBStreamID% BufEvent = %MBEvent% MidiEvent = %MEEvent%
;pause
BufOffset := BufOffset + 12
}
;--------------Set Tempo/Timebase for Stream------------------------------------
;Tempo and timebase are set by calling the midiStreamProperty function
;MMRESULT midiStreamProperty(
; HMIDISTRM hm, handle to midi out device
; LPBYTE lppropdata, Pointer to Property data
; DWORD dwProperty Flags to specify what to change
;);
SetTempoandTimebase(h_stream, BPM, PPQ)
;BPM = tempo in beats per minute, PPQ = ticks (parts) per quarter note
{
;Create MIDIPROPTIMEDIV structure
;typedef struct {
; DWORD cbStruct; seems to always = 8? why is this even needed?
; DWORD dwTimeDiv; contains number of ticks per quarter note
;} MIDIPROPTIMEDIV;
VarSetCapacity(MIDIPROPTIMEDIV, 8)
InsertInteger(8, MIDIPROPTIMEDIV, 0, 4)
InsertInteger(PPQ, MIDIPROPTIMEDIV, 4, 4)
;call function to set TimeDiv
result := DllCall("winmm.dll\midiStreamProperty"
, UInt, h_stream
, UInt, &MIDIPROPTIMEDIV
, UInt, 0x80000001 ;flags = MIDIPROPSET (0x80000000) and MIDIPROP_TIMEDIV (1)
, "UInt")
;msgbox % errorlevel . " " . result
if (result)
{
msgbox There was an error setting the Timebase.
pause
return -1
}
;Create MIDIPROPTEMPO structure and call function to set tempo
;note - default tempo is 120BPM, we are changing to 125BPM since that makes each midi tick almost exactly .5ms
;typedef struct {
; DWORD cbStruct;
; DWORD dwTempo; tempo as microseconds per quarter = 1/[125BPM/60(s/m)/1000000(us/s)] = 480,000
;} MIDIPROPTEMPO;
;calculate tempo in micro-seconds per beat
Tempo := 6.E7/BPM
VarSetCapacity(MIDIPROPTEMPO, 8)
InsertInteger(8, MIDIPROPTEMPO, 0, 4)
InsertInteger(Tempo, MIDIPROPTEMPO, 4, 4)
result := DllCall("winmm.dll\midiStreamProperty"
, UInt, h_stream
, UInt, &MIDIPROPTEMPO
, UInt, 0x80000002 ;flags = MIDIPROPSET (0x80000000) and MIDIPROP_TEMPO (2)
, "UInt")
if (result)
{
msgbox There was an error setting the tempo.
return -1
}
return
}
;---------------------------Play Midi Buffer------------------------------------------
;Once the Buffer is created it's header must be 'Prepared' before sending it to the stream device.
;typedef struct {
; LPSTR lpData; pointer to midi data stream
; DWORD dwBufferLength; size of buffer
; DWORD dwBytesRecorded; number of bytes of actual midi data in buffer
; DWORD_PTR dwUser; custom user data
; DWORD dwFlags; should be 0
; struct midihdr_tag far * lpNext; do not use
; DWORD_PTR reserved; do not use
; DWORD dwOffset; offset generated by callback - not used in this routine
; DWORD_PTR dwReserved[4]; do not use
;} MIDIHDR;
midiOutputBuffer(h_stream, ByRef MidiBuffer, BufSize, BufDur)
;BufSize is size of buffer in bytes.
;BufDur is the duration in ms of the buffer
{
Global MIDIHDR ;necessary so other functions can access MIDIHDR
VarSetCapacity(MIDIHDR, 36, 0)
InsertInteger(&MidiBuffer, MIDIHDR, 0, 4)
InsertInteger(BufSize, MIDIHDR, 4, 4)
InsertInteger(BufSize, MIDIHDR, 8, 4)
; remaining props can all be 0
;Send header to prepare header function
;MMRESULT midiOutPrepareHeader(
; HMIDIOUT hmo,
; LPMIDIHDR lpMidiOutHdr,
; UINT cbMidiOutHdr
;);
result := DllCall("winmm.dll\midiOutPrepareHeader"
, UInt, h_stream
, UInt, &MIDIHDR
, UInt, 36 ;size of header
, "UInt")
if (result)
{
msgbox There was an error in the midiOutPrepareHeader call.
return -1
}
;Send Header to MidiOut
;Sends Midi Buffer to Stream Output device, ready to play.
;Note - this function does not actually play the buffer, it just cues it up.
;Use midiStreamRestart to play the buffer
;MMRESULT midiStreamOut(
; HMIDISTRM hMidiStream, handle for midi stream
; LPMIDIHDR lpMidiHdr, pointer to MIDIHDR
; UINT cbMidiHdr size of MIDIHDR
;);
result := DllCall("winmm.dll\midiStreamOut"
, UInt, h_stream
, UInt, &MIDIHDR
, UInt, 36 ;size of header
, "UInt")
if (result)
{
msgbox There was an error in the midiStreamOut function.
return -1
}
;Start playback
result := DllCall("winmm.dll\midiStreamRestart"
, UInt, h_stream
, "UInt")
if (result)
{
msgbox There was an error in the midiStreamRestart function.
return -1
}
;Wait for duration of entire buffer - actual wait time will be at least this long
Sleep, BufDur
;Stop Stream - this keeps it from going to sleep. If this is not done, the stream seems to get suspended when not in use for about 1s, which causes it to
;send the next buffer's events all at the same time.
DllCall("winmm.dll\midiStreamStop", UInt, h_stream)
return
}
;------------------When closing routine, unprepare header and close stream:------------------
midiOutCloseStream(h_stream, ByRef MIDIHDR)
;Unprepare Header
;uses identical format to midiOutPrepareHeader
{
result := DllCall("winmm.dll\midiOutUnprepareHeader"
, UInt, h_stream
, UInt, &MIDIHDR
, UInt, 36 ;size of header
, "UInt")
if (result)
{
msgbox There was an error in the midiOutUnprepareHeader function.
return -1
}
;CloseMidiStream
result := DllCall("winmm.dll\midiStreamClose"
, UInt, h_stream
, "UInt")
if (result)
{
msgbox There was an error closing the midi stream.
return -1
}
return
}
;*********************************************************************************************
;***********************************Utility Functions*****************************************
;*********************************************************************************************
;Get number of midi output devices on system
;Note that the first device has an ID of 0
MidiOutGetNumDevs()
{
result := DllCall("winmm.dll\midiOutGetNumDevs")
return %result%
}
;Get name of a midiOut device for a given ID
MidiOutNameGet(uDeviceID = 0)
{
;MMRESULT midiOutGetDevCaps(
; UINT_PTR uDeviceID,
; LPMIDIOUTCAPS lpMidiOutCaps,
; UINT cbMidiOutCaps
;);
;typedef struct {
; WORD wMid;
; WORD wPid;
; MMVERSION vDriverVersion;
; CHAR szPname[MAXPNAMELEN];
; WORD wTechnology;
; WORD wVoices;
; WORD wNotes;
; WORD wChannelMask;
; DWORD dwSupport;
;} MIDIOUTCAPS;
;Setup midiOutCaps structure (the only value we care about is szPname)
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, There was an error retrieving the name of midi output %uDeviceID%
return -1
}
VarSetCapacity(PortName, 32)
DllCall("RtlMoveMemory", str, PortName, Uint, &MidiOutCaps + OffsettoPortName, Uint, PortNameSize)
;PortName := ExtractInteger(MidiOutCaps, OffsettoPortName, False, 4)
;SubStr(MidiOutCaps, OffsettoPortName, PortNameSize)
return %PortName%
}
MidiOutsEnumerate()
{
;Returns the number of midi output devices, and also creates
;a global array called MidiOutPortName with the names of each device
Global ;variables created will be global by default
local NumPorts, PortName, PortID
NumPorts := MidiOutGetNumDevs()
Loop, %NumPorts%
{
PortID := A_Index -1
MidiOutPortName%PortID% := MidiOutNameGet(PortID)
;PortList = %PortList%PortID %PortID%: %PortName%`n
}
;msgbox % msg
return % NumPorts
}
ExtractInteger(ByRef pSource, pOffset = 0, pIsSigned = false, pSize = 4)
; pSource is a string (buffer) whose memory area contains a raw/binary integer at pOffset.
; The caller should pass true for pSigned to interpret the result as signed vs. unsigned.
; pSize is the size of PSource's integer in bytes (e.g. 4 bytes for a DWORD or Int).
; pSource must be ByRef to avoid corruption during the formal-to-actual copying process
; (since pSource might contain valid data beyond its first binary zero).
{
Loop %pSize% ; Build the integer by adding up its bytes.
result += *(&pSource + pOffset + A_Index-1) << 8*(A_Index-1)
if (!pIsSigned OR pSize > 4 OR result < 0x80000000)
return result ; Signed vs. unsigned doesn't matter in these cases.
; Otherwise, convert the value (now known to be 32-bit) to its signed counterpart:
return -(0xFFFFFFFF - result + 1)
}
InsertInteger(pInteger, ByRef pDest, pOffset = 0, pSize = 4)
; The caller must ensure that pDest has sufficient capacity. To preserve any existing contents in pDest,
; only pSize number of bytes starting at pOffset are altered in it.
{
Loop %pSize% ; Copy each byte in the integer into the structure as raw binary data.
DllCall("RtlFillMemory", "UInt", &pDest + pOffset + A_Index-1, "UInt", 1, "UChar", pInteger >> 8*(A_Index-1) & 0xFF)
}
Re: Actions based on which window has focus
Ahem, ... cough cough. Well, thank you for posting this lot. I have to look at this and it will take time.
I have in the meantime checked through my AHK examples and I found this Musik.ahk I will edit later when I find where it came from originally. The author is called Anthony Zhang.
Is that any good to you?
I have in the meantime checked through my AHK examples and I found this Musik.ahk I will edit later when I find where it came from originally. The author is called Anthony Zhang.
Musik.ahk
Example with the above MIDI code:
Code: Select all
#Include, Music.ahk
SetBatchLines, -1
Return
;-------------------------------------------------------------------------------
Watchdog(wParam, lParam := "") { ; monitor activating windows
;-------------------------------------------------------------------------------
static init := DllCall("RegisterShellHookWindow", Ptr, A_ScriptHwnd)
, MsgNum := DllCall("RegisterWindowMessage", Str, "SHELLHOOK")
, neglect := OnMessage(MsgNum, "Watchdog")
, CleanUp := {base: {__Delete: "Watchdog"}}
If Not IsObject(CleanUp) {
OnMessage(MsgNum, "")
DllCall("DeregisterShellHookWindow", Ptr, A_ScriptHwnd)
}
If (wParam = 1) ; HSHELL_WINDOWCREATED
Or (wParam = 4) ; HSHELL_WINDOWACTIVATED
Or (wParam = 32772) ; HSHELL_RUDEAPPACTIVATED
{
WinGetTitle, Title, ahk_id %lParam%
If InStr(Title, "Notepad")
MIDI()
}
}
;-------------------------------------------------------------------------------
MIDI() { ; first test
;-------------------------------------------------------------------------------
static n := new NotePlayer
n.Instrument(9)
n.Repeat := False
MusicStart := n.Offset
n.Note(48,1000).Delay(1000)
n.Note(47,1000).Delay(1000)
n.Note(48,1000).Delay(1000)
n.Note(45,1000).Delay(1000)
n.Offset := MusicStart
n.Note(55,500).Delay(500)
n.Note(55,500).Delay(500)
n.Note(56,500).Delay(500)
n.Note(55,500).Delay(500)
n.Start()
}
Re: Actions based on which window has focus
Hey man,
sorry for flooding you. I feared it would be too much But no worries in case it's too much.
I'll look into the Musik.ahk, thanks!
sorry for flooding you. I feared it would be too much But no worries in case it's too much.
I'll look into the Musik.ahk, thanks!
Re: Actions based on which window has focus
LOL, No that's exactly what I asked for and expected to see. I was hoping to get some easy to work with MIDI stuff.
I'm not sure either way, which code is better in that regard, but the example in Musik.ahk works right off the bat.
PS: A github link is included in the musik file.
I'm not sure either way, which code is better in that regard, but the example in Musik.ahk works right off the bat.
PS: A github link is included in the musik file.
Re: Actions based on which window has focus
Okay, so when Musik.ahk works right off the bat, I'll get my head around it!! I'm just happy when it works in the end
Re: Actions based on which window has focus
OK, first impression:
Your code with the MIDI-Functions seems to want access to the variable h_midiout. Maybe make that global?Not tested though.
Your code with the MIDI-Functions seems to want access to the variable h_midiout. Maybe make that global?
Code: Select all
;Open the Windows midi API dll
hModule := OpenMidiAPI()
;pause
;Open the midi port
global h_midiout := midiOutOpen(18) ; <<<<< here
;pause
Re: Actions based on which window has focus
DUDE, you rock!!!! I Just tried that and it works perfectly! Let me paste my final code, so everyone else can make use of it:
And the original MIDI scripts are from here:
https://autohotkey.com/board/topic/1721 ... -from-ahk/
Code: Select all
;Routine to send a single middle C note by using the midiOutShortMsg function.
;Note that the timing of the note-off command cannot be precisely controlled using this method
;since AHK's Sleep command doesn't always sleep for the specified duration.
;The midiOutShortMsg command is better used for sending single events, such as program changes or control changes
;To send a series of events that require precise timing, use the MidiStream functions instead.
;This script provides an example of the correct order and format that the functions need to be called in to send a midi event.
#Include Midi Functions.ahk
SetBatchLines, -1
; the constants below aren't needed in my case, but kept for documentation purposes
;Constants:
channel := 1 ;midi channel to send on
MidiDevice := 0 ;number of midi output device to use.
Note := 61 ;midi number for middle C
NoteDur := 100 ;duration to hold note for (approx.)
NoteVel := 100 ;velocity of note to send
;Open the Windows midi API dll
hModule := OpenMidiAPI()
;pause
;Open the midi port
global h_midiout := midiOutOpen(18)
;pause
;-------------------------------------------------------------------------------
Watchdog(wParam, lParam := "") { ; monitor activating windows
;-------------------------------------------------------------------------------
static init := DllCall("RegisterShellHookWindow", Ptr, A_ScriptHwnd)
, MsgNum := DllCall("RegisterWindowMessage", Str, "SHELLHOOK")
, neglect := OnMessage(MsgNum, "Watchdog")
, CleanUp := {base: {__Delete: "Watchdog"}}
If Not IsObject(CleanUp) {
OnMessage(MsgNum, "")
DllCall("DeregisterShellHookWindow", Ptr, A_ScriptHwnd)
}
If (wParam = 1) ; HSHELL_WINDOWCREATED
Or (wParam = 4) ; HSHELL_WINDOWACTIVATED
Or (wParam = 32772) ; HSHELL_RUDEAPPACTIVATED
{
IfWinActive, Cubase
;-------------Send middle C-----------------------------------------------------
; "N1" is shorthand for "NoteOn". See comments in midiOutShortMsg for a full list of allowable event types
midiOutShortMsg(h_midiout, "N1", 1, 60, 127)
;pause
Sleep %NoteDur%
;Send Note-Off command for C3
midiOutShortMsg(h_midiout, "N0", 1, 60, 0)
}
IfWinActive, foobar2000
{
;-------------Send middle C-----------------------------------------------------
; "N1" is shorthand for "NoteOn". See comments in midiOutShortMsg for a full list of allowable event types
midiOutShortMsg(h_midiout, "N1", 1, 62, 127)
;pause
Sleep %NoteDur%
;Send Note-Off command for D3
midiOutShortMsg(h_midiout, "N0", 1, 62, 0)
}
IfWinActive, Key Editor
{
;-------------Send middle C-----------------------------------------------------
; "N1" is shorthand for "NoteOn". See comments in midiOutShortMsg for a full list of allowable event types
midiOutShortMsg(h_midiout, "N1", 1, 61, 127)
;pause
Sleep %NoteDur%
;Send Note-Off command for C#3
midiOutShortMsg(h_midiout, "N0", 1, 61, 0)
}
}
And the original MIDI scripts are from here:
https://autohotkey.com/board/topic/1721 ... -from-ahk/