1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
extern crate libsoundio_sys as raw;

use super::error::*;
use super::format::*;
use super::sample::*;
use super::util::*;

use std::marker::PhantomData;
use std::os::raw::{c_double, c_int};
use std::ptr;
use std::slice;

/// This is called when an outstream needs to be written to. The `OutStreamUserData` struct is obtained
/// from the stream.userdata, then the user-supplied callback is called with an `OutStreamWriter`
/// object.
pub extern "C" fn outstream_write_callback(
    stream: *mut raw::SoundIoOutStream,
    frame_count_min: c_int,
    frame_count_max: c_int,
) {
    // Use stream.userdata to get a reference to the OutStreamUserData object.
    let raw_userdata_pointer = unsafe { (*stream).userdata as *mut OutStreamUserData };
    let userdata = unsafe { &mut (*raw_userdata_pointer) };

    let mut stream_writer = OutStreamWriter {
        outstream: userdata.outstream,
        frame_count_min: frame_count_min as _,
        frame_count_max: frame_count_max as _,
        write_started: false,
        channel_areas: Vec::new(),
        frame_count: 0,
        phantom: PhantomData,
    };

    (userdata.write_callback)(&mut stream_writer);
}

pub extern "C" fn outstream_underflow_callback(stream: *mut raw::SoundIoOutStream) {
    // Use stream.userdata to get a reference to the OutStreamUserData object.
    let raw_userdata_pointer = unsafe { (*stream).userdata as *mut OutStreamUserData };
    let userdata = unsafe { &mut (*raw_userdata_pointer) };

    if let Some(ref mut cb) = userdata.underflow_callback {
        cb();
    } else {
        println!("Underflow!");
    }
}

pub extern "C" fn outstream_error_callback(stream: *mut raw::SoundIoOutStream, err: c_int) {
    // Use stream.userdata to get a reference to the OutStreamUserData object.
    let raw_userdata_pointer = unsafe { (*stream).userdata as *mut OutStreamUserData };
    let userdata = unsafe { &mut (*raw_userdata_pointer) };

    if let Some(ref mut cb) = userdata.error_callback {
        cb(err.into());
    } else {
        println!("Error: {}", Error::from(err));
    }
}

/// OutStream represents an output stream for playback.
///
/// It is obtained from `Device` using `Device::open_outstream()` and
/// can be started and paused.
pub struct OutStream<'a> {
    pub userdata: Box<OutStreamUserData<'a>>,

    // This is just here to say that OutStream cannot outlive the Device it was created from.
    pub phantom: PhantomData<&'a ()>,
}

// The callbacks required for an outstream are stored in this object. We also store a pointer
// to the raw outstream so that it can be passed to `OutStreamWriter` in the write callback.
pub struct OutStreamUserData<'a> {
    pub outstream: *mut raw::SoundIoOutStream,

    pub write_callback: Box<dyn FnMut(&mut OutStreamWriter) + 'a>,
    pub underflow_callback: Option<Box<dyn FnMut() + 'a>>,
    pub error_callback: Option<Box<dyn FnMut(Error) + 'a>>,
}

impl<'a> Drop for OutStreamUserData<'a> {
    fn drop(&mut self) {
        unsafe {
            raw::soundio_outstream_destroy(self.outstream);
        }
    }
}

impl<'a> OutStream<'a> {
    /// Starts the stream, returning `Ok(())` if it started successfully. Once
    /// started the write callback will be periodically called according to the
    /// requested latency.
    ///
    /// `start()` should only ever be called once on an `OutStream`.
    /// Do not use `start()` to resume a stream after pausing it. Instead call `pause(false)`.
    ///
    /// This function might directly call the write callback.
    ///
    /// # Errors
    ///
    /// * `Error::Streaming`
    /// * `Error::NoMem`
    /// * `Error::SystemResources`
    /// * `Error::BackendDisconnected`
    ///
    pub fn start(&mut self) -> Result<()> {
        match unsafe { raw::soundio_outstream_start(self.userdata.outstream) } {
            0 => Ok(()),
            x => Err(x.into()),
        }
    }

    /// Clears the output stream buffer.
    ///
    /// This function can be called regardless of whether the outstream is paused
    /// or not. Some backends do not support clearing the buffer. On these backends this
    /// function will return `Error::IncompatibleBackend`.
    ///
    /// Some devices do not support clearing the buffer. On these devices this
    /// function might return `Error::IncompatibleDevice`.
    ///
    /// # Errors
    ///
    /// * `Error::Streaming`
    /// * `Error::IncompatibleBackend`
    /// * `Error::IncompatibleDevice`
    ///
    pub fn clear_buffer(&mut self) -> Result<()> {
        match unsafe { raw::soundio_outstream_clear_buffer(self.userdata.outstream) } {
            0 => Ok(()),
            e => Err(e.into()),
        }
    }

