I have been following GroovyMAME real-time tips and configuration in the real-time.md instructions.
As to my understanding I now have a real-time system up and running which is very very cool :-).
The PART real-time audio backend requires exclusive ALSA access. This is an issue when using a frontend.
To that end I modified and beautified a launcher script I've been using for some years.
Of course, this works on my system but it might require some modifications on other systems as I am running a completely custom installed Arch.
So, I now have AttracModePlus happily playing audio, managing GroovyMame that is able to output sound through its PART real-time audio backend.
The only (very minor) drawback is that you will have to wait one second or so for the audio server to be restored when exiting a game.
Whereas previously AM+ re-appeared quickly this now takes a tiny bit longer but hey ... I do not care.
Of course, this comes with 0.0% warranty.
#!/bin/bash
# =============================================================================
# groovymame-launch.sh
# Version 0.0.1 - 09 June 2026
#
# Wrapper script for launching GroovyMAME(real-time) from AttractMode Plus.
# Handles exclusive ALSA access by suspending the audio server before launch
# and restoring it cleanly after exit.
#
# Usage:
# groovymame-launch.sh <machine> [rom]
#
# Arguments:
# machine MAME machine name (or full path/filename — basename and extension
# are stripped automatically)
# rom Optional ROM/cartridge path passed via -cart
#
# -----------------------------------------------------------------------------
# HOW TO DETERMINE WHICH AUDIO SERVER TO STOP
# -----------------------------------------------------------------------------
# GroovyMAME PART real-time audio backend requires exclusive ALSA access for
# latency-free audio. Any audio server holding the ALSA device open will block
# this. To find out which server is running on your system, run the following
# while the frontend is active (before launching any game):
#
# fuser /dev/snd/*
#
# This prints the PIDs of all processes with the audio device open. To resolve
# those PIDs to process names:
#
# fuser /dev/snd/* 2>/dev/null | tr ' ' '\n' | grep -v '^$' | xargs -I{} ps -p {} -o pid,comm=
#
# Common results and the corresponding stop/start commands:
#
# PipeWire + WirePlumber ( my own current system):
# stop: systemctl --user stop wireplumber pipewire-pulse pipewire
# start: systemctl --user start pipewire pipewire-pulse wireplumber
#
# PulseAudio:
# stop: pulseaudio --kill
# start: pulseaudio --start
#
# JACK (jackd):
# stop: jack_control stop
# start: jack_control start
#
# Note: stop and start order matters for PipeWire — always stop wireplumber
# before pipewire, and start pipewire before wireplumber.
# =============================================================================
set -euo pipefail # exit on unhandled errors, unset variables, and pipe failures.
# -----------------------------------------------------------------------------
# Configuration
# -----------------------------------------------------------------------------
readonly SCRIPT_DIR="$(dirname "$0")"
readonly EXECUTABLE="mame"
readonly LD_PATH="/usr/local/lib"
readonly AUDIO_SETTLE_SECS=0.5 # time to wait after stopping audio server
# -----------------------------------------------------------------------------
# Logging helpers
# -----------------------------------------------------------------------------
log() { echo "[$(date '+%H:%M:%S')] $*"; }
warn() { echo "[$(date '+%H:%M:%S')] WARNING: $*" >&2; }
die() { echo "[$(date '+%H:%M:%S')] ERROR: $*" >&2; exit 1; }
# -----------------------------------------------------------------------------
# Audio server management
# -----------------------------------------------------------------------------
audio_stop() {
log "Suspending PipeWire audio stack for exclusive ALSA access ..."
systemctl --user stop wireplumber pipewire-pulse.socket pipewire-pulse pipewire.socket pipewire
sleep "$AUDIO_SETTLE_SECS"
log "Audio stack suspended."
}
audio_start() {
log "Restoring PipeWire audio stack ..."
systemctl --user start pipewire pipewire.socket pipewire-pulse pipewire-pulse.socket wireplumber
log "Audio stack restored."
}
# Ensure audio is always restored even if the script is interrupted
trap audio_start EXIT
# -----------------------------------------------------------------------------
# Argument handling
# -----------------------------------------------------------------------------
if [[ $# -eq 0 ]]; then
die "No machine specified. Expected: $0 <machine> [rom]"
fi
machine="$1"
machine="$(basename -- "$machine")" # strip leading path if supplied
machine="${machine%%.*}" # strip extension if supplied
rom="${2:-}" # use $2 if set, otherwise use an empty string
log "Invoking GroovyMAME launch script"
log "Machine : $machine"
log "ROM : ${rom:-<none>}" # use $rom if set, otherwise substitute literal
# -----------------------------------------------------------------------------
# Launch
# -----------------------------------------------------------------------------
cd "$SCRIPT_DIR"
audio_stop
log "Launching GroovyMAME ..."
if [[ -z "$rom" ]]; then
env LD_LIBRARY_PATH="$LD_PATH" ./"$EXECUTABLE" -cheat "$machine"
else
env LD_LIBRARY_PATH="$LD_PATH" ./"$EXECUTABLE" -cheat "$machine" -cart "$rom"
fi
log "GroovyMAME exited cleanly."
# audio_start is called automatically via the EXIT trap above