Skip to main content

WavWriter

Struct WavWriter 

Source
pub struct WavWriter<W: Write + Seek> { /* private fields */ }

Implementations§

Source§

impl<W: Write + Seek> WavWriter<W>

Source

pub fn new(sink: W, sample_rate: u32) -> Result<Self>

Begin a file, writing a provisional header.

§Errors

If the provisional header cannot be written.

Source

pub fn new_trimming_tail(sink: W, sample_rate: u32) -> Result<Self>

As WavWriter::new, but drops the model’s end-of-utterance noise burst.

See trailing_noise_samples for what is removed and why it is safe. The cost is that the last quarter second is held in memory until WavWriter::finish, so this is for file output only; a raw PCM stream must keep its latency and stays untrimmed.

§Errors

If the provisional header cannot be written.

Examples found in repository?
examples/tail_writer_probe.rs (line 26)
6fn main() {
7    let path = std::env::args()
8        .nth(1)
9        .expect("usage: tail_writer_probe <wav>");
10    let bytes = std::fs::read(&path).expect("readable wav");
11    let pcm: Vec<f32> = bytes[44..]
12        .as_chunks::<2>()
13        .0
14        .iter()
15        .map(|pair| f32::from(i16::from_le_bytes(*pair)) / 32_767.0)
16        .collect();
17
18    let offline = ftts_core::audio::trailing_noise_samples(&pcm, 24_000);
19    let level = ftts_core::audio::speech_level(&pcm, 24_000);
20    println!("input samples      : {}", pcm.len());
21    println!("offline detector   : {offline} samples");
22    println!("utterance level    : {level:.5}");
23
24    // Exactly the CLI's packetization.
25    let mut writer =
26        ftts_core::audio::WavWriter::new_trimming_tail(Cursor::new(Vec::new()), 24_000)
27            .expect("header");
28    for packet in pcm.chunks(1_920) {
29        writer.write_samples(packet).expect("write");
30    }
31    let out = writer.finish().expect("finish").into_inner();
32    let written = (out.len() - 44) / 2;
33    println!(
34        "writer wrote       : {written} samples (trimmed {})",
35        pcm.len() - written
36    );
37
38    // What the detector sees when handed only the tail, which is the writer's situation.
39    let hold = 24_000 * 250 / 1000;
40    let tail = &pcm[pcm.len().saturating_sub(hold)..];
41    println!(
42        "tail-only relative : {} samples (tail len {})",
43        ftts_core::audio::trailing_noise_samples_relative_to(tail, 24_000, level),
44        tail.len()
45    );
46}
Source

pub fn write_samples(&mut self, pcm: &[f32]) -> Result<()>

Append one packet of decoded f32 samples.

§Errors

If the sink rejects the write. The count of samples already accepted stays accurate, so a later WavWriter::finish still describes the file truthfully.

Examples found in repository?
examples/tail_writer_probe.rs (line 29)
6fn main() {
7    let path = std::env::args()
8        .nth(1)
9        .expect("usage: tail_writer_probe <wav>");
10    let bytes = std::fs::read(&path).expect("readable wav");
11    let pcm: Vec<f32> = bytes[44..]
12        .as_chunks::<2>()
13        .0
14        .iter()
15        .map(|pair| f32::from(i16::from_le_bytes(*pair)) / 32_767.0)
16        .collect();
17
18    let offline = ftts_core::audio::trailing_noise_samples(&pcm, 24_000);
19    let level = ftts_core::audio::speech_level(&pcm, 24_000);
20    println!("input samples      : {}", pcm.len());
21    println!("offline detector   : {offline} samples");
22    println!("utterance level    : {level:.5}");
23
24    // Exactly the CLI's packetization.
25    let mut writer =
26        ftts_core::audio::WavWriter::new_trimming_tail(Cursor::new(Vec::new()), 24_000)
27            .expect("header");
28    for packet in pcm.chunks(1_920) {
29        writer.write_samples(packet).expect("write");
30    }
31    let out = writer.finish().expect("finish").into_inner();
32    let written = (out.len() - 44) / 2;
33    println!(
34        "writer wrote       : {written} samples (trimmed {})",
35        pcm.len() - written
36    );
37
38    // What the detector sees when handed only the tail, which is the writer's situation.
39    let hold = 24_000 * 250 / 1000;
40    let tail = &pcm[pcm.len().saturating_sub(hold)..];
41    println!(
42        "tail-only relative : {} samples (tail len {})",
43        ftts_core::audio::trailing_noise_samples_relative_to(tail, 24_000, level),
44        tail.len()
45    );
46}
Source

