libtcspc C++ API
Streaming TCSPC and time tag data processing
Loading...
Searching...
No Matches
swabian_tag.hpp
1/*
2 * This file is part of libtcspc
3 * Copyright 2019-2026 Board of Regents of the University of Wisconsin System
4 * SPDX-License-Identifier: MIT
5 */
6
7#pragma once
8
9#include "common.hpp"
10#include "core.hpp"
11#include "data_types.hpp"
12#include "int_arith.hpp"
13#include "int_types.hpp"
14#include "introspect.hpp"
15#include "npint.hpp"
16#include "processor.hpp"
17#include "read_integers.hpp"
18#include "time_tagged_events.hpp"
19
20#include <array>
21#include <cassert>
22#include <cstddef>
23#include <ostream>
24#include <span>
25#include <sstream>
26#include <type_traits>
27
28namespace tcspc {
29
30// The raw tag stream format (struct Tag) is documented in Swabian's Time
31// Tagger C++ API Manual (part of their software download). See the 16-byte
32// 'Tag' struct.
33
34// Design note: swabian_tag_event does not have an alignment requirement. This
35// means that tags can be read from, for example, a memory-mapped file that was
36// written without consideration of alignment. On platforms where unaligned
37// access requires extra instructions, this may add significant overhead.
38// However, on the platforms where this code is most likely to be commonly run
39// (x86-64 and aarch64), unaligned load/store usually does not require special
40// instructions; therefore there is little advantage to enforcing alignment at
41// this level, limiting usage scenarios. If the CPU can perform aligned loads
42// and stores faster, this will happen automatically when the buffer is aligned
43// at run time.
44
58 std::array<std::byte, 16> bytes;
59
63 enum class tag_type : u8 {
64 time_tag = 0,
65 error = 1,
66 overflow_begin = 2,
67 overflow_end = 3,
68 missed_events = 4,
69 };
70
74 [[nodiscard]] constexpr auto type() const noexcept -> tag_type {
75 return tag_type(read_u8_at<0>(std::span(bytes)).value());
76 }
77
78 // bytes[1] is reserved and should be written zero.
79
83 [[nodiscard]] constexpr auto missed_event_count() const noexcept -> u16np {
84 return read_u16le_at<2>(std::span(bytes));
85 }
86
90 [[nodiscard]] constexpr auto channel() const noexcept -> i32np {
91 return read_i32le_at<4>(std::span(bytes));
92 }
93
97 [[nodiscard]] constexpr auto time() const noexcept -> i64np {
98 return read_i64le_at<8>(std::span(bytes));
99 }
100
110 static constexpr auto make_time_tag(i64np time, i32np channel) noexcept
112 return make_from_fields(tag_type::time_tag, 0_u16np, channel, time);
113 }
114
122 static constexpr auto make_error(i64np time) noexcept
124 return make_from_fields(tag_type::error, 0_u16np, 0_i32np, time);
125 }
126
135 static constexpr auto make_overflow_begin(i64np time) noexcept
137 return make_from_fields(tag_type::overflow_begin, 0_u16np, 0_i32np,
138 time);
139 }
140
148 static constexpr auto make_overflow_end(i64np time) noexcept
150 return make_from_fields(tag_type::overflow_end, 0_u16np, 0_i32np,
151 time);
152 }
153
165 static constexpr auto make_missed_events(i64np time, i32np channel,
166 u16np count) noexcept
168 return make_from_fields(tag_type::missed_events, count, channel, time);
169 }
170
172 friend auto operator==(swabian_tag_event const &lhs,
173 swabian_tag_event const &rhs) noexcept
174 -> bool = default;
175
177 friend auto operator<<(std::ostream &strm, swabian_tag_event const &e)
178 -> std::ostream & {
179 return strm << "swabian_tag(type=" << static_cast<int>(e.type())
180 << ", missed=" << e.missed_event_count()
181 << ", channel=" << e.channel() << ", time=" << e.time()
182 << ")";
183 }
184
185 private:
186 static constexpr auto make_from_fields(tag_type type, u16np missed,
187 i32np channel, i64np time) noexcept
189 return swabian_tag_event{{
190 std::byte(type),
191 std::byte(0), // Padding
192 std::byte(u8np(missed).value()),
193 std::byte(u8np(missed >> 8).value()),
194 std::byte(u8np(channel).value()),
195 std::byte(u8np(channel >> 8).value()),
196 std::byte(u8np(channel >> 16).value()),
197 std::byte(u8np(channel >> 24).value()),
198 std::byte(u8np(time).value()),
199 std::byte(u8np(time >> 8).value()),
200 std::byte(u8np(time >> 16).value()),
201 std::byte(u8np(time >> 24).value()),
202 std::byte(u8np(time >> 32).value()),
203 std::byte(u8np(time >> 40).value()),
204 std::byte(u8np(time >> 48).value()),
205 std::byte(u8np(time >> 56).value()),
206 }};
207 }
208};
209
210namespace internal {
211
212template <typename DataTypes, typename Downstream> class decode_swabian_tags {
213 static_assert(processor<Downstream, detection_event<DataTypes>,
214 begin_lost_interval_event<DataTypes>,
215 end_lost_interval_event<DataTypes>,
216 lost_counts_event<DataTypes>, warning_event>);
217
218 static_assert(representable_in<i64, typename DataTypes::abstime_type>);
219 static_assert(representable_in<i32, typename DataTypes::channel_type>);
220 static_assert(representable_in<u16, typename DataTypes::count_type>);
221
222 Downstream downstream;
223
224 LIBTCSPC_NOINLINE
225 void handle_coldpath_tag(swabian_tag_event const &event) {
226 switch (event.type()) {
227 using tag_type = swabian_tag_event::tag_type;
228 case tag_type::time_tag:
229 assert(false); // Handled in hot path.
230 case tag_type::error:
231 return downstream.handle(warning_event{"error tag encountered"});
232 case tag_type::overflow_begin:
233 return downstream.handle(
234 begin_lost_interval_event<DataTypes>{event.time().value()});
235 case tag_type::overflow_end:
236 return downstream.handle(
237 end_lost_interval_event<DataTypes>{event.time().value()});
238 case tag_type::missed_events:
239 return downstream.handle(lost_counts_event<DataTypes>{
240 event.time().value(), event.channel().value(),
241 event.missed_event_count().value()});
242 }
243
244 std::ostringstream stream;
245 stream << "unknown event type ("
246 << static_cast<
247 std::underlying_type_t<swabian_tag_event::tag_type>>(
248 event.type())
249 << ")";
250 downstream.handle(warning_event{stream.str()});
251 }
252
253 public:
254 explicit decode_swabian_tags(Downstream downstream)
255 : downstream(std::move(downstream)) {}
256
257 [[nodiscard]] auto introspect_node() const -> processor_info {
258 return processor_info(this, "decode_swabian_tags");
259 }
260
261 [[nodiscard]] auto introspect_graph() const -> processor_graph {
262 return downstream.introspect_graph().push_entry_point(this);
263 }
264
265 template <typename Event>
266 requires std::convertible_to<std::remove_cvref_t<Event>,
267 swabian_tag_event>
268 void handle(Event &&event) {
269 if (event.type() == swabian_tag_event::tag_type::time_tag) {
270 downstream.handle(detection_event<DataTypes>{
271 event.time().value(), event.channel().value()});
272 } else {
273 handle_coldpath_tag(event);
274 }
275 }
276
277 template <typename Event>
278 requires(not std::convertible_to<std::remove_cvref_t<Event>,
279 swabian_tag_event> and
280 handler_for<Downstream, std::remove_cvref_t<Event>>)
281 void handle(Event &&event) {
282 downstream.handle(std::forward<Event>(event));
283 }
284
285 void flush() { downstream.flush(); }
286};
287
288} // namespace internal
289
314template <typename DataTypes = default_data_types, typename Downstream>
315auto decode_swabian_tags(Downstream downstream) {
316 return internal::decode_swabian_tags<DataTypes, Downstream>(
317 std::move(downstream));
318}
319
320} // namespace tcspc
constexpr auto read_i32le_at(std::span< T, N > bytes) noexcept -> i32np
Read a little-endian 32-bit signed integer from bytes at offset.
Definition read_integers.hpp:146
npint< i64 > i64np
Non-promoted signed 64-bit integer.
Definition npint.hpp:341
constexpr auto read_u8_at(std::span< T, N > bytes) noexcept -> u8np
Read an 8-bit unsigned integer from bytes at offset.
Definition read_integers.hpp:77
constexpr auto read_i64le_at(std::span< T, N > bytes) noexcept -> i64np
Read a little-endian 64-bit signed integer from bytes at offset.
Definition read_integers.hpp:156
npint< i32 > i32np
Non-promoted signed 32-bit integer.
Definition npint.hpp:334
npint< u8 > u8np
Non-promoted unsigned 8-bit integer.
Definition npint.hpp:292
npint< u16 > u16np
Non-promoted unsigned 16-bit integer.
Definition npint.hpp:299
constexpr auto read_u16le_at(std::span< T, N > bytes) noexcept -> u16np
Read a little-endian 16-bit unsigned integer from bytes at offset.
Definition read_integers.hpp:90
auto count(access_tracker< count_access > &&tracker, Downstream downstream)
Create a processor that counts events of a given type.
Definition count.hpp:313
auto decode_swabian_tags(Downstream downstream)
Create a processor that decodes Swabian Tag events.
Definition swabian_tag.hpp:315
std::uint8_t u8
Short name for uint8_t.
Definition int_types.hpp:24
libtcspc namespace.
Definition acquire.hpp:29
Binary record interpretation for 16-byte Swabian time tag.
Definition swabian_tag.hpp:54
friend auto operator==(swabian_tag_event const &lhs, swabian_tag_event const &rhs) noexcept -> bool=default
Equality comparison operator.
constexpr auto time() const noexcept -> i64np
Read the time (picoseconds).
Definition swabian_tag.hpp:97
static constexpr auto make_overflow_end(i64np time) noexcept -> swabian_tag_event
Make an event representing the end of an overflow interval.
Definition swabian_tag.hpp:148
friend auto operator<<(std::ostream &strm, swabian_tag_event const &e) -> std::ostream &
Stream insertion operator.
Definition swabian_tag.hpp:177
constexpr auto missed_event_count() const noexcept -> u16np
Read the missed event count if this is a missed events event.
Definition swabian_tag.hpp:83
constexpr auto type() const noexcept -> tag_type
Read the event type.
Definition swabian_tag.hpp:74
static constexpr auto make_time_tag(i64np time, i32np channel) noexcept -> swabian_tag_event
Make an event representing a time tag.
Definition swabian_tag.hpp:110
static constexpr auto make_missed_events(i64np time, i32np channel, u16np count) noexcept -> swabian_tag_event
Make an event representing a missed event count.
Definition swabian_tag.hpp:165
std::array< std::byte, 16 > bytes
Bytes of the 16-byte format from Swabian API.
Definition swabian_tag.hpp:58
tag_type
8-bit type for the type field.
Definition swabian_tag.hpp:63
static constexpr auto make_error(i64np time) noexcept -> swabian_tag_event
Make an event representing an error.
Definition swabian_tag.hpp:122
static constexpr auto make_overflow_begin(i64np time) noexcept -> swabian_tag_event
Make an event representing the beginning of an overflow interval.
Definition swabian_tag.hpp:135
constexpr auto channel() const noexcept -> i32np
Read the channel if this is a time tag or missed events event.
Definition swabian_tag.hpp:90