Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

MIDI Output from AHK


  • Please log in to reply
65 replies to this topic
paxophobe
  • Members
  • 93 posts
  • Last active: Jan 22 2011 07:01 PM
  • Joined: 10 Nov 2007
Hi,

I made some keyjazz thing using the modified script from Laszlo. However, its _almost_ primetime. Theres just one bug I can't figure out.

I have started a thread in the "ask for help" section. Being as you guys are the midi gurus and a somewhat small demographic in relation to all that autohotkey encompasses, I thought I'd post the link to it here so as to get the most qualified people to at least glimpse the topic (the help section can move pretty quickly!)

thanks, heres the link: <!-- m -->http://www.autohotke... ... 060#242060<!-- m -->

thanks.

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
see answer there

genmce
  • Members
  • 144 posts
  • Last active: May 21 2015 03:09 PM
  • Joined: 10 Jan 2009

I am using a dllcall directly, for speed. If you have a pitch bend value 0 <= PB < 16384 (with the center value 8192 corresponding to no bend, 0 to the max downward bend and 16383 to the max upward bend), a possible call is

DllCall("winmm.dll\midiOutShortMsg", UInt,h_midiout, UInt,(channel+0xDF)|(PB&0x7F)<<8|(PB>>7)<<16) ; channel = 1..16
That is, Param1 := PB & 127, Param2 := PB>>7. We need the bit-manipulation operations because only 7 bits of the PB value should be included in MIDI messages.


I am having a heck of a time understanding this... I am obviously not a programmer.

I have attempted
; one pitchbend test

#NoEnv
#Include general functions.ahk ;laszlo's file from the midi output thread on ahk forum


/* I need help making 1 pitchbend work properly.  
   I need 1 keypress to make the pitchbend go up 
   and 1 to make it go down.
   I will end up making 9 of these. On one each midi chan 1-9	
   From what I have done I can get a pitchbend to output, 
   however, i have not idea how to format param1 and param2 as you stated in 

reply to someone else.
*/
  
    ;channel  := 1          ; midi channel to send on
    
    Param1   := PB & 127   ; I have no idea how to format this
    Param2   := PB>>7      ; or this.....
  

  ; Create a dialog to select midi port

    MsgBox 4, Enumerate Midi Outputs?
      , Do you want to select from a list of midi outputs on this system
      , and their associated IDs?`n`nIf you select NO, the default midi 

