Jump to content

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

MIDI IN support in AutoHotkey


  • Please log in to reply
66 replies to this topic
Zan
  • Guests
  • Last active:
  • Joined: --
Note that you can very easily integrate AutoHotKey with Div's Midi Utilities sendmidi program.

However, it is quite resource heavy and not very fast, ie there is a little bit of lag. I assume this is because everytime you use sendmidi it has to open up the midi device, as opposed to a program that opens it up and leaves it open.

What would be ideal is if someone could build a little app that opened up the device, then accept windows messages which would be translated into midi events.

BoBo
  • Guests
  • Last active:
  • Joined: --
Zan's recom: [Div's Midi Utilities] 8)

majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006
Thats great :!:
Posted Image

daonlyfreez
  • Members
  • 995 posts
  • Last active: Jan 23 2013 08:16 AM
  • Joined: 16 Mar 2005
Ok, a little update, I'm eager to find a working solution for a Midi2AHK/AHK2Midi library... :)

Experiment: Use 3rd party application to write Midi messages to file:

As demonstrated here, one can use midimon, or some other app to capture midi, write to file, and read it in.

Unfortunately, this generates an ever growing file, that cannot be flushed without stopping the writing, since the file is in use :(

Experiment: Use CMDret-AHK functions to write 3rd party application Midi messages to memory:

My next experiment was with midimon and the CMDret-AHK functions. I found out that the lpbuffer variable somehow seemed to contain the latest received data, so I adapted the function to capture that.

Unfortunately, this slowly eats memory, and is unstable (I force-close midimon to stop sofar), and, since the data is written to memory and not flushed, there is not much advantage over the previous method yet. I have no idea how to do the flushing of the buffer or a 'correct' close of midimon. :( :?

Maybe someone with a little more knowledge of DLLCalls could look at this.

#NoEnv
SetBatchLines, -1
OnExit, Cleanup

; check for existance of midimon.exe in script directory
IfNotExist, midimon.exe
{
  MsgBox, 16, Error,
  (
  Error!
  
  midimon.exe not found in script directory
  
  Make sure you have a working copy of the midimon executable
  in the directory where your script resides.
  
  You can get it from:
  http://www.sreal.com:8000/~div/midi-utilities/
  
  Exiting...
  )
  ExitApp
} 

; check for running instance of midimon, close if found
Process, Exist, midimon.exe
If errorlevel != 0
  Process, Close, %errorlevel%

; Gui
Gui, +AlwaysOnTop
Gui, Add, ListBox, vListBoxTrigger x2 y2 w436 h380
Gui, Add, Button, x2 y376 w86 h20 gStart, Start fetching
Gui, Add, Button, x90 y376 w86 h20 gStop, Stop fetching
Gui, Add, Text, x200 y376 w200 h40, EXPERIMENTAL! Use at your own risk!
Gui, Show, center h400 w440, MIDIasInput4ahk - Method CMDret-AHK
Return

Start:
CMDret_RunReturn("midimon")
Return

Stop:
Process, Close, %cmdretPID%
return

Cleanup:
GuiClose:
Process, Close, %cmdretPID%
ExitApp
return

UpdateMidi(fromMidimon)
{
;GuiControl,, ListBoxTrigger, |
GuiControl,, ListBoxTrigger, |%fromMidimon%
}

; ****************************************************************** 
; CMDret-AHK functions 
; version 1.08 beta 
; 
; Updated: March 31, 2006 
; by: corrupt 
; Code modifications and/or contributions made by: 
; Laszlo, shimanov, toralf  
; ****************************************************************** 
; Usage: 
; CMDin - command to execute 
; ****************************************************************** 
; Known Issues: 
; - If using dir be sure to specify a path (example: cmd /c dir c:\) 
; - Running 16 bit console applications may not produce output. Use 
; a 32 bit application to start the 16 bit process to receive output  
; ****************************************************************** 
; Additional requirements: 
; - none 
; ****************************************************************** 
; Code Start 
; ****************************************************************** 

CMDret_RunReturn(CMDin) 
{ 
  Global cmdretPID testing
  idltm := A_TickCount + 20 
  CMsize = 1 
  VarSetCapacity(CMDout, 1, 32) 
  VarSetCapacity(sui,68, 0) 
  VarSetCapacity(pi, 16, 0) 
  VarSetCapacity(pa, 12, 0) 
  Loop, 4 { 
    DllCall("RtlFillMemory", UInt,&pa+A_Index-1, UInt,1, UChar,12 >> 8*A_Index-8) 
    DllCall("RtlFillMemory", UInt,&pa+8+A_Index-1, UInt,1, UChar,1 >> 8*A_Index-8) 
  } 
  IF (DllCall("CreatePipe", "UInt*",hRead, "UInt*",hWrite, "UInt",&pa, "Int",0) <> 0) { 
    Loop, 4 
      DllCall("RtlFillMemory", UInt,&sui+A_Index-1, UInt,1, UChar,68 >> 8*A_Index-8) 
    DllCall("GetStartupInfo", "UInt", &sui) 
    Loop, 4 { 
      DllCall("RtlFillMemory", UInt,&sui+44+A_Index-1, UInt,1, UChar,257 >> 8*A_Index-8) 
      DllCall("RtlFillMemory", UInt,&sui+60+A_Index-1, UInt,1, UChar,hWrite >> 8*A_Index-8) 
      DllCall("RtlFillMemory", UInt,&sui+64+A_Index-1, UInt,1, UChar,hWrite >> 8*A_Index-8) 
      DllCall("RtlFillMemory", UInt,&sui+48+A_Index-1, UInt,1, UChar,0 >> 8*A_Index-8) 
    } 
    IF (DllCall("CreateProcess", Int,0, Str,CMDin, Int,0, Int,0, Int,1, "UInt",0, Int,0, Int,0, UInt,&sui, UInt,&pi) <> 0) { 
      Loop, 4 
        cmdretPID += *(&pi+8+A_Index-1) << 8*A_Index-8 
      Loop { 
        idltm2 := A_TickCount - idltm 
        If (idltm2 < 10) { 
          DllCall("Sleep", Int, 10) 
          Continue 
        } 
        IF (DllCall("PeekNamedPipe", "uint", hRead, "uint", 0, "uint", 0, "uint", 0, "uint*", bSize, "uint", 0 ) <> 0 ) { 
          Process, Exist, %cmdretPID% 
          IF (ErrorLevel OR bSize > 0) { 
            IF (bSize > 0) { 
              VarSetCapacity(lpBuffer, bSize+1) 
              IF (DllCall("ReadFile", "UInt",hRead, "Str", lpBuffer, "Int",bSize, "UInt*",bRead, "Int",0) > 0) { 
                IF (bRead > 0) { 
                  TRead += bRead 
                  VarSetCapacity(CMcpy, (bRead+CMsize+1), 0) 
                  CMcpy = a 
                  DllCall("RtlMoveMemory", "UInt", &CMcpy, "UInt", &CMDout, "Int", CMsize) 
                  DllCall("RtlMoveMemory", "UInt", &CMcpy+CMsize, "UInt", &lpBuffer, "Int", bRead) 

; ********************************** ADDED

                  testing := lpbuffer
                  UpdateMidi(testing) 

; ********************************** /ADDED

                  CMsize += bRead 
                  VarSetCapacity(CMDout, (CMsize + 1), 0) 
                  CMDout=a    
                  DllCall("RtlMoveMemory", "UInt", &CMDout, "UInt", &CMcpy, "Int", CMsize) 
                } 
              } 
            } 
          } 
          ELSE 
            break 
        } 
        ELSE 
          break 
        idltm := A_TickCount 
      } 
    } 
    cmdretPID= 
    DllCall("CloseHandle", UInt, hWrite) 
    DllCall("CloseHandle", UInt, hRead) 
  } 
  IF (StrLen(CMDout) < TRead) { 
    VarSetCapacity(CMcpy, TRead, 32) 
    TRead2 = %TRead% 
    Loop { 
      DllCall("RtlZeroMemory", "UInt", &CMcpy, Int, TRead) 
      NULLptr := StrLen(CMDout) 
      cpsize := Tread - NULLptr 
      DllCall("RtlMoveMemory", "UInt", &CMcpy, "UInt", (&CMDout + NULLptr + 2), "Int", (cpsize - 1)) 
      DllCall("RtlZeroMemory", "UInt", (&CMDout + NULLptr), Int, cpsize) 
      DllCall("RtlMoveMemory", "UInt", (&CMDout + NULLptr), "UInt", &CMcpy, "Int", cpsize) 
      TRead2 -- 
      IF (StrLen(CMDout) > TRead2) 
        break 
    } 
  } 
  StringTrimLeft, CMDout, CMDout, 1 
  Return, CMDout 
}

Idea: Create custom driver that uses SendMessage/GetMessage to communicate:

Create a program (or maybe better: a DLL), that can do the following:

Midi2AHK:
- Open a Midi In port, and start listening
- Send the received Midi information as a system message

Now one could use an AutoHotkey script to monitor for those messages (OnMessage).

AHK2Midi:
- Listen to a specific system message
- Open a Midi Out port, and start sending

Now one could use an AutoHotkey script to send those messages (PostMessage/SendMessage).

Ideally:

- Option to set Midi In (and Out) channel
- Option to set Midi Thru
- Option to set the window handle and system message number to send on/listen to
- Option to transform/filter messages to send (like in cinmidi, further below)

While stumbling around all kinds of sites, looking for a solution, I found two interesting projects:

Improv

Improv is a C++ environment for writing programs that enable musician/computer interaction using MIDI instruments. Improv programs can be written in special pre-defined environments, or they can be written from scratch using just the basic MIDI input and output classes.


Permission is granted to use Improv code for non-commercial purposes including music composition, music performance or academic research and education. All other uses of Improv must be licensed.


PortMidi

PortMidi Portable Real-Time MIDI Library, BSD license.

PortMusic is a set of APIs and library implementations for music.
PortMusic is open-source and runs on Windows, Macintosh, and Linux.
Currently, libraries support Audio I/O and MIDI I/O.
Our goal is to extend the PortAudio foundation to support MIDI, sound files, and perhaps other basic music software interfaces.


I'm no programmer, but this doesn't look too difficult, so: would any C/C++ coder be willing to take a look at those sources?

To get you started ... :wink: ... here is some code about Midi In, really doesn't look that difficult (but again: I'm no programmer):

From: Improv How-To - Basic MIDI input

Example 1 demonstrates how to receive a MIDI message from an external MIDI synthesizer connected to your computer via a MIDI cable. MIDI synthesizers and other MIDI devices send MIDI messages which are typically three-bytes long. MIDI messages start with the MIDI command byte (a number between 128 and 255) which is then followed by 1, 2, or a variable length of parameter bytes depending on the MIDI command. Typical MIDI messages include note-ons, note-offs, program changes, continuous controllers, and system-exclusive messages, among others.

Example 1: A simple MIDI input program.

#include "improv.h"

int main(void) {
MidiInput midi(0);
MidiMessage message;

while (1) {
while (midi.getCount > 0) {
message = midi.extract();
cout << message << endl;
}
}

return 0;
}

Line 1 includes the file improv.h which contains definitions needed for reading MIDI input.
Line 4 creates a MidiInput object connected to the first available MIDI input port (0).
You can ask the MidiInput class how many input ports are available by using the member function getNumPorts().
You can ask for the names of the ports with the MidiInput member function getName().
Line 5 creates a MidiMessage object which will be used to extract MIDI messages from the MidiInput object whenever MIDI data arrives.
Lines 7-12 form a continuous loop which will extract MIDI messages as they arrive at the MIDI input port and print out the message to the terminal.
Lines 8-11 check the MidiInput object to see if it has any messages waiting in its buffer. If so, then extract the messages one-by-one and print them. The extract command extracts the oldest MIDI message which has not yet been extracted.
You can examine MIDI messages without extracting them by using the [] operator; for example, to look at the most recent complete MIDI message which has arrived, use midi[0]. To look at the second-to-last message which has arrived, you would use midi[1] or midi[-1].
Line 9 removes one incoming MIDI message from the input buffer and stores it in the message variable.


Where it looks like red could be replaced by a SendMessage/PostMessage

More elaborate example can be found here cinmidi

Display/Record MIDI input data.

Usage: cinmidi.exe [-a][-i][-x|-d|-2] [-o output][-p port]

Options:
-a = display MIDI timing in absolute time instead of delta time
-i = don't display interpretation of MIDI message as comments
-d = display MIDI bytes in decimal form
-x = display MIDI bytes in hexadecimal form
-2 = display MIDI bytes in binary form
-n = do not display note-off messages
-f string = filter out specified MIDI commands (e.g. "bc" will
filter out control change and patch change messages)
-c string = filter out specified MIDI channels, offset zero
(e.g., "09AF", filters channels 1, 10, 11, and 16)
-p port = use the specified MIDI input port
-l = list MIDI input ports and then exit
-o filename = send display to file as well as to screen
-u string = specify a user name for the header information
-s = display time values in seconds
-k = disable keyboard input
--options = list all options, default values, and aliases


...

// function declarations
void checkOptions (Options& opts);
void displayMessage (ostream& out, MidiMessage message, int style);
void displayHeader (ostream& out);

...


Where it looks like red could be replaced by a SendMessage/PostMessage

PortMidi doesn't look that much different/more difficult...

To all you programmers: I keep my fingers crossed... (I installed Dev-C++ and fiddled around myself, but I'm a programming noob) :)
Posted Image mirror 1mirror 2mirror 3ahk4.me • PM or Posted Image

hamoid
  • Members
  • 19 posts
  • Last active: Nov 06 2006 10:45 AM
  • Joined: 17 Mar 2005
I tried looking for help at groups.google.com

Here seems to be information about MIDI.

hamoid
  • Members
  • 19 posts
  • Last active: Nov 06 2006 10:45 AM
  • Joined: 17 Mar 2005
I just did a new experiment in my quest for controlling Photoshop and other programs with a MIDI controller :)

MIDI-OX can be scripted using Windows Script Host (WSH).

So I did a small .js program that receives MIDI data and converts it to key strokes. This is without using AHK.

var mox;
var n;
var notesOn;
var baseChan;
var SH;

SH = WScript.CreateObject("WScript.Shell")

mox = WScript.CreateObject("Midiox.MoxScript.1", "On_");

n = mox.InstanceCount;

notesOn  = 0;
baseChan = 0;

mox.DivertMidiInput = 1;
mox.FireMidiInput = 1;

WScript.Echo( "Press OK to end MIDI translate Loop" );

mox.FireMidiInput = 0;
mox.DivertMidiInput = 0;

mox    = null;

function On_MidiInput( ts, port, stat, dat1, dat2)
{
   if (dat2)
   {
     SH.SendKeys("+{HOME}" + (dat2*2))
   }
}

Double clicking in this .js file starts MIDI-OX. Then I switch to something, like notepad, and move a midi controller. It sends Shift+Home (to select previous text) and types the new number many times per second, so you control a number in notepad with MIDI. The fun part is, if I am in Photoshop, open an image, hit CTRL+L (levels), put the cursor at the end of one of the number fields and move the MIDI controller, the image brightness changes accordingly, in real time.

Of course this is a very limited example: I can't assing different controllers to different inputs in Photoshop. That would require AHK MIDI support (to focus on desired input fields, I don't think that can be done with WSH).

