scirs2-signal 0.1.0-rc.2

Signal processing module for SciRS2 (scirs2-signal)
Documentation
// Example demonstrating the use of the phase vocoder for time stretching and pitch shifting
//
// This example generates a chirp signal and applies different time stretching
// and pitch shifting operations using the phase vocoder.

use scirs2_signal::phase_vocoder::{phase_vocoder, PhaseVocoderConfig};
use std::f64::consts::PI;

#[allow(dead_code)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
    println!("Phase Vocoder Example for Time Stretching and Pitch Shifting");
    println!("=========================================================");

    // Parameters for the test signal
    let sample_rate = 44100; // 44.1 kHz
    let duration = 1.0; // 1 second
    let samples = (sample_rate as f64 * duration) as usize;

    println!("Generating a chirp signal (440 Hz to 880 Hz)");
    println!(
        "Sample rate: {} Hz, Duration: {} seconds",
        sample_rate, duration
    );

    // Generate a chirp signal that goes from 440 Hz to 880 Hz
    let signal = generate_chirp(samples, 440.0, 880.0, sample_rate as f64);

    // Create output directory if it doesn't exist
    std::fs::create_dir_all("output").unwrap_or(());

    // Time stretching examples
    println!("\nApplying time stretching with different factors:");

    let stretch_factors = [0.5, 1.5, 2.0];

    for &factor in &stretch_factors {
        println!("  - Time stretch factor: {:.1}", factor);

        // Create a phase vocoder configuration
        let config = PhaseVocoderConfig {
            time_stretch: factor,
            window_size: 2048,
            hop_size: 512,
            ..Default::default()
        };

        // Apply phase vocoder
        let result = phase_vocoder(&signal, &config)?;

        let new_duration = duration * factor;
        println!("    Output duration: {:.2} seconds", new_duration);
        println!("    Output samples: {}", result.len());

        // Calculate signal statistics
        let rms = calculate_rms(&result);
        println!("    Output RMS: {:.4}", rms);
    }

    // Pitch shifting examples
    println!("\nApplying pitch shifting with different semitones:");

    let pitch_shifts = [-12.0, 7.0, 12.0]; // -1 octave, perfect fifth, +1 octave

    for &semitones in &pitch_shifts {
        let note_name = semitone_to_note_name(semitones);
        println!(
            "  - Pitch shift: {:.1} semitones ({})",
            semitones, note_name
        );

        // Create a phase vocoder configuration
        let config = PhaseVocoderConfig {
            time_stretch: 1.0, // No time stretching
            pitch_shift: Some(semitones),
            window_size: 2048,
            hop_size: 512,
            ..Default::default()
        };

        // Apply phase vocoder
        let result = phase_vocoder(&signal, &config)?;

        println!("    Output samples: {}", result.len());

        // Calculate signal statistics
        let rms = calculate_rms(&result);
        println!("    Output RMS: {:.4}", rms);
    }

    // Combined time stretching and pitch shifting example
    println!("\nCombining time stretching and pitch shifting:");

    let config = PhaseVocoderConfig {
        time_stretch: 1.5,      // 50% longer
        pitch_shift: Some(7.0), // Perfect fifth up
        window_size: 2048,
        hop_size: 512,
        ..Default::default()
    };

    // Apply phase vocoder
    let result = phase_vocoder(&signal, &config)?;

    println!(
        "  - Time stretch: {:.1}x, Pitch shift: +7 semitones (perfect fifth)",
        config.time_stretch
    );
    println!(
        "    Output duration: {:.2} seconds",
        duration * config.time_stretch
    );
    println!("    Output samples: {}", result.len());

    // Calculate signal statistics
    let rms = calculate_rms(&result);
    println!("    Output RMS: {:.4}", rms);

    // Formant preservation example
    println!("\nPitch shifting with formant preservation:");

    let config = PhaseVocoderConfig {
        time_stretch: 1.0,
        pitch_shift: Some(12.0), // One octave up
        preserve_formants: true,
        window_size: 2048,
        hop_size: 512,
        ..Default::default()
    };

    // Apply phase vocoder
    let result = phase_vocoder(&signal, &config)?;

    println!("  - Pitch shift: +12 semitones (octave) with formant preservation");
    println!("    Output samples: {}", result.len());

    // Calculate signal statistics
    let rms = calculate_rms(&result);
    println!("    Output RMS: {:.4}", rms);

    println!("\nNote: In a real application, you would save the processed audio to a file");
    println!("      or play it through an audio output device.");

    Ok(())
}

/// Generate a chirp signal (frequency sweep)
#[allow(dead_code)]
fn generate_chirp(_samples: usize, start_freq: f64, end_freq: f64, samplerate: f64) -> Vec<f64> {
    let mut signal = Vec::with_capacity(_samples);

    for i in 0.._samples {
        let t = i as f64 / sample_rate;
        let duration = _samples as f64 / sample_rate;

        // Linear frequency sweep
        let _freq = start_freq + (end_freq - start_freq) * t / duration;

        // Calculate phase (integral of frequency)
        let phase =
            2.0 * PI * (start_freq * t + (end_freq - start_freq) * t * t / (2.0 * duration));

        signal.push(phase.sin());
    }

    signal
}

/// Calculate the RMS (Root Mean Square) value of a signal
#[allow(dead_code)]
fn calculate_rms(signal: &[f64]) -> f64 {
    let sum_squared: f64 = signal.iter().map(|&x| x * x).sum();
    (sum_squared / signal.len() as f64).sqrt()
}

/// Convert a semitone shift to a musical note name
#[allow(dead_code)]
fn semitone_to_note_name(semitones: f64) -> String {
    if _semitones == 0.0 {
        return "unison".to_string();
    }

    let semitones_int = semitones.round() as i32;

    match semitones_int.abs() % 12 {
        0 => format!("{} octave", if semitones_int > 0 { "+" } else { "-" }),
        1 => format!(
            "minor 2nd {}",
            if semitones_int > 0 { "up" } else { "down" }
        ),
        2 => format!(
            "major 2nd {}",
            if semitones_int > 0 { "up" } else { "down" }
        ),
        3 => format!(
            "minor 3rd {}",
            if semitones_int > 0 { "up" } else { "down" }
        ),
        4 => format!(
            "major 3rd {}",
            if semitones_int > 0 { "up" } else { "down" }
        ),
        5 => format!(
            "perfect 4th {}",
            if semitones_int > 0 { "up" } else { "down" }
        ),
        6 => format!("tritone {}", if semitones_int > 0 { "up" } else { "down" }),
        7 => format!(
            "perfect 5th {}",
            if semitones_int > 0 { "up" } else { "down" }
        ),
        8 => format!(
            "minor 6th {}",
            if semitones_int > 0 { "up" } else { "down" }
        ),
        9 => format!(
            "major 6th {}",
            if semitones_int > 0 { "up" } else { "down" }
        ),
        10 => format!(
            "minor 7th {}",
            if semitones_int > 0 { "up" } else { "down" }
        ),
        11 => format!(
            "major 7th {}",
            if semitones_int > 0 { "up" } else { "down" }
        ),
        _ => unreachable!(),
    }
}