mirror of
https://github.com/iluvcapra/bwavfile.git
synced 2026-05-17 12:43:25 +00:00
Compare commits
9 Commits
master
...
code-cover
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5781a91d3 | ||
|
|
a3d7206ecd | ||
|
|
5cc4fa763a | ||
|
|
d91c9193c4 | ||
|
|
afa3e24f49 | ||
|
|
d7803ba5ae | ||
|
|
d3eb2377ec | ||
|
|
f28a400c78 | ||
|
|
63067f52ab |
38
.github/workflows/coverage.yml
vendored
Normal file
38
.github/workflows/coverage.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
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
|
||||||
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
# 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
4
Cargo.lock
generated
@@ -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 = 4
|
version = 3
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ansi_term"
|
name = "ansi_term"
|
||||||
@@ -30,7 +30,7 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bwavfile"
|
name = "bwavfile"
|
||||||
version = "2.0.2"
|
version = "2.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"clap",
|
"clap",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bwavfile"
|
name = "bwavfile"
|
||||||
version = "2.0.2"
|
version = "2.0.1"
|
||||||
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"
|
||||||
@@ -26,3 +26,5 @@ serde_json = "1.0.61"
|
|||||||
[profile.release]
|
[profile.release]
|
||||||
debug = true
|
debug = true
|
||||||
|
|
||||||
|
[build]
|
||||||
|
profiler = true
|
||||||
|
|||||||
@@ -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 = 22u16;
|
let cb_size = 24u16;
|
||||||
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)?;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
/// Format tags, UUIDs and utilities
|
/// Format tags, UUIDs and utilities
|
||||||
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
/// Format tag for integer LPCM
|
/// Format tag for integer LPCM
|
||||||
@@ -89,9 +90,7 @@ 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)) => {
|
(WAVE_TAG_EXTENDED, Some(WAVE_UUID_BFORMAT_FLOAT)) => Self::AmbisonicBFormatIeeeFloatPCM,
|
||||||
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),
|
||||||
}
|
}
|
||||||
|
|||||||
11
src/fmt.rs
11
src/fmt.rs
@@ -1,5 +1,3 @@
|
|||||||
#![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;
|
||||||
|
|
||||||
@@ -10,6 +8,7 @@ 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.
|
||||||
///
|
///
|
||||||
@@ -155,7 +154,7 @@ pub struct WaveFmtExtended {
|
|||||||
/// - [Sampler Metadata](http://www.piclist.com/techref/io/serial/midi/wave.html)
|
/// - [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
|
/// - [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
|
||||||
|
|
||||||
@@ -395,7 +394,7 @@ pub trait ReadWavAudioData {
|
|||||||
|
|
||||||
impl<T> ReadWavAudioData for T
|
impl<T> ReadWavAudioData for T
|
||||||
where
|
where
|
||||||
T: std::io::Read,
|
T: std::io::Read
|
||||||
{
|
{
|
||||||
/// # Panics:
|
/// # 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
|
||||||
@@ -405,7 +404,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().is_multiple_of(format.channel_count as usize));
|
assert!(into.len() % format.channel_count as usize == 0);
|
||||||
|
|
||||||
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) {
|
||||||
@@ -425,7 +424,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().is_multiple_of(format.channel_count as usize));
|
assert!(into.len() % format.channel_count as usize == 0);
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,9 +50,7 @@ pub use common_format::{
|
|||||||
};
|
};
|
||||||
pub use cue::Cue;
|
pub use cue::Cue;
|
||||||
pub use errors::Error;
|
pub use errors::Error;
|
||||||
pub use fmt::{
|
pub use fmt::{ADMAudioID, ChannelDescriptor, ChannelMask, WaveFmt, WaveFmtExtended, ReadWavAudioData};
|
||||||
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};
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ 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 {
|
||||||
@@ -259,7 +258,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.saturating_sub(8 + this_displacement),
|
remaining: remaining - 8 - this_displacement,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,35 +292,3 @@ 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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().is_multiple_of(channel_count) {
|
if buffer.len() % channel_count != 0 {
|
||||||
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};
|
||||||
|
|||||||
@@ -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().is_multiple_of(channel_count) {
|
if buffer.len() % channel_count != 0 {
|
||||||
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().is_multiple_of(2) {
|
if data.len() % 2 == 0 {
|
||||||
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)?;
|
||||||
|
|||||||
Reference in New Issue
Block a user