|
| 1 | +use hound::WavReader; |
| 2 | +use mp4m::{AudioProcessor, AudioProcessorConfigBuilder, OutputDestination, sample_rate}; |
| 3 | +use rand::Rng; |
| 4 | + |
| 5 | +fn main() -> Result<(), Box<dyn std::error::Error>> { |
| 6 | + env_logger::init(); |
| 7 | + |
| 8 | + let input_file1 = "data/speaker-mono.wav"; |
| 9 | + let input_file2 = "data/input.wav"; |
| 10 | + let output_file = "data/tmp/diff-audio-mixed.wav"; |
| 11 | + |
| 12 | + log::debug!("Reading WAV files: {}, {}", input_file1, input_file2); |
| 13 | + |
| 14 | + let mut reader1 = WavReader::open(input_file1)?; |
| 15 | + let mut reader2 = WavReader::open(input_file2)?; |
| 16 | + |
| 17 | + let spec1 = reader1.spec(); |
| 18 | + let spec2 = reader2.spec(); |
| 19 | + |
| 20 | + log::debug!("Audio specs - File 1: {:?}", spec1); |
| 21 | + log::debug!("Audio specs - File 2: {:?}", spec2); |
| 22 | + |
| 23 | + let all_samples1: Vec<f32> = match spec1.sample_format { |
| 24 | + hound::SampleFormat::Float => reader1.samples::<f32>().collect::<Result<Vec<f32>, _>>()?, |
| 25 | + hound::SampleFormat::Int => reader1 |
| 26 | + .samples::<i16>() |
| 27 | + .map(|s| s.map(|v| v as f32)) |
| 28 | + .collect::<Result<Vec<f32>, _>>()?, |
| 29 | + }; |
| 30 | + let all_samples2: Vec<f32> = match spec2.sample_format { |
| 31 | + hound::SampleFormat::Float => reader2.samples::<f32>().collect::<Result<Vec<f32>, _>>()?, |
| 32 | + hound::SampleFormat::Int => reader2 |
| 33 | + .samples::<i16>() |
| 34 | + .map(|s| s.map(|v| v as f32)) |
| 35 | + .collect::<Result<Vec<f32>, _>>()?, |
| 36 | + }; |
| 37 | + |
| 38 | + log::debug!("Total samples - File 1: {}", all_samples1.len()); |
| 39 | + log::debug!("Total samples - File 2: {}", all_samples2.len()); |
| 40 | + |
| 41 | + let max_samples = all_samples1.len().max(all_samples2.len()); |
| 42 | + log::debug!("Max samples across files: {}", max_samples); |
| 43 | + |
| 44 | + let samples_per_ms1 = (spec1.sample_rate as f32 / 1000.0) as usize * spec1.channels as usize; |
| 45 | + let samples_per_ms2 = (spec2.sample_rate as f32 / 1000.0) as usize * spec2.channels as usize; |
| 46 | + |
| 47 | + let min_chunk_samples1 = (500.0 * samples_per_ms1 as f32) as usize; |
| 48 | + let max_chunk_samples1 = (1000.0 * samples_per_ms1 as f32) as usize; |
| 49 | + let min_chunk_samples2 = (500.0 * samples_per_ms2 as f32) as usize; |
| 50 | + let max_chunk_samples2 = (1000.0 * samples_per_ms2 as f32) as usize; |
| 51 | + |
| 52 | + log::debug!( |
| 53 | + "Track 1 - Samples per ms: {} ({} channels)", |
| 54 | + samples_per_ms1, |
| 55 | + spec1.channels |
| 56 | + ); |
| 57 | + log::debug!( |
| 58 | + "Track 1 - Min chunk samples (500ms): {}", |
| 59 | + min_chunk_samples1 |
| 60 | + ); |
| 61 | + log::debug!( |
| 62 | + "Track 1 - Max chunk samples (1000ms): {}", |
| 63 | + max_chunk_samples1 |
| 64 | + ); |
| 65 | + log::debug!( |
| 66 | + "Track 2 - Samples per ms: {} ({} channels)", |
| 67 | + samples_per_ms2, |
| 68 | + spec2.channels |
| 69 | + ); |
| 70 | + log::debug!( |
| 71 | + "Track 2 - Min chunk samples (500ms): {}", |
| 72 | + min_chunk_samples2 |
| 73 | + ); |
| 74 | + log::debug!( |
| 75 | + "Track 2 - Max chunk samples (1000ms): {}", |
| 76 | + max_chunk_samples2 |
| 77 | + ); |
| 78 | + |
| 79 | + let config = AudioProcessorConfigBuilder::default() |
| 80 | + .target_sample_rate(sample_rate::CD) |
| 81 | + .convert_to_mono(true) |
| 82 | + .output_destination(Some(OutputDestination::File(output_file.into()))) |
| 83 | + .build()?; |
| 84 | + |
| 85 | + let mut processor = AudioProcessor::new(config); |
| 86 | + let sender1 = processor.add_track(spec1); |
| 87 | + let sender2 = processor.add_track(spec2); |
| 88 | + |
| 89 | + let mut rng = rand::rng(); |
| 90 | + let mut processed_samples1 = 0; |
| 91 | + let mut processed_samples2 = 0; |
| 92 | + |
| 93 | + while processed_samples1 < all_samples1.len() || processed_samples2 < all_samples2.len() { |
| 94 | + let remaining_samples1 = all_samples1.len() - processed_samples1; |
| 95 | + let remaining_samples2 = all_samples2.len() - processed_samples2; |
| 96 | + |
| 97 | + let chunk_size1 = if remaining_samples1 == 0 { |
| 98 | + 0 |
| 99 | + } else if remaining_samples1 < min_chunk_samples1 { |
| 100 | + remaining_samples1 |
| 101 | + } else { |
| 102 | + rng.random_range(min_chunk_samples1..=max_chunk_samples1.min(remaining_samples1)) |
| 103 | + }; |
| 104 | + |
| 105 | + let chunk_size2 = if remaining_samples2 == 0 { |
| 106 | + 0 |
| 107 | + } else if remaining_samples2 < min_chunk_samples2 { |
| 108 | + remaining_samples2 |
| 109 | + } else { |
| 110 | + rng.random_range(min_chunk_samples2..=max_chunk_samples2.min(remaining_samples2)) |
| 111 | + }; |
| 112 | + |
| 113 | + let chunk1 = if chunk_size1 > 0 { |
| 114 | + let end = processed_samples1 + chunk_size1; |
| 115 | + &all_samples1[processed_samples1..end] |
| 116 | + } else { |
| 117 | + &[] |
| 118 | + }; |
| 119 | + |
| 120 | + let chunk2 = if chunk_size2 > 0 { |
| 121 | + let end = processed_samples2 + chunk_size2; |
| 122 | + &all_samples2[processed_samples2..end] |
| 123 | + } else { |
| 124 | + &[] |
| 125 | + }; |
| 126 | + |
| 127 | + log::debug!( |
| 128 | + "Processing chunks - Track1: {} samples, Track2: {} samples", |
| 129 | + chunk1.len(), |
| 130 | + chunk2.len() |
| 131 | + ); |
| 132 | + |
| 133 | + if !chunk1.is_empty() { |
| 134 | + sender1.send(chunk1.to_vec())?; |
| 135 | + processed_samples1 += chunk_size1; |
| 136 | + } |
| 137 | + if !chunk2.is_empty() { |
| 138 | + sender2.send(chunk2.to_vec())?; |
| 139 | + processed_samples2 += chunk_size2; |
| 140 | + } |
| 141 | + |
| 142 | + processor.process_samples()?; |
| 143 | + |
| 144 | + std::thread::sleep(std::time::Duration::from_millis(10)); |
| 145 | + } |
| 146 | + |
| 147 | + processor.flush()?; |
| 148 | + |
| 149 | + log::debug!("Two-track audio mixing completed!"); |
| 150 | + log::debug!("Mixed output saved to: {}", output_file); |
| 151 | + |
| 152 | + Ok(()) |
| 153 | +} |
0 commit comments