pub const fn samples_written(&self) -> usize

Samples accepted so far.

Source

pub const fn duration_millis(&self) -> u64

Duration of the audio written so far, in milliseconds.

Source

pub fn finish(self) -> Result<W>

Patch the header to the real length and flush.

§Errors

If seeking back to the header, rewriting it, or flushing fails.

Examples found in repository?
examples/tail_writer_probe.rs (line 31)
6fn main() {
7    let path = std::env::args()
8        .nth(1)
9        .expect("usage: tail_writer_probe <wav>");
10    let bytes = std::fs::read(&path).expect("readable wav");
11    let pcm: Vec<f32> = bytes[44..]
12        .as_chunks::<2>()
13        .0
14        .iter()
15        .map(|pair| f32::from(i16::from_le_bytes(*pair)) / 32_767.0)
16        .collect();
17
18    let offline = ftts_core::audio::trailing_noise_samples(&pcm, 24_000);
19    let level = ftts_core::audio::speech_level(&pcm, 24_000);
20    println!("input samples      : {}", pcm.len());
21    println!("offline detector   : {offline} samples");
22    println!("utterance level    : {level:.5}");
23
24    // Exactly the CLI's packetization.
25    let mut writer =
26        ftts_core::audio::WavWriter::new_trimming_tail(Cursor::new(Vec::new()), 24_000)
27            .expect("header");
28    for packet in pcm.chunks(1_920) {
29        writer.write_samples(packet).expect("write");
30    }
31    let out = writer.finish().expect("finish").into_inner();
32    let written = (out.len() - 44) / 2;
33    println!(
34        "writer wrote       : {written} samples (trimmed {})",
35        pcm.len() - written
36    );
37
38    // What the detector sees when handed only the tail, which is the writer's situation.
39    let hold = 24_000 * 250 / 1000;
40    let tail = &pcm[pcm.len().saturating_sub(hold)..];
41    println!(
42        "tail-only relative : {} samples (tail len {})",
43        ftts_core::audio::trailing_noise_samples_relative_to(tail, 24_000, level),
44        tail.len()
45    );
46}
Source

pub fn finish_reporting(self) -> Result<(W, usize)>

As WavWriter::finish, also reporting how many samples the file actually contains.

Callers that publish a sample count need this rather than their own tally: tail trimming (and any short write) makes “samples handed to the writer” differ from “samples in the file”, and a reported count that describes audio the file does not hold is a false number in a machine-readable stream.

§Errors

If flushing the held tail, seeking back to the header, rewriting it, or flushing fails.

Trait Implementations§

Source§

impl<W: Write + Seek> Drop for WavWriter<W>

Source§

fn drop(&mut self)

Best-effort finalisation for a writer dropped without WavWriter::finish.

A dropped writer means an abnormal end — a panic, an early return, a cancelled run. Leaving the provisional zero-length header would make the file claim it contains no audio while holding a megabyte of it. The error is deliberately swallowed because Drop cannot report, which is exactly why finish exists and should be called explicitly.

Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. Read more

Auto Trait Implementations§

§

impl<W> Freeze for WavWriter<W>
where W: Freeze,

§

impl<W> RefUnwindSafe for WavWriter<W>
where W: RefUnwindSafe,

§

impl<W> Send for WavWriter<W>
where W: Send,

§

impl<W> Sync for WavWriter<W>
where W: Sync,

§

impl<W> Unpin for WavWriter<W>
where W: Unpin,

§

impl<W> UnsafeUnpin for WavWriter<W>
where W: UnsafeUnpin,

§

impl<W> UnwindSafe for WavWriter<W>
where W: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, _span: NoopSpan) -> Self

Instruments this future with a span (no-op when disabled).
Source§

fn in_current_span(self) -> Self

Instruments this future with the current span (no-op when disabled).
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V