18 Commits

Author SHA1 Message Date
Jamie Hardt
3315f00a89 Update CONTRIBUTING.md 2026-05-03 12:09:29 -07:00
Jamie Hardt
6819f63c74 Create CONTRIBUTING.md 2026-05-03 12:01:39 -07:00
1c83094888 Nudged version 2026-05-03 11:01:29 -07:00
977c8263d2 Silencing clippy errors 2026-05-03 10:44:31 -07:00
da4bad0e87 Fixed a WAVEFORMATEX cb_size typo 2026-05-03 10:30:27 -07:00
c89a98f7e4 Fixed a parser bug
Parsing was failing for malformed wav files
where the final chunk was not padded to a
word boundary.
2026-05-03 10:29:12 -07:00
Jamie Hardt
8e341990fa Rustfmt 2024-12-10 17:41:50 -08:00
Jamie Hardt
2dfddff0b5 Update Cargo.toml
Nudge version
2023-06-02 21:04:12 -07:00
Jamie Hardt
aa8365a38d Documentation 2023-06-02 20:37:44 -07:00
Jamie Hardt
b8a428e757 Documentation and exposure
Documentation in the fmt module has been improved.

ReadWaveData trait is now public.

The
2023-06-02 11:06:53 -07:00
Jamie Hardt
c1d2b2c836 Documentation fixes. Made a trait public. 2023-06-02 09:53:23 -07:00
Jamie Hardt
f41b7ea575 Update lib.rs 2023-06-02 08:53:27 -07:00
Jamie Hardt
9a62bdc375 Update issue templates 2023-06-02 08:47:38 -07:00
Jamie Hardt
368ef4366d Delete bwavefile.code-workspace 2023-06-02 08:42:55 -07:00
Jamie Hardt
bfa51a4e4c Update issue templates 2023-06-02 08:42:27 -07:00
Jamie Hardt
4270dc9866 Merge pull request #16 from iluvcapra/release
Committing lock file
2023-05-26 22:31:27 -07:00
Jamie Hardt
92d76289e4 Update README.md 2023-05-26 12:29:28 -07:00
Jamie Hardt
15f9a240c0 Committing lock file 2023-05-22 18:04:53 -07:00
14 changed files with 203 additions and 53 deletions

28
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,28 @@
---
name: Bug report
about: Create a report to help us improve
title: "[Bug] __Enter a title here__"
labels: bug
assignees: iluvcapra
---
**Describe the bug**
A clear and concise description of what the bug is.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Example Code**:
```rust
// Give an example of code that is failing here.
```
**Platform**:
- OS
- `rustc` version: ____ (from `rustup -V`)
- Host: ____ (from `rustup show` e.g. x86_64-apple-darwin)
- Rust toolchain: ____ (e.g. stable-x86_64-apple-darwin)
**Additional context**
Add any other context about the problem here.

View File

@@ -0,0 +1,25 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[Feature] __Name the Feature__"
labels: enhancement
assignees: iluvcapra
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like:**
A clear and concise description of what you want to happen.
**Code Example:**
```rust
// Give an example of how you would like your feature to work.
```
**Describe alternatives you've considered:**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context:**
Add any other context or screenshots about the feature request here.

