Newer
Older
EARS-LINUX / program / SoundLopper.cs
using System;
using System.Diagnostics;
using System.IO;
using System.Media;
using System.Threading;

public class SoundLooper : IDisposable
{
    // ── unix backend ──
    private Process _proc;
    private Thread  _thread;
    private volatile bool _running;
    private string  _path;

    // ── windows backend ──
    private SoundPlayer _player;

    public event Action<string> Log;

    public bool IsPlaying { get { return _running; } }

    private static bool IsWindows
    {
        get
        {
            var p = Environment.OSVersion.Platform;
            return p != PlatformID.Unix && p != PlatformID.MacOSX;
        }
    }

    public void Start(string wavPath)
    {
        Stop();

        if (string.IsNullOrEmpty(wavPath) || !File.Exists(wavPath))
        {
            Emit("Sound file not found: " + wavPath);
            return;
        }

        _path = wavPath;

        if (IsWindows) StartWindows();
        else           StartUnix();
    }

    public void Stop()
    {
        if (!_running) return;
        _running = false;

        if (_player != null)
        {
            try { _player.Stop(); } catch { }
            try { _player.Dispose(); } catch { }
            _player = null;
        }

        try
        {
            var p = _proc;
            if (p != null && !p.HasExited) p.Kill();
        }
        catch { }
        _proc = null;

        if (_thread != null && _thread.IsAlive) _thread.Join(500);
        _thread = null;

        Emit("Looping stopped");
    }

    // ── windows: SoundPlayer loops natively, no thread needed ──────────

    private void StartWindows()
    {
        try
        {
            _player = new SoundPlayer(_path);
            _player.Load();          // throws here if the WAV isn't plain PCM
            _player.PlayLooping();   // gapless, runs until Stop()
            _running = true;
            Emit("Looping started (SoundPlayer): " + _path);
        }
        catch (Exception ex)
        {
            Emit("SoundPlayer failed for " + _path + ": " + ex.Message);
            _player = null;
            _running = false;
        }
    }

    // ── unix: respawn a CLI player each pass ───────────────────────────

    private void StartUnix()
    {
        _running = true;
        _thread = new Thread(LoopWorker);
        _thread.IsBackground = true;
        _thread.Start();
        Emit("Looping started (" + PlayerCommand() + "): " + _path);
    }

    private void LoopWorker()
    {
        while (_running)
        {
            try
            {
                var psi = new ProcessStartInfo
                {
                    FileName = PlayerCommand(),
                    Arguments = "\"" + _path + "\"",
                    UseShellExecute = false,
                    RedirectStandardError = true,
                    RedirectStandardOutput = true,
                    CreateNoWindow = true
                };

                _proc = Process.Start(psi);
                _proc.WaitForExit();
            }
            catch (Exception ex)
            {
                Emit("Playback error: " + ex.Message);
                _running = false;
                return;
            }
        }
    }

    private static string _player_cmd;
    private static string PlayerCommand()
    {
        if (_player_cmd != null) return _player_cmd;
        _player_cmd = Exists("paplay") ? "paplay" : "aplay";
        return _player_cmd;
    }

    private static bool Exists(string cmd)
    {
        try
        {
            var p = Process.Start(new ProcessStartInfo
            {
                FileName = "which",
                Arguments = cmd,
                UseShellExecute = false,
                RedirectStandardOutput = true
            });
            p.WaitForExit();
            return p.ExitCode == 0;
        }
        catch { return false; }
    }

    private void Emit(string msg)
    {
        var h = Log;
        if (h != null) h(msg);
        else Console.WriteLine("[SoundLooper] " + msg);
    }

    public void Dispose() { Stop(); }
}