6 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
8 changed files with 111 additions and 13 deletions

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 = "2.0.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.1" 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

@@ -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,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.
/// ///
@@ -404,7 +405,7 @@ where
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) {
@@ -424,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

@@ -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)?;