namespace Nonemm.Recorder; /// A capture device in use: takes its interleaved samples apart and hands each /// one to the channel that records it. internal sealed class OpenDevice { private readonly float[] scratch = new float[RecorderEngine.SampleRate]; private volatile RecorderChannel?[] byDeviceChannel; public OpenDevice(AudioDeviceInfo info) { Info = info; byDeviceChannel = new RecorderChannel?[Math.Max(1, info.Channels)]; } public AudioDeviceInfo Info { get; } public IDisposable Handle { get; set; } = null!; // set right after the device opens /// Sets which channel gets which of the device's channels. public void Route(IEnumerable users) { RecorderChannel?[] routing = new RecorderChannel?[byDeviceChannel.Length]; foreach (RecorderChannel user in users) { if (user.Source.Channel < routing.Length) { routing[user.Source.Channel] = user; } } byDeviceChannel = routing; } public void OnSamples(ReadOnlySpan interleaved) { RecorderChannel?[] routing = byDeviceChannel; int width = routing.Length; int frames = Math.Min(interleaved.Length / width, scratch.Length); for (int index = 0; index < width; index++) { if (routing[index] is not { } channel) { continue; } float level = 0; for (int frame = 0; frame < frames; frame++) { float sample = interleaved[frame * width + index]; scratch[frame] = sample; level = Math.Max(level, Math.Abs(sample)); } channel.Add(scratch.AsSpan(0, frames), level); } } }