@@ -63,6 +63,184 @@ pub trait AudioOutputWriter {
6363 ) ;
6464}
6565
66+ /// A typed view of an interleaved output buffer for a single callback.
67+ ///
68+ /// This type is internal and exists to support backend callback adapters.
69+ #[ allow( dead_code) ]
70+ pub ( crate ) enum AudioOutputBuffer < ' buffer > {
71+ /// Interleaved `f32` samples.
72+ F32 ( & ' buffer mut [ f32 ] ) ,
73+ /// Interleaved `i16` samples.
74+ I16 ( & ' buffer mut [ i16 ] ) ,
75+ /// Interleaved `u16` samples.
76+ U16 ( & ' buffer mut [ u16 ] ) ,
77+ }
78+
79+ impl < ' buffer > AudioOutputBuffer < ' buffer > {
80+ #[ allow( dead_code) ]
81+ fn len ( & self ) -> usize {
82+ match self {
83+ Self :: F32 ( buffer) => {
84+ return buffer. len ( ) ;
85+ }
86+ Self :: I16 ( buffer) => {
87+ return buffer. len ( ) ;
88+ }
89+ Self :: U16 ( buffer) => {
90+ return buffer. len ( ) ;
91+ }
92+ }
93+ }
94+
95+ fn sample_format ( & self ) -> AudioSampleFormat {
96+ match self {
97+ Self :: F32 ( _) => {
98+ return AudioSampleFormat :: F32 ;
99+ }
100+ Self :: I16 ( _) => {
101+ return AudioSampleFormat :: I16 ;
102+ }
103+ Self :: U16 ( _) => {
104+ return AudioSampleFormat :: U16 ;
105+ }
106+ }
107+ }
108+ }
109+
110+ /// An [`AudioOutputWriter`] implementation for interleaved buffers.
111+ ///
112+ /// This type is internal and exists to support backend callback adapters.
113+ #[ allow( dead_code) ]
114+ pub ( crate ) struct InterleavedAudioOutputWriter < ' buffer > {
115+ channels : u16 ,
116+ frames : usize ,
117+ buffer : AudioOutputBuffer < ' buffer > ,
118+ }
119+
120+ impl < ' buffer > InterleavedAudioOutputWriter < ' buffer > {
121+ #[ allow( dead_code) ]
122+ pub fn new ( channels : u16 , buffer : AudioOutputBuffer < ' buffer > ) -> Self {
123+ let channels_usize = channels as usize ;
124+ let frames = if channels_usize == 0 {
125+ 0
126+ } else {
127+ buffer. len ( ) / channels_usize
128+ } ;
129+
130+ return Self {
131+ channels,
132+ frames,
133+ buffer,
134+ } ;
135+ }
136+
137+ #[ allow( dead_code) ]
138+ pub fn sample_format ( & self ) -> AudioSampleFormat {
139+ return self . buffer . sample_format ( ) ;
140+ }
141+ }
142+
143+ #[ allow( dead_code) ]
144+ fn clamp_normalized_sample ( sample : f32 ) -> f32 {
145+ if sample > 1.0 {
146+ return 1.0 ;
147+ }
148+
149+ if sample < -1.0 {
150+ return -1.0 ;
151+ }
152+
153+ return sample;
154+ }
155+
156+ impl < ' buffer > AudioOutputWriter for InterleavedAudioOutputWriter < ' buffer > {
157+ fn channels ( & self ) -> u16 {
158+ return self . channels ;
159+ }
160+
161+ fn frames ( & self ) -> usize {
162+ return self . frames ;
163+ }
164+
165+ fn clear ( & mut self ) {
166+ match & mut self . buffer {
167+ AudioOutputBuffer :: F32 ( buffer) => {
168+ buffer. fill ( 0.0 ) ;
169+ return ;
170+ }
171+ AudioOutputBuffer :: I16 ( buffer) => {
172+ buffer. fill ( 0 ) ;
173+ return ;
174+ }
175+ AudioOutputBuffer :: U16 ( buffer) => {
176+ buffer. fill ( 32768 ) ;
177+ return ;
178+ }
179+ }
180+ }
181+
182+ fn set_sample (
183+ & mut self ,
184+ frame_index : usize ,
185+ channel_index : usize ,
186+ sample : f32 ,
187+ ) {
188+ let channels = self . channels as usize ;
189+ if channels == 0 {
190+ return ;
191+ }
192+
193+ if channel_index >= channels {
194+ if cfg ! ( all( debug_assertions, not( test) ) ) {
195+ eprintln ! (
196+ "audio: set_sample channel_index out of range (channel_index={channel_index} channels={channels})"
197+ ) ;
198+ }
199+ return ;
200+ }
201+
202+ if frame_index >= self . frames {
203+ if cfg ! ( all( debug_assertions, not( test) ) ) {
204+ eprintln ! (
205+ "audio: set_sample frame_index out of range (frame_index={frame_index} frames={})" ,
206+ self . frames
207+ ) ;
208+ }
209+ return ;
210+ }
211+
212+ let sample_index = frame_index * channels + channel_index;
213+ if sample_index >= self . buffer . len ( ) {
214+ if cfg ! ( all( debug_assertions, not( test) ) ) {
215+ eprintln ! (
216+ "audio: set_sample buffer index out of range (sample_index={sample_index} len={})" ,
217+ self . buffer. len( )
218+ ) ;
219+ }
220+ return ;
221+ }
222+
223+ let sample = clamp_normalized_sample ( sample) ;
224+
225+ match & mut self . buffer {
226+ AudioOutputBuffer :: F32 ( buffer) => {
227+ buffer[ sample_index] = sample;
228+ return ;
229+ }
230+ AudioOutputBuffer :: I16 ( buffer) => {
231+ let scaled = ( sample * 32767.0 ) . round ( ) ;
232+ buffer[ sample_index] = scaled as i16 ;
233+ return ;
234+ }
235+ AudioOutputBuffer :: U16 ( buffer) => {
236+ let scaled = ( ( sample + 1.0 ) * 0.5 * 65535.0 ) . round ( ) ;
237+ buffer[ sample_index] = scaled as u16 ;
238+ return ;
239+ }
240+ }
241+ }
242+ }
243+
66244/// Metadata describing an available audio output device.
67245#[ derive( Clone , Debug , PartialEq , Eq ) ]
68246pub struct AudioDeviceInfo {
@@ -329,4 +507,82 @@ mod tests {
329507 }
330508 }
331509 }
510+
511+ #[ test]
512+ fn writer_clear_sets_silence_for_all_formats ( ) {
513+ let mut buffer_f32 = [ 1.0 , -1.0 , 0.5 , -0.5 ] ;
514+ let mut writer = InterleavedAudioOutputWriter :: new (
515+ 2 ,
516+ AudioOutputBuffer :: F32 ( & mut buffer_f32) ,
517+ ) ;
518+ writer. clear ( ) ;
519+ assert_eq ! ( buffer_f32, [ 0.0 , 0.0 , 0.0 , 0.0 ] ) ;
520+
521+ let mut buffer_i16 = [ 1 , -1 , 200 , -200 ] ;
522+ let mut writer = InterleavedAudioOutputWriter :: new (
523+ 2 ,
524+ AudioOutputBuffer :: I16 ( & mut buffer_i16) ,
525+ ) ;
526+ writer. clear ( ) ;
527+ assert_eq ! ( buffer_i16, [ 0 , 0 , 0 , 0 ] ) ;
528+
529+ let mut buffer_u16 = [ 0 , 1 , 65535 , 12345 ] ;
530+ let mut writer = InterleavedAudioOutputWriter :: new (
531+ 2 ,
532+ AudioOutputBuffer :: U16 ( & mut buffer_u16) ,
533+ ) ;
534+ writer. clear ( ) ;
535+ assert_eq ! ( buffer_u16, [ 32768 , 32768 , 32768 , 32768 ] ) ;
536+ }
537+
538+ #[ test]
539+ fn writer_set_sample_clamps_and_converts ( ) {
540+ let mut buffer_f32 = [ 0.0 , 0.0 , 0.0 , 0.0 ] ;
541+ let mut writer = InterleavedAudioOutputWriter :: new (
542+ 2 ,
543+ AudioOutputBuffer :: F32 ( & mut buffer_f32) ,
544+ ) ;
545+ writer. set_sample ( 0 , 0 , 2.0 ) ;
546+ writer. set_sample ( 0 , 1 , -2.0 ) ;
547+ assert_eq ! ( buffer_f32[ 0 ] , 1.0 ) ;
548+ assert_eq ! ( buffer_f32[ 1 ] , -1.0 ) ;
549+
550+ let mut buffer_i16 = [ 0 , 0 , 0 , 0 ] ;
551+ let mut writer = InterleavedAudioOutputWriter :: new (
552+ 2 ,
553+ AudioOutputBuffer :: I16 ( & mut buffer_i16) ,
554+ ) ;
555+ writer. set_sample ( 0 , 0 , 1.0 ) ;
556+ writer. set_sample ( 0 , 1 , -1.0 ) ;
557+ writer. set_sample ( 1 , 0 , 0.0 ) ;
558+ assert_eq ! ( buffer_i16[ 0 ] , 32767 ) ;
559+ assert_eq ! ( buffer_i16[ 1 ] , -32767 ) ;
560+ assert_eq ! ( buffer_i16[ 2 ] , 0 ) ;
561+
562+ let mut buffer_u16 = [ 0 , 0 , 0 , 0 ] ;
563+ let mut writer = InterleavedAudioOutputWriter :: new (
564+ 2 ,
565+ AudioOutputBuffer :: U16 ( & mut buffer_u16) ,
566+ ) ;
567+ writer. set_sample ( 0 , 0 , -1.0 ) ;
568+ writer. set_sample ( 0 , 1 , 0.0 ) ;
569+ writer. set_sample ( 1 , 0 , 1.0 ) ;
570+ assert_eq ! ( buffer_u16[ 0 ] , 0 ) ;
571+ assert_eq ! ( buffer_u16[ 1 ] , 32768 ) ;
572+ assert_eq ! ( buffer_u16[ 2 ] , 65535 ) ;
573+ }
574+
575+ #[ test]
576+ fn writer_set_sample_is_noop_for_out_of_range_indices ( ) {
577+ let mut buffer_f32 = [ 0.25 , 0.25 , 0.25 , 0.25 ] ;
578+ let mut writer = InterleavedAudioOutputWriter :: new (
579+ 2 ,
580+ AudioOutputBuffer :: F32 ( & mut buffer_f32) ,
581+ ) ;
582+
583+ writer. set_sample ( 10 , 0 , 1.0 ) ;
584+ writer. set_sample ( 0 , 10 , 1.0 ) ;
585+
586+ assert_eq ! ( buffer_f32, [ 0.25 , 0.25 , 0.25 , 0.25 ] ) ;
587+ }
332588}
0 commit comments