A slow workaround is possible: MIDI-OX receives data. I filter the data and convert to SendKeys sequences. I program AHK to parse those key sequences. AHK focuses on fields and sends keys again. So basically, building a SendKeys communication between .js and AHK. A bit twisted and probably slow, but would work.

hamoid
  • Members
  • 19 posts
  • Last active: Nov 06 2006 10:45 AM
  • Joined: 17 Mar 2005
This is something new: maybe something better than MIDI could be implemented?

Open Sound Control (OSC) is an modern open standard for communicating computers, software and devices. It tries to solve the limitations of MIDI, which was created very long time ago.

OSC allows communication between programs, either local or through the Internet.

There are lots of implementations:
http://cnmat.berkele...enSoundControl/

Here a C++ library:
http://www.audiomulc...b/code/oscpack/

Adding OSC to AHK would allow sending and receiving commands from most programming languages and many types of software. For example it could allow communicating two computers running AHK, without doing DLL tricks.

About the original post, it looks like MIDI can run inside OSC, so implementing OSC should "solve" the original MIDI idea in this thread.

Anybody interested? :)

Chris
  • Administrators
  • 10727 posts
  • Last active:
  • Joined: 02 Mar 2004
I'd be interested in working on this someday, but it will probably be a long time due to other priorities. Hopefully, someone will beat me to it by developing an interface DLL, or by directly modifying AutoHotkey.

