#! /usr/bin/env python3 import argparse import os import sys import logging import subprocess import shutil import time from pathlib import Path logger = logging.getLogger(__name__) WINEASIO_SRC_PATH = Path("~/src/wineasio").expanduser() 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( "--bottle", "-b", help="Use the specified bottle", default="Ableton 11 Standard" ) ap.add_argument( "--program", "-p", help="Program to run", default="Ableton Live 11 Standard" ) 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 wineasio if not (WINEASIO_SRC_PATH / "build64").exists(): 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") 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", } ) # Launch Ableton via bottles logger.info("Launching Ableton") return_code = subprocess.call( ["bottles-cli", "run", "-b", args.bottle, "-p", args.program], env=ableton_env ) return return_code if __name__ == "__main__": sys.exit(main())