56template <
typename SampleType =
float, std::
size_t MaxSamples = 8192>
class ScopeCaptureT {
57 static_assert(std::is_floating_point_v<SampleType>);
58 static_assert(MaxSamples > 0);
65 config.capture_samples = std::min<std::size_t>(1024, MaxSamples);
66 config.pretrigger_samples = std::min<std::size_t>(256,
config.capture_samples - 1);
72 const double low =
config.trigger_level -
config.hysteresis;
73 const double high =
config.trigger_level +
config.hysteresis;
78 if (
config.capture_samples == 0 ||
config.capture_samples > MaxSamples ||
80 !std::isfinite(
config.trigger_level) || !std::isfinite(
config.hysteresis) ||
81 config.hysteresis < 0.0 || !std::isfinite(low) || !std::isfinite(high) ||
82 !representable_(low) || !representable_(high) || !mode_valid) {
91 workspace_.fill(SampleType{0});
93 state_ = State::armed;
97 holdoff_remaining_ = 0;
100 nonfinite_samples_seen_ = 0;
101 capture_trigger_sample_index_ = 0;
102 rising_primed_ =
false;
103 falling_primed_ =
false;
109 if (state_ == State::capturing) {
110 history_count_ = capture_write_;
111 history_write_ = capture_write_ % MaxSamples;
113 state_ = State::armed;
115 holdoff_remaining_ = 0;
116 rising_primed_ =
false;
117 falling_primed_ =
false;
121 void process(std::span<const SampleType> input)
noexcept {
122 for (
const auto sample : input)
123 process_sample_(sample);
129 [[nodiscard]]
bool armed() const noexcept {
130 return state_ == State::armed;
133 return state_ == State::capturing;
143 return samples_seen_;
146 return holdoff_remaining_;
149 return config_.capture_samples - config_.pretrigger_samples - 1;
153 enum class State : std::uint8_t {
armed,
capturing, holdoff, stopped };
155 static bool representable_(
double value)
noexcept {
156 return value >=
static_cast<double>(std::numeric_limits<SampleType>::lowest()) &&
157 value <=
static_cast<double>(std::numeric_limits<SampleType>::max());
160 void process_sample_(SampleType input)
noexcept {
161 const bool finite = std::isfinite(input);
162 const SampleType sample = finite ? input : SampleType{0};
164 ++nonfinite_samples_seen_;
166 if (state_ == State::capturing) {
167 workspace_[capture_write_++] = sample;
168 if (capture_write_ == config_.capture_samples)
174 if (state_ == State::holdoff) {
175 if (holdoff_remaining_ > 0)
176 --holdoff_remaining_;
177 if (holdoff_remaining_ == 0) {
178 state_ = State::armed;
179 rising_primed_ =
false;
180 falling_primed_ =
false;
182 push_history_(sample);
187 if (state_ == State::armed) {
189 const bool edge_triggered = finite && !
immediate && triggered_(sample);
190 if (history_count_ >= config_.pretrigger_samples &&
192 begin_capture_(sample);
196 update_priming_(sample);
200 push_history_(sample);
204 void update_priming_(SampleType sample)
noexcept {
205 const auto low =
static_cast<SampleType
>(config_.trigger_level - config_.hysteresis);
206 const auto high =
static_cast<SampleType
>(config_.trigger_level + config_.hysteresis);
208 rising_primed_ =
true;
210 falling_primed_ =
true;
213 bool triggered_(SampleType sample)
noexcept {
214 const auto low =
static_cast<SampleType
>(config_.trigger_level - config_.hysteresis);
215 const auto high =
static_cast<SampleType
>(config_.trigger_level + config_.hysteresis);
216 const bool rising = rising_primed_ && sample >=
high;
217 const bool falling = falling_primed_ && sample <=
low;
219 rising_primed_ =
false;
221 falling_primed_ =
false;
222 switch (config_.trigger_mode) {
235 void begin_capture_(SampleType trigger_sample)
noexcept {
236 if (config_.pretrigger_samples > 0) {
237 const auto history_end = history_count_ == MaxSamples ? MaxSamples : history_count_;
238 const auto history_start =
239 (history_write_ + MaxSamples - config_.pretrigger_samples) % MaxSamples;
240 std::rotate(workspace_.begin(), workspace_.begin() + history_start,
241 workspace_.begin() + history_end);
243 workspace_[config_.pretrigger_samples] = trigger_sample;
244 capture_trigger_sample_index_ = samples_seen_;
245 capture_write_ = config_.pretrigger_samples + 1;
246 state_ = State::capturing;
247 if (capture_write_ == config_.capture_samples)
251 void complete_capture_() noexcept {
252 std::copy_n(workspace_.begin(), config_.capture_samples, frame_.samples.begin());
253 frame_.sample_count = config_.capture_samples;
254 frame_.trigger_offset = config_.pretrigger_samples;
255 frame_.trigger_sample_index = capture_trigger_sample_index_;
256 frame_.generation = ++generation_;
257 frame_.nonfinite_samples_seen = nonfinite_samples_seen_;
259 history_count_ = config_.capture_samples;
260 history_write_ = config_.capture_samples % MaxSamples;
262 if (!config_.continuous) {
263 state_ = State::stopped;
266 holdoff_remaining_ = config_.holdoff_samples;
267 state_ = holdoff_remaining_ == 0 ? State::armed : State::holdoff;
268 rising_primed_ =
false;
269 falling_primed_ =
false;
272 void push_history_(SampleType sample)
noexcept {
273 workspace_[history_write_] = sample;
274 history_write_ = (history_write_ + 1) % MaxSamples;
275 history_count_ = std::min(history_count_ + 1, MaxSamples);
278 ScopeCaptureConfig config_{};
279 std::array<SampleType, MaxSamples> workspace_{};
281 State state_ = State::armed;
282 std::size_t history_write_ = 0;
283 std::size_t history_count_ = 0;
284 std::size_t capture_write_ = 0;
285 std::uint64_t holdoff_remaining_ = 0;
286 std::uint64_t samples_seen_ = 0;
287 std::uint64_t generation_ = 0;
288 std::uint64_t nonfinite_samples_seen_ = 0;
289 std::uint64_t capture_trigger_sample_index_ = 0;
290 bool rising_primed_ =
false;
291 bool falling_primed_ =
false;