    // TODO: pause() can be called from the write callback, so add it to
    // OutStreamWriter.

    /// If the underlying backend and device support pausing, this pauses the
    /// stream. The `write_callback()` may be called a few more times if
    /// the buffer is not full.
    ///
    /// Pausing might put the hardware into a low power state which is ideal if your
    /// software is silent for some time.
    ///
    /// This should not be called before `start()`. Pausing when already paused or
    /// unpausing when already unpaused has no effect and returns `Ok(())`.
    ///
    /// # Errors
    ///
    /// * `Error::BackendDisconnected`
    /// * `Error::Streaming`
    /// * `Error::IncompatibleDevice` - device does not support
    ///    pausing/unpausing. This error code might not be returned even if the
    ///    device does not support pausing/unpausing.
    /// * `Error::IncompatibleBackend` - backend does not support
    ///    pausing/unpausing.
    /// * `Error::Invalid` - outstream not opened and started
    ///
    pub fn pause(&mut self, pause: bool) -> Result<()> {
        match unsafe { raw::soundio_outstream_pause(self.userdata.outstream, pause as i8) } {
            0 => Ok(()),
            e => Err(e.into()),
        }
    }

    /// Returns the stream format.
    pub fn format(&self) -> Format {
        unsafe { (*self.userdata.outstream).format.into() }
    }

    /// Sample rate is the number of frames per second.
    pub fn sample_rate(&self) -> i32 {
        unsafe { (*self.userdata.outstream).sample_rate as _ }
    }

    /// Ignoring hardware latency, this is the number of seconds it takes for
    /// the last sample in a full buffer to be played.
    /// After you call `Device::open_instream()`, this value is replaced with the
    /// actual software latency, as near to this value as possible.
    ///
    /// On systems that support clearing the buffer, this defaults to a large
    /// latency, potentially upwards of 2 seconds, with the understanding that
    /// you will call `clear_buffer()` when you want to reduce
    /// the latency to 0. On systems that do not support clearing the buffer,
    /// this defaults to a reasonable lower latency value.
    ///
    /// On backends with high latencies (such as 2 seconds), `frame_count_min`
    /// will be 0, meaning you don't have to fill the entire buffer. In this
    /// case, the large buffer is there if you want it; you only have to fill
    /// as much as you want. On backends like JACK, `frame_count_min` will be
    /// equal to `frame_count_max` and if you don't fill that many frames, you
    /// will get glitches.
    ///
    /// If the device has unknown software latency min and max values, you may
    /// still set this (in `Device::open_outstream()`), but you might not get
    /// the value you requested.
    /// For PulseAudio, if you set this value to non-default, it sets
    /// `PA_STREAM_ADJUST_LATENCY` and is the value used for `maxlength` and
    /// `tlength`.
    ///
    /// For JACK, this value is always equal to
    /// `Device::software_latency().current`.
    pub fn software_latency(&self) -> f64 {
        unsafe { (*self.userdata.outstream).software_latency as _ }
    }

    /// The name of the stream, which defaults to "SoundIoOutStream".
    ///
    /// PulseAudio uses this for the stream name.
    /// JACK uses this for the client name of the client that connects when you
    /// open the stream.
    /// WASAPI uses this for the session display name.
    /// Must not contain a colon (":").
    ///
    /// TODO: Currently there is no way to set this.
    pub fn name(&self) -> String {
        unsafe { utf8_to_string((*self.userdata.outstream).name) }
    }

    /// The number of bytes per frame, equal to the number of bytes
    /// per sample, multiplied by the number of channels.
    pub fn bytes_per_frame(&self) -> i32 {
        unsafe { (*self.userdata.outstream).bytes_per_frame as _ }
    }

    /// The number of bytes in a sample, e.g. 3 for `i24`.
    pub fn bytes_per_sample(&self) -> i32 {
        unsafe { (*self.userdata.outstream).bytes_per_sample as _ }
    }
}

/// `OutStreamWriter` is passed to the write callback and can be used to write to the stream.
///
/// You start by calling `begin_write()` then you can write the samples. When the `OutStreamWriter``
/// is dropped the write is committed. An error at that point is written to the console and ignored.
///
pub struct OutStreamWriter<'a> {
    outstream: *mut raw::SoundIoOutStream,
    frame_count_min: usize,
    frame_count_max: usize,

    write_started: bool,

    // The memory area to write to - one for each channel. Populated after begin_write()
    channel_areas: Vec<raw::SoundIoChannelArea>,
    // The actual frame count. Populated after begin_write()
    frame_count: usize,

    // This cannot outlive the scope that it is spawned from (in the write callback).
    phantom: PhantomData<&'a ()>,
}

