#!/bin/sh
# playmp3 — play an audio file through /dev/dsp, so music playback can be
# checked in one command.
#
#   playmp3                     plays /Media/The_ride.mp3 if it is there
#   playmp3 /Media/song.mp3     plays that file
#   playmp3 song.mp3 30         plays the first 30 seconds only
#
# IT NEGOTIATES THE FORMAT AND USES WHAT IT IS GIVEN. The first version of this
# script hardcoded 8000 Hz / mono / 8-bit because `fd.c` resets /dev/dsp to that
# on open — and it played pure noise. The HDA backend has NO RESAMPLER: it snaps
# any requested rate to the nearer of 44100/48000, programs that, and returns
# what it programmed. So 8 kHz samples were clocked out at 44100 and everything
# came out 5.5x fast, which is heard as harsh noise with crackle, not as a
# recognisable tune.
#
# The lesson is the general one: ASK THE DEVICE WHAT IT DID, do not assume the
# value you requested was honoured. Every OSS ioctl here writes back the value
# actually programmed, and this script feeds ffmpeg exactly that.
#
# THE STREAM IS NOT STOPPED BY close(2) on older kernels. Before k1571 the DMA
# ring kept looping after the writer exited and the machine played forever. The
# explicit reset on exit — including on Ctrl-C — is kept because it is correct
# on every kernel and costs nothing.
set -u

DSP=${DSP:-/dev/dsp}
FILE=${1:-/Media/The_ride.mp3}
SECS=${2:-}

usage() { sed -n '2,9p' "$0" | sed 's/^# \{0,1\}//'; exit 0; }
case "${1:-}" in -h|--help) usage ;; esac

[ -c "$DSP" ] || { echo "playmp3: $DSP is not a character device — no audio driver?" >&2; exit 1; }
[ -f "$FILE" ] || { echo "playmp3: no such file: $FILE" >&2
                    echo "  put one in /Media, or pass a path: playmp3 /path/to/song.mp3" >&2; exit 1; }
command -v ffmpeg >/dev/null 2>&1 || { echo "playmp3: ffmpeg not found" >&2; exit 1; }
command -v python3 >/dev/null 2>&1 || { echo "playmp3: python3 not found (needed for the OSS ioctls)" >&2; exit 1; }

DUR_ARG=""
[ -n "$SECS" ] && DUR_ARG="-t $SECS"

# python holds the fd across the ioctls AND the write, which a shell redirect
# cannot: a `> /dev/dsp` redirect opens a FRESH fd that never saw the format
# negotiation, so the settings would be discarded before the first sample.
DSP="$DSP" FILE="$FILE" DUR_ARG="$DUR_ARG" python3 - <<'EOF'
import fcntl, os, struct, subprocess, sys

DSP  = os.environ["DSP"]
FILE = os.environ["FILE"]
DUR  = os.environ["DUR_ARG"].split()

SNDCTL_DSP_RESET    = 0x00005000
SNDCTL_DSP_SPEED    = 0xc0045002
SNDCTL_DSP_SETFMT   = 0xc0045005
SNDCTL_DSP_CHANNELS = 0xc0045006
AFMT_S16_LE         = 0x00000010

def ioctl_int(fd, req, val):
    """Every one of these writes back what was ACTUALLY programmed."""
    buf = fcntl.ioctl(fd, req, struct.pack("i", val))
    return struct.unpack("i", buf)[0]

fd = os.open(DSP, os.O_WRONLY)
try:
    try:
        fcntl.ioctl(fd, SNDCTL_DSP_RESET)      # start from an empty ring
    except OSError:
        pass

    # Order matters: format, then channels, then rate — a backend may clamp the
    # rate differently per format, and the LAST answer is the one that counts.
    fmt  = ioctl_int(fd, SNDCTL_DSP_SETFMT,   AFMT_S16_LE)
    chan = ioctl_int(fd, SNDCTL_DSP_CHANNELS, 2)
    rate = ioctl_int(fd, SNDCTL_DSP_SPEED,    44100)

    sfmt = {AFMT_S16_LE: "s16le", 8: "u8"}.get(fmt)
    if sfmt is None:
        print("playmp3: device returned an unknown format %d — refusing to guess" % fmt,
              file=sys.stderr)
        sys.exit(2)

    print("playmp3: %s -> %s" % (FILE, DSP))
    print("playmp3: device programmed %d Hz, %d channel(s), %s (asked 44100/2/s16le)"
          % (rate, chan, sfmt))

    cmd = (["ffmpeg", "-nostdin", "-loglevel", "error", "-i", FILE] + DUR +
           ["-f", sfmt, "-ar", str(rate), "-ac", str(chan), "-"])
    p = subprocess.Popen(cmd, stdout=subprocess.PIPE)

    written = 0
    while True:
        chunk = p.stdout.read(16384)
        if not chunk:
            break
        os.write(fd, chunk)
        written += len(chunk)
    p.stdout.close()
    rc = p.wait()

    bps = rate * chan * (2 if sfmt == "s16le" else 1)
    print("playmp3: wrote %d bytes = %.1f s of audio at the programmed format"
          % (written, written / float(bps)))
    if rc != 0:
        print("playmp3: ffmpeg exited %d" % rc, file=sys.stderr)
        sys.exit(rc)
finally:
    try:
        fcntl.ioctl(fd, SNDCTL_DSP_RESET)      # close(2) does not stop it pre-k1571
    except OSError:
        pass
    os.close(fd)
EOF
rc=$?
echo "playmp3: done (exit $rc)"
exit "$rc"
