Automation Challenge

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
elad770
Posts: 82
Joined: 30 Oct 2020, 16:21

Re: Automation Challenge

Post by elad770 » 03 Nov 2020, 09:40

In terms of the WMP

The mp3 file mentioning was just an example to convey my idea more clearly.

In reality, the files are not sound files (but they are executable)
User avatar
mikeyww
Posts: 26437
Joined: 09 Sep 2014, 18:38

Re: Automation Challenge

Post by mikeyww » 03 Nov 2020, 09:51

If you look at the SendMIDI home page, it contains a list of the commands for port, SysEx, etc. If you post an example of your information that matches any of the parameters there, we can see if a simple demonstration works, and then incorporate from there.
elad770
Posts: 82
Joined: 30 Oct 2020, 16:21

Re: Automation Challenge

Post by elad770 » 03 Nov 2020, 10:20

I did look into it, believe me! and I've seen all the commands

I also have ALL the data necessary: I know the midi port name and channel number but I don't know WHERE
to place the code in the script. Can you show me an example? without the need of having midi device

Midi device name: VV
Channel receive/send: 1

How do I place it in the script?

Your script works perfectly but it's not listening to anything
elad770
Posts: 82
Joined: 30 Oct 2020, 16:21

Re: Automation Challenge

Post by elad770 » 03 Nov 2020, 10:21

Oh.....

You are saying that i should post this section of the problem over there? I got it!!!
User avatar
mikeyww
Posts: 26437
Joined: 09 Sep 2014, 18:38

Re: Automation Challenge

Post by mikeyww » 03 Nov 2020, 10:45

You can post it here. Is there a MIDI string that you have? You would probably need to test yourself with SendMIDI (just some Windows command lines) to see if you get any of the commands working. The home page there has examples already made, so you would change the device, channel, etc. Once you have strings that work, they can be incorporated into a test script.

It looks like ReceiveMIDI would store the commands for you, and SendMIDI would then be used to replay them. Would look at ReceiveMIDI first, to see if you can store a sequence.
The output of the ReceiveMIDI tool is compatible with the SendMIDI tool, allowing you to store MIDI message sequences and play them back later.
elad770
Posts: 82
Joined: 30 Oct 2020, 16:21

Re: Automation Challenge

Post by elad770 » 03 Nov 2020, 12:54

Thank you again Mikey for not giving up on me!!!

Maybe I simply not referring to the home page you are. Can you post a link to what you are referring to?

Here's a typical string of Sysex message that my synth produces:

F0 43 73 01 52 25 00 01 01 00 01 01 F7

I can place the name of the Midi device - not a problem, just show me where - Let's assume the name is: VV, Port:1 , Channel:1
elad770
Posts: 82
Joined: 30 Oct 2020, 16:21

Re: Automation Challenge

Post by elad770 » 03 Nov 2020, 12:56

But hold on:

The point was that the script will activate a listening button either way. So, the provision of the string message is irrelevant

I thought that the script upon clicking on F3 will go into listening mode and I will click on whatever and a given string will be generated.

Are we on the same page with the functionality?
User avatar
mikeyww
Posts: 26437
Joined: 09 Sep 2014, 18:38

Re: Automation Challenge

Post by mikeyww » 03 Nov 2020, 13:50

I see. I went back to your original post.

1. On demand: receive (capture) a MIDI string -> assign to file

2. Open the file -> send (play) the MIDI string (did you want this?)

3. Receive (capture) the MIDI string -> Assigned file will open

#1 and #2 are straightforward. For #3, it appears that ReceiveMIDI may be able to do that, but it would have to be tested to see if CPU performance is an issue.
This tool is mainly intended for quickly monitoring the messages that are sent to your computer from a particular MIDI device. By providing filter commands, it's possible to only focus on particular MIDI messages.
malcev
Posts: 1769
Joined: 12 Aug 2014, 12:37

Re: Automation Challenge

Post by malcev » 03 Nov 2020, 14:19

As for midi You can use this library
https://github.com/dannywarren/AutoHotkey-Midi
elad770
Posts: 82
Joined: 30 Oct 2020, 16:21

