Expose public gesture timeline API
This commit is contained in:
parent
5e1463fcb9
commit
1bf220027d
9 changed files with 198 additions and 9 deletions
|
|
@ -1,12 +1,63 @@
|
|||
namespace AquaSynth.Dsl;
|
||||
|
||||
public sealed record ControlSurfaceValue(string SurfacePath, float NormalizedValue);
|
||||
|
||||
public sealed class ControlSurfaceCatalog
|
||||
{
|
||||
private readonly Dictionary<string, ControlSurface> _surfacesByPath;
|
||||
|
||||
public ControlSurfaceCatalog(IEnumerable<ControlSurface> surfaces, IEnumerable<ControlSpline>? splines = null)
|
||||
{
|
||||
Surfaces = surfaces.OrderBy(surface => surface.Path, StringComparer.OrdinalIgnoreCase).ToArray();
|
||||
Splines = (splines ?? Array.Empty<ControlSpline>())
|
||||
.OrderBy(spline => spline.Name, StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
_surfacesByPath = Surfaces.ToDictionary(surface => surface.Path, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public IReadOnlyList<ControlSurface> Surfaces { get; }
|
||||
|
||||
public IReadOnlyList<ControlSpline> Splines { get; }
|
||||
|
||||
public IReadOnlyList<string> SurfacePaths => Surfaces.Select(surface => surface.Path).ToArray();
|
||||
|
||||
public static ControlSurfaceCatalog FromPatch(SynthPatch patch) =>
|
||||
new(patch.ControlSurfaces, patch.ControlSplines);
|
||||
|
||||
public bool TryGetSurface(string path, out ControlSurface surface) =>
|
||||
_surfacesByPath.TryGetValue(path, out surface!);
|
||||
|
||||
public ControlSurface Surface(string path) =>
|
||||
TryGetSurface(path, out var surface)
|
||||
? surface
|
||||
: throw new KeyNotFoundException($"unknown control surface `{path}`");
|
||||
|
||||
public ControlSplineTimeline CreateTimeline(bool includePatchSplines = true) =>
|
||||
new(this, includePatchSplines ? Splines : Array.Empty<ControlSpline>());
|
||||
|
||||
public IReadOnlyDictionary<string, float> Defaults() =>
|
||||
Surfaces.ToDictionary(surface => surface.Path, surface => surface.DefaultNormalized, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public sealed class ControlSplineTimeline
|
||||
{
|
||||
private readonly ControlSurfaceCatalog? catalog;
|
||||
private readonly Dictionary<string, List<ControlSplinePoint>> _pointsBySurface;
|
||||
|
||||
public ControlSplineTimeline(IEnumerable<ControlSpline> splines)
|
||||
{
|
||||
_pointsBySurface = splines
|
||||
catalog = null;
|
||||
_pointsBySurface = BuildPointsBySurface(splines);
|
||||
}
|
||||
|
||||
public ControlSplineTimeline(ControlSurfaceCatalog catalog, IEnumerable<ControlSpline>? splines = null)
|
||||
{
|
||||
this.catalog = catalog;
|
||||
_pointsBySurface = BuildPointsBySurface(splines ?? Array.Empty<ControlSpline>());
|
||||
}
|
||||
|
||||
private static Dictionary<string, List<ControlSplinePoint>> BuildPointsBySurface(IEnumerable<ControlSpline> splines) =>
|
||||
splines
|
||||
.Where(spline => spline.Enabled)
|
||||
.GroupBy(spline => spline.SurfacePath, StringComparer.OrdinalIgnoreCase)
|
||||
.ToDictionary(
|
||||
|
|
@ -16,12 +67,18 @@ public sealed class ControlSplineTimeline
|
|||
.OrderBy(point => point.TimeSeconds)
|
||||
.ToList(),
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public IReadOnlyList<string> SurfacePaths => _pointsBySurface.Keys.ToArray();
|
||||
|
||||
public void SetFuturePoint(string surfacePath, float timeSeconds, float normalizedValue, float nowSeconds) =>
|
||||
SetFuturePoint(surfacePath, new ControlSplinePoint(timeSeconds, normalizedValue), nowSeconds);
|
||||
|
||||
public void SetFuturePoint(string surfacePath, ControlSplinePoint point, float nowSeconds)
|
||||
{
|
||||
if (catalog is not null && !catalog.TryGetSurface(surfacePath, out _))
|
||||
{
|
||||
throw new KeyNotFoundException($"unknown control surface `{surfacePath}`");
|
||||
}
|
||||
if (point.TimeSeconds < nowSeconds)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(point), "future spline edits must not rewrite past control points");
|
||||
|
|
@ -56,6 +113,31 @@ public sealed class ControlSplineTimeline
|
|||
return Evaluate(points, timeSeconds);
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<string, float> ControlValuesAt(float timeSeconds)
|
||||
{
|
||||
if (catalog is null)
|
||||
{
|
||||
return _pointsBySurface.ToDictionary(
|
||||
pair => pair.Key,
|
||||
pair => Evaluate(pair.Value, timeSeconds),
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
var controls = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var surface in catalog.Surfaces)
|
||||
{
|
||||
controls[surface.Path] = ValueAt(surface.Path, timeSeconds, surface.DefaultNormalized);
|
||||
}
|
||||
|
||||
return controls;
|
||||
}
|
||||
|
||||
public IReadOnlyList<ControlSurfaceValue> SurfaceValuesAt(float timeSeconds) =>
|
||||
ControlValuesAt(timeSeconds)
|
||||
.OrderBy(pair => pair.Key, StringComparer.OrdinalIgnoreCase)
|
||||
.Select(pair => new ControlSurfaceValue(pair.Key, pair.Value))
|
||||
.ToArray();
|
||||
|
||||
public static float Evaluate(ControlSpline spline, float timeSeconds, float fallback = 0)
|
||||
{
|
||||
if (!spline.Enabled || spline.Points.Count == 0)
|
||||
|
|
|
|||
|
|
@ -48,13 +48,15 @@ public sealed class AquaSynthCompiledPatch : IDisposable
|
|||
IntPtr factory,
|
||||
AquaSynthNativeManifest manifest,
|
||||
IReadOnlyList<string> controlPaths,
|
||||
IReadOnlyList<string> probePaths)
|
||||
IReadOnlyList<string> probePaths,
|
||||
ControlSurfaceCatalog? controlSurfaceCatalog = null)
|
||||
{
|
||||
this.toolchain = toolchain;
|
||||
this.factory = factory;
|
||||
Manifest = manifest;
|
||||
ControlPaths = controlPaths;
|
||||
ProbePaths = probePaths;
|
||||
ControlSurfaces = controlSurfaceCatalog ?? new ControlSurfaceCatalog([]);
|
||||
}
|
||||
|
||||
public AquaSynthNativeManifest Manifest { get; }
|
||||
|
|
@ -63,6 +65,16 @@ public sealed class AquaSynthCompiledPatch : IDisposable
|
|||
|
||||
public IReadOnlyList<string> ProbePaths { get; }
|
||||
|
||||
public ControlSurfaceCatalog ControlSurfaces { get; }
|
||||
|
||||
public IReadOnlyDictionary<string, float> ControlValuesAt(ControlSplineTimeline timeline, float timeSeconds)
|
||||
{
|
||||
var exported = ControlPaths.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
return timeline.ControlValuesAt(timeSeconds)
|
||||
.Where(pair => exported.Contains(pair.Key) || exported.Contains(pair.Key.TrimStart('/')))
|
||||
.ToDictionary(pair => pair.Key, pair => pair.Value, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public float[] Render(float gain = 1.0f) => Render(null, gain);
|
||||
|
||||
public AquaSynthStreamingPatch CreateStreamingPatch()
|
||||
|
|
@ -501,7 +513,7 @@ public sealed class AquaSynthNativeCompiler : IDisposable
|
|||
var export = FaustEmitter.Emit(patch, new FaustExportOptions(faustName));
|
||||
var duration = EstimateDuration(patch, options);
|
||||
var sourcePath = WriteDspSource(identity.Id, export.Source, options.DspSourceDirectory);
|
||||
return CompileSource(identity, faustName, export.Source, duration, sourcePath);
|
||||
return CompileSource(identity, faustName, export.Source, duration, sourcePath, ControlSurfaceCatalog.FromPatch(patch));
|
||||
}
|
||||
|
||||
public AquaSynthCompiledPatch CompileSource(
|
||||
|
|
@ -509,7 +521,8 @@ public sealed class AquaSynthNativeCompiler : IDisposable
|
|||
string faustName,
|
||||
string source,
|
||||
float durationSeconds,
|
||||
string? dspSourcePath = null)
|
||||
string? dspSourcePath = null,
|
||||
ControlSurfaceCatalog? controlSurfaceCatalog = null)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(disposed, this);
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
|
|
@ -566,7 +579,7 @@ public sealed class AquaSynthNativeCompiler : IDisposable
|
|||
toolchain.Version,
|
||||
toolchain.Home,
|
||||
dspSourcePath);
|
||||
return new AquaSynthCompiledPatch(toolchain, factory, manifest, controlPaths, probePaths);
|
||||
return new AquaSynthCompiledPatch(toolchain, factory, manifest, controlPaths, probePaths, controlSurfaceCatalog);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue