EchoMap 2026-07-25 6d3977c
An experimental cross-platform digital signal processing application for sound-source localisation.
Loading...
Searching...
No Matches
Signal.cpp
Go to the documentation of this file.
1
7
8#include "Signal.hpp"
9
10#include <cassert>
11#include <cmath>
12
13#include "../utility/Logger.hpp"
15
16namespace echomap
17{
18
19Signal::Signal(
21 const std::optional<Source>& source
22) :
23 Object(name),
24 fs_source(source)
25{
26}
27
28bool Signal::Source::operator<(
29 const Source& other
30) const
31{
32 if (channel < other.channel)
33 return true;
34
35 return path < other.path;
36}
37
39{
40 return samples.size();
41}
42
44{
45 return fs_source;
46}
47
48Signal::Sample::TimeT Signal::get_time_offset() const noexcept
49{
51}
52
53std::size_t Signal::get_sample_rate() const noexcept
54{
56}
57
58decltype(Signal::samples)::const_iterator Signal::begin() const
59{
60 return samples.begin();
61}
62
63decltype(Signal::samples)::const_iterator Signal::end() const
64{
65 return samples.end();
66}
67
68decltype(Signal::samples)::const_iterator Signal::cbegin() const noexcept
69{
70 return samples.cbegin();
71}
72
73decltype(Signal::samples)::const_iterator Signal::cend() const noexcept
74{
75 return samples.cend();
76}
77
78Signal::Sample::AmplitudeT Signal::operator[](
79 const std::size_t index
80) const noexcept
81{
82 return samples[index];
83}
84
85std::span<const Signal::Sample::AmplitudeT> Signal::amplitudes() const noexcept
86{
87 return samples;
88}
89
90bool Signal::is_uniformly_sampled() const noexcept
91{
92 return !time_offsets.has_value();
93}
94
96 const std::size_t index
97) const noexcept
98{
99 assert(index < samples.size());
100 const Sample::TimeT baseline_time =
101 timing_baseline.time_offset + static_cast<Sample::TimeT>(index) * timing_baseline.sample_rate_r;
102 return time_offsets.has_value() ? baseline_time + (*time_offsets)[index] : baseline_time;
103}
104
105Signal::Signal(
106 const Signal& old_signal
107) :
108 Object(CopyTag{},
109 old_signal),
110 samples(old_signal.samples),
112 fs_source(old_signal.fs_source),
113 time_offsets(old_signal.time_offsets)
114{
115}
116
117Signal::Signal(
118 const Signal& old_signal,
119 const std::string_view new_name
120) :
121 Object(CopyTag{},
122 old_signal,
123 new_name),
124 samples(old_signal.samples),
125 timing_baseline(old_signal.timing_baseline),
126 fs_source(old_signal.fs_source),
127 time_offsets(old_signal.time_offsets)
128{
129}
130
132 const Sample::AmplitudeT amplitude
133)
134{
135 assert(amplitude >= normalised_range.first && amplitude <= normalised_range.second);
136 samples.emplace_back(amplitude);
137 if (time_offsets.has_value())
138 time_offsets->emplace_back(0);
139
140 if (fs_source.has_value())
141 fs_source->dirty = true;
142}
143
145 const Sample::TimeT time,
146 const Sample::AmplitudeT amplitude
147)
148{
149 assert(amplitude >= normalised_range.first && amplitude <= normalised_range.second);
150 samples.emplace_back(amplitude);
151 emplace_time(time);
152
153 if (fs_source.has_value())
154 fs_source->dirty = true;
155}
156
158 const Sample& sample
159)
160{
161 emplace_sample(sample.time, sample.amplitude);
162}
163
165 const Sample::AmplitudeT amplitude
166)
167{
168 assert(amplitude >= normalised_range.first && amplitude <= normalised_range.second);
169 samples.emplace_back(amplitude);
170 if (time_offsets.has_value())
171 time_offsets->emplace_back(0);
172}
173
175 const Sample::TimeT time,
176 const Sample::AmplitudeT amplitude
177)
178{
179 assert(amplitude >= normalised_range.first && amplitude <= normalised_range.second);
180 samples.emplace_back(amplitude);
181 emplace_time(time);
182}
183
185 const Sample& sample
186)
187{
188 return emplace_sample_from_source(sample.time, sample.amplitude);
189}
190
192 const std::size_t count
193)
194{
195 samples.reserve(count);
196
197 if (time_offsets.has_value())
198 time_offsets->reserve(count);
199}
200
201void Signal::set_source(
202 const std::filesystem::path& path,
203 const std::size_t channel
204)
205{
206 fs_source = Source(path, channel);
207}
208
209void Signal::set_time_offset(
210 const Sample::TimeT new_time_offset
211) noexcept
212{
213 timing_baseline.time_offset = new_time_offset;
214}
215
216void Signal::set_sample_rate(
217 const std::size_t new_sample_rate
218) noexcept
219{
220 timing_baseline.sample_rate = new_sample_rate;
221 timing_baseline.sample_rate_r = new_sample_rate == 0 ? 0 : 1.0f / static_cast<float>(new_sample_rate);
222}
223
225 const Sample::TimeT given_time
226)
227{
228 assert(!samples.empty());
229
230 constexpr Sample::TimeT epsilon = 1.0e-6f;
231 const auto expected =
232 timing_baseline.time_offset + static_cast<float>(samples.size() - 1) * timing_baseline.sample_rate_r;
233
234 // Check the time derived from the baseline, as if we're continuing with a uniformly sampled signal.
235 if (const auto offset = given_time - expected; std::abs(offset) <= epsilon) {
236 /*
237 * If the given time matches what we expect, we don't need to do anything. Only update the explicit offsets
238 * (with an offset of zero) if they're already there.
239 */
240 if (time_offsets.has_value())
241 time_offsets->emplace_back(0.0f);
242 } else {
243 /*
244 * If the given time doesn't match what we expect, then a variably sampled entry has been introduced. We need to
245 * consider:
246 *
247 * 1. if insertion of the time would violate the class invariant, such that time values are monotonically
248 * increasing, then an exception is due; or
249 *
250 * 2. if the time preserves the invariant, but is the first non-uniform entry, then we need to create offset
251 * entries (which will have offset of zero, since they followed the uniform pattern by construction), and
252 * emplace our offset on the end.
253 *
254 * 3. if the time preserves the invariant, and is being emplaced into an already-variable signal, then we
255 * simply note the offset.
256 */
257
258 if (samples.size() >= 2)
259 if (const auto previous_time = get_time_at_index(samples.size() - 2); given_time <= previous_time + epsilon)
260 // Case 1.
261 throw std::runtime_error(
263 "Signal {} rejected out-of-order sample at time {}s (previous sample was accepted at "
264 "time {}s).",
265 get_name(),
266 given_time,
267 previous_time
268 )
269 );
270
271 if (time_offsets.has_value())
272 // Case 3.
273 time_offsets->emplace_back(offset);
274 else {
275 // Case 2.
276 time_offsets.emplace(samples.size(), 0.0f);
277 time_offsets->back() = offset;
278 }
279 }
280}
281
282} // namespace echomap
EchoMap portable logger specification.
Wave file specification.
Audio signal class specification.
A participant in a runtime object model with a numerically unique identifier and display name.
Definition Object.hpp:36
A single channel of discretely sampled audio data.
Definition Signal.hpp:40
void reserve_samples(std::size_t count)
Reserves memory to store the given number of total samples in the channel.
Definition Signal.cpp:191
std::uint64_t get_sample_count() const noexcept
Retrieves the total number of samples in the Signal stream.
Definition Signal.cpp:38
static constexpr std::pair< Sample::AmplitudeT, Sample::AmplitudeT > normalised_range
The range within which the amplitude values are normalised.
Definition Signal.hpp:74
void emplace_sample_from_source(Sample::AmplitudeT amplitude)
Emplace an externally sourced sample to the back of the channel sample data.
Definition Signal.cpp:164
void emplace_sample(Sample::AmplitudeT amplitude)
Emplace a sample to the back of the channel sample data.
Definition Signal.cpp:131
std::vector< Sample::AmplitudeT > samples
Amplitude sample stream.
Definition Signal.hpp:55
std::optional< Source > fs_source
External source, if any, of the Signal Sample stream.
Definition Signal.hpp:257
Baseline timing_baseline
A baseline of timing parameters.
Definition Signal.hpp:256
std::optional< std::vector< Sample::TimeT > > time_offsets
An optional vector of explicit timestamps for each sample.
Definition Signal.hpp:281
const std::optional< Source > & observe_source() const noexcept
Retrieves the optional Source of the Signal.
Definition Signal.cpp:43
Sample::TimeT get_time_at_index(std::size_t index) const noexcept
Determines the corresponding time of the amplitude appearing at the given index in the sample array.
Definition Signal.cpp:95
void emplace_time(Sample::TimeT given_time)
Implementation helper to associate the latest amplitude sample with the given time.
Definition Signal.cpp:224
T format(T... args)
The main EchoMap outermost namespace for all non-exported symbols.
Sample::TimeT time_offset
Timestamp, in seconds, of the first sample.
Definition Signal.hpp:142
std::size_t sample_rate
Constant sample rate, in Hz, of the signal.
Definition Signal.hpp:143
A PCM float-32 sampled audio point at an explicit time offset.
Definition Signal.hpp:46
float AmplitudeT
Type for sample amplitudes.
Definition Signal.hpp:48
float TimeT
Type for sample times.
Definition Signal.hpp:47
std::size_t channel
The channel number of the Signal within the given file.
Definition Signal.hpp:64
std::filesystem::path path
The path (absolute or relative to CWD) of the source wave file.
Definition Signal.hpp:63