Re: Automation Challenge

Post by elad770 » 03 Nov 2020, 14:39

mikeyww wrote:
03 Nov 2020, 13:50
I see. I went back to your original post.

1. On demand: receive (capture) a MIDI string -> assign to file -- On demand = F3 ? If yes so my answer is ----->YES YES YES! :)

2. Open the file -> send (play) the MIDI string (did you want this?) ----> YES!

3. Receive (capture) the MIDI string -> Assigned file will open ------> YES!

#1 and #2 are straightforward. For #3, it appears that ReceiveMIDI may be able to do that, but it would have to be tested to see if CPU performance is an issue.
This tool is mainly intended for quickly monitoring the messages that are sent to your computer from a particular MIDI device. By providing filter commands, it's possible to only focus on particular MIDI messages.
elad770
Posts: 82
Joined: 30 Oct 2020, 16:21

Re: Automation Challenge

Post by elad770 » 03 Nov 2020, 14:43

1. Are you saying that a revision of the script is necessary?


2. And thank you for the link - I Was looking elsewhere. LOL

3. Do I add the script line or you already placed such a thing in the script?

My understanding is that I need to add the following and simply change the parameters that suit my configuration?

#include AutoHotkey-Midi/Midi.ahk

midi := new Midi()
midi.OpenMidiIn( 5 )

Return
User avatar
mikeyww
Posts: 26437
Joined: 09 Sep 2014, 18:38

Re: Automation Challenge

Post by mikeyww » 03 Nov 2020, 14:47

I can't advise you specifically, but yes, I would try a separate AHK or Windows batch script to see if you can demonstrate that you can use ReceiveMIDI to capture a string. The author of the @malcev-mentioned AHK script might be able to help or provide you with examples, too, upon request. I don't know whether @malcev or someone else produced that.
malcev
Posts: 1769
Joined: 12 Aug 2014, 12:37

Re: Automation Challenge

Post by malcev » 03 Nov 2020, 15:20

Code is not perfect - need change some uint to ptr.
But for me it works like this (I have 2 midi devices)

Code: Select all

; Midi.ahk
; Add MIDI input event handling to your AutoHotkey scripts
;
; Danny Warren <[email protected]>
; https://github.com/dannywarren/AutoHotkey-Midi
;


; Always use gui mode when using the midi library, since we need something to
; attach midi events to
Gui, +LastFound

; Defines the string size of midi devices returned by windows (see mmsystem.h)
Global MIDI_DEVICE_NAME_LENGTH := 32

; Defines the size of a midi input struct MIDIINCAPS (see mmsystem.h)
Global MIDI_DEVICE_IN_STRUCT_LENGTH  := 44

; Defines for midi event callbacks (see mmsystem.h)
Global MIDI_CALLBACK_WINDOW   := 0x10000
Global MIDI_CALLBACK_TASK     := 0x20000
Global MIDI_CALLBACK_FUNCTION := 0x30000

; Defines for midi event types (see mmsystem.h)
Global MIDI_OPEN      := 0x3C1
Global MIDI_CLOSE     := 0x3C2
Global MIDI_DATA      := 0x3C3
Global MIDI_LONGDATA  := 0x3C4
Global MIDI_ERROR     := 0x3C5
Global MIDI_LONGERROR := 0x3C6
Global MIDI_MOREDATA  := 0x3CC

; Defines the size of the standard chromatic scale
Global MIDI_NOTE_SIZE := 12

; Defines the midi notes 
Global MIDI_NOTES     := [ "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" ]

; Defines the octaves for midi notes
Global MIDI_OCTAVES   := [ -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8 ]


; This is where we will keep the most recent midi in event data so that it can
; be accessed via the Midi object, since we cannot store it in the object due
; to how events work
; We will store the last event by the handle used to open the midi device, so
; at least we won't clobber midi events from other devices if the user wants 
; to fetch them specifically
Global __midiInEvent        := {}
Global __midiInHandleEvent  := {}

; List of all midi input devices on the system
Global __midiInDevices := {}

; List of midi input devices to listen to messages for, we do this globally
; since only one instance of the class can listen to a device anyhow
Global __midiInOpenHandles := {}

