93 lines
4.4 KiB
C#
93 lines
4.4 KiB
C#
using System.Diagnostics;
|
|
using System.Text.Json;
|
|
|
|
namespace AquaSynthDingsMcp;
|
|
|
|
public sealed class AquaDaemonClient : IAsyncDisposable
|
|
{
|
|
private readonly SemaphoreSlim gate = new(1, 1);
|
|
private readonly JsonSerializerOptions json = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
|
|
private Process? process;
|
|
|
|
public async Task<string> RenderNoteAsync(DingInstrument instrument, float frequency, float gain, CancellationToken cancellationToken)
|
|
{
|
|
await gate.WaitAsync(cancellationToken);
|
|
try
|
|
{
|
|
EnsureStarted();
|
|
var id = $"ding-{Guid.NewGuid():N}";
|
|
var envelope = new
|
|
{
|
|
command = "instrument.sample",
|
|
payload = new
|
|
{
|
|
commandId = id,
|
|
patchId = $"dings.{instrument.Id}",
|
|
faustName = $"dings_{instrument.Id.Replace('-', '_')}",
|
|
script = instrument.Script,
|
|
durationSeconds = 1.5f,
|
|
gain,
|
|
controls = new Dictionary<string, float> { ["/ding/frequency"] = frequency },
|
|
revision = 1
|
|
}
|
|
};
|
|
await process!.StandardInput.WriteLineAsync(JsonSerializer.Serialize(envelope, json));
|
|
await process.StandardInput.FlushAsync(cancellationToken);
|
|
var line = await process.StandardOutput.ReadLineAsync(cancellationToken)
|
|
?? throw new InvalidOperationException("Aqua daemon closed before returning a render receipt.");
|
|
using var receipt = JsonDocument.Parse(line);
|
|
var render = receipt.RootElement.GetProperty("renderReceipt");
|
|
if (!string.Equals(render.GetProperty("status").GetString(), "succeeded", StringComparison.Ordinal))
|
|
throw new InvalidOperationException(render.TryGetProperty("failureMessage", out var failure) ? failure.GetString() : "Aqua render failed.");
|
|
return new Uri(render.GetProperty("wavUri").GetString()!).LocalPath;
|
|
}
|
|
finally { gate.Release(); }
|
|
}
|
|
|
|
private void EnsureStarted()
|
|
{
|
|
if (process is { HasExited: false }) return;
|
|
var root = FindRepositoryRoot();
|
|
var daemonDll = ResolveDaemonDll(root);
|
|
var arguments = $"\"{daemonDll}\" daemon --store \"{Path.Combine(root, ".aquasynth", "dings-mcp")}\"";
|
|
process = Process.Start(new ProcessStartInfo("dotnet", arguments)
|
|
{
|
|
WorkingDirectory = root,
|
|
RedirectStandardInput = true,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
UseShellExecute = false,
|
|
CreateNoWindow = true
|
|
}) ?? throw new InvalidOperationException("Could not start AquaSynth daemon.");
|
|
process.ErrorDataReceived += (_, e) => { if (e.Data is not null) Console.Error.WriteLine($"[aqua] {e.Data}"); };
|
|
process.BeginErrorReadLine();
|
|
}
|
|
|
|
private static string ResolveDaemonDll(string root)
|
|
{
|
|
var configured = Environment.GetEnvironmentVariable("AQUASYNTH_DAEMON_DLL");
|
|
if (!string.IsNullOrWhiteSpace(configured) && File.Exists(configured)) return Path.GetFullPath(configured);
|
|
var bin = Path.Combine(root, "tools", "AquaSynthDaemon", "bin");
|
|
var candidate = Directory.EnumerateFiles(bin, "AquaSynthDaemon.dll", SearchOption.AllDirectories)
|
|
.OrderByDescending(File.GetLastWriteTimeUtc)
|
|
.FirstOrDefault();
|
|
return candidate ?? throw new InvalidOperationException("AquaSynthDaemon.dll is not built. Build AquaSynthDingsMcp once or set AQUASYNTH_DAEMON_DLL.");
|
|
}
|
|
|
|
private static string FindRepositoryRoot()
|
|
{
|
|
for (var dir = new DirectoryInfo(AppContext.BaseDirectory); dir is not null; dir = dir.Parent)
|
|
if (File.Exists(Path.Combine(dir.FullName, "AquaSynth.sln"))) return dir.FullName;
|
|
for (var dir = new DirectoryInfo(Environment.CurrentDirectory); dir is not null; dir = dir.Parent)
|
|
if (File.Exists(Path.Combine(dir.FullName, "AquaSynth.sln"))) return dir.FullName;
|
|
throw new InvalidOperationException("Cannot locate AquaSynth.sln. Start the MCP service from the AquaSynth repository or set AQUASYNTH_DAEMON_DLL.");
|
|
}
|
|
|
|
public ValueTask DisposeAsync()
|
|
{
|
|
if (process is { HasExited: false }) process.Kill(entireProcessTree: true);
|
|
process?.Dispose();
|
|
gate.Dispose();
|
|
return ValueTask.CompletedTask;
|
|
}
|
|
}
|