12 releases (6 stable)

1.2.2 Jan 7, 2024
1.2.0 Dec 30, 2023
1.1.1 Sep 13, 2023
1.1.0 Jul 12, 2023
0.1.1 Oct 18, 2021

#45 in Audio

Download history 180/week @ 2024-07-21 285/week @ 2024-07-28 606/week @ 2024-08-04 451/week @ 2024-08-11 200/week @ 2024-08-18 449/week @ 2024-08-25 311/week @ 2024-09-01 767/week @ 2024-09-08 389/week @ 2024-09-15 433/week @ 2024-09-22 324/week @ 2024-09-29 264/week @ 2024-10-06 119/week @ 2024-10-13 173/week @ 2024-10-20 134/week @ 2024-10-27 295/week @ 2024-11-03

740 downloads per month
Used in 5 crates (via web-audio-api)

MIT/Apache

125KB
2K SLoC

🏞️ creek 🏞️

Documentation Crates.io License

Realtime-safe streaming to/from audio files on disk.

The included decoder uses Symphonia. Refer to Symphonia's documentation for supported codecs. Symphonia's Cargo features are exposed with the prefix decode-, except aac and isomp4 which creek does not work with yet. For example, to enable MP3 decoding in creek, enable the decode-mp3 feature.

The included encoder only supports the WAV format.

How the Read Stream Works

how it works

The stream internally has two types of buffers: a cache buffer and look-ahead buffer.

A cache buffer is a pre-loaded user-defined range of samples in the file.

The stream can have as many cache buffers as desired. A common use case for this is to cache the start of a file or loop region for seamless looping. When seeking to a frame in the file, creek searches if there exists a cache that contains that frame. If one exists, then playback can resume immediately without buffering.

The look-ahead buffer is used to automatically load frames ahead of the playhead, ensuring that data will always be ready even in a worse-case IO latency scenario.

If a suitable cache is not found (or the cache is not yet loaded), then the look-ahead buffer will need to be filled first before any more data can be read. When that happens, you may choose to either continue playback (which will output silence), or temporarily pause playback until data is available.

Creek automatically spawns an "IO server" thread that handles the non-realtime operations. This server is automatically closed when the stream is dropped.

How the Write Stream Works

The write stream works how you would expect. Once a block is filled with data, it is sent to the IO server thread to be written. Once the server thread is done with that block, it sends it back to the write stream to be reused.

Examples

Simple Usage Example

use creek::{
    SymphoniaDecoder, SeekMode, ReadDiskStream,
    WriteDiskStream, WavEncoder, wav_bit_depth
};

// Open a read stream.


let mut read_disk_stream = ReadDiskStream::<SymphoniaDecoder>::new(
    "./test_files/wav_f32_stereo.wav",  // Path to file.
    0,  // The frame in the file to start reading from.
    Default::default(),  // Use default read stream options.
).unwrap();

// Cache the start of the file into cache with index `0`.
let _ = read_disk_stream.cache(0, 0);

// Tell the stream to seek to the beginning of file. This will also alert the stream to the existence
// of the cache with index `0`.
read_disk_stream.seek(0, Default::default()).unwrap();

// Wait until the buffer is filled before sending it to the process thread.
//
// NOTE: Do ***not*** use this method in a real-time thread.
read_disk_stream.block_until_ready().unwrap();

// (Send `read_stream` to the audio processing thread)


// Open a write stream.


WriteDiskStream::<WavEncoder<wav_bit_depth::Float32>>::new(
    "./test_files/wav_f32_stereo_out.wav",  // Path to file.
    2,  // The number of channels in the file
    44100,  // The sample rate of the file
    Default::default(),  // Use default write stream options.
).unwrap();

// (Send `write_stream` to the audio processing thread)


// -------------------------------------------------------------


// In the realtime audio processing thread:


// Update read client and check if it is ready.
//
// NOTE: You should avoid using `unwrap()` in realtime code.
if !read_disk_stream.is_ready().unwrap() {
    // If the look-ahead buffer is still buffering, We can choose to either continue
    // reading (which will return silence), or pause playback until the buffer is filled.
}

let read_data = read_disk_stream.read(num_frames_in_output_buffer).unwrap();

println!("{}", read_data.num_frames());
println!("{}", read_data.num_channels());

// Seek to a new position in the file.
read_disk_stream.seek(50000, SeekMode::Auto};

assert_eq!(read_dist_stream.playhead(), 50000);


// Send stereo data to be written to disk.

write_disk_stream.write(
    &[read_data.read_channel(0), read_data.read_channel(1)]
).unwrap();

Demos

  • A basic player app that plays a single wav file with adjustable loop regions.
  • A basic writer app that records a tone to a wav file.

Contributing

We use various pre-commit hooks to ensure that the code style and formatting is consistent.

Follow the installation instructions on how to set up pre-commit for your development system. This allows you to run the checks locally before committing code and waiting for the CI results:

pre-commit run --all-files

Git

Conventions for working with Git:

  • Commits should follow the seven rules.
  • Versions are tagged with the prefix v followed by the crate's version number to match those displayed on the crates.io page.

Dependencies

~0–0.8MB
~23K SLoC