; Count of open handles, since ahk doesn't have a method to actually count the
; members of an array (it instead just returns the highest index, which isn't
; the same thing)
Global __midiInOpenHandlesCount := 0

; Holds a refence to the system wide midi dll, so we don't have to open it
; multiple times
Global __midiDll := 0

; The window to attach the midi callback listener to, which will default to
; our gui window
Global __midiInCallbackWindow := WinExist()


; Default label prefix
Global midiLabelPrefix := "Midi"

; Enable or disable label event handling
Global midiLabelCallbacks := True

; Enable or disable lazy midi in event debugging via tooltips
Global midiEventTooltips  := False




midi := new Midi()
midi.OpenMidiIn( 0 )
midi.OpenMidiIn( 1 )





; Midi class interface
Class Midi
{

  ; Instance creation
  __New()
  {

    ; Initialize midi environment
    this.LoadMidi()
    this.QueryMidiInDevices()
    this.SetupDeviceMenus()

  }

  ; Instance destruction
  __Delete()
  {

    ; Close all midi in devices and then unload the midi environment
    this.CloseMidiIns()
    this.UnloadMidi()

  }


  ; Load midi dlls
  LoadMidi()
  {
    
    __midiDll := DllCall( "LoadLibrary", "Str", "winmm.dll", "Ptr" )
    
    If ( ! __midiDll )
    {
      MsgBox, Missing system midi library winmm.dll
      ExitApp
    }

  }


  ; Unload midi dlls
  UnloadMidi()
  {

    If ( __midiDll )
    {
      DllCall( "FreeLibrary", "Ptr", __midiDll )
    }

  }


  ; Open midi in device and start listening
  OpenMidiIn( midiInDeviceId )
  {

    __OpenMidiIn( midiInDeviceId )
  
  }


  ; Close midi in device and stop listening
  CloseMidiIn( midiInDeviceId )
  {

    __CLoseMidiIn( midiInDeviceId )
    
  }


  ; Close all currently open midi in devices
  CloseMidiIns()
  {

    If ( ! __midiInOpenHandlesCount )
    {
      Return
    }

    ; We have to store the handles we are going to close in advance, because
    ; autohotkey gets confused if we are removing things from an array while
    ; iterative over it
    deviceIdsToClose := {}

    ; Iterate once to get a list of ids to close
    For midiInDeviceId In __midiInOpenHandles
    {
      deviceIdsToClose.Insert( midiInDeviceId )
    }

    ; Iterate again to actually close them
    For index, midiInDeviceId In deviceIdsToClose
    {
      this.CloseMidiIn( midiInDeviceId )
    }

  }


  ; Query the system for a list of active midi input devices
  QueryMidiInDevices()
  {

    midiInDevices := []

    deviceCount := DllCall( "winmm.dll\midiOutGetNumDevs" ) - 1

    Loop %deviceCount% 
    {

      midiInDevice := {}

      deviceNumber := A_Index - 1

      VarSetCapacity( midiInStruct, MIDI_DEVICE_IN_STRUCT_LENGTH, 0 )

      midiQueryResult := DllCall( "winmm.dll\midiInGetDevCapsA", UINT, deviceNumber, PTR, &midiInStruct, UINT, MIDI_DEVICE_IN_STRUCT_LENGTH )

      ; Error handling
      If ( midiQueryResult )
      {
        MsgBox, Failed to query midi devices
        Return
      }

      manufacturerId := NumGet( midiInStruct, 0, "USHORT" )
      productId      := NumGet( midiInStruct, 2, "USHORT" )
      driverVersion  := NumGet( midiInStruct, 4, "UINT" )
      deviceName     := StrGet( &midiInStruct + 8, MIDI_DEVICE_NAME_LENGTH, "CP0" )
      support        := NumGet( midiInStruct, 4, "UINT" )

      midiInDevice.deviceNumber   := deviceNumber
      midiInDevice.deviceName     := deviceName
      midiInDevice.productID      := productID
      midiInDevice.manufacturerID := manufacturerID
      midiInDevice.driverVersion  := ( driverVersion & 0xF0 ) . "." . ( driverVersion & 0x0F )

      __MidiEventDebug( midiInDevice )

      midiInDevices.Insert( deviceNumber, midiInDevice )

    }

    __midiInDevices := midiInDevices

  }


  ; Set up device selection menus
  SetupDeviceMenus()
  {

    For key, value In __midiInDevices
    {
      menuName := value.deviceName
      Menu, __MidInDevices, Add, %menuName%, __SelectMidiInDevice
    }

    Menu, Tray, Add
    Menu, Tray, Add, MIDI Input Devices, :__MidInDevices

    Return

    __SelectMidiInDevice:

      midiInDeviceId := A_ThisMenuItemPos - 1

      if ( __midiInOpenHandles[midiInDeviceId] > 0 )
      {
        __CloseMidiIn( midiInDeviceId )
      }
      else
      {
        __OpenMidiIn( midiInDeviceId )        
      }

      Return

  }


  ; Returns the last midi in event values
  MidiIn()
  {
    Return __MidiInEvent
  }

}


