Jump to content

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

MIDI input library


  • Please log in to reply
85 replies to this topic
orbik
  • Members
  • 35 posts
  • Last active: Feb 12 2011 11:16 PM
  • Joined: 09 Apr 2008
(Moved from the relevant topic in Wishlist http://www.autohotke.../topic2831.html.. now in the correct sub forum)


I made a small library for integrating midi input to ahk scripts. It consists of a dll that uses a callback function to store the most recent value for each key velocity, cc, pitch wheel and channel aftertouch for every channel. To get midi input as windows messages you command the dll to send specific data only, and to specific message numbers. You can also directly ask any of the stored most recent values

I also wrote an ahk layer on top of the dll supposed to make it easier to use. It also creates a tray icon menu to select/change midi in device.

The dll file: http://ihme.org/~orb...ahk/midi_in.dll
Dll source (as a vc++ project): http://ihme.org/~orb...ahk/dll_source/
Dll function reference: http://ihme.org/~orb...i_in.readme.txt

midi_in_lib.ahk:
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

And an example script to use the library:
SendMode Input
SetWorkingDir %A_ScriptDir% 

OnExit, sub_exit
if (midi_in_Open(0))
	ExitApp

;--------------------  Midi "hotkey" mappings  -----------------------
listenNoteRange(48, 52, "playSomeSounds", 0x02)

return
;----------------------End of auto execute section--------------------

sub_exit:
	midi_in_Close()
ExitApp

;-------------------------Miscellaneous hotkeys-----------------------
Esc::ExitApp

;-------------------------Midi "hotkey" functions---------------------
playSomeSounds(note, vel)
{
	if (vel) ; vel == 0 means note off
	{
		SoundPlay drum%note%.wav
	}
}

;-------------------------  Midi input library  ----------------------
#include midi_in_lib.ahk

All relevant files: http://ihme.org/~orbik/midi4ahk/

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
This looks very nice. Thanks for sharing it!

Do I understand right the following? If I connect my USB midi keyboard to a PC, I can play music and listen to it through the PC sound system. That is, I don’t need another set of speakers or sound synthesizers. I only need to run a midi player AHK script. The logic of the player script has to be:
- midi_in_Open
- listenNoteRange
- listenWheel
- listen...
- Gui event or Hotkey: midi_in_Close

In the call to listenNoteRange a function name is given (which can have a 0..7 number affixed). They will be called, whenever a keyboard key is hit, with the parameters note, and the velocity of the key press. These functions have to call the necessary Windows midi API functions to play the sound. Similarly, in the call to listenWheel another function name is given, which has to process the wheel events.

At every call the listening functions increase the message number, starting from 0x2000, for which the player function reacts. The maximum is 0xFFFFFFFF, that will practically never be reached: 0xFFFFDFFF/10/60/60/24/365 = ~13.6 years of continuous playing, with 10 keys hit every second, therefore we need not worry about recycling expired message numbers.

The player function has to return really fast, so it can handle frequent calls. Maybe it has just to queue up requests and return. A timer subroutine will then perform the actions (sound playing), otherwise some keys get lost at fast music playing. An alternative is to set MaxThreads to a large value (20) in the AHK script. One can experiment with Critical, too, (or globally, with Thread, Interrupt, -1) what should buffer messages until the function returns. (It is not documented, how many unprocessed messages can AHK buffer – hopefully enough for normal players.)

orbik
  • Members
  • 35 posts
  • Last active: Feb 12 2011 11:16 PM
  • Joined: 09 Apr 2008

...
I connect my USB midi keyboard to a PC, I can play music and listen to it through the PC sound system. That is, I don’t need another set of speakers or sound synthesizers. I only need to run a midi player AHK script.

In theory you could make the script play sounds on midi input but what's the point of that? If you want music, just play a vst soft synth or the sound card's midi out with something like midi-ox.
The point here is to make your midi controller useful outside the standard midi applications. (launch apps, key presses, numerical data input with knobs...)

I guess I should make better documentation before posting stuff like this. I'll update the above post when ready.

Here's how you'd use it:

1: call midi_in_Open(deviceID)
Leave deviceID blank to default to no input and select it later

2: call listen*** for all input types you want to react to
A specified function will be called when matching midi message is received. With listenNoteRange you can have each note call a different function (set flag 1), where the function name is of the form funcName#, and # = position of the key within the specified range.
e.g. listenRange(36, 47, myfynction) will call myfunction1 on Note On and Note Off messages where note number is 36, myfunction2 when it's 37, etc

3: Each function called on midi input will receive 2 or 1 values as arguments: wParam and lParam. In note on/off messages wParam is the note number and lParam the velocity. More information in the readme file.


The message numbers are only incresed when the listen* functions are called. The other way would be to directly call the corresponding dll function with a desired message number, then call OnMessage with the same number and a function name. I didnt know how to avoid this extra layer (receive message -> call function), so I did my best to hide it.

The player function has to return really fast, so it can handle frequent calls. Maybe it has just to queue up requests and return.


I've understood that, with functions called by OnMessage, only 1 thread will execute per function, and other messages can still start a thread while another is executing.

Anyway I don't understand why you would use the midi input to play music. Ok, my example plays drum sounds, but it's just to show how it works.

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

In theory you could make the script play sounds on midi input but what's the point of that?

It could be a very small (hundred-line) script, which takes input from the midi keyboard *and* from the PC keyboard. If your midi keyboard does not have enough buttons, or if they are not configurable, a PC keyboard key could activate pre-set instruments, patches, a metronome, drums, auto-base... You can make a synthesizer with your own effects, etc. I am sure you can find programs for all these, but not the way you may like it.

Of course, it is cool to start a Windows application with a short tune or with an accord played on the midi keyboard, especially, if it is on your desk, anyway. You can have tunes for often repeated info, like your name or address, but my Logitech G15 PC keyboard has three sets of 18 programmable keys, more than enough for my day-to-day work.

ProfessorY91
  • Members
  • 12 posts
  • Last active: Dec 15 2009 10:53 PM
  • Joined: 03 Mar 2008

It could be a very small (hundred-line) script, which takes input from the midi keyboard *and* from the PC keyboard. If your midi keyboard does not have enough buttons, or if they are not configurable, a PC keyboard key could activate pre-set instruments, patches, a metronome, drums, auto-base...


Has this been done on this thread, or is there a script to do this availible from either of you?

I would attempt to code it myself, but I am currently knee deep in getting the "midi keyboard" script (located under topic: <!-- m -->http://www.autohotke... ... 952#190952<!-- m --> )
to work on my computer... (an issue which stems from my PC only).

So I ask...

Is there a working wonder script for the like?

orbik
  • Members
  • 35 posts
  • Last active: Feb 12 2011 11:16 PM
  • Joined: 09 Apr 2008
@ ProfessorY91
Although not sure what you're after, you probably don't need this library, but MIDI Output http://www.autohotke...p=119466#119466.
To use your default midi out port (midi mapper) just use -1 for device id.

  • Guests
  • Last active:
  • Joined: --
running your ahk script gives me an error message, that

DllCall("FreeLibrary", UInt,midi_in_hModule), midi_in_hModule := ""

is not a recognised function. What's the FreeLibrary .dll?

  • Guests
  • Last active:
  • Joined: --
Do you have the latest version of AutoHotkey?

Anyways, FreeLibrary is part of the Win32 API, and is documented here:
http://msdn.microsof...y/ms683152.aspx

stutter
  • Members
  • 11 posts
  • Last active: Feb 03 2009 01:10 AM
  • Joined: 13 Feb 2008
didn't have the latest version, now installed, sorted out that issue... now to try this thing out

edit: the test patch works, this is fantastic news, thankyou very much Orbik

stutter
  • Members
  • 11 posts
  • Last active: Feb 03 2009 01:10 AM
  • Joined: 13 Feb 2008
this is brilliant. Not sure I've really understood it all yet, but this is the first time I've used dllcall. Have it working for me by editing, copy and pasting the example script into the form I want (with functions from the readme), and then pasting in my autohotkey script for Ableton.

Anyway to set a midi device as default from this script. I presume that I can use
defaultDevID := -1
in the midi_in_lib.ahk, but I'm curious if I can do it from outside of that (e.g. in this case from what was the example file)?

I don't think I understand the difference between the Listen and Get functions. When would you use e.g. getCC instead of listenCC?

Thanks again, been wanting this for some time.

n-l-i-d
  • Guests
  • Last active:
  • Joined: --

Anyway to set a midi device as default from this script. I presume that I can use

defaultDevID := -1
in the midi_in_lib.ahk, but I'm curious if I can do it from outside of that (e.g. in this case from what was the example file)?


You need not (necessarily) change the lib. Use something like this:

; midi_in_Stop()
midi_in_Close() ; closes current device
midi_in_Open(0) ; opens device

I don't think I understand the difference between the Listen and Get functions. When would you use e.g. getCC instead of listenCC?


The Listen functions create an OnMessage() handler, they provide the script with the stream of data you asked to get (in the example file the sub-routine playSomeSounds is created with the listenNoteRange function call).

The Get functions return the most recent value (only).

HTH

stutter
  • Members
  • 11 posts
  • Last active: Feb 03 2009 01:10 AM
  • Joined: 13 Feb 2008
thanks n-l-i-d

n-l-i-d
  • Guests
  • Last active:
  • Joined: --

"What is this anyway, what can it do, why should I use it and what do I need?"

What is this anyway?

Important: do not confuse the midi file format with the protocol. The midi protocol is used for sending/receiving event information real-time between midi-enabled devices, usually musical instruments.

Now, with this excellent dll, you can directly use incoming midi information from within AutoHotkey (it has become fully midi-enabled).

What can it do?

Every MIDI connection is a one-way connection from the MIDI Out connector of the sending device to the MIDI In connector of the receiving device. Each such connection can carry a stream of MIDI messages, with most messages representing a common musical performance event or gesture such as note-on, note-off, controller value change (including volume, pedal, modulation signals, etc.), pitch bend, program change, aftertouch, channel pressure. All of those messages include channel number. There are 16 possible channels in the protocol. The channels are used to separate "voices" or "instruments", somewhat like tracks in a multi-track mixer.


A sending device (also called: Midi-controller) probably has:

* keys / pads / buttons

Properties:
- note (which one is hit/released)
- velocity (how hard is it hit)
- aftertouch (if pressed after hit, how hard is the key pressed)

...it might also have:

* knobs / sliders / wheels

Properties:
- layout (up-down, left-right or centered)
- value

Good to note:

- Midi-controllers output 0 thru 127 as values

Why should I use it?

Examples:
- launch an application after a keypress (or combination: melody)
- scroll a page with a rotating knob or slider
- control your Photoshop pots and sliders with real knobs and sliders

So, for very little money, you can add a multitude of extra controllers to your computer.

What do I need

1. Get midi support for your computer if not available. You could:
- use the soundcard's (SoundBlaster) gameport2midi option
- add a soundcard with direct midi support
- hook up an external converter with midi support (firewire/usb)
2. Get a midi-cable
3. Hook up any midi-controller's midi-output with the midi-input from your computer
4. Check the midi routing in the software/drivers for your specific midi-support
5. Set up the script/dll to start receiving
6. Start sending from the midi-controller

You can now control your computer with your midi-controller...

Sources:
MIDI controller @ Wikipedia
Musical Instrument Digital Interface @ Wikipedia
The MIDI 1.0 Protocol @ Wikipedia
Game port/DA-15 connector @ Wikipedia



stutter
  • Members
  • 11 posts
  • Last active: Feb 03 2009 01:10 AM
  • Joined: 13 Feb 2008
Could anybody tell me how to use timers in conjunction with this? Such that I can listen for a string of cc's, and if I do not receive one (or after the cc inout stops) in a set time, I can activate a subroutine...

I have been trying to emulate the Novation Speed-dial - use one dial to clickdrag the mouse up or down, when over a control. Here's what I have, which works to a degree:

OnExit, sub_exit
if (midi_in_Open(13))
   ExitApp

;--------------------  Midi "hotkey" mappings  -----------------------
listenCC(122, "DialL", 16)
listenCC(123, "DialR", 16)

return
;----------------------End of auto execute section--------------------

sub_exit:
   midi_in_Close()
ExitApp

;-------------------------Miscellaneous hotkeys-----------------------
Esc::ExitApp

;-------------------------Midi "hotkey" functions---------------------


DialL(Note,Vel)
{
   if (1)
	GoSub TurnLeft  
}
return

DialR(Note,Vel)
{
	if (65)
	GoSub TurnRight
}
return

;-------------------------  Midi input library  ----------------------
#include midi_in_lib.ahk

;-------------------------  Subs  ----------------------

TurnLeft:
{
Mouseclickdrag, Left, 0, 0, -2, 2, 100, r
}
return

TurnRight:
Mouseclickdrag, Left, 0, 0, 2, -2, 100, r
return


I'm not sure where to use a timer - tried it in the Gosub's, but each of those is activated each time a value is received by ListenCC - in other words I am getting a string of 2 pixel clickdrag's, rather than one longer one; a longer drag would disable the actual mouse while in use, and optionally allow me to use Mousegetpos to return the cursor to it's original position.
I assume that I need the timer at the top of the script somewhere, but I've got to the point of tiredness where I can't figure it out for myself, so any help would be appreciated.
Thanks.

ribbet.1
  • Members
  • 198 posts
  • Last active: Feb 07 2012 01:21 AM
  • Joined: 20 Feb 2007
this looks like it really might do something that I've been wanting for a long time. If I understand you correctly, orbik, I'll be able to use my MIDI keyboard to (in this instance) send a different text string with each note. In such case, I would not want anything except for the note number to be sent, and have that map to a text string. Does that sound about right ?

I'll try downloading this library at some point soon and playing around with it. Thanks for your great work!