SSPC-Tester/Driver/PCIE8586/Pcie8586Probe/Hardware/SimulatedDigitizer.cs

121 lines
4.1 KiB
C#

using System.Runtime.CompilerServices;
using Pcie8586Probe.Acquisition;
using Pcie8586Probe.Models;
namespace Pcie8586Probe.Hardware;
public sealed class SimulatedDigitizer : IDigitizer
{
private AcquisitionConfig _config = AcquisitionConfig.Default;
private bool _isOpen;
private long _nextSampleIndex;
public bool IsOpen => _isOpen;
public ValueTask<IReadOnlyList<DeviceInfo>> EnumerateDevicesAsync(CancellationToken cancellationToken)
{
IReadOnlyList<DeviceInfo> devices =
[
new DeviceInfo(0, 0, "\u6A21\u62DF PCIe8586M \u91C7\u96C6\u5361", true)
];
return ValueTask.FromResult(devices);
}
public ValueTask OpenAsync(DeviceInfo device, CancellationToken cancellationToken)
{
_isOpen = true;
_nextSampleIndex = 0;
return ValueTask.CompletedTask;
}
public ValueTask ConfigureAsync(AcquisitionConfig config, CancellationToken cancellationToken)
{
config.Validate();
_config = config;
return ValueTask.CompletedTask;
}
public async IAsyncEnumerable<SampleBlock> StartAcquisitionAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
if (!_isOpen)
{
throw new InvalidOperationException("Device is not open.");
}
var sampleRate = _config.SampleRateHz;
var blockSamples = (int)Math.Clamp(sampleRate / 30.0, 128, 8192);
var emitted = 0L;
while (!cancellationToken.IsCancellationRequested)
{
var remaining = _config.Mode == AcquisitionMode.Finite
? _config.FiniteSamplesPerChannel - emitted
: long.MaxValue;
if (remaining <= 0)
{
yield break;
}
var samplesThisBlock = (int)Math.Min(blockSamples, remaining);
var raw = GenerateRaw(samplesThisBlock, _config.ChannelCount, sampleRate, _nextSampleIndex, _config.InputRange);
var channels = CodeConverter.Deinterleave(raw, _config.ChannelCount, _config.InputRange);
var block = new SampleBlock(channels, sampleRate, _nextSampleIndex, DateTimeOffset.Now);
_nextSampleIndex += samplesThisBlock;
emitted += samplesThisBlock;
yield return block;
var delayMs = Math.Clamp(samplesThisBlock / sampleRate * 1000.0, 1.0, 50.0);
await Task.Delay(TimeSpan.FromMilliseconds(delayMs), cancellationToken);
}
}
public ValueTask StopAsync()
{
return ValueTask.CompletedTask;
}
public ValueTask CloseAsync()
{
_isOpen = false;
return ValueTask.CompletedTask;
}
public void Dispose()
{
_isOpen = false;
}
private static ushort[] GenerateRaw(int samplesPerChannel, int channelCount, double sampleRate, long startSample, InputRange range)
{
var raw = new ushort[samplesPerChannel * channelCount];
var fullScale = range == InputRange.PlusMinus5V ? 5.0 : 1.0;
for (var sample = 0; sample < samplesPerChannel; sample++)
{
var absoluteSample = startSample + sample;
var time = absoluteSample / sampleRate;
for (var channel = 0; channel < channelCount; channel++)
{
var frequency = 250.0 * (channel + 1);
var signal = Math.Sin(2.0 * Math.PI * frequency * time);
var harmonic = 0.15 * Math.Sin(2.0 * Math.PI * frequency * 0.31 * time + channel);
var noise = 0.01 * Math.Sin(2.0 * Math.PI * (97.0 + channel * 13.0) * time);
var volts = Math.Clamp((signal * 0.65 + harmonic + noise) * fullScale, -fullScale, fullScale);
raw[sample * channelCount + channel] = VoltsToCode(volts, range);
}
}
return raw;
}
private static ushort VoltsToCode(double volts, InputRange range)
{
var span = range == InputRange.PlusMinus5V ? 10.0 : 2.0;
var min = range == InputRange.PlusMinus5V ? -5.0 : -1.0;
var normalized = (volts - min) / span;
return (ushort)Math.Clamp(Math.Round(normalized * 65_536.0), 0, 65_535);
}
}