200 lines
6.1 KiB
Python
Executable File
200 lines
6.1 KiB
Python
Executable File
#! /usr/bin/env python3
|
|
"""
|
|
# `ableton-linux`: Ableton Wrapper for Linux
|
|
|
|
This script handles the necessary setup and pre-configuration to run Ableton Live on Linux with low-latency audio.
|
|
"""
|
|
|
|
import argparse
|
|
import os
|
|
import sys
|
|
import logging
|
|
import subprocess
|
|
import shutil
|
|
import time
|
|
import pypresence
|
|
from pathlib import Path
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
WINEASIO_SRC_PATH = Path("~/src/wineasio").expanduser()
|
|
DISCORD_CLIENT_ID = 1175091631913963610
|
|
DISCORD_ICON = "ableton_grey"
|
|
|
|
|
|
def build_wineasio():
|
|
# If the wineasio source directory doesn't exist, clone it
|
|
if not WINEASIO_SRC_PATH.is_dir():
|
|
logger.info("Cloning wineasio source")
|
|
subprocess.check_call(
|
|
[
|
|
"git",
|
|
"clone",
|
|
"https://github.com/wineasio/wineasio",
|
|
str(WINEASIO_SRC_PATH),
|
|
]
|
|
)
|
|
subprocess.check_call(
|
|
["git", "submodule", "update", "--init", "--recursive"],
|
|
cwd=str(WINEASIO_SRC_PATH),
|
|
)
|
|
|
|
# Make sure `pipewire-jack` is installed
|
|
logger.info("Installing pipewire-jack")
|
|
|
|
# Call make to build 64-bit libs
|
|
logger.info("Building wineasio")
|
|
try:
|
|
subprocess.check_call(["make", "64"], cwd=str(WINEASIO_SRC_PATH))
|
|
except subprocess.CalledProcessError:
|
|
logger.error("Failed to build wineasio")
|
|
logger.info(
|
|
"Make sure you have `pipewire-jack-audio-connection-kit-devel` installed"
|
|
)
|
|
logger.info("Make sure you have `wine-devel` installed")
|
|
sys.exit(1)
|
|
|
|
# We need to copy the libs for wine to find them
|
|
logger.info("Copying wineasio libs")
|
|
subprocess.check_call(
|
|
["sudo", "cp", "build64/wineasio64.dll", "/usr/lib64/wine/x86_64-windows/"],
|
|
cwd=str(WINEASIO_SRC_PATH),
|
|
)
|
|
subprocess.check_call(
|
|
["sudo", "cp", "build64/wineasio64.dll.so", "/usr/lib64/wine/x86_64-unix/"],
|
|
cwd=str(WINEASIO_SRC_PATH),
|
|
)
|
|
|
|
|
|
def bottles_winepfx_from_name(bottle_name: str) -> Path:
|
|
return Path("~/.local/share/bottles/bottles/").expanduser() / (
|
|
bottle_name.replace(" ", "-")
|
|
)
|
|
|
|
|
|
def main() -> int:
|
|
# Handle program arguments
|
|
ap = argparse.ArgumentParser(
|
|
prog="ableton-linux", description="Executes Ableton on Linux"
|
|
)
|
|
ap.add_argument(
|
|
"--no-presence", "-n", help="Hide activity from Discord", action="store_true"
|
|
)
|
|
ap.add_argument(
|
|
"--bottle", "-b", help="Use the specified bottle", default="Ableton 11 Suite"
|
|
)
|
|
ap.add_argument(
|
|
"--program", "-p", help="Program to run", default="Ableton Live 11 Suite"
|
|
)
|
|
ap.add_argument(
|
|
"--dry-run", help="Don't actually launch Ableton", action="store_true"
|
|
)
|
|
ap.add_argument(
|
|
"-v", "--verbose", help="Enable verbose logging", action="store_true"
|
|
)
|
|
args = ap.parse_args()
|
|
|
|
# Configure logging
|
|
logging.basicConfig(
|
|
level=logging.DEBUG if args.verbose else logging.INFO,
|
|
format="%(levelname)s: %(message)s",
|
|
)
|
|
|
|
# Ensure we have bottles
|
|
if not shutil.which("bottles-cli"):
|
|
logger.error("You can't do this without bottles installed")
|
|
return 1
|
|
|
|
# Configure discord presence
|
|
discord_presence = pypresence.Presence(DISCORD_CLIENT_ID)
|
|
if not args.no_presence:
|
|
discord_presence.connect()
|
|
launch_start = int(time.time())
|
|
|
|
# Ensure we have wineasio
|
|
if not (WINEASIO_SRC_PATH / "build64").exists():
|
|
if not args.no_presence:
|
|
discord_presence.update(
|
|
start=launch_start,
|
|
large_image=DISCORD_ICON,
|
|
details="Compiling WineASIO...",
|
|
)
|
|
build_wineasio()
|
|
|
|
# Figure out the wineprefix
|
|
wineprefix = bottles_winepfx_from_name(args.bottle)
|
|
logger.info(f"Wine prefix is: {wineprefix}")
|
|
|
|
# Ensure that the bottle has the wineasio dll
|
|
if not (wineprefix / ".wineasio-installed").is_file():
|
|
logger.info("Registering wineasio")
|
|
if not args.no_presence:
|
|
discord_presence.update(
|
|
start=launch_start,
|
|
large_image=DISCORD_ICON,
|
|
details="Registering WineASIO with Ableton...",
|
|
)
|
|
subprocess.check_call(
|
|
[WINEASIO_SRC_PATH / "wineasio-register"],
|
|
env={"WINEPREFIX": str(wineprefix)},
|
|
)
|
|
shutil.copy(
|
|
WINEASIO_SRC_PATH / "build64" / "wineasio64.dll.so",
|
|
wineprefix / "drive_c" / "windows" / "system" / "wineasio64.dll",
|
|
)
|
|
shutil.copy(
|
|
WINEASIO_SRC_PATH / "build64" / "wineasio64.dll.so",
|
|
wineprefix / "drive_c" / "windows" / "system32" / "wineasio64.dll",
|
|
)
|
|
(wineprefix / ".wineasio-installed").touch()
|
|
|
|
logger.info("Waiting 15 seconds to let wine do its thing")
|
|
time.sleep(15)
|
|
|
|
# Build a modified environment for ableton
|
|
ableton_env = os.environ.copy()
|
|
ableton_env.update(
|
|
{
|
|
"WINEASIO_NUMBER_INPUTS": "16",
|
|
"WINEASIO_NUMBER_OUTPUTS": "16",
|
|
"WINEASIO_CONNECT_TO_HARDWARE": "1",
|
|
"WINEASIO_PREFERRED_BUFFERSIZE": "2048",
|
|
"WINEASIO_FIXED_BUFFERSIZE": "1",
|
|
# "PIPEWIRE_LATENCY": "2048/48000", # Buffer size / sample rate
|
|
}
|
|
)
|
|
|
|
# Update the presence message
|
|
if not args.no_presence:
|
|
discord_presence.update(
|
|
start=launch_start,
|
|
large_image=DISCORD_ICON,
|
|
details="Working on a project",
|
|
buttons=[
|
|
{"label": "Check out my music!", "url": "https://ewpratten.com/music"}
|
|
],
|
|
)
|
|
|
|
# Launch Ableton via bottles
|
|
if not args.dry_run:
|
|
logger.info("Launching Ableton")
|
|
return_code = subprocess.call(
|
|
["bottles-cli", "run", "-b", args.bottle, "-p", args.program],
|
|
env=ableton_env,
|
|
)
|
|
if not args.no_presence:
|
|
discord_presence.close()
|
|
return return_code
|
|
|
|
else:
|
|
logger.info("Dry run, not launching Ableton")
|
|
logger.info("Press enter to continue")
|
|
input()
|
|
if not args.no_presence:
|
|
discord_presence.close()
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|