stt-cli 0.2.1

Speech to text Cli using Groq API and OpenAI API
// src/transcription/mod.rs
//
// This module handles the transcription of audio data to text.
// It processes audio chunks, converts them to the appropriate format,
// and sends them to the transcription provider.

pub mod result_handler;

use crate::config;
use anyhow::{Context, Result};
use std::io::Cursor;
use std::time::Duration;
use tokio::sync::Mutex;
use tracing::{error, info, instrument, trace, warn};

use crate::audio::buffer::{CHANNELS, SAMPLE_RATE};
use crate::providers::TranscriptionProvider;

/// Convert raw audio samples to WAV format
#[instrument(skip(samples))]
pub fn convert_samples_to_wav(samples: &[f32]) -> Result<Vec<u8>> {
    if samples.is_empty() {
        return Ok(Vec::new());
    }

    trace!("Converting {} samples to WAV format", samples.len());

    let mut cursor = Cursor::new(Vec::new());

    // Create WAV writer with appropriate specifications
    {
        let spec = hound::WavSpec {
            channels: CHANNELS,
            sample_rate: SAMPLE_RATE,
            bits_per_sample: 32,
            sample_format: hound::SampleFormat::Float,
        };

        // Reserve space for the WAV data
        cursor.get_mut().reserve(samples.len() * 4 + 44);

        // Create WAV writer and write samples
        let mut writer =
            hound::WavWriter::new(&mut cursor, spec).context("Failed to create WAV writer")?;

        for &sample in samples {
            writer
                .write_sample(sample)
                .context("Failed to write sample")?;
        }

        writer.finalize().context("Failed to finalize WAV")?;
    }

    let wav_data = cursor.into_inner();
    trace!("WAV conversion complete: {} bytes", wav_data.len());

    Ok(wav_data)
}

#[cfg(test)]
mod tests {
    use super::*;
    use hound::WavReader;
    use std::time::Duration;

    /// Test converting audio samples to WAV format
    #[test]
    fn test_convert_samples_to_wav_basic() {
        // Create a simple sine wave at 440Hz
        let sample_rate = 16000;
        let duration_secs = 1.0;
        let frequency = 440.0; // A4 note

        let num_samples = (sample_rate as f32 * duration_secs) as usize;
        let mut samples = Vec::with_capacity(num_samples);

        for i in 0..num_samples {
            let t = i as f32 / sample_rate as f32;
            let sample = (t * frequency * 2.0 * std::f32::consts::PI).sin();
            samples.push(sample);
        }

        // Convert to WAV
        let wav_data = convert_samples_to_wav(&samples).unwrap();

        // Verify WAV data is not empty
        assert!(!wav_data.is_empty());

        // Verify WAV header (44 bytes) + data
        assert!(wav_data.len() > 44);

        // Verify WAV data size matches expected size
        // Each sample is 4 bytes (32-bit float) + 44 bytes for header
        let expected_size = samples.len() * 4 + 44;
        assert_eq!(wav_data.len(), expected_size);

        // Verify we can read it back as a valid WAV file
        let cursor = std::io::Cursor::new(&wav_data);
        let reader = WavReader::new(cursor).unwrap();

        // Verify WAV specs
        let spec = reader.spec();
        assert_eq!(spec.channels, CHANNELS);
        assert_eq!(spec.sample_rate, SAMPLE_RATE);
        assert_eq!(spec.bits_per_sample, 32);
        assert_eq!(spec.sample_format, hound::SampleFormat::Float);
    }

    /// Test converting empty samples to WAV format
    #[test]
    fn test_convert_samples_to_wav_empty() {
        let empty: Vec<f32> = vec![];
        let wav_data = convert_samples_to_wav(&empty).unwrap();

        // Should return empty vec for empty input
        assert!(wav_data.is_empty());
    }

    /// Test converting large audio samples to WAV format
    #[test]
    fn test_convert_samples_to_wav_large() {
        // Create 5 seconds of audio at 16kHz
        let sample_rate = 16000;
        let duration_secs = 5.0;
        let num_samples = (sample_rate as f32 * duration_secs) as usize;

        let samples = vec![0.5; num_samples];

        // Convert to WAV
        let wav_data = convert_samples_to_wav(&samples).unwrap();

        // Verify WAV data size matches expected size
        // Each sample is 4 bytes (32-bit float) + 44 bytes for header
        let expected_size = samples.len() * 4 + 44;
        assert_eq!(wav_data.len(), expected_size);

        // This should be large enough for the 5-second minimum
        assert!(wav_data.len() > 16000 * 2 * 5);
    }

    /// Test the full audio processing pipeline with simulated chunks
    #[test]
    fn test_audio_processing_pipeline() {
        // This is a basic test to verify the pipeline components work together
        // In a real integration test, we would mock the provider and test the full flow

        // Create some audio data (3 seconds)
        let sample_rate = 16000;
        let duration_secs = 3.0;
        let num_samples = (sample_rate as f32 * duration_secs) as usize;
        let samples = vec![0.5; num_samples];

        // Convert to WAV
        let wav_data = convert_samples_to_wav(&samples).unwrap();

        // Verify WAV data is large enough for the 5-second minimum
        // assert!(wav_data.len() > 16000 * 2 * 5);
    }
}