; Open a handle to a midi device and start listening for messages
__OpenMidiIn( midiInDeviceId )
{

  ; Look this device up in our device list
  device := __midiInDevices[midiInDeviceId]

  ; Create variable to store the handle the dll open will give us
  ; NOTE: Creating variables this way doesn't work with class variables, so
  ; we have to create it locally and then store it in the class later after
  VarSetCapacity( midiInHandle, 4, 0 )

  ; Open the midi device and attach event callbacks
  midiInOpenResult := DllCall( "winmm.dll\midiInOpen", UINT, &midiInHandle, UINT, midiInDeviceId, UINT, __midiInCallbackWindow, UINT, 0, UINT, MIDI_CALLBACK_WINDOW )

  ; Error handling
  If ( midiInOpenResult || ! midiInHandle )
  {
    MsgBox, Failed to open midi in device
    Return
  }

  ; Fetch the actual handle value from the pointer
  midiInHandle := NumGet( midiInHandle, UINT )

  ; Start monitoring midi signals
  midiInStartResult := DllCall( "winmm.dll\midiInStart", UINT, midiInHandle )

  ; Error handling
  If ( midiInStartResult )
  {
    MsgBox, Failed to start midi in device
    Return
  }

  ; Create a spot in our global event storage for this midi input handle
  __MidiInHandleEvent[midiInHandle] := {}

  ; Register a callback for each midi event
  ; We only need to do this once for all devices, so only do it if we are
  ; the first device to be opened
  if ( ! __midiInOpenHandlesCount )
  {
    OnMessage( MIDI_OPEN,      "__MidiInCallback" )
    OnMessage( MIDI_CLOSE,     "__MidiInCallback" )
    OnMessage( MIDI_DATA,      "__MidiInCallback" )
    OnMessage( MIDI_LONGDATA,  "__MidiInCallback" )
    OnMessage( MIDI_ERROR,     "__MidiInCallback" )
    OnMessage( MIDI_LONGERROR, "__MidiInCallback" )
    OnMessage( MIDI_MOREDATA,  "__MidiInCallback" ) 
  }

  ; Add this device handle to our list of open devices
  __midiInOpenHandles.Insert( midiInDeviceId, midiInHandle )

  ; Increase the tally for the number of open handles we have
  __midiInOpenHandlesCount++

  ; Check this device as enabled in the menu
  menuDeviceName := device.deviceName
  Menu __MidInDevices, Check, %menuDeviceName%

}