Anatoly Larkin
  • Members
  • 32 posts
  • Last active: Sep 21 2011 08:16 PM
  • Joined: 28 Nov 2004
So, I have been trying to follow the idea and use winmm.dll to open and play notes through my onboard midi.

I do this
result := DllCall("winmm.dll\midiOutOpen"
					, Int, h_midiout
					, UInt, 0
					, UInt, dwCallback
					, UInt, dwCallbackInstance
					, UInt, dwFlags
					, "UInt")
and the result = 11

h_midiout doesn't get initialized to anything.

Any clue?
||||
------
!!

PhiLho
  • Moderators
  • 6850 posts
  • Last active: Jan 02 2012 10:09 PM
  • Joined: 27 Dec 2005
Cannot test it, but since h_midiout receives a value, you have to give its address:
h_midiout := 0000 ; Important!
result := DllCall("winmm.dll\midiOutOpen"
		, UInt, &h_midiout
		, UInt, uDeviceID
		, UInt, 0
		, UInt, 0
		, UInt, dwFlags
		, "UInt") 

Posted Image vPhiLho := RegExReplace("Philippe Lhoste", "^(\w{3})\w*\s+\b(\w{3})\w*$", "$1$2")

Anatoly Larkin
  • Members
  • 32 posts
  • Last active: Sep 21 2011 08:16 PM
  • Joined: 28 Nov 2004