impl<'a> OutStreamWriter<'a> {
    /// Start a write. You can only call this once per callback otherwise it panics.
    ///
    /// frame_count is the number of frames you want to write. It must be between
    /// frame_count_min and frame_count_max or `begin_write()` will panic.
    ///
    /// It returns the number of frames you must actually write. The returned value
    /// will always be less than or equal to the provided value.
    ///
    /// # Errors
    ///
    /// * `Error::Invalid`
    ///   * `frame_count` <= 0
    ///   * `frame_count` < `frame_count_min` or `frame_count` > `frame_count_max`
    ///   * function called too many times without respecting `frame_count_max`
    /// * `Error::Streaming`
    /// * `Error::Underflow` - an underflow caused this call to fail. You might
    ///   also get an `underflow_callback()`, and you might not get
    ///   this error code when an underflow occurs. Unlike `Error::Streaming`,
    ///   the outstream is still in a valid state and streaming can continue.
    /// * `Error::IncompatibleDevice` - in rare cases it might just now
    ///   be discovered that the device uses non-byte-aligned access, in which
    ///   case this error code is returned.
    ///
    pub fn begin_write(&mut self, frame_count: usize) -> Result<usize> {
        assert!(
            frame_count >= self.frame_count_min && frame_count <= self.frame_count_max,
            "frame_count out of range"
        );

        let mut areas: *mut raw::SoundIoChannelArea = ptr::null_mut();
        let mut actual_frame_count: c_int = frame_count as _;

        match unsafe {
            raw::soundio_outstream_begin_write(
                self.outstream,
                &mut areas as *mut _,
                &mut actual_frame_count as *mut _,
            )
        } {
            0 => {
                self.write_started = true;
                self.frame_count = actual_frame_count as _;
                // Return now if there's no frames to actually read.
                if actual_frame_count <= 0 {
                    return Ok(0);
                }
                let cc = self.channel_count();
                self.channel_areas = vec![
                    raw::SoundIoChannelArea {
                        ptr: ptr::null_mut(),
                        step: 0
                    };
                    cc
                ];
                unsafe {
                    self.channel_areas.copy_from_slice(slice::from_raw_parts::<
                        raw::SoundIoChannelArea,
                    >(areas, cc));
                }
                Ok(actual_frame_count as _)
            }
            e => Err(e.into()),
        }
    }

    /// Commits the write that you began with `begin_write()`.
    ///
    /// Errors are currently are just printed to the console and ignored.
    ///
    /// # Errors
    ///
    /// * `Error::Streaming`
    /// * `Error::Underflow` - an underflow caused this call to fail. You might
    ///   also get an `underflow_callback()`, and you might not get
    ///   this error code when an underflow occurs. Unlike `Error::Streaming`,
    ///   the outstream is still in a valid state and streaming can continue.
    pub fn end_write(&mut self) {
        if self.write_started {
            unsafe {
                match raw::soundio_outstream_end_write(self.outstream) {
                    0 => {
                        self.write_started = false;
                    }
                    x => println!("Error writing outstream: {}", Error::from(x)),
                }
            }
        }
    }

    /// Get the minimum frame count that you can call `begin_write()` with.
    /// Retreive this value before calling `begin_write()` to ensure you read the correct number
    /// of frames.
    pub fn frame_count_min(&self) -> usize {
        self.frame_count_min
    }

    /// Get the maximum frame count that you can call `begin_write()` with.
    /// Retreive this value before calling `begin_write()` to ensure you read the correct number
    /// of frames.
    pub fn frame_count_max(&self) -> usize {
        self.frame_count_max
    }

    /// Get the actual frame count that you did call `begin_write()` with. Panics if you haven't called
    /// `begin_write()` yet.
    pub fn frame_count(&self) -> usize {
        assert!(self.write_started);
        self.frame_count
    }

    /// Get latency due to software only, not including hardware.
    pub fn software_latency(&self) -> f64 {
        unsafe { (*self.outstream).software_latency as _ }
    }

    /// Return the number of channels in this stream. Guaranteed to be at least 1.
    pub fn channel_count(&self) -> usize {
        unsafe { (*self.outstream).layout.channel_count as _ }
    }

    /// Get the sample rate in Hertz.
    pub fn sample_rate(&self) -> i32 {
        unsafe { (*self.outstream).sample_rate as _ }
    }