__CloseMidiIn( midiInDeviceId )
{
 
  ; Look this device up in our device list
  device := __midiInDevices[midiInDeviceId]

  ; Unregister callbacks if we are the last open handle
  if ( __midiInOpenHandlesCount <= 1 )
  {
     OnMessage( MIDI_OPEN,      "" )
     OnMessage( MIDI_CLOSE,     "" )
     OnMessage( MIDI_DATA,      "" )
     OnMessage( MIDI_LONGDATA,  "" )
     OnMessage( MIDI_ERROR,     "" )
     OnMessage( MIDI_LONGERROR, "" )
     OnMessage( MIDI_MOREDATA,  "" )
   }

  ; Destroy any midi in events that might be left over
  __MidiInHandleEvent[midiInHandle] := {}

  ; Stop monitoring midi
  midiInStopResult := DllCall( "winmm.dll\midiInStop", UINT, __midiInOpenHandles[midiInDeviceId] )

  ; Error handling
  If ( midiInStartResult )
  {
    MsgBox, Failed to stop midi in device
    Return
  }

  ; Close the midi handle
  midiInStopResult := DllCall( "winmm.dll\midiInClose", UINT, __midiInOpenHandles[midiInDeviceId] )

  ; Error handling
  If ( midiInStartResult )
  {
    MsgBox, Failed to close midi in device
    Return
  }

  ; Finally, remove the handle from the array
  __midiInOpenHandles.Remove( midiInDeviceId )

  ; Decrease the tally for the number of open handles we have
  __midiInOpenHandlesCount--

  ; Check this device as enabled in the menu
  menuDeviceName := device.deviceName
  Menu __MidInDevices, Uncheck, %menuDeviceName%

}


; Event callback for midi input event
; Note that since this is a callback method, it has no concept of "this" and
; can't access class members
__MidiInCallback( wParam, lParam, msg )
{

  ; Will hold the midi event object we are building for this event
  midiEvent := {}

  ; Will hold the labels we call so the user can capture this midi event, we
  ; always start with a generic ":Midi" label so it always gets called first
  labelCallbacks := [ midiLabel ]

  ; Grab the raw midi bytes
  rawBytes := lParam

  ; Split up the raw midi bytes as per the midi spec
  highByte  := lParam & 0xF0 
  lowByte   := lParam & 0x0F
  data1     := (lParam >> 8) & 0xFF
  data2     := (lParam >> 16) & 0xFF
  msgbox % highByte "`n" lowByte "`n" data1 "`n" data2
}



; Send event information to a listening debugger
__MidiEventDebug( midiEvent )
{

  debugStr := ""

  For key, value In midiEvent
    debugStr .= key . ":" . value . "`n"

  debugStr .= "---`n"

  ; Always output event debug to any listening debugger
  OutputDebug, % debugStr 

  ; If lazy tooltip debugging is enabled, do that too
  if midiEventTooltips
    ToolTip, % debugStr

}
User avatar
mikeyww
Posts: 26437
Joined: 09 Sep 2014, 18:38

Re: Automation Challenge

Post by mikeyww » 03 Nov 2020, 16:37

Thank you, @malcev. @elad770, please feel free to let me know here if you figure out how to get this part working! The MIDI experts here or there might be able to help you. I can assist after that point but probably not before, because I don't have any access to MIDI. It looks like the code here might be an alternative to ReceiveMIDI.
elad770
Posts: 82
Joined: 30 Oct 2020, 16:21

Re: Automation Challenge

Post by elad770 » 03 Nov 2020, 16:40

malcev,

Is this a separate script that runs simultaneously? or this is a part I need to copy-paste at the end of the current script?
malcev
Posts: 1769
Joined: 12 Aug 2014, 12:37

Re: Automation Challenge

Post by malcev » 03 Nov 2020, 16:43

Try like this

Code: Select all

Global mode := ""
DllCall("LoadLibrary", "Str", "winmm.dll", "Ptr")
OnMessage(MIDI_OPEN := 0x3C1, "MidiInCallback")
OnMessage(MIDI_CLOSE := 0x3C2, "MidiInCallback")
OnMessage(MIDI_DATA := 0x3C3, "MidiInCallback")
OnMessage(MIDI_LONGDATA := 0x3C4, "MidiInCallback")
OnMessage(MIDI_ERROR := 0x3C5, "MidiInCallback")
OnMessage(MIDI_LONGERROR := 0x3C6, "MidiInCallback")
OnMessage(MIDI_MOREDATA := 0x3CC, "MidiInCallback")
OpenMidiIn(0)
OpenMidiIn(1)
return

f1::
mode := "listen"
; some code
mode := ""
return



