11use std:: env;
2- use std:: f32:: consts:: PI ;
2+ use std:: f32:: consts:: TAU ;
3+ use std:: io:: Write ;
34use std:: process;
5+ use std:: vec:: Vec ;
46
57static SUPPORTED_SAMPLE_RATES : [ u32 ; 3 ] = [
68 16_000 , // 16 kHz is commonly used for speech and telephony applications
@@ -43,15 +45,54 @@ impl SampleWidth {
4345 }
4446}
4547
48+ // https://ccrma.stanford.edu/courses/422-winter-2014/projects/WaveFormat/
49+ #[ repr( C , packed) ]
50+ #[ allow( dead_code) ]
51+ struct WavHeader {
52+ chunk_id : [ u8 ; 4 ] , // 0
53+ chunk_size : u32 , //4
54+ format : [ u8 ; 4 ] , //8
55+ subchunk_1_id : [ u8 ; 4 ] , //12
56+ subchunk_1_size : u32 , // 16
57+ audio_format : u16 , // 20
58+ num_channels : u16 , // 22
59+ sample_rate : u32 , // 24
60+ byte_rate : u32 , // 28
61+ block_align : u16 , // 32
62+ bits_per_sample : u16 , // 34
63+ subchunk_2_id : [ u8 ; 4 ] , //36
64+ subchunk_2_size : u32 , //40
65+ }
66+
67+ impl WavHeader {
68+ pub fn new ( ) -> Self {
69+ Self {
70+ chunk_id : * b"RIFF" ,
71+ chunk_size : 0 ,
72+ format : * b"WAVE" ,
73+ subchunk_1_id : * b"fmt " ,
74+ subchunk_1_size : 16 ,
75+ audio_format : 0x0001 , //WINDOWS PCM
76+ num_channels : 1 ,
77+ sample_rate : 44_100 ,
78+ byte_rate : 176_400 ,
79+ block_align : 2 ,
80+ bits_per_sample : 16 ,
81+ subchunk_2_id : * b"data" ,
82+ subchunk_2_size : 0 ,
83+ }
84+ }
85+ }
86+
4687// Get the maximum absolute value for a given sample width.
4788// Digital Audio Representation:
4889/*
4990|----------|-----------------------------|------------------|
50- | Format | Integer Type | Max Positive |
91+ | Format | Integer Type | Max Positive |
5192|----------|-----------------------------|------------------|
52- | 16-bit | int16_t | 32767 |
53- | 24-bit | int32_t (in 24 bits) | 8,388,607 |
54- | 32-bit | int32_t | 2,147,483,647 |
93+ | 16-bit | int16_t | 32767 |
94+ | 24-bit | int32_t (in 24 bits) | 8,388,607 |
95+ | 32-bit | int32_t | 2,147,483,647 |
5596|----------|-----------------------------|------------------|
5697*/
5798fn get_range ( sample_width : SampleWidth ) -> f32 {
@@ -79,6 +120,7 @@ enum OutputFormat {
79120 RustArray ,
80121 RawBytes ,
81122 Info ,
123+ WavFile ,
82124}
83125
84126impl OutputFormat {
@@ -89,12 +131,13 @@ impl OutputFormat {
89131 "rustarray" | "rust" => Some ( OutputFormat :: RustArray ) ,
90132 "raw" | "bytes" => Some ( OutputFormat :: RawBytes ) ,
91133 "info" => Some ( OutputFormat :: Info ) ,
134+ "wav" => Some ( OutputFormat :: WavFile ) ,
92135 _ => None ,
93136 }
94137 }
95138}
96139
97- fn print_usage ( ) {
140+ fn print_usage ( ) {
98141 println ! ( "Usage: singen [OPTIONS]" ) ;
99142 println ! ( ) ;
100143 println ! ( "Options:" ) ;
@@ -109,6 +152,7 @@ fn print_usage() {
109152 println ! ( " carray - C-style array declaration" ) ;
110153 println ! ( " rustarray - Rust array declaration" ) ;
111154 println ! ( " raw - Raw binary bytes (stdout)" ) ;
155+ println ! ( " wav - Windows audio file format (stdout)" ) ;
112156 println ! ( " info - Only show buffer info, no data" ) ;
113157 println ! ( " -a, --analyze Analyze only (don't generate data)" ) ;
114158 println ! ( " -h, --help Show this help message" ) ;
@@ -219,44 +263,47 @@ fn parse_args() -> Config {
219263 config
220264}
221265
222- // Create a sine wave audio buffer for a given frequency, sample rate, channel count, and sample width.
223- fn create_sine_array (
224- freq : f32 ,
225- sample_rate : f32 ,
226- channel_count : u8 ,
227- sample_width : SampleWidth ,
228- duration_ms : f32 ,
229- ) -> ( Vec < u8 > , usize , usize ) {
230- // Calculate the phase increment for each sample and the total number of samples needed for the specified duration
231- let mut phase: f32 = 0.0 ;
232- let phase_inc: f32 = freq / sample_rate * 2.0 * PI ; // Radians per sample
233- let total_samples: usize = ( ( duration_ms * sample_rate) / 1000.0 ) . round ( ) as usize ; // Number of samples in the specified duration
234-
235- // If USB packet mode, adjust to fit 64-byte packets
236- let bytes_per_sample = sample_width as usize ;
237- let bytes_per_frame = bytes_per_sample * channel_count as usize ;
238- let total_bytes = total_samples * bytes_per_frame;
239-
240- // Pre-allocate buffer with the total number of bytes needed for the sine wave
241- let mut buffer = Vec :: with_capacity ( total_bytes) ;
242- let max_value = get_range ( sample_width) ;
243-
244- // Fill buffer with sine wave
245- for _ in 0 ..total_samples {
246- let sample = ( phase. sin ( ) * max_value) as i32 ;
247- let bytes = sample. to_le_bytes ( ) ;
248-
249- for _ in 0 ..channel_count {
250- for n in 0 ..sample_width as usize {
251- buffer. push ( bytes[ n] ) ;
266+ /// Generate a linear chirp from `f0` Hz to `f1` Hz over `duration_secs`.
267+ /// Returns a vector of floating‑point samples in the range [-1.0, 1.0].
268+ fn generate_linear_chirp (
269+ f0 : f32 , // start frequency (Hz)
270+ f1 : f32 , // end frequency (Hz)
271+ sample_rate : f32 , // samples per second
272+ duration_secs : f32 , // total duration in seconds
273+ ) -> Vec < f32 > {
274+ let dt = 1.0 / sample_rate;
275+ let num_samples = ( duration_secs * sample_rate) . round ( ) as usize ;
276+ let mut samples = Vec :: with_capacity ( num_samples) ;
277+ let mut phase = 0.0 ;
278+
279+ for i in 0 ..num_samples {
280+ let t = i as f32 * dt;
281+ // Instantaneous frequency at time t (linear interpolation)
282+ let freq = f0 + ( f1 - f0) * ( t / duration_secs) ;
283+ // Phase increment for this sample
284+ phase += TAU * freq * dt;
285+ // Keep phase in [-π, π] range to avoid floating-point drift (optional)
286+ phase = phase. rem_euclid ( TAU ) ;
287+ samples. push ( phase. sin ( ) ) ;
288+ }
289+
290+ samples
291+ }
292+
293+ fn float_samples_to_bytes ( samples : & [ f32 ] , channels : u8 , sample_width : SampleWidth ) -> Vec < u8 > {
294+ let max_val = get_range ( sample_width) ;
295+ let mut buffer = Vec :: with_capacity ( samples. len ( ) * channels as usize * sample_width as usize ) ;
296+
297+ for & sample in samples {
298+ let scaled = ( sample * max_val) . round ( ) as i32 ;
299+ let bytes = scaled. to_le_bytes ( ) ;
300+ for _ in 0 ..channels {
301+ for b in & bytes[ 0 ..sample_width as usize ] {
302+ buffer. push ( * b) ;
252303 }
253304 }
254-
255- // Keep phase reset when it exceeds 2PI to prevent discontinuities at the reset point
256- phase = ( phase + phase_inc) % ( 2.0 * PI ) ;
257305 }
258-
259- ( buffer, total_samples, total_bytes)
306+ buffer
260307}
261308
262309fn print_buffer_info ( config : & Config , total_samples : usize , total_bytes : usize ) {
@@ -287,7 +334,7 @@ fn print_buffer_info(config: &Config, total_samples: usize, total_bytes: usize)
287334 println ! (
288335 " Full cycles: {:.2}" ,
289336 total_samples as f32 / period_samples
290- ) ;
337+ ) ;
291338}
292339
293340fn print_buffer_hex ( buffer : & [ u8 ] , bytes_per_line : usize ) {
@@ -393,16 +440,47 @@ fn print_raw_bytes(buffer: &[u8]) {
393440 handle. write_all ( buffer) . unwrap ( ) ;
394441}
395442
443+ fn create_wav_file_array (
444+ buffer : & [ u8 ] ,
445+ sample_rate : u32 ,
446+ channels : u16 ,
447+ sample_width : SampleWidth ,
448+ ) -> Vec < u8 > {
449+ let wav_header_len = std:: mem:: size_of :: < WavHeader > ( ) ;
450+ let buffer_len = buffer. len ( ) ;
451+
452+ let mut wav_hdr = WavHeader :: new ( ) ;
453+ wav_hdr. chunk_size = ( 36 + buffer_len) as u32 ; // 4 + (24) + 8 + buffer_len
454+ wav_hdr. num_channels = channels;
455+ wav_hdr. sample_rate = sample_rate;
456+ wav_hdr. byte_rate = sample_rate as u32 * channels as u32 * sample_width as u32 ;
457+ wav_hdr. block_align = channels * sample_width as u16 ; // fixed formula
458+ wav_hdr. bits_per_sample = sample_width as u16 * 8 ;
459+ wav_hdr. subchunk_2_size = buffer_len as u32 ;
460+
461+ let mut file = Vec :: with_capacity ( wav_header_len + buffer_len) ;
462+ let ptr = & wav_hdr as * const WavHeader as * const u8 ;
463+ // SAFETY: WavHeader is repr(C, packed) so it has no padding.
464+ file. write_all ( unsafe { std:: slice:: from_raw_parts ( ptr, wav_header_len) } )
465+ . unwrap ( ) ;
466+ file. write_all ( buffer) . unwrap ( ) ;
467+ file
468+ }
469+
396470fn main ( ) {
397471 let config = parse_args ( ) ;
398472
399- let ( buffer, total_samples, total_bytes) = create_sine_array (
473+ let total_samples =
474+ ( ( config. duration_ms * config. sample_rate as f32 ) / 1000.0 ) . round ( ) as usize ;
475+ let total_bytes = total_samples * ( config. sample_width as u8 * config. channels ) as usize ;
476+
477+ let float_samples = generate_linear_chirp (
478+ config. frequency ,
400479 config. frequency ,
401480 config. sample_rate as f32 ,
402- config. channels ,
403- config. sample_width ,
404- config. duration_ms ,
481+ config. duration_ms / 1000.0 ,
405482 ) ;
483+ let buffer = float_samples_to_bytes ( & float_samples, config. channels , config. sample_width ) ;
406484
407485 match config. output_format {
408486 OutputFormat :: Info => {
@@ -426,5 +504,14 @@ fn main() {
426504 OutputFormat :: RawBytes => {
427505 print_raw_bytes ( & buffer) ;
428506 }
507+ OutputFormat :: WavFile => {
508+ let file = create_wav_file_array (
509+ & buffer,
510+ config. sample_rate ,
511+ config. channels as u16 ,
512+ config. sample_width ,
513+ ) ;
514+ print_raw_bytes ( file. as_ref ( ) ) ;
515+ }
429516 }
430517}
0 commit comments