7 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
11 changed files with 120 additions and 59 deletions

View File

@@ -1,38 +0,0 @@
name: Test Coverage
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Create Test Media
run: cd tests; sh create_test_media.sh
- name: Install rustfilt
run: cargo install rustfilt
- name: Install tapaulin
run: cargo install cargo-tarpaulin
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo tarpaulin
env:
RUSTFLAGS: "-C instrument-coverage"
- name: rust-grcov
# You may pin to the exact commit or the version.
# uses: actions-rs/grcov@bb47b1ed7883a1502fa6875d562727ace2511248
uses: actions-rs/grcov@v0.1.5
# with:
# Path to the configuration file (optional, relative to the repository root)
# config: # optional, default is .github/actions-rs/grcov.yml
- name: Codecov
uses: codecov/codecov-action@v3.1.4

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.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "ansi_term"
@@ -30,7 +30,7 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bwavfile"
version = "2.0.0"
version = "2.0.2"
dependencies = [
"byteorder",
"clap",

View File

@@ -1,6 +1,6 @@
[package]
name = "bwavfile"
version = "2.0.1"
version = "2.0.2"
authors = ["Jamie Hardt <jamiehardt@me.com>", "Ian Hobson <ian.r.hobson@gmail.com>"]
edition = "2018"
license = "MIT"
@@ -26,5 +26,3 @@ serde_json = "1.0.61"
[profile.release]
debug = true
[build]
profiler = true

View File

@@ -37,7 +37,7 @@ where
self.write_u16::<LittleEndian>(format.block_alignment)?;
self.write_u16::<LittleEndian>(format.bits_per_sample)?;
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>(ext.valid_bits_per_sample)?;
self.write_u32::<LittleEndian>(ext.channel_mask)?;

View File

@@ -1,5 +1,4 @@
/// Format tags, UUIDs and utilities
use uuid::Uuid;
/// Format tag for integer LPCM
@@ -90,7 +89,9 @@ impl CommonFormat {
(WAVE_TAG_EXTENDED, Some(WAVE_UUID_PCM)) => Self::IntegerPCM,
(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_FLOAT)) => Self::AmbisonicBFormatIeeeFloatPCM,
(WAVE_TAG_EXTENDED, Some(WAVE_UUID_BFORMAT_FLOAT)) => {
Self::AmbisonicBFormatIeeeFloatPCM
}
(WAVE_TAG_EXTENDED, Some(x)) => CommonFormat::UnknownExtended(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::Sample;
@@ -8,7 +10,6 @@ use byteorder::LittleEndian;
use byteorder::ReadBytesExt;
// Need more test cases for ADMAudioID
#[allow(dead_code)]
/// ADM Audio ID record.
///
@@ -116,7 +117,7 @@ impl ChannelMask {
/**
* Extended Wave Format
*
* Resources:
* Resources:
* * [WAVEFORMATEXTENSIBLE structure](https://docs.microsoft.com/en-us/windows/win32/api/mmreg/ns-mmreg-waveformatextensible)
*/
#[derive(Debug, Copy, Clone)]
@@ -154,7 +155,7 @@ pub struct WaveFmtExtended {
/// - [Sampler Metadata](http://www.piclist.com/techref/io/serial/midi/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)
/// (August 1991), IBM Corporation and Microsoft Corporation
/// (August 1991), IBM Corporation and Microsoft Corporation
///
/// [rfc3261]: https://tools.ietf.org/html/rfc2361
@@ -394,17 +395,17 @@ pub trait ReadWavAudioData {
impl<T> ReadWavAudioData for T
where
T: std::io::Read
T: std::io::Read,
{
/// # Panics:
/// * If the format's [valid bits per sample](WaveFmt::valid_bits_per_sample) is
/// * 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(
&mut self,
format: WaveFmt,
into: &mut [i32],
) -> 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 {
*frame = match (format.valid_bits_per_sample(), format.bits_per_sample) {
@@ -424,7 +425,7 @@ where
format: WaveFmt,
into: &mut [f32],
) -> 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!()
}
}

View File

@@ -50,7 +50,9 @@ pub use common_format::{
};
pub use cue::Cue;
pub use errors::Error;
pub use fmt::{ADMAudioID, ChannelDescriptor, ChannelMask, WaveFmt, WaveFmtExtended, ReadWavAudioData};
pub use fmt::{
ADMAudioID, ChannelDescriptor, ChannelMask, ReadWavAudioData, WaveFmt, WaveFmtExtended,
};
pub use sample::{Sample, I24};
pub use wavereader::{AudioFrameReader, WaveReader};
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;
#[derive(Debug)]
#[allow(dead_code)]
pub enum Event {
StartParse,
ReadHeader {
@@ -258,7 +259,7 @@ impl<R: Read + Seek> Parser<R> {
state = State::ReadyForChunk {
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 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 {
buffer_size: buffer.len(),
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
/// inconsistency that makes the stream unreadable or if it's missing
/// essential components that make interpreting the audio data impossible.
///
/// ```rust
/// use std::fs::File;
/// use std::io::{Error,ErrorKind};

View File

@@ -51,7 +51,7 @@ where
let format = &self.inner.inner.format;
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 {
buffer_size: buffer.len(),
channel_count: format.channel_count,
@@ -339,7 +339,7 @@ where
assert!(data.len() < u32::MAX as usize);
self.inner.write_u32::<LittleEndian>(data.len() as u32)?;
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)?;
} else {
self.inner.write_u8(0)?;