OpenMidiIn(id)
{
   DllCall("winmm.dll\midiInOpen", "ptr*", midiInHandle, "uint", id, "ptr", A_ScriptHwnd, "uint", 0, "uint", MIDI_CALLBACK_WINDOW := 0x10000)
   DllCall("winmm.dll\midiInStart", "ptr", midiInHandle)
   return
}
MidiInCallback(wParam, lParam, msg)
{
   Critical
   highByte := lParam & 0xF0 
   lowByte := lParam & 0x0F
   data1 := (lParam >> 8) & 0xFF
   data2 := (lParam >> 16) & 0xFF
   if (data2 = 0)
      return
   key := highByte "-" lowByte "-" data1
   if (mode = "listen")
   {
      msgbox % "listen`n" key
      return
   }
   ; here get info from ini file if key exist then run its value
   msgbox  % "run`n" key
   return
}
elad770
Posts: 82
Joined: 30 Oct 2020, 16:21

Re: Automation Challenge

Post by elad770 » 03 Nov 2020, 16:49

malcev,

Can you pinpoint to me (preferable with screen capture), with parameters I need to change?
elad770
Posts: 82
Joined: 30 Oct 2020, 16:21

Re: Automation Challenge

Post by elad770 » 03 Nov 2020, 16:52

I'm a bit confused because I don't know if I need to use your script instead or in addition?

I feel we are SO CLOSE!!!! :)

can you please be more specific as if you are talking to a 10-year-old?
elad770
Posts: 82
Joined: 30 Oct 2020, 16:21

Re: Automation Challenge

Post by elad770 » 03 Nov 2020, 16:57

Could be malcev and Mikey are coordinating to work it out and I'm just in the middle, whining? :) LOL

I'm so sorry if this is the case
malcev
Posts: 1769
Joined: 12 Aug 2014, 12:37

Re: Automation Challenge

Post by malcev » 03 Nov 2020, 17:06

Run this code.
Have You got any messages when You press Your midi?

Code: Select all

Global mode := ""
DllCall("LoadLibrary", "Str", "winmm.dll", "Ptr")
OnMessage(MIDI_OPEN := 0x3C1, "MidiInCallback")
OnMessage(MIDI_CLOSE := 0x3C2, "MidiInCallback")
OnMessage(MIDI_DATA := 0x3C3, "MidiInCallback")
OnMessage(MIDI_LONGDATA := 0x3C4, "MidiInCallback")
OnMessage(MIDI_ERROR := 0x3C5, "MidiInCallback")
OnMessage(MIDI_LONGERROR := 0x3C6, "MidiInCallback")
OnMessage(MIDI_MOREDATA := 0x3CC, "MidiInCallback")
OpenMidiIn(0)
OpenMidiIn(1)
return

f1::
mode := "listen"
; some code
mode := ""
return



OpenMidiIn(id)
{
   hr := DllCall("winmm.dll\midiInOpen", "ptr*", midiInHandle, "uint", id, "ptr", A_ScriptHwnd, "uint", 0, "uint", MIDI_CALLBACK_WINDOW := 0x10000)
   if hr or ErrorLevel
   {
      msgbox % "midiInOpen error `nid = " id "`nhr = " hr "`nErrorLevel = " ErrorLevel
      exitapp
   }
   hr := DllCall("winmm.dll\midiInStart", "ptr", midiInHandle)
   if hr or ErrorLevel
   {
      msgbox % "midiInStart error `nid = " id "`nhr = " hr "`nErrorLevel = " ErrorLevel
      exitapp
   }
   return
}
MidiInCallback(wParam, lParam, msg)
{
   Critical
   highByte := lParam & 0xF0 
   lowByte := lParam & 0x0F
   data1 := (lParam >> 8) & 0xFF
   data2 := (lParam >> 16) & 0xFF
   if (data2 = 0)
      return
   key := highByte "-" lowByte "-" data1
   if (mode = "listen")
   {
      msgbox % "listen`n" key
      return
   }
   ; here get info from ini file if key exist then run its value
   msgbox  % "run`n" key
   return
}
Post Reply

Return to “Ask for Help (v1)”