Растяг в расте¶
Растяг — это техника растяжения звука, при которой длительность ноты или семпла увеличивается без изменения тональности, за счёт повторного воспроизведения отдельных фрагментов аудио. В расте (Rust) — языке программирования, который часто используется для написания музыкальных инструментов и обработки аудио — растяг реализуется программно, с помощью алгоритмов цифровой обработки звука (DSP).
¶Алгоритмы растяжения звука
В цифровой обработке звука используются несколько основных подходов к растяжке:
- Time-stretching — изменение длительности аудиосигнала без изменения высоты тона. Основан на анализе сигнала и повторном синтезе его фрагментов.
- Granular synthesis — зерновой синтез, при котором звук разбивается на короткие фрагменты (зерна) длительностью 10–100 мс, которые затем повторяются или перекрываются.
- Phase vocoder — алгоритм, основанный на преобразовании Фурье, позволяющий растягивать звук с сохранением спектральных характеристик.
- PSOLA (Pitch-Synchronous Overlap-Add) — алгоритм, основанный на наложении и добавлении фрагментов звука синхронно с периодом основного тона.
¶Реализация на Rust
Rust — язык системного программирования, разработанный Mozilla Research и выпущенный в 2010 году. Он обеспечивает контроль над памятью, высокую производительность и безопасность, что делает его подходящим для обработки аудио в реальном времени.
¶Использование библиотек
Для растяжки звука в Rust можно использовать существующие библиотеки:
- rubato — библиотека для ресемплинга и растяжки аудио, поддерживающая несколько алгоритмов.
- dasp — набор инструментов для цифровой обработки звука.
- hound — библиотека для чтения и записи WAV-файлов.
- rustfft — библиотека для быстрого преобразования Фурье, необходимого для phase vocoder.
¶Пример: растяжка с помощью rubato
```rust use rubato::{Resampler, SincFixedIn}; use hound::{WavReader, WavWriter};
fn stretch_audio(input_path: &str, output_path: &str, factor: f64) -> Result<(), Box<dyn std::error::Error>> { // Чтение исходного WAV-файла let mut reader = WavReader::open(input_path)?; let spec = reader.spec(); let samples: Vec<f32> = reader .samples::<i16>() .map(|s| s.map(|s| s as f32 / i16::MAX as f32)) .collect::<Result<_, _>>()?;
// Создание ресемплера с заданным коэффициентом растяжки let mut resampler = SincFixedIn::<f32>::new( 1.0 / factor, // коэффициент растяжки 2.0, rubato::ResamplerParams::default(), 1024, 1, )?;
// Разделение на каналы let channels = spec.channels as usize; let mut input_channels: Vec<Vec<f32>> = vec![Vec::new(); channels]; for (i, sample) in samples.iter().enumerate() { input_channels[i % channels].push(*sample); }
// Растяжка let mut output_channels: Vec<Vec<f32>> = vec![Vec::new(); channels]; let mut input_index = 0; while input_index + 1024 <= input_channels[0].len() { let input_frames: Vec<&[f32]> = input_channels .iter() .map(|ch| &ch[input_index..input_index + 1024]) .collect(); let output_frames = resampler.process(&input_frames, None)?; for (ch_idx, frame) in output_frames.iter().enumerate() { output_channels[ch_idx].extend_from_slice(frame); } input_index += 1024; }
// Запись результата let mut writer = WavWriter::create(output_path, spec)?; let max_len = output_channels.iter().map(|ch| ch.len()).min().unwrap(); for i in 0..max_len { for ch in 0..channels { let sample = (output_channels[ch][i] * i16::MAX as f32) as i16; writer.write_sample(sample)?; } } writer.finalize()?;
Ok(()) } ```
¶Пример: зерновой синтез (granular synthesis)
Для более качественного растяжения можно реализовать зерновой синтез:
```rust fn granular_stretch(samples: &[f32], factor: f64, grain_size: usize) -> Vec<f32> { let output_len = (samples.len() as f64 * factor) as usize; let mut output = vec![0.0f32; output_len]; let hop_size = (grain_size as f64 / factor) as usize;
let mut pos = 0usize; let mut out_pos = 0usize; while out_pos < output_len { let end = (pos + grain_size).min(samples.len()); let grain = &samples[pos..end]; let mut window = vec![0.0f32; grain.len()]; for (i, w) in window.iter_mut().enumerate() { w = 0.5 (1.0 - (2.0 std::f32::consts::PI i as f32 / grain.len() as f32).cos()); } for (i, s) in grain.iter().enumerate() { if out_pos + i < output_len { output[out_pos + i] += s * window[i]; } } out_pos += hop_size; pos = (pos + hop_size / factor as usize).min(samples.len().saturating_sub(grain_size)); } output } ```
¶Практические рекомендации
- Для растяжки без искажений рекомендуется использовать коэффициенты в диапазоне от 0.5 до 2.0.
- Зерновой синтез даёт лучшее качество при больших коэффициентах растяжки, но требует больше вычислительных ресурсов.
- При растяжке аудио с постоянным тоном (например, синусоиды) phase vocoder работает лучше, чем зерновой синтез.
- Для обработки речи и вокала лучше всего подходит алгоритм PSOLA.
¶Источники
- Книга «Designing Audio Effect Plugins in C++» — Роджер Джулия
- Документация библиотеки rubato
- Документация библиотеки hound
- Книга «The Audio Programming Book» — Бенджамин Фейст
