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(); }
}