    /// Obtain the total number of seconds that the next frame written after the
    /// last frame written from the write callback will take to become
    /// audible. This includes both software and hardware latency. In other words,
    /// if you call this function directly after dropping the `OutStreamWriter`,
    /// this gives you the number of seconds that the next frame written will take
    /// to become audible.
    ///
    /// # Errors
    ///
    /// * `Error::Streaming`
    ///
    pub fn get_latency(&mut self) -> Result<f64> {
        let mut x: c_double = 0.0;
        match unsafe { raw::soundio_outstream_get_latency(self.outstream, &mut x as *mut c_double) }
        {
            0 => Ok(x),
            e => Err(e.into()),
        }
    }

    /// Set the value of a sample/channel. This panics if the `channel` or `frame` are
    /// out of range or if you haven't called `begin_write()` yet.
    ///
    /// If you use a different type from the actual one it will be converted.
    ///
    /// # Examples
    ///
    /// ```
    /// fn write_callback(stream: &mut soundio::OutStreamWriter) {
    ///     let frame_count_max = stream.frame_count_max();
    ///     stream.begin_write(frame_count_max).unwrap();
    ///     for c in 0..stream.channel_count() {
    ///         for f in 0..stream.frame_count() {
    ///             stream.set_sample::<f32>(c, f, 0.0 as f32);
    ///         }
    ///     }
    /// }
    /// ```
    pub fn set_sample<T: Sample>(&mut self, channel: usize, frame: usize, sample: T) {
        assert!(self.write_started);

        assert!(channel < self.channel_count(), "Channel out of range");
        assert!(frame < self.frame_count(), "Frame out of range");

        unsafe {
            let ptr = self.channel_areas[channel]
                .ptr
                .add(frame * self.channel_areas[channel].step as usize)
                as *mut u8;

            match (*self.outstream).format {
                raw::SoundIoFormat::SoundIoFormatS8 => i8::to_raw_le(T::to_i8(sample), ptr),
                raw::SoundIoFormat::SoundIoFormatU8 => u8::to_raw_le(T::to_u8(sample), ptr),
                raw::SoundIoFormat::SoundIoFormatS16LE => i16::to_raw_le(T::to_i16(sample), ptr),
                raw::SoundIoFormat::SoundIoFormatS16BE => i16::to_raw_be(T::to_i16(sample), ptr),
                raw::SoundIoFormat::SoundIoFormatU16LE => u16::to_raw_le(T::to_u16(sample), ptr),
                raw::SoundIoFormat::SoundIoFormatU16BE => u16::to_raw_be(T::to_u16(sample), ptr),
                raw::SoundIoFormat::SoundIoFormatS24LE => i24::to_raw_le(T::to_i24(sample), ptr),
                raw::SoundIoFormat::SoundIoFormatS24BE => i24::to_raw_be(T::to_i24(sample), ptr),
                raw::SoundIoFormat::SoundIoFormatU24LE => u24::to_raw_le(T::to_u24(sample), ptr),
                raw::SoundIoFormat::SoundIoFormatU24BE => u24::to_raw_be(T::to_u24(sample), ptr),
                raw::SoundIoFormat::SoundIoFormatS32LE => i32::to_raw_le(T::to_i32(sample), ptr),
                raw::SoundIoFormat::SoundIoFormatS32BE => i32::to_raw_be(T::to_i32(sample), ptr),
                raw::SoundIoFormat::SoundIoFormatU32LE => u32::to_raw_le(T::to_u32(sample), ptr),
                raw::SoundIoFormat::SoundIoFormatU32BE => u32::to_raw_be(T::to_u32(sample), ptr),
                raw::SoundIoFormat::SoundIoFormatFloat32LE => {
                    f32::to_raw_le(T::to_f32(sample), ptr)
                }
                raw::SoundIoFormat::SoundIoFormatFloat32BE => {
                    f32::to_raw_be(T::to_f32(sample), ptr)
                }
                raw::SoundIoFormat::SoundIoFormatFloat64LE => {
                    f64::to_raw_le(T::to_f64(sample), ptr)
                }
                raw::SoundIoFormat::SoundIoFormatFloat64BE => {
                    f64::to_raw_be(T::to_f64(sample), ptr)
                }
                _ => panic!("Unknown format"),
            }
        }
    }

    // TODO: To acheive speed *and* safety I can use iterators. That will be in a future API.
}

impl<'a> Drop for OutStreamWriter<'a> {
    /// This will drop all of the frames from when you called `begin_write()`.
    ///
    /// Errors are currently are just printed to the console and ignored.
    ///
    /// # Errors
    ///
    /// * `Error::Streaming`
    fn drop(&mut self) {
        if self.write_started {
            unsafe {
                match raw::soundio_outstream_end_write(self.outstream) {
                    0 => {}
                    x => println!("Error writing outstream: {}", Error::from(x)),
                }
            }
        }
    }
}