Thanks.
Your suggestion improved things to the point where now the result = 0, rather than "11".

However, I don't know if h_midiout gets anything.
If I do
msgbox %h_midiout%

nothing gets put in the msgbox.

might it contain some value that msgbox cannot display.
In which case, how do I view it?
||||
------
!!

PhiLho
  • Moderators
  • 6850 posts
  • Last active: Jan 02 2012 10:09 PM
  • Joined: 27 Dec 2005
Strange, it should have 0000 or, if success, a number.
You should check also ErrorLevel.
What values do you put in uDeviceID and dwFlags? (No idea of what should go there, I just took a look at the function page...)
Posted Image vPhiLho := RegExReplace("Philippe Lhoste", "^(\w{3})\w*\s+\b(\w{3})\w*$", "$1$2")

Anatoly Larkin
  • Members
  • 32 posts
  • Last active: Sep 21 2011 08:16 PM
  • Joined: 28 Nov 2004
Ok, I think everythin is fine now.
I made sure to initialize every single dw variable to := 0 first.
And now I am getting some 3 byte thing put in h_midiout (obviously, the handle).

Now, the next struggle is to get the device to play something through things like midiOutShortMsg.

I'll see if I run into more problems.
||||
------
!!

Chris
  • Administrators
  • 10727 posts
  • Last active:
  • Joined: 02 Mar 2004
By the way, I don't recommend using Var:=0000 because it's undocumented behavior that could change. Anything to the right of the := operator is an expression, and in this case it's purely numeric so could wind up being stored as a single zero someday.

So instead, I think Var:="0000" is preferable.

Zed Gecko
  • Members
  • 149 posts
  • Last active:
  • Joined: 23 Sep 2006
regarding this topic, i found a webpage:
http://www.logosfoun...t/gmt_midi.html

I don´t understand a lot of it, but it seems to me,
that it contains the sourcecode for a dll, which handles
MIDI In/Output. It says that i can be compiled in C or in C++.
I have no experience with any of this, but maybe someone
else could look at this and make us a usable dll