64
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,64 @@
# Contributing
Contributions to `bwavfile` are welcome!
This project is currently in a maintenance phase, new features are entertained if we
believe there is some demand for them but for the most part our current priorities
are:
* Addressing bugs.
* Keeping the codebase modern as the Rust language evolves.
This being said, there are some features that we've been wanting for some time and
new contributors are welcome to take a swing at these:
* Reading `levl` metadata for the generation of waveform overviews.
* Reading `smpl` metadata for reading sampler data, note assignments, loops etc.
## Adding New Features
If you are adding a large amount of new functionality, please weigh the amount of
work you're doing against the potential benefit, the burden on the maintainers to
review the work, and the technical debt incurred.
In general,
* new features should address new technologies; do not expend large amounts of
effort to implement features that are primarily of historical interest.
* new features should address the needs of professional users in a music or media
production environment.
Features that implement new reading functionality must, when submitted, include
test WAV files created empirically by third-party software.
## Regarding use of Agents
`bwavfile` is an open-source project that is offered free for no commerical gain, and
is developed and maintained for educational and creative reasons.
If you use an agent or LLM to produce code for it you are missing out on the benefits
of contributing to an open-source project, particularly community, collaboration with
other developers and designers, and being able to learn and experiment without the
burden of deadlines or worrying about business cases or profits.
This project is supposed to be fun, do not let machines have fun for you.
We can't prevent you from using LLMs to contribute to this project but we ask you
abide by the following eitiquette when doing so:
* All communication with the maintainers must be written by a human in their own
voice. Never use an LLM to craft thread comments, discussion posts, issues, emails
or other correspondence with other developers or the maintainers.
* PRs must be submitted by a person. Do not allow an agent to submit its own PRs to
this project.
* Especially if you are a new contributor to this project, please submit only one PR
at a time and please restrict the subject matter of the PR to a specific unit,
module or tool. All submissions have to be reviewed and understood by the
maintainers before they can be merged.
Obviously we can't verify if you follow all of these rules but certain telltale
traits of LLM-predicted text or code will raise a flag: lack of brevity in
descriptions or code comments, large amounts of text describing your process or
steps that add little to understanding the changes you've made, use of an
obsequious tone or being excessively accomodating, immediately doing requests
without further discussion or clarifications.

4
Cargo.lock generated
View File

@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 4
[[package]] [[package]]
name = "ansi_term" name = "ansi_term"
@@ -30,7 +30,7 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bwavfile" name = "bwavfile"
version = "1.1.0" version = "2.0.2"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"clap", "clap",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "bwavfile" name = "bwavfile"
version = "2.0.0" version = "2.0.2"
authors = ["Jamie Hardt <jamiehardt@me.com>", "Ian Hobson <ian.r.hobson@gmail.com>"] authors = ["Jamie Hardt <jamiehardt@me.com>", "Ian Hobson <ian.r.hobson@gmail.com>"]
edition = "2018" edition = "2018"
license = "MIT" license = "MIT"

View File

@@ -19,12 +19,12 @@ reading and creating new wave audio files.
* Unpacked reading and writing of Integer PCM and IEEE float audio data * Unpacked reading and writing of Integer PCM and IEEE float audio data
formats. formats.
* A unified interface for standard `WaveFormat` and extended `WaveFormatEx` * A unified interface for standard `WaveFormat` and extended `WaveFormatEx`
wave data format specification. wave data format.
The library has extensive metadata support, with emphasis on film and video The library has extensive metadata support, with emphasis on film and video
production metadata: production metadata:
* Broadcast-Wave metdata extension, including long description, originator * Broadcast-Wave metadata extension, including long description, originator,
information, SMPTE UMID and coding history. SMPTE UMID and coding history.
* Reading and writing of embedded iXML and axml/ADM metadata. * Reading and writing of embedded iXML and axml/ADM metadata.
* Reading and writing of timed cues and and timed cue regions. * Reading and writing of timed cues and and timed cue regions.
* Multichannel, surround, and ambisonic audio data description including * Multichannel, surround, and ambisonic audio data description including

View File

@@ -1,8 +0,0 @@
{
"folders": [
{
"path": "."
}
],
"settings": {}
}

View File

