mirror of
https://github.com/iluvcapra/bwavfile.git
synced 2026-05-17 04:33:26 +00:00
Compare commits
18 Commits
v2.0.0
...
3315f00a89
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3315f00a89 | ||
|
|
6819f63c74 | ||
| 1c83094888 | |||
| 977c8263d2 | |||
| da4bad0e87 | |||
| c89a98f7e4 | |||
|
|
8e341990fa | ||
|
|
2dfddff0b5 | ||
|
|
aa8365a38d | ||
|
|
b8a428e757 | ||
|
|
c1d2b2c836 | ||
|
|
f41b7ea575 | ||
|
|
9a62bdc375 | ||
|
|
368ef4366d | ||
|
|
bfa51a4e4c | ||
|
|
4270dc9866 | ||
|
|
92d76289e4 | ||
|
|
15f9a240c0 |
28
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
28
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal 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.
|
||||||
25
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
25
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal 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
64
CONTRIBUTING.md
Normal 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
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 = 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",
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"folders": [
|
|
||||||
{
|
|
||||||
"path": "."
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"settings": {}
|
|
||||||
}
|
|
||||||
@@ -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)?;
|
||||||
|
|||||||
@@ -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),
|
||||||
}
|
}
|
||||||
|
|||||||
32
src/fmt.rs
32
src/fmt.rs
@@ -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!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
22
src/lib.rs
22
src/lib.rs
@@ -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};
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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};
|
||||||
|
|||||||
@@ -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)?;
|
||||||
|
|||||||
Reference in New Issue
Block a user