output will be used.
    
     IfMsgBox Yes
      {
      msg := "", NumPorts := MidiOutsEnumerate() ; fills global array 

MidiOutPortName
      Loop % NumPorts 
     
        {
        Port := A_Index -1
        msg := msg . "ID: " . Port . " --> " . MidiOutPortName%Port% . "`n"
        }
     
      InputBox MidiDevice, Select Midi Port, Enter the number of the port 

you would like to use`n`n%msg%,, 450, % (NumPorts * 35 + 200),,,,,0
      If (ErrorLevel)
        Exit
      } 
    
     Else MidiDevice = 1 ; default midi device (for me midi yoke #1)
     

;I am not sure where to put this next line.
DllCall("winmm.dll\midiOutShortMsg", UInt,h_midiout, UInt,(channel+0xDF)|

(PB&0x7F)<<8|(PB>>7)<<16) ; channel = 1..16

  
  OpenCloseMidiAPI()
  h_midiout := midiOutOpen(MidiDevice) 

  
 ;Map a fader test 1
	
	^1::midiOutShortMsg(h_midiout, "wheel", 1, 0, 16383) ; fader
  	^q::midiOutShortMsg(h_midiout, "wheel", 1, 0, 0) ; fader down

  

  midiOutClose(h_midiout)
  OpenCloseMidiAPI()

I can get the pitchbend to show up on midi ox however I can get values other than zeros.

Please help me get my keys outputing pitchbends.

I will also need to figure out how to have midi input from daw as the place to start the output values that this pitch bend outputs.
Did that make sense?
If you send me an email, I can explain in more detail.
genmce(at)yahoo.com

In the end I need to do this 9 times and have midi input to compare before sending pb data out.

Basically taking fader postion from daw to compare before sending the output from the key to midi fader script here back to daw
So I don't get fader jumps.

Thanks

KeyMce/GenMce - mackie emulator for pc keyboard/Convert your controller to mackie.
Midi I/O - Want to play with midi/ahk? links dead.. pm me


Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
The following piece of code increases the pitch bend (PB) at the hotkey press of Ctrl-Alt-Up arrow, and decreases the bend at the hotkey Ctrl-Alt-Down arrow, on a computer keyboard. (I have not tried midi input.)
PB := 8192   ; initially no pitch bend

PBdelta = 33 ; the amount of change at a key press, set as you like

;...



^!up::

   PB := PB+PBdelta < 16384 ? PB+PBdelta : 16383

   DllCall("winmm.dll\midiOutShortMsg", UInt,h_midiout, UInt,(channel+0xDF)|(PB&0x7F)<<8|(PB>>7)<<16)

Return



^!down::

   PB := PB-PBdelta > 0 ? PB-PBdelta : 0

   DllCall("winmm.dll\midiOutShortMsg", UInt,h_midiout, UInt,(channel+0xDF)|(PB&0x7F)<<8|(PB>>7)<<16)

Return
You have to assign the desired pitch bend value to the variable PB. Your code did not do it. (It is similar to the wheel messages: you have to send the desired value in the short midi-out message.)

genmce
  • Members
  • 144 posts
  • Last active: May 21 2015 03:09 PM
  • Joined: 10 Jan 2009
Thank you so much,
that is doing what it should!

Do I need to have a Return after every command?
Meaning if I have other keys converted to note on messages, do they need Return

Or just after the pitchbends because they are doing the dllcall?

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
The returns are terminating the hotkey subroutines. They start with a key combination and double colons (::). If some command follows in the same line, it is a one-line hotkey, which does not need a return. If the rest of the first line is blank, the subsequent lines of code are executed when the hokey is triggered, until the terminating Return.

specter333
  • Members
  • 627 posts
  • Last active: Oct 07 2016 07:43 AM
  • Joined: 15 Jan 2007

Device -1,0,1... all give error code 2 (bad device ID), although the Select Midi Port inputbox lists ID: 0 --> Microsoft GS Wavetable SW Synth

I think it's because he strangely hard coded DeviceID to 1 like

midiOutOpen(1)
midiStreamOpen(1)

Try to change 1 to 0 or -1.


This fixed the problem I was having too. On my system ws synth is port 1 and my usb device is 0. Apparently the message box doesn't change the port since it always goes to port 1. I'm not a programmer but shouldn't the midiOutOpen be followed with the variable from the message box instead of just 1?

Anyway this tool is outstanding. I'm going to use it to control an old digital mixer I have that mixes my keyboards, my computer and other instruments. I'll be triggering the script from EventGhost so I'll be able to control all the volumes with a remote control. Thank you for writing this.

specter333
  • Members
  • 627 posts
  • Last active: Oct 07 2016 07:43 AM
  • Joined: 15 Jan 2007
Took me all night but I got this working. Since I have no programing experience I don't know if this is the best way to do this so please let me know if this needs to be changed.

Digital Mixer Volume Control with MIDI
;Open the Windows midi API dll
  hModule := OpenMidiAPI()

  ;Open the midi port
  h_midiout := midiOutOpen(0)


#Include Midi Functions.ahk  
 
   Hotkey, ^1,1up
   Hotkey, !1,1dn
Pause

1up:
   Loop, 10 {
   a++
   var%A_Index%:= a
   ch:= % var%A_Index%  
   
   midiOutShortMsg(h_midiout, "CC", 1, 1, ch)
   } 
Return

1dn:
   Loop, 10 {
   a--
   var%A_Index%:= a
   ch:= % var%A_Index%  
   
   midiOutShortMsg(h_midiout, "CC", 1, 1, ch)
   }  
Return

Thanks for this great code.

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
You can define hotkeys directly with "keyname::". The array var1, var2... is not used later, so get rid of it. If you want gradual change, add Sleep to the loop, otherwise remove the loop and increment/decrement "a" by 10. It is better to initialize "a" to your desired value. Check for overflow in "a":
#Include Midi Functions.ahk



;Open the Windows midi API dll

  hModule := OpenMidiAPI()



;Open the midi port

  h_midiout := midiOutOpen(0)



a := 64 ; initial value

Return  ; end of the auto-execute section



^1::

   Loop, 10 {

      a := a < 127 ? a+1 : 127

      midiOutShortMsg(h_midiout, "CC", 1, 1, a)

      Sleep 33

   }

Return



!1::

   Loop, 10 {

      a := a > 0 ? a-1 : 0

      midiOutShortMsg(h_midiout, "CC", 1, 1, a)

      Sleep 33

   }

Return


specter333
  • Members
  • 627 posts
  • Last active: Oct 07 2016 07:43 AM
  • Joined: 15 Jan 2007
Thanks for the quick response and forgive me for not understanding but literally every thing I know about programing I learned from the AHK help file so I don't understand most of it, I don't even understand most of what I did, I just picked through other peoples code until I found parts that looked like would work for me.


The array var1, var2... is not used later, so get rid of it.

I don't see a var1, var2?

It is better to initialize "a" to your desired value. Check for overflow in "a"

I was thinking of writing "a" to an ini file, then the initial value would always be the where the volume was last set. Does this sound like a good idea? What is overflow? I'll try to find how to do this in the manual.

One big problem I had last night is when using a hotkey like "a::=64" I always got an error message "Problems sending to MIDI device" or something like that. Using the old code seemed to fix it.

This little project is something I've wanted to do for a long time inspired by how poor the Windows audio mixer is. Decreasing volume does very little between 100% and 10% then in the last 10% the volume drops rapidly making it very hard to get precise settings. Windows sounds tend to be too loud even at low levels and video is too quiet at 100% meaning I have to turn up the amp for video watching and back down for everything else. Using the linear faders of the mixer should help give control over levels and add headroom to turn up without having to get up.

I haven't had a chance to try the code you made yet but I'm sure it will work great. Thanks again for the help.

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005

I don't see a var1, var2

You have var%A_Index%, where A_Index is the internal loop variable, taking values 1,2,... This creates the array variables var1, var2,...

writing "a" to an ini file, then the initial value would always be the where the volume was last set.

Good idea.

What is overflow?

The volume can be 0...127. If you increment "a" beyond 127, you get unpredictable results in the MIDI command. Therefore, prevent it to go too large (>127) or negative.

"a::=64"

It is not a hotkey. You write a key name, like ^1 for Ctrl+1, followed by double collon, alone in a line. This will be the name of a subroutine, activated when Ctrl and 1 is pressed together. The code until Return will be performed.

specter333
  • Members
  • 627 posts
  • Last active: Oct 07 2016 07:43 AM
  • Joined: 15 Jan 2007
As suspected your script works flawlessly. I did tweak it a little to get it to move smoother when controlled by the remote.

I didn't do a very good job with my hotkey sample before, I would have had to look it up to remember what exactly I'd done. The AHK instructions say to use one line unless your trigger multiple events, something like
^a::run notepad.

Thanks again for your help.

genmce
  • Members
  • 144 posts
  • Last active: May 21 2015 03:09 PM
  • Joined: 10 Jan 2009

After a couple of days debugging I ended up rewriting the whole code. Now it works with the default midi device: ID 0: Microsoft GS Wavetable SW Synth. Most of the changes were bug fixes and simplifications, to reduce code size. The hardest part is TomB's original discovery work, which made midi output possible from an AHK script.


Laszlo's general functions: unedited from this thread

;;;;;;;;; AHK functions for midi output by calling winmm.dll ;;;;;;;;;;
;http://msdn.microsoft.com/library/default.asp?url=/library/en-us/multimed/htm/_win32_multimedia_functions.asp

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 Individual 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 [email protected](&strh_midiout)
}

