#include "audio_processing.h" #include #include #include #include namespace sd::audio { // Match torchaudio's Hann-windowed sinc resampler. std::vector resample_audio(const float* samples, uint64_t sample_count, uint32_t orig_sample_rate, uint32_t target_sample_rate) { if (samples == nullptr || sample_count == 0 || orig_sample_rate == 0 || target_sample_rate == 0) { return {}; } if (orig_sample_rate == target_sample_rate) { return std::vector(samples, samples + sample_count); } constexpr int kLowpassFilterWidth = 6; constexpr double kRolloff = 0.99; constexpr double kPi = 3.14159265358979323846; const uint64_t gcd = std::gcd(static_cast(orig_sample_rate), static_cast(target_sample_rate)); const int64_t orig_freq = static_cast(orig_sample_rate / gcd); const int64_t new_freq = static_cast(target_sample_rate / gcd); const double base_freq = static_cast(std::min(orig_freq, new_freq)) * kRolloff; const int64_t width = static_cast(std::ceil(kLowpassFilterWidth * orig_freq / base_freq)); const int64_t kernel_size = 2 * width + orig_freq; std::vector kernel(static_cast(new_freq) * kernel_size); for (int64_t j = 0; j < new_freq; ++j) { for (int64_t i = 0; i < kernel_size; ++i) { double t = -static_cast(j) / new_freq + static_cast(i - width) / orig_freq; t *= base_freq; t = std::clamp(t, -static_cast(kLowpassFilterWidth), static_cast(kLowpassFilterWidth)); const double cos_arg = std::cos(t * kPi / kLowpassFilterWidth / 2); const double window = cos_arg * cos_arg; double s = t * kPi; const double sinc = (s == 0.0) ? 1.0 : std::sin(s) / s; kernel[j * kernel_size + i] = sinc * window * (base_freq / orig_freq); } } const uint64_t num_phases = static_cast(sample_count / orig_freq) + 1; const uint64_t target_length = (static_cast(new_freq) * sample_count + static_cast(orig_freq) - 1) / static_cast(orig_freq); std::vector out(target_length); for (uint64_t phase = 0; phase < num_phases; ++phase) { const int64_t src_base = static_cast(phase * orig_freq) - width; for (int64_t j = 0; j < new_freq; ++j) { const uint64_t out_index = phase * new_freq + j; if (out_index >= target_length) { break; } const double* k = &kernel[j * kernel_size]; double acc = 0.0; for (int64_t i = 0; i < kernel_size; ++i) { const int64_t src = src_base + i; if (src >= 0 && src < static_cast(sample_count)) { acc += samples[src] * k[i]; } } out[out_index] = static_cast(acc); } } return out; } std::vector downmix_to_mono(const float* interleaved_samples, uint64_t sample_count, uint32_t channels) { std::vector mono; if (interleaved_samples == nullptr || sample_count == 0 || channels == 0) { return mono; } mono.resize(static_cast(sample_count)); if (channels == 1) { std::memcpy(mono.data(), interleaved_samples, static_cast(sample_count) * sizeof(float)); return mono; } const float scale = 1.0f / static_cast(channels); for (uint64_t i = 0; i < sample_count; ++i) { float sum = 0.0f; for (uint32_t c = 0; c < channels; ++c) { sum += interleaved_samples[i * channels + c]; } mono[static_cast(i)] = sum * scale; } return mono; } } // namespace sd::audio