 |
AutoHotkey Community Let's help each other out
|
| View previous topic :: View next topic |
| Author |
Message |
orbik
Joined: 09 Apr 2008 Posts: 25 Location: Espoo, Finland
|
Posted: Sat Apr 12, 2008 4:18 pm Post subject: MIDI input library |
|
|
(Moved from the relevant topic in Wishlist http://www.autohotkey.com/forum/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/~orbik/midi4ahk/midi_in.dll
Dll source (as a vc++ project): http://ihme.org/~orbik/midi4ahk/dll_source/
Dll function reference: http://ihme.org/~orbik/midi4ahk/midi_in.readme.txt
midi_in_lib.ahk:
| Code: | 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:
| Code: | 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/ |
|
| Back to top |
|
 |
Laszlo
Joined: 14 Feb 2005 Posts: 4016 Location: Pittsburgh
|
Posted: Sat Apr 12, 2008 4:40 pm Post subject: |
|
|
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.) |
|
| Back to top |
|
 |
orbik
Joined: 09 Apr 2008 Posts: 25 Location: Espoo, Finland
|
Posted: Sat Apr 12, 2008 8:35 pm Post subject: |
|
|
| Laszlo wrote: | ...
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.
| Laszlo wrote: |
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. |
|
| Back to top |
|
 |
Laszlo
Joined: 14 Feb 2005 Posts: 4016 Location: Pittsburgh
|
Posted: Sat Apr 12, 2008 9:59 pm Post subject: |
|
|
| orbik wrote: | | 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. |
|
| Back to top |
|
 |
ProfessorY91
Joined: 03 Mar 2008 Posts: 8 Location: Inside your mind.
|
Posted: Mon Apr 14, 2008 10:55 pm Post subject: |
|
|
| Quote: | | 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: http://www.autohotkey.com/forum/viewtopic.php?p=190952#190952 )
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? _________________ If you dont follow your instincts then your instincts will follow you.
www.alamalhaloteam.webs.com |
|
| Back to top |
|
 |
orbik
Joined: 09 Apr 2008 Posts: 25 Location: Espoo, Finland
|
|
| Back to top |
|
 |
Guest
|
Posted: Sat May 17, 2008 4:31 pm Post subject: |
|
|
running your ahk script gives me an error message, that
| Code: | | DllCall("FreeLibrary", UInt,midi_in_hModule), midi_in_hModule := "" |
is not a recognised function. What's the FreeLibrary .dll? |
|
| Back to top |
|
 |
Guest
|
|
| Back to top |
|
 |
stutter
Joined: 13 Feb 2008 Posts: 11
|
Posted: Sat May 17, 2008 5:03 pm Post subject: |
|
|
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 |
|
| Back to top |
|
 |
stutter
Joined: 13 Feb 2008 Posts: 11
|
Posted: Mon May 19, 2008 10:26 pm Post subject: |
|
|
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
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. |
|
| Back to top |
|
 |
n-l-i-d Guest
|
Posted: Tue May 20, 2008 9:01 am Post subject: |
|
|
| Quote: | Anyway to set a midi device as default from this script. I presume that I can use
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:
| Code: | ; midi_in_Stop()
midi_in_Close() ; closes current device
midi_in_Open(0) ; opens device |
| Quote: | | 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 |
|
| Back to top |
|
 |
stutter
Joined: 13 Feb 2008 Posts: 11
|
Posted: Tue May 20, 2008 1:48 pm Post subject: |
|
|
| thanks n-l-i-d |
|
| Back to top |
|
 |
n-l-i-d Guest
|
Posted: Tue May 20, 2008 3:55 pm Post subject: |
|
|
| Short tutorial wrote: | "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?
| Wikipedia wrote: | | 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
|
|
|
| Back to top |
|
 |
stutter
Joined: 13 Feb 2008 Posts: 11
|
Posted: Sat Jun 28, 2008 12:41 am Post subject: |
|
|
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:
| Code: | 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. |
|
| Back to top |
|
 |
ribbet.1
Joined: 20 Feb 2007 Posts: 36 Location: D.C.
|
Posted: Thu Aug 07, 2008 9:52 am Post subject: |
|
|
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) since a different tax 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! |
|
| Back to top |
|
 |
|
|
You can post new topics in this forum You can reply to topics in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|