Открыть сервисСервис

Растяг в расте

Растяг — это техника растяжения звука, при которой длительность ноты или семпла увеличивается без изменения тональности, за счёт повторного воспроизведения отдельных фрагментов аудио. В расте (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» — Бенджамин Фейст
Заметили ошибку или не согласны с информацией в статье? Напишите нам support@bfometr.ru