10#define DR_WAV_IMPLEMENTATION
31bool SignalFactory::operator==(
41 if (other.
target ==
nullptr)
48bool SignalFactory::operator<(
49 const SignalFactory& other
52 if (
target ==
nullptr || other.target ==
nullptr || !
target->observe_source().has_value() ||
53 !other.target->observe_source().has_value())
57 const auto& us = *
target->observe_source();
58 const auto& them = *
target->observe_source();
64 const char*
const file_path
68 if (drwav_init_file(&drwav_info, file_path,
nullptr) == 0u)
73 signals.
resize(drwav_info.channels);
76 for (
auto& channel : signals) {
77 const auto formatted_name =
std::format(
"{}#{}", typed_path.stem().c_str(), channel_num);
95 drwav_uninit(&drwav_info);
99 drwav_uninit(&drwav_info);
104 const char*
const file_path,
109 if (drwav_init_file(&drwav_info, file_path,
nullptr) == 0u)
112 assert(drwav_info.channels >= channel_factories.
size());
122 channels.
resize(drwav_info.channels,
nullptr);
125 for (
const auto*
const factory : channel_factories) {
126 if (factory !=
nullptr && factory->target !=
nullptr)
127 channels[channel_idx] = factory->target.get();
133 drwav_uninit(&drwav_info);
137 drwav_uninit(&drwav_info);
142 const float downsample_factor,
147 if (
static_cast<float>(sample_count) < downsample_factor)
148 sample_count =
static_cast<std::uint64_t>(downsample_factor);
153 std::format(
"{} ({}x downsampled)", source.get_name(), downsample_factor)
158 "Created {} as {}x-LTTB variant of {} with {} samples.",
159 downsampled->get_name(),
162 downsampled->get_sample_count()
170 auto signal = std::move(
target);
175const Signal& SignalFactory::observe_signal() const noexcept
180void SignalFactory::emplace_sample(
184 target->emplace_sample(amplitude);
187void SignalFactory::emplace_sample(
191 target->emplace_sample(sample);
194void SignalFactory::emplace_sample(
199 target->emplace_sample(time, amplitude);
202void SignalFactory::emplace_sample_from_source(
206 target->emplace_sample_from_source(amplitude);
209void SignalFactory::emplace_sample_from_source(
213 target->emplace_sample_from_source(sample);
216void SignalFactory::emplace_sample_from_source(
221 target->emplace_sample_from_source(time, amplitude);
224void SignalFactory::set_signal_name(
225 const std::string_view name
231void SignalFactory::set_time_offset(
235 target->set_time_offset(time_offset);
238void SignalFactory::set_sample_rate(
239 const std::size_t sample_rate
242 target->set_sample_rate(sample_rate);
245void SignalFactory::set_source(
246 const std::filesystem::path& path,
247 const std::size_t channel
250 target->set_source(path, channel);
259 assert(drwav_info.channels <= std::ranges::size(signal_ptrs));
261 for (
auto*
const channel : signal_ptrs)
262 if (channel !=
nullptr) {
263 channel->reserve_samples(drwav_info.totalPCMFrameCount);
264 channel->set_sample_rate(drwav_info.sampleRate);
273 constexpr drwav_uint64 chunk_frame_count = 8192;
275 drwav_uint64 remaining_frames = drwav_info.totalPCMFrameCount;
277 while (remaining_frames > 0) {
278 const auto frame_count =
std::min(remaining_frames, chunk_frame_count);
279 if (drwav_read_pcm_frames_f32(&drwav_info, frame_count, interleaved.
data()) != frame_count)
283 for (drwav_uint64 frame_idx = 0; frame_idx < frame_count; ++frame_idx)
284 for (drwav_uint16 channel_idx = 0; channel_idx < drwav_info.channels; ++channel_idx) {
285 if (
auto*
const destination =
std::ranges::begin(signal_ptrs)[channel_idx]; destination !=
nullptr)
290 destination->emplace_sample_from_source(interleaved[frame_idx * drwav_info.channels + channel_idx]);
293 remaining_frames -= frame_count;
296 if (remaining_frames != 0)
299 for (
auto*
const channel : signal_ptrs | std::views::filter([](
auto ptr) {
return ptr; })) {
301 assert(channel->fs_source.has_value());
302 channel->fs_source->is_loaded =
true;
304 "Loaded signal \"{}\" with {} samples at {} Hz, starting at {} s.",
306 channel->get_sample_count(),
307 channel->get_sample_rate(),
308 channel->get_time_offset()
315 const size_t threshold,
323 if (threshold == 0 || source_size == 0)
327 if (threshold >= source_size)
332 downsampled->reserve_samples(threshold);
334 if (threshold == 1) {
340 if (threshold == 2) {
343 downsampled->emplace_sample(source.
get_time_at_index(source_size - 1), source[source_size - 1]);
347 const auto bucket_size =
348 static_cast<unsigned int>(
static_cast<double>(source_size - 2) /
static_cast<double>(threshold - 2));
354 for (
std::size_t dst_point_idx = 0; dst_point_idx < threshold - 2; ++dst_point_idx) {
360 const auto average_range_start =
static_cast<std::size_t>(
std::floor((dst_point_idx + 1) * bucket_size)) + 1;
361 const auto average_range_end =
363 const auto average_range_length = average_range_end - average_range_start;
365 for (
auto range_idx = average_range_start; range_idx < average_range_end; ++range_idx) {
367 average_amplitude += source[range_idx];
375 const auto fp_amplitude = source[fixed_point_idx];
386 auto next_fixed_point_idx = range_lower;
389 for (
auto range_idx = range_lower; range_idx < range_upper; ++range_idx) {
390 const float area = std::abs(
391 (fp_time - average_time) * (source[range_idx] - fp_amplitude) -
392 (fp_time - source.
get_time_at_index(range_idx)) * (average_amplitude - fp_amplitude)
395 if (area > max_area) {
397 next_fixed_point_idx = range_idx;
405 downsampled->emplace_sample(source.
get_time_at_index(next_fixed_point_idx), source[next_fixed_point_idx]);
407 fixed_point_idx = next_fixed_point_idx;
411 downsampled->emplace_sample(source.
get_time_at_index(source_size - 1), source[source_size - 1]);
413 assert(downsampled->get_sample_count() == threshold);
EchoMap ConfigurationError exception specification.
EchoMap portable logger specification.
#define LOG_F_DEBUG(msg,...)
Conditionally logs a formatted debug-level message using echomap::Logger::log_f.
Audio signal class specification.
T back_inserter(T... args)
A ConfigurationError indicates an error encountered during the initial configuration of the EchoMap g...
Provides various convenience functions for constructing Signal objects in exotic ways.
static void load_wave_file_into_channels(drwav &drwav_info, std::string_view file_path, std::span< Signal *const > signal_ptrs)
Loads the time-series sampled data into the given Signal objects.
std::unique_ptr< Signal > target
The Signal being built by the factory.
static std::unique_ptr< Signal > lttb_downsample(const Signal &source, size_t threshold, std::string_view name)
Create a new Signal by downsampling the data points of an existing Signal to the given threshold.
static std::unique_ptr< Signal > downsample(const Signal &source, float downsample_factor, std::string_view name={})
Downsamples an existing Signal instance across all channels to the given number of samples.
SignalFactory()
Begin constructing a new Signal.
static std::vector< std::unique_ptr< Signal > > load_wave_file(const char *file_path)
Loads a WAV file from the file system.
A single channel of discretely sampled audio data.
std::uint64_t get_sample_count() const noexcept
Retrieves the total number of samples in the Signal stream.
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.
The main EchoMap outermost namespace for all non-exported symbols.
A PCM float-32 sampled audio point at an explicit time offset.
float AmplitudeT
Type for sample amplitudes.
float TimeT
Type for sample times.
Indicates an external source of a Signal on the filesystem.