midiOutShortMsg(h_midiout, EventType, Channel, Param1, Param2) {
  ;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
}

;;;;;;;;;;;;;;; Functions for Stream Output ;;;;;;;;;;;;;;;

midiStreamOpen(DeviceID) { ; Open the midi port for streaming
  ;MMRESULT    midiStreamOpen( --> handle to midi stream, used by midi stream out functions
  ;LPHMIDISTRM lphStream,  Pointer to handle to stream - filled by call to midiStreamOpen
  ;LPUINT      puDeviceID, Pointer to DeviceID
  ;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

  VarSetCapacity(strh_stream, 4, 0)
  result:=DllCall("winmm.dll\midiStreamOpen", UInt,&strh_stream, UIntP,DeviceID, UInt,1, 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 [email protected](&strh_stream)
}

AddEventToBuffer(ByRef MidiBuffer, DeltaTime, EventType, Channel, Param1, Param2, NewBuffer = 0) {
; MIDIEVENT Structure
;    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
; BufferSize = 12 * number of events

  Static BufOffset = 0     ; keep track of where in the buffer the next event goes
  If (NewBuffer)
     BufOffset = 0

  If (BufOffset + 12 > VarSetCapacity(MidiBuffer)) {
     MsgBox Midi Buffer is full.`nEvent %EventType% %Channel% %Param1% %Param2%`n could not be added.
     Return -1
  }

  If (EventType = "NoteOn" OR EventType = "N1")  ; Calc MidiStatus byte (~ midiOutShortMsg Function)
    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
    Param1 := Param1 & 0x00FF
  }
  Else {
    MsgBox Invalid EventType.
    Return -1
  }

  PokeInt(DeltaTime, &MidiBuffer+BufOffset)
  PokeInt(0, &MidiBuffer+BufOffset+4)
  PokeInt(MidiStatus|(Param1 << 8)|(Param2 << 16), &MidiBuffer+BufOffset+8)
  BufOffset += 12
}

SetTempoAndTimebase(h_stream, BPM, PPQ) { ; BPM = tempo in Beats Per Minute, PPQ = ticks (Parts) Per Quarter note
  VarSetCapacity(struct, 8) ; structure
  PokeInt( 8,   &struct)    ; always = 8 (?)
  PokeInt(PPQ,  &struct+4)  ; contains number of ticks per quarter note

  result := DllCall("winmm.dll\midiStreamProperty", UInt,h_stream, UInt,&struct
         ,  UInt,0x80000001, UInt)   ; flags = MIDIPROPSET (0x80000000) and MIDIPROP_TIMEDIV (1)
  If (result) {
    MsgBox Error %result% in setting the Timebase
    Return -1
  }

  PokeInt(6.e7//BPM,&struct+4) ; dwTempo as microseconds per quarter note
  result := DllCall("winmm.dll\midiStreamProperty", UInt,h_stream, UInt,&struct
         ,  UInt,0x80000002, UInt)   ; flags = MIDIPROPSET (0x80000000) and MIDIPROP_TEMPO (2)
  If (result) {
    MsgBox Error %result% in setting the Tempo
    Return -1
  }
}

;MIDIHDR 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

midiOutputBuffer(h_stream, ByRef MidiBuffer, BufSize, BufDur) { ; Play Midi Buffer... Buf-fer Dur-ation in ms
  Global MIDIHDR      ; other functions can access MIDIHDR
  VarSetCapacity(MIDIHDR, 36, 0)
  PokeInt(&MidiBuffer,&MIDIHDR)
  PokeInt(BufSize,    &MIDIHDR+4)
  PokeInt(BufSize,    &MIDIHDR+8) ; remaining props can all be 0

  result := DllCall("winmm.dll\midiOutPrepareHeader", UInt,h_stream, UInt,&MIDIHDR, UInt,36, UInt) ; 36 = size of header
  If (result)  {
    MsgBox Error %result% in midiOutPrepareHeader
    Return -1
  }
  result := DllCall("winmm.dll\midiStreamOut", UInt,h_stream, UInt,&MIDIHDR, UInt,36, UInt) ; Queue up buffer, ready to play
  If (result)  {
    MsgBox Error %result% in midiStreamOut
    Return -1
  }
  result := DllCall("winmm.dll\midiStreamRestart", UInt,h_stream, UInt) ; Start playback
  If (result) {
    MsgBox Error %result% in midiStreamRestart
    Return -1
  }

  Sleep %BufDur% ; Wait for duration of entire buffer

  DllCall("winmm.dll\midiStreamStop", UInt, h_stream) ; Stop Stream - keeps it from sleep.
}

midiOutCloseStream(h_stream, ByRef MIDIHDR) { ; unprepare header and close stream
  result := DllCall("winmm.dll\midiOutUnprepareHeader", UInt,h_stream, UInt,&MIDIHDR, UInt,36, UInt)
  If (result) {
    MsgBox Error %result% in midiOutUnprepareHeader
    Return -1
  }
  result := DllCall("winmm.dll\midiStreamClose", UInt,h_stream, UInt) ; CloseMidiStream
  If (result)  {
    MsgBox Error %result% in midiStreamClose
    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
}

[email protected](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)
}

Laszlo, or another...

Can you please make a midi port selection in a traymenu item called
midi_out port into your general functions.ahk (midi out)?

The midi_in_lib from orbik does this, but I can't, figure out how do it for the midi output part?

Rockum and I have something very interesting we have been collaborating on and will release it soon, but we have a few stumbling blocks, to making it look nice.

You have already helped a lot, but we need this part too.
Please!
Here is the code from orbik's midi_in_lib

midi_in_Open(defaultDevID = -1)
{
   global
   if ((midi_in_hModule := DllCall("LoadLibrary", Str,A_ScriptDir . "\midi_in.dll")) == 0)
   {
      MsgBox Cannot load library midi_in.dll"
      return 1
   }
   if (defaultDevID >= DllCall("midi_in.dll\getNumDevs"))
      defaultDevID := -1
   
   midi_in_MakeTrayMenu(defaultDevID)
   if (defaultDevID >= 0)
      midi_in_OpenDevice(defaultDevID)
   return 0
}

midi_in_MakeTrayMenu(defaultDevID)
{
   numDevs := DllCall("midi_in.dll\getNumDevs")
   global midi_in_lastSelectedMenuItem
   
   Menu devNameMenu, Add, No input, sub_menu_openinput
   Menu devNameMenu, Add ; separator
   if (defaultDevID < 0)
      midi_in_lastSelectedMenuItem := "No Input"

   loop %numDevs%
   {
      devID := A_Index-1
      if ((devName := DllCall("midi_in.dll\getDevName", Int,devID, Str)) == 0)
      {
         MsgBox, Error in creating midi input device list
         return 1
      }
      Menu devNameMenu, Add, %devName%, sub_menu_openinput
      if (devID == defaultDevID)
      {
         Menu devNameMenu, Check, %devName%
         midi_in_lastSelectedMenuItem := devName
      }
   }
   Menu TRAY, Add, MIDI-in device, :devNameMenu
}

sub_menu_openinput:
   midi_in_OpenDevice(A_ThisMenuItemPos-3)
   ; Move the check mark to new position
   Menu %A_ThisMenu%, Check, %A_ThisMenuItem%
   Menu %A_ThisMenu%, Uncheck, %midi_in_lastSelectedMenuItem%
   midi_in_lastSelectedMenuItem := A_ThisMenuItem
return

midi_in_OpenDevice(deviceID) ;deviceID < 0 means no input
{
   Critical
   midi_in_Stop()
   
   Gui +LastFound
   hWnd := WinExist()

   curDevID := DllCall("midi_in.dll\getCurDevID", Int)
   if (deviceID == curDevID)
      return 0
   if (curDevID >= 0)
      result := DllCall("midi_in.dll\close")
   if (result)
   {
      MsgBox Error closing midi device`nmidi_in.dll\close returned %result%
      return 1
   }
   if (deviceID < 0)
      return 0

      result := DllCall("midi_in.dll\open", UInt,hWnd, Int,deviceID, Int)
   if (result)
   {
      MsgBox Error opening midi device`nmidi_in.dll\open(%hWnd%, %deviceID%) returned %result%
      return 1
   }
;   MsgBox Press OK to start midi input
   midi_in_Start()
   return 0
}
   
midi_in_Close()
{
   global
   if (midi_in_hModule)
   DllCall("FreeLibrary", UInt,midi_in_hModule), midi_in_hModule := ""
}

midi_in_Start()
{
   DllCall("midi_in.dll\start")
}

midi_in_Stop()
{
   DllCall("midi_in.dll\stop")
}

listenNote(noteNumber, funcName, channel=0)
{
   global msgNum
   GoSub, sub_increase_msgnum
   DllCall("midi_in.dll\listenNote", Int,noteNumber, Int,channel, Int,msgNum)
   OnMessage(msgNum, funcName)
}

listenNoteRange(rangeStart, rangeEnd, funcName, flags=0, channel=0)
{
   global msgNum
   GoSub, sub_increase_msgnum
   msgCount := DllCall("midi_in.dll\listenNoteRange", int,rangeStart, int,rangeEnd, int,(flags & 0x07), int,channel, int,msgNum)

   
   if (msgCount <= 0)
      return
   if (flags & 0x01)
      loop %msgCount%
      {
         OnMessage(msgNum, funcName . A_Index)
         GoSub, sub_increase_msgnum
      }
   else
      OnMessage(msgNum, funcName)
}

listenCC(ccNumber, funcName, channel=0)
{
   global msgNum
   GoSub, sub_increase_msgnum
   DllCall("midi_in.dll\listenCC", Int,ccNumber, Int,channel, Int,msgNum)
   OnMessage(msgNum, funcName)
}

listenWheel(funcName, channel=0)
{
   global msgNum
   GoSub, sub_increase_msgnum
   DllCall("midi_in.dll\listenWheel", Int,channel, Int,msgNum)
   OnMessage(msgNum, funcName)
}

listenChanAT(funcName, channel=0)
{
   global msgNum
   GoSub, sub_increase_msgnum
   DllCall("midi_in.dll\listenChanAT", Int,channel, Int,msgNum)
   OnMessage(msgNum, funcName)
}


getNoteOn(noteNumber, channel)
{
   return DllCall("midi_in.dll\getNoteOn", Int,noteNumber, Int,channel)
}

getCC(ccNumber, channel)
{
   return DllCall("midi_in.dll\getCC", Int,ccNumber, Int,channel)
}

getWheel(channel)
{
   return DllCall("midi_in.dll\getWheel", Int,channel)
}

getChanAT(channel)
{
   return DllCall("midi_in.dll\getChanAT", Int,channel)
}

sub_increase_msgnum:
   if msgNum
      msgNum++
   else
      msgNum := 0x2000
return

We just need the midi out port on the traymenu along with the midi in port (which is already there).

Here was my lame attempt ....

; This was copied from your file, general functions.
; I tired to insert my commented code into your general functions
; below this point
; above code snipped.

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
}
/*
midi_out_MakeTrayMenu(uDeviceID)
{
	numDevs :=  MidiOutNameGet(PortID)  ;DllCall("midi_in.dll\getNumDevs")
	global midi_out_lastSelectedMenuItem
	
	Menu devNameMenu, Add, No input, sub_menu_openOutput
	Menu devNameMenu, Add ; separator 
	if (uDeviceID < 0)
		midi_out_lastSelectedMenuItem := "No Output"

	loop %numDevs%
	{
		uDeviceID := A_Index-1
		if ((devName := DllCall("midi_in.dll\getDevName", Int,uDeviceID, Str)) == 0)
		{
			MsgBox, Error in creating midi output device list
			return 1
		}
		Menu devNameMenu, Add, %devName%, sub_menu_openOutput
		if (uDeviceID == defaultuDeviceID)
		{
			Menu devNameMenu, Check, %devName%
			midi_out_lastSelectedMenuItem := devName
		}
	}
	Menu TRAY, Add, MIDI-out device, :devNameMenu
}

sub_menu_openOutput:
	midi_out_OpenDevice(A_ThisMenuItemPos-3)
	; Move the check mark to new position
	Menu %A_ThisMenu%, Check, %A_ThisMenuItem%
	Menu %A_ThisMenu%, Uncheck, %midi_out_lastSelectedMenuItem%
	midi_out_lastSelectedMenuItem := A_ThisMenuItem
return

*/


A million thanks to you!

KeyMce/GenMce - mackie emulator for pc keyboard/Convert your controller to mackie.
Midi I/O - Want to play with midi/ahk? links dead.. pm me


Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
Menu TRAY, Add, MIDI-out device, MOselect

MidiDev = 0 ; Default



;... you code here



Return



!z::ExitApp



MOselect:

   Gui Add, DropDownList,% "w170 AltSubmit vMidiDev Choose" MidiDev+1, % MidiOutsList(NumPorts)

   Gui Add, Button, , Cancel

   Gui Add, Button, x+5, OK

   Gui Show,, Midi Out

Return



ButtonOK:

   Gui Submit

   MidiDev -= 1 ; the 0-based index of the selected new Midi output device

ButtonCancel:

   Gui Destroy

Return



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)

}


genmce
  • Members
  • 144 posts
  • Last active: May 21 2015 03:09 PM
  • Joined: 10 Jan 2009
Thank you very much!
Especially for the quick response.

This is working, however, we have one more request.
Can you make it so the ports show up in a submenu of the MO menu for selection and which ever one is selected has a check mark.
Just like the midi_in menu in the midi_in_lib.ahk above?
We want user to be able to rt click on the tray icon then click the midi in or midi out ports to select from submenus.

Sorry to be picky... and sorry I have not quite grasped how to make this happen.

thanks again.

We are getting so close...

KeyMce/GenMce - mackie emulator for pc keyboard/Convert your controller to mackie.
Midi I/O - Want to play with midi/ahk? links dead.. pm me