@@ -37,7 +37,7 @@ where
self.write_u16::<LittleEndian>(format.block_alignment)?; self.write_u16::<LittleEndian>(format.block_alignment)?;
self.write_u16::<LittleEndian>(format.bits_per_sample)?; self.write_u16::<LittleEndian>(format.bits_per_sample)?;
if let Some(ext) = format.extended_format { if let Some(ext) = format.extended_format {
let cb_size = 24u16; let cb_size = 22u16;
self.write_u16::<LittleEndian>(cb_size)?; self.write_u16::<LittleEndian>(cb_size)?;
self.write_u16::<LittleEndian>(ext.valid_bits_per_sample)?; self.write_u16::<LittleEndian>(ext.valid_bits_per_sample)?;
self.write_u32::<LittleEndian>(ext.channel_mask)?; self.write_u32::<LittleEndian>(ext.channel_mask)?;

View File

@@ -1,8 +1,16 @@
/// Format tags, UUIDs and utilities
use uuid::Uuid; use uuid::Uuid;
/// Format tag for integer LPCM
pub const WAVE_TAG_PCM: u16 = 0x0001; pub const WAVE_TAG_PCM: u16 = 0x0001;
/// Format tag for float LPCM
pub const WAVE_TAG_FLOAT: u16 = 0x0003; pub const WAVE_TAG_FLOAT: u16 = 0x0003;
/// Format tag for MPEG1
pub const WAVE_TAG_MPEG: u16 = 0x0050; pub const WAVE_TAG_MPEG: u16 = 0x0050;
/// Format tag indicating extended format
pub const WAVE_TAG_EXTENDED: u16 = 0xFFFE; pub const WAVE_TAG_EXTENDED: u16 = 0xFFFE;
/* RC 2361 §4: /* RC 2361 §4:
@@ -15,34 +23,38 @@ pub const WAVE_TAG_EXTENDED: u16 = 0xFFFE;
*/ */
/// Extended format UUID for integer PCM
pub const WAVE_UUID_PCM: Uuid = Uuid::from_bytes([ pub const WAVE_UUID_PCM: Uuid = Uuid::from_bytes([
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71,
]); ]);
/// Extended format UUID for float PCM
pub const WAVE_UUID_FLOAT: Uuid = Uuid::from_bytes([ pub const WAVE_UUID_FLOAT: Uuid = Uuid::from_bytes([
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71,
]); ]);
/// Extended format UUID for MPEG1 data
pub const WAVE_UUID_MPEG: Uuid = Uuid::from_bytes([ pub const WAVE_UUID_MPEG: Uuid = Uuid::from_bytes([
0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71,
]); ]);
/// Extended format for integer Ambisonic B-Format
pub const WAVE_UUID_BFORMAT_PCM: Uuid = Uuid::from_bytes([ pub const WAVE_UUID_BFORMAT_PCM: Uuid = Uuid::from_bytes([
0x01, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1, 0xca, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1, 0xca, 0x00, 0x00, 0x00,
]); ]);
/// Extended format for float Ambisonic B-Format
pub const WAVE_UUID_BFORMAT_FLOAT: Uuid = Uuid::from_bytes([ pub const WAVE_UUID_BFORMAT_FLOAT: Uuid = Uuid::from_bytes([
0x03, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1, 0xca, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1, 0xca, 0x00, 0x00, 0x00,
]); ]);
/// Generate an extended format UUID for the given basic format tag from [WaveFmt::tag].
fn uuid_from_basic_tag(tag: u16) -> Uuid { fn uuid_from_basic_tag(tag: u16) -> Uuid {
let tail: [u8; 6] = [0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71]; let tail: [u8; 6] = [0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71];
Uuid::from_fields_le(tag as u32, 0x0000, 0x0010, &tail).unwrap() Uuid::from_fields_le(tag as u32, 0x0000, 0x0010, &tail).unwrap()
} }
/// Sample format of the Wave file. /// Sample format of the Wave file.
///
///
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]
pub enum CommonFormat { pub enum CommonFormat {
/// Integer linear PCM /// Integer linear PCM
@@ -77,7 +89,9 @@ impl CommonFormat {
(WAVE_TAG_EXTENDED, Some(WAVE_UUID_PCM)) => Self::IntegerPCM, (WAVE_TAG_EXTENDED, Some(WAVE_UUID_PCM)) => Self::IntegerPCM,
(WAVE_TAG_EXTENDED, Some(WAVE_UUID_FLOAT)) => Self::IeeeFloatPCM, (WAVE_TAG_EXTENDED, Some(WAVE_UUID_FLOAT)) => Self::IeeeFloatPCM,
(WAVE_TAG_EXTENDED, Some(WAVE_UUID_BFORMAT_PCM)) => Self::AmbisonicBFormatIntegerPCM, (WAVE_TAG_EXTENDED, Some(WAVE_UUID_BFORMAT_PCM)) => Self::AmbisonicBFormatIntegerPCM,
(WAVE_TAG_EXTENDED, Some(WAVE_UUID_BFORMAT_FLOAT)) => Self::AmbisonicBFormatIeeeFloatPCM, (WAVE_TAG_EXTENDED, Some(WAVE_UUID_BFORMAT_FLOAT)) => {
Self::AmbisonicBFormatIeeeFloatPCM
}
(WAVE_TAG_EXTENDED, Some(x)) => CommonFormat::UnknownExtended(x), (WAVE_TAG_EXTENDED, Some(x)) => CommonFormat::UnknownExtended(x),
(x, _) => CommonFormat::UnknownBasic(x), (x, _) => CommonFormat::UnknownBasic(x),
} }

View File

@@ -1,3 +1,5 @@
#![allow(dead_code)]
use crate::common_format::{CommonFormat, WAVE_UUID_BFORMAT_PCM, WAVE_UUID_PCM}; use crate::common_format::{CommonFormat, WAVE_UUID_BFORMAT_PCM, WAVE_UUID_PCM};
use crate::Sample; use crate::Sample;
@@ -8,7 +10,6 @@ use byteorder::LittleEndian;
use byteorder::ReadBytesExt; use byteorder::ReadBytesExt;
// Need more test cases for ADMAudioID // Need more test cases for ADMAudioID
#[allow(dead_code)]
/// ADM Audio ID record. /// ADM Audio ID record.
/// ///
@@ -116,7 +117,8 @@ impl ChannelMask {
/** /**
* Extended Wave Format * Extended Wave Format
* *
* https://docs.microsoft.com/en-us/windows/win32/api/mmreg/ns-mmreg-waveformatextensible * Resources:
* * [WAVEFORMATEXTENSIBLE structure](https://docs.microsoft.com/en-us/windows/win32/api/mmreg/ns-mmreg-waveformatextensible)
*/ */
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct WaveFmtExtended { pub struct WaveFmtExtended {
@@ -151,9 +153,9 @@ pub struct WaveFmtExtended {
/// ### Other resources /// ### Other resources
/// - [RFC 3261][rfc3261] (June 1998) "WAVE and AVI Codec Registries" /// - [RFC 3261][rfc3261] (June 1998) "WAVE and AVI Codec Registries"
/// - [Sampler Metadata](http://www.piclist.com/techref/io/serial/midi/wave.html) /// - [Sampler Metadata](http://www.piclist.com/techref/io/serial/midi/wave.html)
/// - [Peter Kabal, McGill University](http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html) /// - [Audio File Format Specifications](http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html) (September 2022) Prof. Peter Kabal, MMSP Lab, ECE, McGill University
/// - [Multimedia Programming Interface and Data Specifications 1.0](http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/riffmci.pdf) /// - [Multimedia Programming Interface and Data Specifications 1.0](http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/riffmci.pdf)
/// (August 1991), IBM Corporation and Microsoft Corporation /// (August 1991), IBM Corporation and Microsoft Corporation
/// ///
/// [rfc3261]: https://tools.ietf.org/html/rfc2361 /// [rfc3261]: https://tools.ietf.org/html/rfc2361
@@ -162,7 +164,7 @@ pub struct WaveFmt {
/// A tag identifying the codec in use. /// A tag identifying the codec in use.
/// ///
/// If this is 0xFFFE, the codec will be identified by a GUID /// If this is 0xFFFE, the codec will be identified by a GUID
/// in `extended_format` /// in [`extended_format`](WaveFmt::extended_format).
pub tag: u16, pub tag: u16,
/// Count of audio channels in each frame /// Count of audio channels in each frame
@@ -198,7 +200,7 @@ pub struct WaveFmt {
/// Extended format description /// Extended format description
/// ///
/// Additional format metadata if `channel_count` is greater than 2, /// Additional format metadata if channel_count is greater than 2,
/// or if certain codecs are used. /// or if certain codecs are used.
pub extended_format: Option<WaveFmtExtended>, pub extended_format: Option<WaveFmtExtended>,
} }
@@ -245,11 +247,11 @@ impl WaveFmt {
} }
} }
/// Create a new integer PCM format `WaveFmt` with a custom channel bitmap. /// Create a new integer PCM format [WaveFmt] with a custom channel bitmap.
/// ///
/// The order of `channels` is not important. When reading or writing /// The order of [channels](WaveFmt::channels) is not important. When reading or writing
/// audio frames you must use the standard multichannel order for Wave /// audio frames you must use the standard multichannel order for Wave
/// files, the numerical order of the cases of `ChannelMask`. /// files, the numerical order of the cases of [ChannelMask].
pub fn new_pcm_multichannel( pub fn new_pcm_multichannel(
sample_rate: u32, sample_rate: u32,
bits_per_sample: u16, bits_per_sample: u16,
@@ -300,7 +302,7 @@ impl WaveFmt {
/// Format or codec of the file's audio data. /// Format or codec of the file's audio data.
/// ///
/// The `CommonFormat` unifies the format tag and the format extension GUID. Use this /// The [CommonFormat] unifies the format tag and the format extension GUID. Use this
/// method to determine the codec. /// method to determine the codec.
pub fn common_format(&self) -> CommonFormat { pub fn common_format(&self) -> CommonFormat {
CommonFormat::make(self.tag, self.extended_format.map(|ext| ext.type_guid)) CommonFormat::make(self.tag, self.extended_format.map(|ext| ext.type_guid))
@@ -377,7 +379,8 @@ impl WaveFmt {
} }
} }
trait ReadWavAudioData { pub trait ReadWavAudioData {
/// Read audio data from the receiver as interleaved [i32] samples.
fn read_i32_frames( fn read_i32_frames(
&mut self, &mut self,
format: WaveFmt, format: WaveFmt,
@@ -394,12 +397,15 @@ impl<T> ReadWavAudioData for T
where where
T: std::io::Read, T: std::io::Read,
{ {
/// # Panics:
/// * If the format's [valid bits per sample](WaveFmt::valid_bits_per_sample) is
/// not compatible with the format's [bits per sample](WaveFmt::bits_per_sample).
fn read_i32_frames( fn read_i32_frames(
&mut self, &mut self,
format: WaveFmt, format: WaveFmt,
into: &mut [i32], into: &mut [i32],
) -> Result<usize, std::io::Error> { ) -> Result<usize, std::io::Error> {
assert!(into.len() % format.channel_count as usize == 0); assert!(into.len().is_multiple_of(format.channel_count as usize));
for frame in into { for frame in into {
*frame = match (format.valid_bits_per_sample(), format.bits_per_sample) { *frame = match (format.valid_bits_per_sample(), format.bits_per_sample) {
@@ -419,7 +425,7 @@ where
format: WaveFmt, format: WaveFmt,
into: &mut [f32], into: &mut [f32],
) -> Result<usize, std::io::Error> { ) -> Result<usize, std::io::Error> {
assert!(into.len() % format.channel_count as usize == 0); assert!(into.len().is_multiple_of(format.channel_count as usize));
todo!() todo!()
} }
} }

View File

@@ -3,22 +3,8 @@
Rust Wave File Reader/Writer with Broadcast-WAV, MBWF and RF64 Support Rust Wave File Reader/Writer with Broadcast-WAV, MBWF and RF64 Support
## Interfaces Refer to the individual modules for relevant documentation. For opening
and writing files begin with [WaveReader] and [WaveWriter] respectively.
### `WaveReader`
`WaveReader` can open and parse a Wave, Broadcast-Wave, or RF64/BW64 64-bit
wave file. Metadata can be accessed and parsed in arbitrary order and audio
samples can be accessed using the `AudioFrameReader` type, created by an
accessor method of `WaveReader`.
### `WaveWriter`
`WaveWriter` can create a new Wave, Broadcast-Wave, or RF64/BW64 64-bit wave
file. Metadata chunks and audio samples are added sequentially, write-only, to
a Wave file which is automatically promoted from standard Wave to RF64 wave
when the total WAVE form size exceeds 0xFFFFFFFF bytes.
## Objectives and Roadmap ## Objectives and Roadmap
@@ -64,7 +50,9 @@ pub use common_format::{
}; };
pub use cue::Cue; pub use cue::Cue;
pub use errors::Error; pub use errors::Error;
pub use fmt::{ADMAudioID, ChannelDescriptor, ChannelMask, WaveFmt, WaveFmtExtended}; pub use fmt::{
ADMAudioID, ChannelDescriptor, ChannelMask, ReadWavAudioData, WaveFmt, WaveFmtExtended,
};
pub use sample::{Sample, I24}; pub use sample::{Sample, I24};
pub use wavereader::{AudioFrameReader, WaveReader}; pub use wavereader::{AudioFrameReader, WaveReader};
pub use wavewriter::{AudioFrameWriter, WaveWriter}; pub use wavewriter::{AudioFrameWriter, WaveWriter};

View File

@@ -18,6 +18,7 @@ use super::fourcc::{BW64_SIG, DATA_SIG, DS64_SIG, RF64_SIG, RIFF_SIG, WAVE_SIG};
const RF64_SIZE_MARKER: u32 = 0xFF_FF_FF_FF; const RF64_SIZE_MARKER: u32 = 0xFF_FF_FF_FF;
#[derive(Debug)] #[derive(Debug)]
#[allow(dead_code)]
pub enum Event { pub enum Event {
StartParse, StartParse,
ReadHeader { ReadHeader {
@@ -258,7 +259,7 @@ impl<R: Read + Seek> Parser<R> {
state = State::ReadyForChunk { state = State::ReadyForChunk {
at: at + 8 + this_displacement, at: at + 8 + this_displacement,
remaining: remaining - 8 - this_displacement, remaining: remaining.saturating_sub(8 + this_displacement),
} }
} }
@@ -292,3 +293,35 @@ impl<R: Read + Seek> Parser<R> {
} }
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn parser_handles_missing_trailing_pad_on_final_odd_chunk() {
let mut buf = Vec::new();
buf.extend_from_slice(b"RIFF");
buf.extend_from_slice(&39u32.to_le_bytes());
buf.extend_from_slice(b"WAVE");
buf.extend_from_slice(b"fmt ");
buf.extend_from_slice(&16u32.to_le_bytes());
buf.extend_from_slice(&[0u8; 16]);
buf.extend_from_slice(b"data");
buf.extend_from_slice(&3u32.to_le_bytes());
buf.extend_from_slice(&[0xAA, 0xBB, 0xCC]);
assert_eq!(buf.len(), 47);
let chunks = Parser::make(Cursor::new(&buf))
.unwrap()
.into_chunk_list()
.expect("parser should accept a missing trailing pad on the final odd-length chunk");
assert_eq!(chunks.len(), 2);
assert_eq!(chunks[0].signature, FourCC::from(*b"fmt "));
assert_eq!(chunks[0].length, 16);
assert_eq!(chunks[1].signature, DATA_SIG);
assert_eq!(chunks[1].length, 3);
}
}

View File

@@ -110,7 +110,7 @@ impl<R: Read + Seek> AudioFrameReader<R> {
let common_format = self.format.common_format(); let common_format = self.format.common_format();
let bits_per_sample = self.format.bits_per_sample; let bits_per_sample = self.format.bits_per_sample;
if buffer.len() % channel_count != 0 { if !buffer.len().is_multiple_of(channel_count) {
return Err(Error::InvalidBufferSize { return Err(Error::InvalidBufferSize {
buffer_size: buffer.len(), buffer_size: buffer.len(),
channel_count: self.format.channel_count, channel_count: self.format.channel_count,
@@ -245,7 +245,7 @@ impl<R: Read + Seek> WaveReader<R> {
/// will return an `Err(errors::Error)` immediately if there is a structural /// will return an `Err(errors::Error)` immediately if there is a structural
/// inconsistency that makes the stream unreadable or if it's missing /// inconsistency that makes the stream unreadable or if it's missing
/// essential components that make interpreting the audio data impossible. /// essential components that make interpreting the audio data impossible.
///
/// ```rust /// ```rust
/// use std::fs::File; /// use std::fs::File;
/// use std::io::{Error,ErrorKind}; /// use std::io::{Error,ErrorKind};

View File

@@ -51,7 +51,7 @@ where
let format = &self.inner.inner.format; let format = &self.inner.inner.format;
let channel_count = format.channel_count as usize; let channel_count = format.channel_count as usize;
if buffer.len() % channel_count != 0 { if !buffer.len().is_multiple_of(channel_count) {
return Err(Error::InvalidBufferSize { return Err(Error::InvalidBufferSize {
buffer_size: buffer.len(), buffer_size: buffer.len(),
channel_count: format.channel_count, channel_count: format.channel_count,
@@ -339,7 +339,7 @@ where
assert!(data.len() < u32::MAX as usize); assert!(data.len() < u32::MAX as usize);
self.inner.write_u32::<LittleEndian>(data.len() as u32)?; self.inner.write_u32::<LittleEndian>(data.len() as u32)?;
self.inner.write_all(data)?; self.inner.write_all(data)?;
if data.len() % 2 == 0 { if data.len().is_multiple_of(2) {
self.increment_form_length(8 + data.len() as u64)?; self.increment_form_length(8 + data.len() as u64)?;
} else { } else {
self.inner.write_u8(0)?; self.inner.write_u8(0)?;