| 188 | } // namespace |
| 189 | |
| 190 | WavData mix_audio_to_reference_shape( |
| 191 | const std::vector<AudioMixInput> & inputs, |
| 192 | bool normalize_peak) { |
| 193 | if (inputs.empty()) { |
| 194 | throw std::runtime_error("audio mix requires at least one input"); |
| 195 | } |
| 196 | const WavData & reference = inputs.front().audio; |
| 197 | const int64_t frames = frame_count(reference); |
| 198 | if (reference.sample_rate <= 0 || reference.channels <= 0 || frames <= 0) { |
| 199 | throw std::runtime_error("audio mix reference input is invalid"); |
| 200 | } |
| 201 | WavData out; |
| 202 | out.sample_rate = reference.sample_rate; |
| 203 | out.channels = reference.channels; |
| 204 | out.samples.assign(static_cast<size_t>(frames * reference.channels), 0.0F); |
| 205 | for (const auto & input : inputs) { |
| 206 | auto adapted = adapt_audio(input.audio, out.sample_rate, out.channels, frames); |
| 207 | if (input.activity_reference.has_value()) { |
| 208 | auto reference_activity = adapt_audio(*input.activity_reference, out.sample_rate, out.channels, frames); |
| 209 | apply_activity_gate_in_place( |
| 210 | adapted, |
| 211 | reference_activity, |
| 212 | out.channels, |
| 213 | out.sample_rate, |
| 214 | input.activity_threshold_dbfs, |
| 215 | input.activity_window_seconds, |
| 216 | input.activity_margin_seconds, |
| 217 | input.activity_fade_seconds); |
| 218 | } |
| 219 | for (size_t i = 0; i < out.samples.size(); ++i) { |
| 220 | out.samples[i] += adapted[i] * input.gain; |
| 221 | } |
| 222 | } |
| 223 | if (normalize_peak) { |
| 224 | normalize_peak_to_unit_range_and_clamp_in_place(out.samples); |
| 225 | } else { |
| 226 | for (float & sample : out.samples) { |
| 227 | sample = std::clamp(sample, -1.0F, 1.0F); |
| 228 | } |
| 229 | } |
| 230 | return out; |
| 231 | } |
| 232 | |
| 233 | WavData read_and_mix_wavs_to_reference_shape( |
| 234 | const std::vector<std::pair<std::filesystem::path, float>> & inputs, |
no test coverage detected