Program Listing for File silence_detect.hpp
↰ Return to documentation for file (include/eat/process/silence_detect.hpp)
//
// Created by Richard Bailey on 13/05/2022.
//
#pragma once
#include <cmath>
#include <cstdint>
#include <numbers>
#include "eat/framework/process.hpp"
#include "eat/process/block.hpp"
namespace eat::process {
namespace detail {
inline double db_to_peak_amp(double dB) { return std::pow(10.0, dB / 20.0); }
inline double peak_amp_to_db(double amp) { return 20.0 * std::log10(amp); }
} // namespace detail
struct AudioInterval {
std::size_t start{0};
std::size_t length{0};
};
struct SilenceDetectionConfig {
std::size_t minimum_length{10};
double threshold{-95.0};
};
class SilenceStatus {
public:
explicit SilenceStatus(SilenceDetectionConfig silence_config)
: config(silence_config),
squared_threshold(detail::db_to_peak_amp(config.threshold) * detail::db_to_peak_amp(config.threshold)) {}
void process(InterleavedSampleBlock &block, std::size_t sample_number) {
complete_interval = false;
auto channelCount = block.info().channel_count;
bool silent = channelCount > 0;
for (std::size_t channel = 0; channel != channelCount; ++channel) {
auto sample = block.sample(channel, sample_number);
silent = silent && (sample * sample < squared_threshold);
if (silent) {
silence();
} else {
signal();
}
++total;
}
}
[[nodiscard]] bool ready() const { return complete_interval; }
[[nodiscard]] AudioInterval getInterval() const {
if (!ready()) {
throw std::runtime_error("Interval requested when none was ready");
}
return interval;
}
void finish() { signal(); }
private:
void silence() {
if (zero_count == 0) {
interval.start = total;
interval.length = 0;
}
++zero_count;
}
void signal() {
if (zero_count >= config.minimum_length) {
interval.length = zero_count;
complete_interval = true;
}
zero_count = 0;
}
SilenceDetectionConfig config;
AudioInterval interval;
bool complete_interval{false};
std::size_t total{0};
std::size_t zero_count{0};
double squared_threshold;
};
class SilenceDetector : public framework::StreamingAtomicProcess {
public:
explicit SilenceDetector(std::string const &name, SilenceDetectionConfig config = {})
: StreamingAtomicProcess{name},
in_samples(add_in_port<framework::StreamPort<InterleavedBlockPtr>>("in_samples")),
out_intervals(add_out_port<framework::DataPort<std::vector<AudioInterval>>>("out_intervals")),
status_config{config},
status{status_config} {}
void initialise() override { status = SilenceStatus{status_config}; }
void process() override {
while (in_samples->available()) {
auto samples = in_samples->pop().move_or_copy();
auto &info = samples->info();
for (std::size_t sample = 0; sample != info.sample_count; ++sample) {
status.process(*samples, sample);
add_interval_if_ready();
}
}
};
void finalise() override {
status.finish();
add_interval_if_ready();
out_intervals->set_value(intervals);
}
private:
void add_interval_if_ready() {
if (status.ready()) {
intervals.push_back(status.getInterval());
}
}
framework::StreamPortPtr<InterleavedBlockPtr> in_samples;
framework::DataPortPtr<std::vector<AudioInterval>> out_intervals;
std::vector<AudioInterval> intervals;
SilenceDetectionConfig status_config;
SilenceStatus status;
};
} // namespace eat::process