| 9 | namespace engine::models::ace_step { |
| 10 | |
| 11 | void ace_step_apply_repaint_waveform_splice( |
| 12 | runtime::AudioBuffer & decoded, |
| 13 | const AceStepRepaintSpliceSource & source, |
| 14 | float crossfade_seconds) { |
| 15 | if (decoded.sample_rate != kAceStepAudioSampleRate || source.audio.sample_rate != kAceStepAudioSampleRate || |
| 16 | decoded.channels != kAceStepAudioChannels || source.audio.channels != kAceStepAudioChannels) { |
| 17 | throw std::runtime_error("ACE-Step repaint waveform splice requires stereo 48 kHz audio"); |
| 18 | } |
| 19 | const int64_t decoded_frames = |
| 20 | static_cast<int64_t>(decoded.samples.size() / static_cast<size_t>(decoded.channels)); |
| 21 | const int64_t source_frames = |
| 22 | static_cast<int64_t>(source.audio.samples.size() / static_cast<size_t>(source.audio.channels)); |
| 23 | const int64_t frames = std::min(decoded_frames, source_frames); |
| 24 | if (frames <= 0) { |
| 25 | throw std::runtime_error("ACE-Step repaint waveform splice requires non-empty audio"); |
| 26 | } |
| 27 | |
| 28 | int64_t start_frame = |
| 29 | static_cast<int64_t>(static_cast<double>(source.start_seconds) * static_cast<double>(kAceStepAudioSampleRate)); |
| 30 | int64_t end_frame = |
| 31 | static_cast<int64_t>(static_cast<double>(source.end_seconds) * static_cast<double>(kAceStepAudioSampleRate)); |
| 32 | start_frame = std::max<int64_t>(0, std::min<int64_t>(start_frame, frames)); |
| 33 | end_frame = std::max<int64_t>(start_frame, std::min<int64_t>(end_frame, frames)); |
| 34 | if (start_frame == 0 && end_frame >= frames) { |
| 35 | return; |
| 36 | } |
| 37 | |
| 38 | const int64_t crossfade_frames = |
| 39 | static_cast<int64_t>(static_cast<double>(crossfade_seconds) * static_cast<double>(kAceStepAudioSampleRate)); |
| 40 | const int64_t fade_start = std::max<int64_t>(0, start_frame - std::max<int64_t>(0, crossfade_frames)); |
| 41 | const int64_t fade_end = std::min<int64_t>(frames, end_frame + std::max<int64_t>(0, crossfade_frames)); |
| 42 | const int64_t left_ramp_len = start_frame - fade_start; |
| 43 | const int64_t right_ramp_len = fade_end - end_frame; |
| 44 | |
| 45 | if (fade_start > 0) { |
| 46 | std::copy( |
| 47 | source.audio.samples.begin(), |
| 48 | source.audio.samples.begin() + static_cast<std::ptrdiff_t>(fade_start * kAceStepAudioChannels), |
| 49 | decoded.samples.begin()); |
| 50 | } |
| 51 | for (int64_t frame = fade_start; frame < start_frame; ++frame) { |
| 52 | const float mask = static_cast<float>(frame - fade_start + 1) / static_cast<float>(left_ramp_len + 1); |
| 53 | const size_t base = static_cast<size_t>(frame * kAceStepAudioChannels); |
| 54 | for (int channel = 0; channel < kAceStepAudioChannels; ++channel) { |
| 55 | const size_t index = base + static_cast<size_t>(channel); |
| 56 | decoded.samples[index] = |
| 57 | mask * decoded.samples[index] + (1.0F - mask) * source.audio.samples[index]; |
| 58 | } |
| 59 | } |
| 60 | for (int64_t frame = end_frame; frame < fade_end; ++frame) { |
| 61 | const float mask = 1.0F - static_cast<float>(frame - end_frame + 1) / static_cast<float>(right_ramp_len + 1); |
| 62 | const size_t base = static_cast<size_t>(frame * kAceStepAudioChannels); |
| 63 | for (int channel = 0; channel < kAceStepAudioChannels; ++channel) { |
| 64 | const size_t index = base + static_cast<size_t>(channel); |
| 65 | decoded.samples[index] = |
| 66 | mask * decoded.samples[index] + (1.0F - mask) * source.audio.samples[index]; |
| 67 | } |
| 68 | } |