AutoHotKey
SMOKING HOT KEYS.
My personal collection of AutoHotKey hacks. Gives you back ALT GR = ALT + CTRL, turns your keyboard into a MIDI controller, gives you an automatic save reminder and much more. Works on Windows only.
1
Code snippets to steal
; AUTOSAVE REMINDER
; -> Nudges you to save regularly while Ableton is running. CTRL+S resets the timer. Snoozes automatically when Melodyne is open.
#Requires AutoHotkey v2.0
#SingleInstance Force
SendMode("Input")
SetWorkingDir(A_ScriptDir)
SetTitleMatchMode(2)
; Reminder cadence.
global AutoSaveInterval := 30 * 60 * 1000 ; 30 min = 30 * 60 * 1000 ms
global AutoSaveShortInterval := 10 * 60 * 1000 ; 10 min = 10 * 60 * 1000 ms
; Runtime state.
global AutoSavePopupActive := false
global WaitingForAbletonActive := false
global AutoSaveGui := ""
SetTimer(CheckAbletonAndSetTimer, 1000)
CheckAbletonAndSetTimer() {
global AutoSaveInterval
if LiveHack_AbletonWindowExists()
{
SetTimer(AutoSaveReminder, AutoSaveInterval)
SetTimer(CheckAbletonAndSetTimer, 0)
}
}
AutoSaveReminder() {
global WaitingForAbletonActive
SetTimer(AutoSaveReminder, 0)
WaitingForAbletonActive := true
SetTimer(WaitForAbletonActive, 1000)
}
WaitForAbletonActive() {
global WaitingForAbletonActive
if (!WaitingForAbletonActive)
return
if (LiveHack_IsAbletonOrPluginActive() and not LiveHack_IsMelodyneActive())
{
WaitingForAbletonActive := false
SetTimer(WaitForAbletonActive, 0)
ShowSavePopup()
return
}
if not LiveHack_AbletonWindowExists()
{
WaitingForAbletonActive := false
SetTimer(WaitForAbletonActive, 0)
SetTimer(CheckAbletonAndSetTimer, 1000)
}
}
ShowSavePopup() {
global AutoSavePopupActive, AutoSaveGui
if (!AutoSavePopupActive)
{
AutoSavePopupActive := true
AutoSaveGui := Gui("+AlwaysOnTop +ToolWindow", "LiveHack: Auto Save")
AutoSaveGui.MarginX := 14
AutoSaveGui.MarginY := 14
AutoSaveGui.SetFont("s10", "Segoe UI")
AutoSaveGui.Add("Text", , "Time to save your Ableton project.")
AutoSaveGui.Add("Text", "y+6", "Save now or snooze for 10 minutes.")
AutoSaveGui.Add("Button", "xm y+14 w100 Default", "Save now").OnEvent("Click", AutoSave_DoSave)
AutoSaveGui.Add("Button", "x+10 w120", "Remind me later").OnEvent("Click", AutoSave_Later)
AutoSaveGui.OnEvent("Close", AutoSave_Later)
AutoSaveGui.OnEvent("Escape", AutoSave_Later)
AutoSaveGui.Show()
}
}
AutoSave_DoSave(*) {
global AutoSavePopupActive, AutoSaveGui, AutoSaveInterval
AutoSaveGui.Destroy()
AutoSavePopupActive := false
if LiveHack_AbletonWindowExists()
{
WinActivate("ahk_class Ableton Live Window Class")
Send("^s")
}
SetTimer(AutoSaveReminder, AutoSaveInterval)
}
AutoSave_Later(*) {
global AutoSavePopupActive, AutoSaveGui, AutoSaveShortInterval
AutoSaveGui.Destroy()
AutoSavePopupActive := false
SetTimer(AutoSaveReminder, AutoSaveShortInterval)
}
#HotIf LiveHack_IsAbletonOrPluginActive()
$^s:: {
global AutoSavePopupActive, AutoSaveGui, WaitingForAbletonActive, AutoSaveInterval
Send("^s")
SetTimer(AutoSaveReminder, 0)
SetTimer(AutoSaveReminder, AutoSaveInterval)
if (AutoSavePopupActive)
{
AutoSaveGui.Destroy()
AutoSavePopupActive := false
}
if (WaitingForAbletonActive)
{
WaitingForAbletonActive := false
SetTimer(WaitForAbletonActive, 0)
}
}
#HotIf
LiveHack_AbletonWindowExists() {
return WinExist("ahk_class Ableton Live Window Class")
}
LiveHack_IsAbletonOrPluginActive() {
return (
WinActive("ahk_class Ableton Live Window Class")
or WinActive("ahk_class Vst3PlugWindow")
or WinActive("ahk_class AbletonVstPlugClass")
)
}
LiveHack_IsMelodyneActive() {
return WinActive("Melodyne")
}
; April 2026 — Released under MIT license by Yannis Lever
; This code is provided as-is with no warranty. Use at your own risk.
; Check out www.livehack.tools for more LiveHacks!
; GET BACK ALT GR
; -> Remaps ALT GR to CTRL+ALT so you can perform the CTRL+ALT+U hotkey with one hand.
; !! Only relevant for some international keyboard layouts. You know if this is for you.
#Requires AutoHotkey v2.0
#SingleInstance Force
SendMode("Input")
SetWorkingDir(A_ScriptDir)
SetTitleMatchMode(2)
#HotIf ((WinActive("ahk_class Ableton Live Window Class") or WinActive("ahk_class Vst3PlugWindow") or WinActive("ahk_class AbletonVstPlugClass")) and not WinActive("Melodyne"))
; this block is now only active in Ableton Live, unless you use Melodyne, where you don't need this remapping.
*RAlt:: {
Send("{LAlt down}")
Send("{LControl down}")
}
*RAlt up:: {
Send("{LAlt up}")
Send("{LControl up}")
}
#HotIf
; April 2026 — Released under MIT license by Yannis Lever
; This code is provided as-is with no warranty. Use at your own risk.
; Check out www.livehack.tools for more LiveHacks!
; MAKE BETTER USE OF F-KEYS
; -> Reassigns F1-F12 in Ableton to genuinely useful actions instead of the default track activators.
; !! Tag your favourite plugins with "AutoHotKey" in Ableton's browser.
; ------------------------------------------------------------------
; WHY OVERRIDE THE F-KEYS?
; Ableton's default F-key assignments mute tracks 1-8.
; In a real production session with 100+ tracks, this is basically useless.
; Let's use these easily accessible keys for something actually helpful:
;
; WHAT THIS SCRIPT DOES:
; F1-F4 → Insert your most-used plugins instantly (via browser search)
; Ctrl+F1-F4 → Insert more plugins.
; F5-F8 → Assign clip colours with one keypress
; Ctrl+F5-F8 → More clip colour options
; F9 → Record (passthrough)
; F10 → Toggle loop
; F12 → (passthrough, Ableton default)
; F13-F18 → Extended shortcuts for browser, key map, view toggles etc.
; (requires a keyboard or macro pad that sends F13+)
;
; ------------------------------------------------------------------
#Requires AutoHotkey v2.0
#SingleInstance Force
SendMode("Input")
SetWorkingDir(A_ScriptDir)
SetTitleMatchMode(2)
#HotIf WinActive("ahk_class Ableton Live Window Class") and not WinActive("Melodyne")
; ------------------------------------------------------------------
; F1-F4: INSERT YOUR MOST-USED PLUGINS
; Tag your plugins with "AutoHotKey" in Ableton's browser for one-key loading.
; Customize these to match YOUR workflow — the plugin names below are examples.
; ------------------------------------------------------------------
; F1 → Insert Compressor (Ableton built-in)
F1::AddTaggedPlugin("AutoHotKey", "Compressor", 0)
; Ctrl+F1 → Insert Pro-C 2 (third-party alternative)
^F1::AddTaggedPlugin("AutoHotKey", "Pro-C 2", 0)
; F2 → Insert Custom Audio or Effect Rack
F2::AddTaggedPlugin("AutoHotKey", "Custom Audio or Effect Rack.adg", 0)
; Ctrl+F2 → Insert OTT preset variant
^F2::AddTaggedPlugin("AutoHotKey", "OTT.adv", 0)
; F3 → Insert EQ Eight (Ableton built-in)
F3::AddTaggedPlugin("AutoHotKey", "EQ Eight", 0)
; Ctrl+F3 → Insert Pro-Q 4 (third-party alternative)
^F3::AddTaggedPlugin("AutoHotKey", "Pro-Q 4", 0)
; F4 → Insert Utility (Ableton built-in)
F4::AddTaggedPlugin("AutoHotKey", "Utility", 0)
; Ctrl+F4 → Insert Phase Invert
^F4::AddTaggedPlugin("AutoHotKey", "Phase Invert", 0)
; If a give search query gives you more than one result,
; then the number downArrow sends that many down arrow key strokes before hitting enter.
; ------------------------------------------------------------------
; F5-F8: ASSIGN CLIP COLOURS
; One-keypress colour assignment for the selected clip.
; These navigate the right-click colour menu. Colours are position-based
; and may shift if Ableton changes the palette in a future update.
; ------------------------------------------------------------------
; F5 → Full Green
F5::DoBlockInputSend("{AppsKey}{End}{Up}{Up}{Up}{Up}{Right}{Right}{Right}{Right}{Right}{Enter}")
; F6 → Light Green
F6::DoBlockInputSend("{AppsKey}{End}{Up}{Up}{Up}{Up}{Up}{Right}{Right}{Right}{Right}{Right}{Enter}")
; F7 → Yellow
F7::DoBlockInputSend("{AppsKey}{End}{Up}{Up}{Up}{Up}{Up}{Right}{Right}{Right}{Right}{Enter}")
; F8 → Magenta
F8::DoBlockInputSend("{AppsKey}{End}{Up}{Up}{Up}{Up}{Up}{Right}{Right}{Right}{Right}{Right}{Right}{Right}{Right}{Right}{Right}{Right}{Right}{Enter}")
; Ctrl+F5 → Dark Green
^F5::DoBlockInputSend("{AppsKey}{End}{Up}{Right}{Right}{Right}{Right}{Right}{Enter}")
; Ctrl+F6 → Turquoise
^F6::DoBlockInputSend("{AppsKey}{End}{Up}{Up}{Up}{Up}{Right}{Right}{Right}{Right}{Right}{Right}{Right}{Enter}")
; Ctrl+F7 → White
^F7::DoBlockInputSend("{AppsKey}{End}{Up}{Up}{Up}{Up}{Left}{Enter}")
; Ctrl+F8 → Black
^F8::DoBlockInputSend("{AppsKey}{End}{Up}{Left}{Enter}")
; ------------------------------------------------------------------
; F9-F12: TRANSPORT & MISC
; ------------------------------------------------------------------
F9::F9 ; Record (passthrough to Ableton default)
F10::Send("^l") ; Toggle loop on/off
F12::F12 ; Passthrough (Ableton default)
; ------------------------------------------------------------------
; F13-F18: EXTENDED KEYS (for keyboards / macro pads with extra F-keys)
; ------------------------------------------------------------------
F13::Send("^!b") ; Toggle browser
^F13:: {
Send("!2")
Send("A")
}
F15::Send("^k") ; Toggle Key Map mode
^F15::Send("^m") ; Toggle MIDI Map mode
F16::return ; Reserved (e.g. mute via MIDI in the MIDI script)
+F17::Send("u") ; Expand groups
F17::Send("^!u") ; Extend take lanes
F18::Send("^!3") ; Open clip view
!F18::Send("^!m") ; Open device view
^F18::Send("^!4") ; Open master view
^!F18::Send("^!E") ; Toggle full-height clip view
#HotIf
; ------------------------------------------------------------------
; HELPER FUNCTIONS
; ------------------------------------------------------------------
DoBlockInputSend(toSend) {
BlockInput("On")
MouseGetPos(&origMouseX, &origMouseY)
MouseMove(0, 0)
Sleep(50)
Send(toSend)
MouseMove(origMouseX, origMouseY)
BlockInput("Off")
}
AddTaggedPlugin(tag, pluginName, downArrow) {
BlockInput("On")
sleepTime := 300
Send("^f")
Sleep(sleepTime)
Send("{Esc}")
Sleep(sleepTime)
Send("^f")
Sleep(sleepTime)
Send("{#}" tag)
Send("{Enter}")
Sleep(sleepTime)
Send(pluginName)
Sleep(sleepTime)
Send("{Enter}")
Sleep(sleepTime)
Loop downArrow
{
Send("{Down}")
Sleep(sleepTime)
}
Send("{Enter}")
Sleep(sleepTime)
Send("{Esc}")
Sleep(sleepTime)
Send("^!b")
BlockInput("Off")
}
; April 2026 — Released under MIT license by Yannis Lever
; This code is provided as-is with no warranty. Use at your own risk.
; Check out www.livehack.tools for more LiveHacks!
; MEDIA KEYS AS MIDI CONTROLLER
; -> Play, stop, skip, and adjust volume in Ableton using your keyboard's media keys.
; ! Install LoopBe1 virtual MIDI port + LiveHack control surface
#Requires AutoHotkey v2.0
#SingleInstance Force
SendMode("Input")
SetWorkingDir(A_ScriptDir)
SetTitleMatchMode(2)
global MidiPortIdentifier := "LoopBe Internal MIDI"
global ControlSurfaceDownloadUrl := "https://livehack.tools/ahk/static/downloads/LiveHack_Ableton_Live_Control_Surface.zip"
global MidiNoteDurationMs := 5
global MidiSetupWarningShown := false
global MidiPortOpenWarningShown := false
global CachedMidiPortIndex := -1
global CachedMidiPortResolvedName := ""
; MIDI device selection:
; Put the Windows MIDI OUTPUT device name you want to use in MidiPortIdentifier.
; Matching is intentionally conservative and tries, in order:
; 1) exact match
; 2) exact match after stripping whitespace
; 3) case-insensitive substring search
; 4) ordered word search (words must appear in sequence)
; MIDI setup required:
; 1. Install LoopBe1 (https://www.nerds.de/en/loopbe1.html) — free virtual MIDI port.
; The port name will be "LoopBe Internal MIDI". If you have a MIDI interface you
; can also wire the output into the input. Change "MidiPortIdentifier" to the
; MIDI port name of your audio interface.
; 2. Download the LiveHack control surface package:
; https://livehack.tools/ahk/static/downloads/LiveHack_Ableton_Live_Control_Surface.zip
; 3. Install the control surface folder in Ableton's Remote Scripts location.
; 4. In Ableton, enable the LoopBe Internal MIDI port for the control surface.
; Without this setup, the script will not control Ableton.
#HotIf WinExist("ahk_class Ableton Live Window Class")
; Mute selected track
Volume_Mute::LiveHack_SendMidiNote(1, 3, 127)
; Coarse track volume
Volume_Down::LiveHack_SendMidiNote(1, 4, 127)
Volume_Up::LiveHack_SendMidiNote(1, 5, 127)
; Transport
Media_Play_Pause::LiveHack_SendMidiNote(1, 6, 127)
Media_Stop::LiveHack_SendMidiNote(1, 7, 127)
Media_Prev::LiveHack_SendMidiNote(1, 8, 127)
Media_Next::LiveHack_SendMidiNote(1, 9, 127)
; Fine track volume
+Volume_Down::LiveHack_SendMidiNote(1, 10, 127)
+Volume_Up::LiveHack_SendMidiNote(1, 11, 127)
; Play/Pause (Ctrl variant)
^Media_Play_Pause::LiveHack_SendMidiNote(1, 12, 127)
; Track pan
^Volume_Down::LiveHack_SendMidiNote(1, 13, 127)
^Volume_Up::LiveHack_SendMidiNote(1, 14, 127)
^+Volume_Down::LiveHack_SendMidiNote(1, 15, 127)
^+Volume_Up::LiveHack_SendMidiNote(1, 16, 127)
; Track solo / arm
^Volume_Mute::LiveHack_SendMidiNote(1, 17, 127)
^+Volume_Mute::LiveHack_SendMidiNote(1, 18, 127)
#HotIf
; Debug: Ctrl+Alt+Shift+L — shows MIDI status and key mappings
^!+l::LiveHack_ShowMidiDebugInfo()
LiveHack_SendMidiNote(channel, note, noteVelocity) {
global MidiPortIdentifier, MidiNoteDurationMs
midiDevice := LiveHack_GetPreferredMidiOutPort(MidiPortIdentifier)
if (midiDevice < 0)
{
LiveHack_ShowMidiSetupWarning()
return
}
hMidiOut := LiveHack_MidiOutOpen(midiDevice)
if (!hMidiOut)
return
LiveHack_MidiOutShortMsg(hMidiOut, "N1", channel, note, noteVelocity)
Sleep(MidiNoteDurationMs)
LiveHack_MidiOutClose(hMidiOut)
}
LiveHack_ShowMidiSetupWarning() {
global MidiSetupWarningShown, MidiPortIdentifier, ControlSurfaceDownloadUrl
if (MidiSetupWarningShown)
return
MidiSetupWarningShown := true
msg := 'Could not find a MIDI output named "' MidiPortIdentifier '".'
. "`n`nInstall LoopBe1 from https://www.nerds.de/en/loopbe1.html"
. '`nThe virtual MIDI port will appear as "LoopBe Internal MIDI".'
. "`n`nThen select the same port for the LiveHack control surface in Ableton."
. "`n`nMatching tries: exact, whitespace-stripped, case-insensitive, then ordered words."
. "`n`nControl-surface download:`n" ControlSurfaceDownloadUrl
. LiveHack_MidiPortListText()
MsgBox(msg, "LiveHack MIDI setup required", "Icon!")
}
LiveHack_ShowMidiDebugInfo() {
global MidiPortIdentifier, CachedMidiPortIndex, CachedMidiPortResolvedName
numPorts := LiveHack_MidiOutGetNumDevs()
resolved := LiveHack_FindMidiOutPort(MidiPortIdentifier)
mappings := LiveHack_GetMappingTable()
cBg := "1E1E2E"
cGrn := "A6E3A1"
cDim := "585B70"
cOK := resolved >= 0 ? "A6E3A1" : "F38BA8"
W := 620
dg := Gui("+AlwaysOnTop -MaximizeBox +ToolWindow", "LiveHack · MIDI Debug")
dg.BackColor := cBg
dg.MarginX := 24
dg.MarginY := 20
; Dark titlebar on Win 10 1903+ / Win 11
DllCall("dwmapi\DwmSetWindowAttribute", "Ptr", dg.Hwnd, "UInt", 20, "Int*", 1, "UInt", 4)
dg.SetFont("s14 w700 c" cGrn, "Segoe UI")
dg.Add("Text", "w" W, "LiveHack · MIDI Debug")
dg.SetFont("s8 c" cDim, "Segoe UI")
dg.Add("Text", "w" W " y+2", FormatTime(, "ddd d MMM yyyy HH:mm:ss") " · Esc to close")
; ── Port ──────────────────────────────────────────────────────────
dg.SetFont("s8 w600 c" cDim, "Segoe UI")
dg.Add("Text", "w" W " y+16", "MIDI OUTPUT")
dg.SetFont("s10 w600 c" cOK, "Consolas")
dg.Add("Text", "w" W " y+4", resolved >= 0
? "[" resolved "] " LiveHack_MidiOutNameGet(resolved) " ✔"
: "NOT FOUND ✘ — check MidiPortIdentifier")
dg.SetFont("s8 c" cDim, "Segoe UI")
dg.Add("Text", "w" W " y+2", 'Configured: "' MidiPortIdentifier '"'
. " Cache: " (CachedMidiPortIndex >= 0 ? "hit [" CachedMidiPortIndex "]" : "miss"))
; Compact device list as plain wrapping text
devLine := ""
Loop numPorts {
i := A_Index - 1
devLine .= (i ? " " : "") "[" i "] " LiveHack_MidiOutNameGet(i) (i = resolved ? " ✓" : "")
}
dg.SetFont("s8 c" cDim, "Consolas")
dg.Add("Text", "w" W " y+4", devLine)
; ── Ableton setup ──────────────────────────────────────────────
dg.SetFont("s8 w600 c" cDim, "Segoe UI")
dg.Add("Text", "w" W " y+16", "ABLETON MIDI SETTINGS")
dg.SetFont("s8 c" cDim, "Consolas")
setupText := " Control Surfaces : LiveHack | Input: LoopBe Internal MIDI | Output: None`n"
. " Input Ports : LoopBe Internal MIDI — Track ○ Sync ○ Remote ○`n"
. " Output Ports : LoopBe Internal MIDI — Track ○ Sync ○ Remote ○`n"
. " Signal flow : AHK → LoopBe Internal MIDI → Ableton → LiveHack CS"
dg.Add("Text", "w" W " y+4", setupText)
; ── Mappings ──────────────────────────────────────────────────────
dg.SetFont("s8 w600 c" cDim, "Segoe UI")
dg.Add("Text", "w" W " y+16", "MIDI MAPPINGS (" mappings.Length ")")
lv := dg.Add("ListView", "w" W " r" Min(mappings.Length + 1, 22) " y+4 -Multi +ReadOnly",
["Hotkey", "Ch", "Note", "CS Action"])
DllCall("uxtheme\SetWindowTheme", "Ptr", lv.Hwnd, "Str", "DarkMode_Explorer", "Ptr", 0)
lv.SetFont("s9", "Consolas")
for m in mappings
lv.Add("", m["key"], m["ch"], m["note"], m["desc"])
lv.ModifyCol(1, 160), lv.ModifyCol(2, 36), lv.ModifyCol(3, 46), lv.ModifyCol(4, W - 260)
; ── OK ────────────────────────────────────────────────────────────
dg.SetFont("s9 w700 c1E1E2E", "Segoe UI")
btn := dg.Add("Button", "w" W " h32 y+16", "OK")
btn.Opt("Background" cGrn)
DllCall("uxtheme\SetWindowTheme", "Ptr", btn.Hwnd, "Str", " ", "Ptr", 0)
btn.OnEvent("Click", (*) => dg.Destroy())
dg.OnEvent("Escape", (*) => dg.Destroy())
dg.OnEvent("Close", (*) => dg.Destroy())
dg.Show("AutoSize Center")
}
LiveHack_GetMappingTable() {
t := [
Map("key", "Volume Mute", "ch", 1, "note", 3, "desc", "Mute selected track"),
Map("key", "Volume Down", "ch", 1, "note", 4, "desc", "Volume −1 dB"),
Map("key", "Volume Up", "ch", 1, "note", 5, "desc", "Volume +1 dB"),
Map("key", "Media Play/Pause", "ch", 1, "note", 6, "desc", "Play / pause transport"),
Map("key", "Media Stop", "ch", 1, "note", 7, "desc", "Stop transport"),
Map("key", "Media Prev", "ch", 1, "note", 8, "desc", "Scroll arranger ←"),
Map("key", "Media Next", "ch", 1, "note", 9, "desc", "Scroll arranger →"),
Map("key", "Shift+Vol Down", "ch", 1, "note", 10, "desc", "Volume − fine"),
Map("key", "Shift+Vol Up", "ch", 1, "note", 11, "desc", "Volume + fine"),
Map("key", "Ctrl+Media Play", "ch", 1, "note", 12, "desc", "Continue playing"),
Map("key", "Ctrl+Vol Down", "ch", 1, "note", 13, "desc", "Pan ← coarse"),
Map("key", "Ctrl+Vol Up", "ch", 1, "note", 14, "desc", "Pan → coarse"),
Map("key", "Ctrl+Shift+Vol Dn", "ch", 1, "note", 15, "desc", "Pan ← fine"),
Map("key", "Ctrl+Shift+Vol Up", "ch", 1, "note", 16, "desc", "Pan → fine"),
Map("key", "Ctrl+Mute", "ch", 1, "note", 17, "desc", "Solo track"),
Map("key", "Ctrl+Shift+Mute", "ch", 1, "note", 18, "desc", "Arm track"),
Map("key", "—", "ch", 1, "note", 19, "desc", "Hide all views"),
Map("key", "—", "ch", 1, "note", 20, "desc", "Prev track"),
Map("key", "—", "ch", 1, "note", 21, "desc", "Next track"),
]
; Ch2: master_track.devices[0].parameters[1..16]
Loop 16
t.Push(Map("key", "—", "ch", 2, "note", A_Index, "desc", "Master device param " A_Index))
return t
}
LiveHack_MidiOutOpen(uDeviceID := 0) {
global MidiPortOpenWarningShown
hMidiOut := 0
result := DllCall("winmm.dll\midiOutOpen", "Ptr*", &hMidiOut, "UInt", uDeviceID, "Ptr", 0, "Ptr", 0, "UInt", 0)
if (result != 0)
{
if (!MidiPortOpenWarningShown)
{
MidiPortOpenWarningShown := true
MsgBox("Could not open the selected MIDI output. Close any app that is locking the port, then try again.", "LiveHack MIDI output unavailable", "Icon!")
}
return 0
}
return hMidiOut
}
LiveHack_MidiOutShortMsg(hMidiOut, eventType, channel, param1, param2) {
; MIDI status byte: event nibble | zero-based channel
; NoteOn = 0x90, NoteOff = 0x80; channel param is 1-based
if (eventType = "NoteOn" || eventType = "N1")
midiStatus := 0x8F + channel
else if (eventType = "NoteOff" || eventType = "N0")
midiStatus := 0x7F + channel
else
return -1
return DllCall("winmm.dll\midiOutShortMsg", "Ptr", hMidiOut, "UInt", midiStatus | (param1 << 8) | (param2 << 16))
}
LiveHack_MidiOutClose(hMidiOut) {
if (hMidiOut)
DllCall("winmm.dll\midiOutClose", "Ptr", hMidiOut)
}
LiveHack_GetPreferredMidiOutPort(preferredName) {
global CachedMidiPortIndex, CachedMidiPortResolvedName
; Cache is valid if the device at the cached index still has the same name
if (CachedMidiPortIndex >= 0)
{
if (LiveHack_MidiOutNameGet(CachedMidiPortIndex) = CachedMidiPortResolvedName)
return CachedMidiPortIndex
CachedMidiPortIndex := -1
CachedMidiPortResolvedName := ""
}
idx := LiveHack_FindMidiOutPort(preferredName)
if (idx >= 0)
{
CachedMidiPortIndex := idx
CachedMidiPortResolvedName := LiveHack_MidiOutNameGet(idx)
}
return idx
}
LiveHack_FindMidiOutPort(preferredName) {
preferredName := Trim(preferredName)
if (preferredName = "")
return -1
midiPorts := []
numPorts := LiveHack_MidiOutGetNumDevs()
Loop numPorts
{
portIndex := A_Index - 1
portName := LiveHack_MidiOutNameGet(portIndex)
if (portName != "")
midiPorts.Push(Map("index", portIndex, "name", portName))
}
; 1) Exact match.
for midiPort in midiPorts
{
if (midiPort["name"] = preferredName)
return midiPort["index"]
}
; 2) Exact match after stripping whitespace.
preferredNoWhitespace := LiveHack_StripWhitespace(preferredName)
for midiPort in midiPorts
{
if (LiveHack_StripWhitespace(midiPort["name"]) = preferredNoWhitespace)
return midiPort["index"]
}
; 3) Case-insensitive search.
preferredLower := StrLower(preferredName)
for midiPort in midiPorts
{
if (InStr(StrLower(midiPort["name"]), preferredLower))
return midiPort["index"]
}
; 4) Ordered word search.
preferredWords := LiveHack_SplitWords(preferredName)
if (preferredWords.Length = 0)
return -1
for midiPort in midiPorts
{
if (LiveHack_PortContainsWordsInOrder(midiPort["name"], preferredWords))
return midiPort["index"]
}
return -1
}
LiveHack_StripWhitespace(text) {
return RegExReplace(text, "\s+")
}
LiveHack_SplitWords(text) {
text := Trim(RegExReplace(text, "\s+", " "))
if (text = "")
return []
return StrSplit(text, " ")
}
LiveHack_PortContainsWordsInOrder(portName, preferredWords) {
if (preferredWords.Length = 0)
return false
portNameLower := StrLower(portName)
searchStartPos := 1
for preferredWord in preferredWords
{
preferredWordLower := StrLower(preferredWord)
foundPos := InStr(portNameLower, preferredWordLower, true, searchStartPos)
if (!foundPos)
return false
searchStartPos := foundPos + StrLen(preferredWordLower)
}
return true
}
LiveHack_MidiPortListText() {
numPorts := LiveHack_MidiOutGetNumDevs()
if (numPorts < 1)
return "`n`nNo MIDI output ports are currently available."
text := "`n`nAvailable MIDI outputs:"
Loop numPorts
{
portIndex := A_Index - 1
portName := LiveHack_MidiOutNameGet(portIndex)
text .= "`n- " . portIndex . ": " . portName
}
return text
}
LiveHack_MidiOutGetNumDevs() {
return DllCall("winmm.dll\midiOutGetNumDevs")
}
LiveHack_MidiOutNameGet(uDeviceID := 0) {
MidiOutCaps := Buffer(50, 0)
result := DllCall("winmm.dll\midiOutGetDevCapsA", "UInt", uDeviceID, "Ptr", MidiOutCaps, "UInt", 50)
if (result != 0)
return ""
return StrGet(MidiOutCaps.Ptr + 8, 32, "CP0")
}
; April 2026 — Released under MIT license by Yannis Lever
; This code is provided as-is with no warranty. Use at your own risk.
; Check out www.livehack.tools for more LiveHacks!
; BLOCK F-KEYS
; -> Blocks F1–F8 while a specific plugin is open so you stop accidentally triggering Ableton shortcuts.
#Requires AutoHotkey v2.0
#SingleInstance Force
SendMode("Input")
SetWorkingDir(A_ScriptDir)
SetTitleMatchMode(2)
; Change plugin name to taste or add more.
#HotIf WinActive("Melodyne")
F1::return
F2::return
F3::return
F4::return
F5::return
F6::return
F7::return
F8::return
#HotIf
; April 2026 — Released under MIT license by Yannis Lever
; This code is provided as-is with no warranty. Use at your own risk.
; Check out www.livehack.tools for more LiveHacks!
; BROWSER QUICKSEARCH
; -> Type a short abbreviation and the script finds and loads the plugin from Ableton's browser.
; !! Required to add custom tags in the browser. This script works by typing really fast. If you interfere, sometimes keys get fired into the wrong context and mess up stuff. Use at your own risk!
#Requires AutoHotkey v2.0
#SingleInstance Force
SendMode("Input")
SetWorkingDir(A_ScriptDir)
SetTitleMatchMode(2)
global SearchSleepMs := 300
#HotIf WinActive("ahk_class Ableton Live Window Class")
; ==================================================================
; HOTSTRING EXAMPLES (type the abbreviation, it auto-expands)
; ==================================================================
; Built-in Ableton devices (two tags: Device + Ableton)
::glue:: {
SearchAndInsertTaggedPlugin("Device", "Ableton", "Glue Compressor", 0)
}
::eq8:: {
SearchAndInsertTaggedPlugin("Device", "Ableton", "EQ Eight", 0)
}
::over:: {
SearchAndInsertTaggedPlugin("Device", "Ableton", "Overdrive", 0)
}
; Third-party VST3 plugins (two tags: vendor + VST3)
::q4:: {
SearchAndInsertTaggedPlugin("FabFilter", "VST3", "Pro-Q 4", 1)
}
::mb:: {
SearchAndInsertTaggedPlugin("FabFilter", "VST3", "Pro-MB", 1)
}
::ds:: {
SearchAndInsertTaggedPlugin("FabFilter", "VST3", "Pro-DS", 1)
}
::ser:: {
SearchAndInsertTaggedPlugin("XFer Records", "AutoHotKey", "Serum 2", 1)
}
::kon:: {
SearchAndInsertTaggedPlugin("Native Instruments", "VST3", "Kontakt 8", 1)
}
::dec:: {
SearchAndInsertTaggedPlugin("Soundtoys", "VST3", "Decapitator", 1)
}
::val:: {
SearchAndInsertTaggedPlugin("Valhalla DSP", "VST3", "ValhallaRoom", 1)
}
; Presets with a custom tag
::myrack:: {
SearchAndInsertTaggedPlugin("Preset", "AutoHotKey", "Your Favorite Rack.adg", 1)
}
#HotIf
; ------------------------------------------------------------------
; HOW TAGS WORK:
; Ableton's browser lets you search by tags using the # prefix.
; You can use TWO tags to narrow down results, for example:
; #Device #Ableton → only Ableton built-in devices
; #FabFilter #VST3 → only FabFilter VST3 plugins
; #Preset #AutoHotKey → only presets you tagged "AutoHotKey"
;
; The `downArrow` parameter lets you pick a specific result when
; the search returns more than one match. Set it to 0 if the search
; string is unique enough to always land on the right result.
;
; BEST PRACTICE: Create a custom tag (e.g. "AutoHotKey") in Ableton
; and assign it to every plugin you want to load via hotkey. That way
; each search always returns exactly one result and you can set
; downArrow to 0 for a clean one-shot insert.
; ------------------------------------------------------------------
; ==================================================================
; SEARCH FUNCTIONS
; ==================================================================
; Two-tag search (for hotstrings):
; Uses two #tag entries to narrow results, then types the plugin name.
SearchAndInsertTaggedPlugin(tag1, tag2, pluginName, downArrow := 0) {
global SearchSleepMs
BlockInput("On")
Send("{#}" tag1)
Sleep(SearchSleepMs)
Send("{Enter}")
Sleep(SearchSleepMs)
Send("{#}" tag2)
Sleep(SearchSleepMs)
Send("{Enter}")
Sleep(SearchSleepMs)
Send(pluginName)
Sleep(SearchSleepMs)
Loop downArrow
{
Send("{Down}")
Sleep(SearchSleepMs)
}
Send("{Enter}")
Sleep(SearchSleepMs)
Send("{Esc}")
BlockInput("Off")
}
; Single-tag search (for F-key bindings):
; Opens the browser search, applies one custom tag, types the plugin name.
AddTaggedPlugin(tag, pluginName, downArrow) {
BlockInput("On")
sleepTime := 300
Send("^f")
Sleep(sleepTime)
Send("{Esc}")
Sleep(sleepTime)
Send("^f")
Sleep(sleepTime)
Send("{#}" tag)
Send("{Enter}")
Sleep(sleepTime)
Send(pluginName)
Sleep(sleepTime)
Send("{Enter}")
Sleep(sleepTime)
Loop downArrow
{
Send("{Down}")
Sleep(sleepTime)
}
Send("{Enter}")
Sleep(sleepTime)
Send("{Esc}")
Sleep(sleepTime)
Send("^!b")
BlockInput("Off")
}
; April 2026 — Released under MIT license by Yannis Lever
; This code is provided as-is with no warranty. Use at your own risk.
; Check out www.livehack.tools for more LiveHacks!
; PROGRAM SPECIFIC HOTKEYS
; -> Template for window-context-aware hotkeys. Different shortcuts activate depending on which app is in focus.
#Requires AutoHotkey v2.0
#SingleInstance Force
SendMode("Input")
SetWorkingDir(A_ScriptDir)
SetTitleMatchMode(2)
; ==================================================================
; CONTEXT 1: ALWAYS ACTIVE (global — runs regardless of which app is in front)
; ==================================================================
; Put hotkeys here that should work everywhere — media key overrides,
; clipboard helpers, global shortcuts, etc.
; Example:
; ^!n::Run("Notepad")
; ==================================================================
; CONTEXT 2: ABLETON LIVE ONLY
; Active when Ableton (or an Ableton plugin window) is in focus
; and Melodyne is NOT open.
; ==================================================================
#HotIf ((WinActive("ahk_class Ableton Live Window Class") or WinActive("ahk_class Vst3PlugWindow") or WinActive("ahk_class AbletonVstPlugClass")) and not WinActive("Melodyne"))
{
; Your Ableton-specific hotkeys go here.
; Examples:
; F1::AddTaggedPlugin("AutoHotKey", "Compressor", 0)
; ^!b::Send("^!b") ; toggle browser
}
#HotIf
; ==================================================================
; CONTEXT 3: ABLETON IS RUNNING (even if it is in the background)
; Useful for media-key MIDI control that should work from any window
; as long as Ableton is open somewhere.
; ==================================================================
#HotIf WinExist("ahk_class Ableton Live Window Class")
{
; Your "Ableton exists" hotkeys go here.
; Examples:
; Media_Play_Pause::SendMidiNote(1, 6, 127)
; Volume_Down::SendMidiNote(1, 4, 127)
}
#HotIf
; ==================================================================
; CONTEXT 4: BITWIG / CUBASE (if you use multiple DAWs)
; Active when Bitwig or Cubase is in focus.
; ==================================================================
#HotIf WinActive("ahk_class bitwig")
{
; Your Bitwig-specific hotkeys go here.
; Example: horizontal scroll with Shift+Wheel
}
#HotIf
#HotIf WinExist("ahk_exe Cubase14.exe")
{
; Your Cubase-specific hotkeys go here.
; Example: settings window toggle
; $^,:: {
; if WinExist("Preferences")
; {
; Send("o")
; return
; }
; else
; {
; Send("^,")
; return
; }
; }
}
#HotIf
; ==================================================================
; CONTEXT 5: NON-ABLETON (active when Ableton is NOT in front)
; Useful for global shortcuts that should not be active in Ableton.
; ==================================================================
#HotIf !WinActive("ahk_class Ableton Live Window Class")
{
; Your "Ableton is not active" hotkeys go here.
; Example: restore Ctrl+Shift+Wheel as system volume
; ^+WheelUp::Volume_Up
; ^+WheelDown::Volume_Down
}
#HotIf
; April 2026 — Released under MIT license by Yannis Lever
; This code is provided as-is with no warranty. Use at your own risk.
; Check out www.livehack.tools for more LiveHacks!
2
Download & run
Download only the files you want
Or simply get everything here. Each code snippet works on its own or copy-paste the snippets you want into your own script.
Customize to taste
Customize the hotkeys so they make sense for your workflow. These scripts use AHK v2 syntax.
Install AutoHotKey
You can get it on autohotkey.com. Download v2.0.
Launch the AHK script
You can launch the script manually or put it into your Autostart folder, so it is always active. In Windows 10 and 11, open the Run dialog by pressing Windows + R and type
shell:startup.
Media Keys as MIDI Controller
To use your Media Keys as a MIDI controller, install LoopBe1. The AHK script sends MIDI notes into the virtual MIDI device, which Ableton Live can receive. Use the LiveHack control surface to map the media keys to your transport and track controls across sessions without manual remapping.
Download LiveHack Control Surface