91 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
Jamie Hardt
da2e6f61ee Merge branch 'master' of https://github.com/iluvcapra/bwavfile 2023-05-22 17:53:44 -07:00
Jamie Hardt
eff4fe199f Update Cargo.toml
Bumping version 2.0.0
2023-05-22 17:45:28 -07:00
Jamie Hardt
4e84756417 Merge pull request #14 from irh/performance-improvements
Performance improvements
2023-05-22 17:44:43 -07:00
Ian Hobson
5592e87f0e Update Cargo.toml author list 2023-05-19 11:05:22 +02:00
Ian Hobson
72fea1a493 Expose and rename the supported wave tags and UUIDs 2023-05-19 10:57:39 +02:00
Ian Hobson
bd09ed207a Re-use the write buffer between calls to write_frames
This resulted in a ~10% speedup in test runs of the deinter example.
2023-05-19 10:57:39 +02:00
Ian Hobson
3d1826007e Avoid panics when incorrect buffer sizes are provided to read/write_frames 2023-05-19 10:57:39 +02:00
Ian Hobson
3400778991 Introduce generic read_frames and write_frames functions 2023-05-19 10:57:39 +02:00
Ian Hobson
7290d5ec81 Rework the deinter example, create writers before reading through the input file 2023-05-19 10:57:39 +02:00
Ian Hobson
0074e13bff Avoid flushing output on each write 2023-05-19 10:57:39 +02:00
Ian Hobson
60a3eac072 clippy: Collapse nested else/if into else if 2023-05-19 10:57:39 +02:00
Ian Hobson
0c46a0e21f clippy: Remove unnecessary import 2023-05-19 10:57:39 +02:00
Ian Hobson
8d34a517fc clippy: Don't warn about the LUFS type name 2023-05-19 10:57:39 +02:00
Ian Hobson
86ffd4310a clippy: Prefer if let Some(_) over Option::map 2023-05-19 10:57:39 +02:00
Ian Hobson
ceb8c4371e clippy: Avoid unnecessary conversion 2023-05-19 10:57:39 +02:00
Ian Hobson
22e8dc79d1 clippy: Use a slice argument instead of the owned equivalent 2023-05-19 10:57:39 +02:00
Ian Hobson
f6f4869b5b clippy: Avoid () as return type 2023-05-19 10:57:39 +02:00
Ian Hobson
cf5ec121da clippy: Avoid unnecessary closures 2023-05-19 10:57:39 +02:00
Ian Hobson
7c01778029 clippy: Use .write_u8() when writing a single value 2023-05-19 10:57:39 +02:00
Ian Hobson
4e370c57c1 clippy: Use .write_all() when writing a slice 2023-05-19 10:57:39 +02:00
Ian Hobson
fc3b411717 clippy: Use .read_exact() when reading a fixed number of bytes 2023-05-19 10:57:39 +02:00
Ian Hobson
4d81ef36cd clippy: Use the += operator 2023-05-19 10:57:39 +02:00
Ian Hobson
651009c96a clippy: Avoid making unnecessary references 2023-05-19 10:57:39 +02:00
Ian Hobson
566ad07247 clippy: Remove unnecessary use of .into() 2023-05-19 10:57:39 +02:00
Ian Hobson
6989520743 clippy: Use Vec::get() instead of iter().nth() 2023-05-19 10:57:39 +02:00
Ian Hobson
b6c20ae410 clippy: Remove unnecessary casts 2023-05-19 10:57:39 +02:00
Ian Hobson
9eb655cd27 clippy: Remove unnecessary use of return 2023-05-19 10:57:39 +02:00
Ian Hobson
bbd84b7bbb clippy: Remove unnecessary uses of ? 2023-05-19 10:57:39 +02:00
Ian Hobson
04d282ccd6 clippy: Use Avoid unnecessary indexing in for loops 2023-05-19 10:57:39 +02:00
Ian Hobson
1234c897a3 clippy: Use stream_position() to get current position 2023-05-19 10:57:39 +02:00
Ian Hobson
313f5408ca Update the comment in wave-deinter.rs 2023-05-19 10:57:38 +02:00
Ian Hobson
e1b93b24ad Run cargo format 2023-05-19 10:57:18 +02:00
Ian Hobson
369d261ba0 Fix build warnings 2023-05-19 10:57:18 +02:00
Jamie Hardt
72d3c4abef Tweaked documentation 2023-05-18 10:41:18 -07:00
Jamie Hardt
6191adf30a Removed dead comment 2023-05-18 10:19:46 -07:00
Jamie Hardt
a69755d8aa Fixed misspelling 2023-05-18 10:18:57 -07:00
Jamie Hardt
11d701b687 More readme tweak 2023-05-18 10:16:33 -07:00
Jamie Hardt
0fe11a48ee Reqording readme 2023-05-18 09:54:12 -07:00
Jamie Hardt
4877aa0458 Update rust.yml
Adding clippy
2023-05-18 09:23:15 -07:00
Jamie Hardt
b9fe3b9701 Merge branch 'master' of https://github.com/iluvcapra/bwavfile 2023-05-13 11:02:52 -07:00
Jamie Hardt
4925f95b68 Removed old src/wavebuffer.rs file 2023-05-13 11:02:02 -07:00
Jamie Hardt
f1ec1cf43a Update README.md
master branch not main branch!
2023-05-13 10:50:02 -07:00
Jamie Hardt
30b3e83fd4 Update README.md
Updated workflow shield
2023-05-13 10:43:17 -07:00
Jamie Hardt
e30d60ff0d Merge branch 'master' of https://github.com/Wuelle/bwavfile into Wuelle-master 2021-12-31 13:28:55 -08:00
Jamie Hardt
442394ad5e Removed dead-code warnings 2021-12-31 12:57:46 -08:00
Jamie Hardt
52c26156a9 Merge branch 'master' of https://github.com/iluvcapra/bwavfile 2021-12-31 12:56:11 -08:00
Jamie Hardt
4ddffa0b4a Read trait 2021-12-31 12:56:08 -08:00
Wuelle
9a010ca0c4 prettify code 2021-12-30 21:59:29 +01:00
Wuelle
5e563cddf8 allow more types to be used as paths 2021-12-30 21:59:17 +01:00
Jamie Hardt
69fd5f310c Fixed some typos in this 2021-09-28 13:49:04 -07:00
Jamie Hardt
a8bb5e8c8e Update to version 1.1 2021-09-28 13:46:34 -07:00
Jamie Hardt
468ed12304 Merge pull request #10 from atoav/cuepoints
Surface cue frame_offset field in UI and test for sounddevices files.
2021-09-28 13:39:09 -07:00
Jamie Hardt
a015d7cf8a Added sounddevices test WAV
And unit tests for cue metadata.
2021-09-28 13:18:07 -07:00
Jamie Hardt
e89e87045c Merge pull request #11 from EaterLabs/misc-fixes
implement basic f32le support and some other misc fixes
2021-09-28 12:30:45 -07:00
Jamie Hardt
ecc1db703d Update Cargo.lock 2021-09-28 12:27:53 -07:00
Jamie Hardt
4cd28c9efe Create wavebuffer.rs 2021-09-28 11:59:56 -07:00
eater
663b9fad43 derive Debug for ADMAudioID and ChannelDescriptor and implement f32
reading
2021-08-22 23:40:07 +02:00
eater
9fe741d913 implement std::error::Error for Error 2021-08-22 23:35:47 +02:00
David Huss
2ecae51e8d Adding sounddevices testfile for cue points
As described in issue #9 the function cue_points() didn't display the
expected frame values for cue points created on a Sound Devices
MixPre-Series recorder. The file added by this commit
(sounddevices_6_cue_points.wav) is short and mono channel, but has 6 cue
points in it.

The expected frames for the cue points are:
0: 90112
1: 176128
2: 237568
3: 294912
4: 380928
5: 385024
2021-08-17 09:08:07 +02:00
David Huss
56dc4d1048 Try fix for cuepoints 2021-08-16 17:03:57 +02:00
Jamie Hardt
1af4c675c7 Merge commit 'a625b485d993d912556cdf72605bf7b2c48c18c2' 2021-06-23 10:21:41 -07:00
Jamie Hardt
a625b485d9 Merge branch 'candidate-v1' into HEAD 2021-06-23 10:19:48 -07:00
Jamie Hardt
d17c6badfa Removed unused import 2021-06-23 10:18:44 -07:00
Jamie Hardt
bc6be79715 Merge commit '53fcecfc451f9d38e2b541a728ea444162d04125' into candidate-v1 2021-06-23 10:13:05 -07:00
Jamie Hardt
763e9a10a3 Merge commit 'eee3c3f62592f024dd1308edeedc1b98565bbcd3' into candidate-v1 2021-06-23 10:08:48 -07:00
Jamie Hardt
319845ae49 In progress format plumbing 2021-01-05 00:32:09 -08:00
Jamie Hardt
6375567af1 Update README.md 2021-01-03 18:41:37 -08:00
Jamie Hardt
1a169da48f removed inapplicable code 2021-01-03 13:27:35 -08:00
Jamie Hardt
50b7b7b640 Merge commit '72d6be406e75ca0c40c134311f558cbec9c9f356' into candidate-v1 2021-01-03 13:26:50 -08:00
Jamie Hardt
94eb36bbb5 Merge branch 'master' into candidate-v1 2021-01-02 18:14:14 -08:00
Jamie Hardt
80be74c8fb Features 2021-01-02 16:04:44 -08:00
Jamie Hardt
75fc40d638 Bump version and license year 2021-01-02 15:45:24 -08:00
Jamie Hardt
2780bfb31b Structuring Readme 2021-01-02 15:44:05 -08:00
27 changed files with 1947 additions and 1330 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.

View File

@@ -11,13 +11,32 @@ env:
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
# - name: Install ffmpeg # - name: Install ffmpeg
# run: sudo apt-get install ffmpeg # run: sudo apt-get install ffmpeg
- name: clippy-check
# You may pin to the exact commit or the version.
# uses: LoliGothick/clippy-check@9cd01de4387f9c644b6489355bcff686da6a00f2
uses: LoliGothick/clippy-check@v0.2.11
with:
# GitHub token
token: ${{ secrets.GITHUB_TOKEN }}
# Arguments for the `cargo clippy` command
# options: # optional
# Sequence of lint warnings (without `clippy::` prefix)
# warn: # optional
# # Sequence of lint allowed (without `clippy::` prefix)
# allow: # optional
# # Sequence of lint denied (without `clippy::` prefix)
# deny: # optional
# # Sequence of lint forbidden (without `clippy::` prefix)
# forbid: # optional
# # Display name of the created GitHub check. Must be unique across several LoliGothick/clippy-check invocations.
# name: # optional, default is clippy
# # working directory where to execute `cargo clippy`.
# working-directory: # optional, default is .
- name: Create Test Media - name: Create Test Media
run: cd tests; sh create_test_media.sh run: cd tests; sh create_test_media.sh
- name: Build - name: Build

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.

47
Cargo.lock generated
View File

@@ -1,5 +1,7 @@
# 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
[[package]] [[package]]
name = "ansi_term" name = "ansi_term"
version = "0.11.0" version = "0.11.0"
@@ -22,16 +24,17 @@ dependencies = [
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.2.1" version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bwavfile" name = "bwavfile"
version = "0.9.3" version = "2.0.2"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"clap", "clap",
"dasp_sample",
"encoding", "encoding",
"serde_json", "serde_json",
"uuid", "uuid",
@@ -39,9 +42,9 @@ dependencies = [
[[package]] [[package]]
name = "byteorder" name = "byteorder"
version = "1.3.4" version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]] [[package]]
name = "clap" name = "clap"
@@ -58,6 +61,12 @@ dependencies = [
"vec_map", "vec_map",
] ]
[[package]]
name = "dasp_sample"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f"
[[package]] [[package]]
name = "encoding" name = "encoding"
version = "0.2.33" version = "0.2.33"
@@ -124,24 +133,24 @@ checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569"
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.1.17" version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [ dependencies = [
"libc", "libc",
] ]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "0.4.7" version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.81" version = "0.2.103"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb" checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6"
[[package]] [[package]]
name = "ryu" name = "ryu"
@@ -151,15 +160,15 @@ checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.118" version = "1.0.130"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800" checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.61" version = "1.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a" checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8"
dependencies = [ dependencies = [
"itoa", "itoa",
"ryu", "ryu",
@@ -183,15 +192,15 @@ dependencies = [
[[package]] [[package]]
name = "unicode-width" name = "unicode-width"
version = "0.1.8" version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "0.8.1" version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
[[package]] [[package]]
name = "vec_map" name = "vec_map"

View File

@@ -1,7 +1,7 @@
[package] [package]
name = "bwavfile" name = "bwavfile"
version = "0.9.3" version = "2.0.2"
authors = ["Jamie Hardt <jamiehardt@me.com>"] authors = ["Jamie Hardt <jamiehardt@me.com>", "Ian Hobson <ian.r.hobson@gmail.com>"]
edition = "2018" edition = "2018"
license = "MIT" license = "MIT"
description = "Rust Wave File Reader/Writer with Broadcast-WAV, MBWF and RF64 Support" description = "Rust Wave File Reader/Writer with Broadcast-WAV, MBWF and RF64 Support"
@@ -15,6 +15,7 @@ keywords = ["audio", "broadcast", "multimedia","smpte"]
[dependencies] [dependencies]
byteorder = "1.3.4" byteorder = "1.3.4"
dasp_sample = "0.11.0"
encoding = "0.2.33" encoding = "0.2.33"
uuid = "0.8.1" uuid = "0.8.1"
clap = "2.33.3" clap = "2.33.3"

View File

@@ -1,75 +1,53 @@
[![Crates.io](https://img.shields.io/crates/l/bwavfile)](LICENSE) [![Crates.io](https://img.shields.io/crates/l/bwavfile)](LICENSE)
[![Crates.io](https://img.shields.io/crates/v/bwavfile)](https://crates.io/crates/bwavfile/) [![Crates.io](https://img.shields.io/crates/v/bwavfile)](https://crates.io/crates/bwavfile/)
![GitHub last commit](https://img.shields.io/github/last-commit/iluvcapra/bwavfile) ![GitHub last commit](https://img.shields.io/github/last-commit/iluvcapra/bwavfile)
[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/iluvcapra/bwavfile/Rust)](https://github.com/iluvcapra/bwavfile/actions?query=workflow%3ARust) [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/iluvcapra/bwavfile/rust.yml?branch=master)](https://github.com/iluvcapra/bwavfile/actions?query=workflow%3ARust)
# bwavfile # bwavfile
Rust Wave File Reader/Writer with Broadcast-WAV, MBWF and RF64 Support Wave File Reader/Writer library in Rust, with Broadcast-WAV, RF64 and production metadata support
### Features
This is currently a work-in-progress! However many features presently work: ## Features
| Feature |Read |Write| __bwavfile__ provides a reader `WaveReader` and writer type `WaveWriter` for
|---------------------------------------|:---:|:-----:| reading and creating new wave audio files.
| Standard .wav files | ☑️ | ☑️ |
| Transparent promotion to RF64/BW64 | ☑️ | ☑️ | `WaveReader` and `WaveWriter` support:
| Unified interface for regular and extended Wave format | ☑️ | ☑️ | * A unified interface for standard RIFF and RF64/BW64 64-bit Wave files.
| Channel/speaker map metadata | ☑️ | ☑️ | * When using `WaveWriter`, wave files are transparently upgraded from RIFF
| Ambisonic B-format metadata | ☑️ | ☑️ | to RF64 when required.
| EBU Broadcast-WAVE metadata | ☑️ | ☑️ | * Unpacked reading and writing of Integer PCM and IEEE float audio data
| Basic iXML/ADM metadata | ☑️ | ☑️ | formats.
| Enhanced iXML metadata support | | | * A unified interface for standard `WaveFormat` and extended `WaveFormatEx`
| ADM `chna` channel metadata | | | wave data format.
| Broadcast-WAVE Level overview `levl` metadata | | |
| Cue list metadata | ☑️ | | The library has extensive metadata support, with emphasis on film and video
| Sampler and instrument metadata | | | production metadata:
| Enhanced Wave file form validation | ☑️ | | * Broadcast-Wave metadata extension, including long description, originator,
SMPTE UMID and coding history.
* Reading and writing of embedded iXML and axml/ADM metadata.
* Reading and writing of timed cues and and timed cue regions.
* Multichannel, surround, and ambisonic audio data description including
surround channel maps, ADM `AudioTrackFormat`, `AudioChannelFormatRef` and
`AudioPackRef` data structures.
## Feature Roadmap
Some features that may be included in the future include:
* Broadcast-Wave `levl` waveform overview data reading and writing.
* Sampler and Instrument metadata.
* Performance improvements.
## Use Examples ## Use Examples
### Examples Directory
Check out the [examples](examples) directory for some practical use cases:
* [blits](examples/blits.rs) shows how to use `WaveWriter` to create a new * [blits](examples/blits.rs) shows how to use `WaveWriter` to create a new
file with BLITS alignment tones. file with BLITS alignment tones.
* [wave-inter](examples/wave-inter.rs) uses `WaveReader` and `WaveWriter` to
### Reading Audio Frames From a File interleave several input Wave files into a single polyphonic Wave file.
* [wave-deinter](examples/wave-deinter.rs) uses `WaveReader` and `WaveWriter`
```rust to de-interleave an input Wave file into several monoarual Wave files.
use bwavfile::WaveReader;
let mut r = WaveReader::open("tests/media/ff_silence.wav").unwrap();
let format = r.format().unwrap();
assert_eq!(format.sample_rate, 44100);
assert_eq!(format.channel_count, 1);
let mut buffer = format.create_frame_buffer();
let mut frame_reader = r.audio_frame_reader().unwrap();
let read = frame_reader.read_integer_frame(&mut buffer).unwrap();
assert_eq!(buffer, [0i32]);
assert_eq!(read, 1);
```
### Accessing Channel Descriptions
```rust
use bwavfile::{WaveReader, ChannelMask};
let mut f = WaveReader::open("tests/media/pt_24bit_51.wav").unwrap();
let chans = f.channels().unwrap();
assert_eq!(chans[0].index, 0);
assert_eq!(chans[0].speaker, ChannelMask::FrontLeft);
assert_eq!(chans[3].index, 3);
assert_eq!(chans[3].speaker, ChannelMask::LowFrequency);
assert_eq!(chans[4].speaker, ChannelMask::BackLeft);
```
## Note on Testing ## Note on Testing

View File

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

View File

@@ -12,16 +12,16 @@ use std::f64;
use std::io; use std::io;
extern crate bwavfile; extern crate bwavfile;
use bwavfile::{WaveWriter, WaveFmt, Error}; use bwavfile::{Error, WaveFmt, WaveWriter};
#[macro_use] #[macro_use]
extern crate clap; extern crate clap;
use clap::{Arg, App}; use clap::{App, Arg};
fn sine_wave(t: u64, amplitude: i32, wavelength: u32) -> i32 { fn sine_wave(t: u64, amplitude: i32, wavelength: u32) -> i32 {
//I did it this way because I'm weird //I did it this way because I'm weird
Some(t).map(|i| (i as f64) * 2f64 * f64::consts::PI / wavelength as f64 ) Some(t)
.map(|i| (i as f64) * 2f64 * f64::consts::PI / wavelength as f64)
.map(|f| f.sin()) .map(|f| f.sin())
.map(|s| (s * amplitude as f64) as i32) .map(|s| (s * amplitude as f64) as i32)
.unwrap() .unwrap()
@@ -39,7 +39,6 @@ fn dbfs_to_signed_int(dbfs: f32, bit_depth: u16) -> i32 {
((full_code as f32) * dbfs_to_f32(dbfs)) as i32 ((full_code as f32) * dbfs_to_f32(dbfs)) as i32
} }
#[derive(Clone, Copy, PartialEq)] #[derive(Clone, Copy, PartialEq)]
enum ToneBurst { enum ToneBurst {
/// Tone of .0 frequency (hz) for .1 duration (ms) at .2 dBfs /// Tone of .0 frequency (hz) for .1 duration (ms) at .2 dBfs
@@ -52,24 +51,21 @@ impl ToneBurst {
fn duration(&self, sample_rate: u32) -> u64 { fn duration(&self, sample_rate: u32) -> u64 {
match self { match self {
Self::Tone(_, dur, _) => *dur * sample_rate as u64 / 1000, Self::Tone(_, dur, _) => *dur * sample_rate as u64 / 1000,
Self::Silence(dur) => *dur * sample_rate as u64 / 1000 Self::Silence(dur) => *dur * sample_rate as u64 / 1000,
} }
} }
} }
trait ToneBurstSignal { trait ToneBurstSignal {
fn duration(&self, sample_rate: u32) -> u64; fn duration(&self, sample_rate: u32) -> u64;
fn signal(&self, t: u64, sample_rate: u32, bit_depth: u16) -> i32; fn signal(&self, t: u64, sample_rate: u32, bit_depth: u16) -> i32;
} }
impl ToneBurstSignal for Vec<ToneBurst> { impl ToneBurstSignal for Vec<ToneBurst> {
fn duration(&self, sample_rate: u32) -> u64 { fn duration(&self, sample_rate: u32) -> u64 {
self.iter().fold(0u64, |accum, &item| { self.iter()
accum + &item.duration(sample_rate) .fold(0u64, |accum, &item| accum + &item.duration(sample_rate))
})
} }
fn signal(&self, t: u64, sample_rate: u32, bit_depth: u16) -> i32 { fn signal(&self, t: u64, sample_rate: u32, bit_depth: u16) -> i32 {
@@ -81,22 +77,18 @@ impl ToneBurstSignal for Vec<ToneBurst> {
Some((this_time_range, item)) Some((this_time_range, item))
}) })
.find(|(range, _)| range.contains(&t)) .find(|(range, _)| range.contains(&t))
.map(|(_, item)| { .map(|(_, item)| match item {
match item {
ToneBurst::Tone(freq, _, dbfs) => { ToneBurst::Tone(freq, _, dbfs) => {
let gain = dbfs_to_signed_int(dbfs, bit_depth); let gain = dbfs_to_signed_int(dbfs, bit_depth);
sine_wave(t, gain, (sample_rate as f32 / freq) as u32) sine_wave(t, gain, (sample_rate as f32 / freq) as u32)
},
ToneBurst::Silence(_) => {
0
} }
} ToneBurst::Silence(_) => 0,
}).unwrap_or(0i32) })
.unwrap_or(0i32)
} }
} }
fn create_blits_file(file_name: &str, sample_rate: u32, bits_per_sample: u16) -> Result<(), Error> { fn create_blits_file(file_name: &str, sample_rate: u32, bits_per_sample: u16) -> Result<(), Error> {
// BLITS Tone signal format // BLITS Tone signal format
// From EBU Tech 3304 §4 - https://tech.ebu.ch/docs/tech/tech3304.pdf // From EBU Tech 3304 §4 - https://tech.ebu.ch/docs/tech/tech3304.pdf
let left_channel_sequence: Vec<ToneBurst> = vec![ let left_channel_sequence: Vec<ToneBurst> = vec![
@@ -104,7 +96,6 @@ fn create_blits_file(file_name: &str, sample_rate : u32, bits_per_sample : u16)
ToneBurst::Tone(880.0, 600, -18.0), ToneBurst::Tone(880.0, 600, -18.0),
ToneBurst::Silence(200), ToneBurst::Silence(200),
ToneBurst::Silence(4000), ToneBurst::Silence(4000),
// LR ident // LR ident
ToneBurst::Tone(1000.0, 1000, -18.0), ToneBurst::Tone(1000.0, 1000, -18.0),
ToneBurst::Silence(300), ToneBurst::Silence(300),
@@ -116,10 +107,9 @@ fn create_blits_file(file_name: &str, sample_rate : u32, bits_per_sample : u16)
ToneBurst::Silence(300), ToneBurst::Silence(300),
ToneBurst::Tone(1000.0, 2000, -18.0), ToneBurst::Tone(1000.0, 2000, -18.0),
ToneBurst::Silence(300), ToneBurst::Silence(300),
// Phase check, // Phase check,
ToneBurst::Tone(2000.0, 3000, -24.0), ToneBurst::Tone(2000.0, 3000, -24.0),
ToneBurst::Silence(200) ToneBurst::Silence(200),
]; ];
let right_channel_sequence: Vec<ToneBurst> = vec![ let right_channel_sequence: Vec<ToneBurst> = vec![
@@ -128,14 +118,12 @@ fn create_blits_file(file_name: &str, sample_rate : u32, bits_per_sample : u16)
ToneBurst::Tone(880.0, 600, -18.0), ToneBurst::Tone(880.0, 600, -18.0),
ToneBurst::Silence(200), ToneBurst::Silence(200),
ToneBurst::Silence(3200), ToneBurst::Silence(3200),
// LR ident // LR ident
ToneBurst::Tone(1000.0, 5100, -18.0), ToneBurst::Tone(1000.0, 5100, -18.0),
ToneBurst::Silence(300), ToneBurst::Silence(300),
// Phase check, // Phase check,
ToneBurst::Tone(2000.0, 3000, -24.0), ToneBurst::Tone(2000.0, 3000, -24.0),
ToneBurst::Silence(200) ToneBurst::Silence(200),
]; ];
let center_channel_sequence: Vec<ToneBurst> = vec![ let center_channel_sequence: Vec<ToneBurst> = vec![
@@ -144,13 +132,11 @@ fn create_blits_file(file_name: &str, sample_rate : u32, bits_per_sample : u16)
ToneBurst::Tone(1320.0, 600, -18.0), ToneBurst::Tone(1320.0, 600, -18.0),
ToneBurst::Silence(200), ToneBurst::Silence(200),
ToneBurst::Silence(2400), ToneBurst::Silence(2400),
// LR ident // LR ident
ToneBurst::Silence(5400), ToneBurst::Silence(5400),
// Phase check, // Phase check,
ToneBurst::Tone(2000.0, 3000, -24.0), ToneBurst::Tone(2000.0, 3000, -24.0),
ToneBurst::Silence(200) ToneBurst::Silence(200),
]; ];
let lfe_channel_sequence: Vec<ToneBurst> = vec![ let lfe_channel_sequence: Vec<ToneBurst> = vec![
@@ -159,13 +145,11 @@ fn create_blits_file(file_name: &str, sample_rate : u32, bits_per_sample : u16)
ToneBurst::Tone(82.5, 600, -18.0), ToneBurst::Tone(82.5, 600, -18.0),
ToneBurst::Silence(200), ToneBurst::Silence(200),
ToneBurst::Silence(1600), ToneBurst::Silence(1600),
// LR ident // LR ident
ToneBurst::Silence(5400), ToneBurst::Silence(5400),
// Phase check, // Phase check,
ToneBurst::Tone(2000.0, 3000, -24.0), ToneBurst::Tone(2000.0, 3000, -24.0),
ToneBurst::Silence(200) ToneBurst::Silence(200),
]; ];
let ls_channel_sequence: Vec<ToneBurst> = vec![ let ls_channel_sequence: Vec<ToneBurst> = vec![
@@ -174,13 +158,11 @@ fn create_blits_file(file_name: &str, sample_rate : u32, bits_per_sample : u16)
ToneBurst::Tone(660.0, 600, -18.0), ToneBurst::Tone(660.0, 600, -18.0),
ToneBurst::Silence(200), ToneBurst::Silence(200),
ToneBurst::Silence(800), ToneBurst::Silence(800),
// LR ident // LR ident
ToneBurst::Silence(5400), ToneBurst::Silence(5400),
// Phase check, // Phase check,
ToneBurst::Tone(2000.0, 3000, -24.0), ToneBurst::Tone(2000.0, 3000, -24.0),
ToneBurst::Silence(200) ToneBurst::Silence(200),
]; ];
let rs_channel_sequence: Vec<ToneBurst> = vec![ let rs_channel_sequence: Vec<ToneBurst> = vec![
@@ -188,28 +170,35 @@ fn create_blits_file(file_name: &str, sample_rate : u32, bits_per_sample : u16)
ToneBurst::Silence(4000), ToneBurst::Silence(4000),
ToneBurst::Tone(660.0, 600, -18.0), ToneBurst::Tone(660.0, 600, -18.0),
ToneBurst::Silence(200), ToneBurst::Silence(200),
// LR ident // LR ident
ToneBurst::Silence(5400), ToneBurst::Silence(5400),
// Phase check, // Phase check,
ToneBurst::Tone(2000.0, 3000, -24.0), ToneBurst::Tone(2000.0, 3000, -24.0),
ToneBurst::Silence(200) ToneBurst::Silence(200),
]; ];
let length = [&left_channel_sequence, &right_channel_sequence, let length = [
&center_channel_sequence, &lfe_channel_sequence, &left_channel_sequence,
&ls_channel_sequence, &rs_channel_sequence].iter() &right_channel_sequence,
&center_channel_sequence,
&lfe_channel_sequence,
&ls_channel_sequence,
&rs_channel_sequence,
]
.iter()
.map(|i| i.duration(sample_rate)) .map(|i| i.duration(sample_rate))
.max().unwrap_or(0); .max()
.unwrap_or(0);
let frames = (0..=length).map(|frame| { let frames = (0..=length).map(|frame| {
(left_channel_sequence.signal(frame, sample_rate, bits_per_sample), (
left_channel_sequence.signal(frame, sample_rate, bits_per_sample),
right_channel_sequence.signal(frame, sample_rate, bits_per_sample), right_channel_sequence.signal(frame, sample_rate, bits_per_sample),
center_channel_sequence.signal(frame, sample_rate, bits_per_sample), center_channel_sequence.signal(frame, sample_rate, bits_per_sample),
lfe_channel_sequence.signal(frame, sample_rate, bits_per_sample), lfe_channel_sequence.signal(frame, sample_rate, bits_per_sample),
ls_channel_sequence.signal(frame, sample_rate, bits_per_sample), ls_channel_sequence.signal(frame, sample_rate, bits_per_sample),
rs_channel_sequence.signal(frame, sample_rate, bits_per_sample)) rs_channel_sequence.signal(frame, sample_rate, bits_per_sample),
)
}); });
let format = WaveFmt::new_pcm_multichannel(sample_rate, bits_per_sample, 0b111111); let format = WaveFmt::new_pcm_multichannel(sample_rate, bits_per_sample, 0b111111);
@@ -219,7 +208,7 @@ fn create_blits_file(file_name: &str, sample_rate : u32, bits_per_sample : u16)
let mut fw = file.audio_frame_writer()?; let mut fw = file.audio_frame_writer()?;
for frame in frames { for frame in frames {
let buf = vec![frame.0, frame.1, frame.2, frame.3, frame.4, frame.5]; let buf = vec![frame.0, frame.1, frame.2, frame.3, frame.4, frame.5];
fw.write_integer_frames(&buf)?; fw.write_frames(&buf)?;
} }
fw.end()?; fw.end()?;
@@ -227,33 +216,41 @@ fn create_blits_file(file_name: &str, sample_rate : u32, bits_per_sample : u16)
} }
fn main() -> io::Result<()> { fn main() -> io::Result<()> {
let matches = App::new("blits") let matches = App::new("blits")
.version(crate_version!()) .version(crate_version!())
.author(crate_authors!()) .author(crate_authors!())
.about("Generate a BLITS 5.1 alignment tone.") .about("Generate a BLITS 5.1 alignment tone.")
.arg(Arg::with_name("sample_rate") .arg(
Arg::with_name("sample_rate")
.long("sample-rate") .long("sample-rate")
.short("s") .short("s")
.help("Sample rate of output") .help("Sample rate of output")
.default_value("48000") .default_value("48000"),
) )
.arg(Arg::with_name("bit_depth") .arg(
Arg::with_name("bit_depth")
.long("bit-depth") .long("bit-depth")
.short("b") .short("b")
.help("Bit depth of output") .help("Bit depth of output")
.default_value("24") .default_value("24"),
) )
.arg(Arg::with_name("OUTPUT") .arg(
Arg::with_name("OUTPUT")
.help("Output wave file") .help("Output wave file")
.default_value("blits.wav") .default_value("blits.wav"),
) )
.get_matches(); .get_matches();
let sample_rate = matches.value_of("sample_rate").unwrap().parse::<u32>() let sample_rate = matches
.value_of("sample_rate")
.unwrap()
.parse::<u32>()
.expect("Failed to read sample rate"); .expect("Failed to read sample rate");
let bits_per_sample = matches.value_of("bit_depth").unwrap().parse::<u16>() let bits_per_sample = matches
.value_of("bit_depth")
.unwrap()
.parse::<u16>()
.expect("Failed to read bit depth"); .expect("Failed to read bit depth");
let filename = matches.value_of("OUTPUT").unwrap(); let filename = matches.value_of("OUTPUT").unwrap();
@@ -261,6 +258,6 @@ fn main() -> io::Result<()> {
match create_blits_file(&filename, sample_rate, bits_per_sample) { match create_blits_file(&filename, sample_rate, bits_per_sample) {
Err(Error::IOError(x)) => panic!("IO Error: {:?}", x), Err(Error::IOError(x)) => panic!("IO Error: {:?}", x),
Err(err) => panic!("Error: {:?}", err), Err(err) => panic!("Error: {:?}", err),
Ok(()) => Ok(()) Ok(()) => Ok(()),
} }
} }

View File

@@ -1,20 +1,28 @@
//! wave-inter.rs //! wave-deinter.rs
//! (c) 2021 Jamie Hardt. All rights reserved. //! (c) 2021 Jamie Hardt. All rights reserved.
//! //!
//! This program demonstrats combining several wave files into a single //! This program demonstrates splitting a multichannel file into separate monophonic files for each
//! polyphonic wave file. //! individual channel.
use std::io; use std::io::{Read, Seek};
use std::path::Path; use std::path::Path;
extern crate bwavfile; extern crate bwavfile;
use bwavfile::{Error,WaveReader, WaveWriter, ChannelDescriptor, ChannelMask, WaveFmt, AudioFrameWriter}; use bwavfile::{
ChannelDescriptor, ChannelMask, CommonFormat, Error, Sample, WaveFmt, WaveReader, WaveWriter,
I24,
};
#[macro_use] #[macro_use]
extern crate clap; extern crate clap;
use clap::{Arg, App}; use clap::{App, Arg};
fn name_suffix(force_numeric : bool, delim : &str, index: usize, channel_descriptor : &ChannelDescriptor) -> String { fn name_suffix(
force_numeric: bool,
delim: &str,
index: usize,
channel_descriptor: &ChannelDescriptor,
) -> String {
if force_numeric || channel_descriptor.speaker == ChannelMask::DirectOut { if force_numeric || channel_descriptor.speaker == ChannelMask::DirectOut {
format!("{}A{:02}", delim, index) format!("{}A{:02}", delim, index)
} else { } else {
@@ -37,83 +45,157 @@ fn name_suffix(force_numeric : bool, delim : &str, index: usize, channel_descrip
ChannelMask::TopBackLeft => "Ltb", ChannelMask::TopBackLeft => "Ltb",
ChannelMask::TopBackCenter => "Ctb", ChannelMask::TopBackCenter => "Ctb",
ChannelMask::TopBackRight => "Rtb", ChannelMask::TopBackRight => "Rtb",
ChannelMask::DirectOut => panic!("Error, can't get here") ChannelMask::DirectOut => panic!("Error, can't get here"),
}; };
format!("{}{}", delim, chan_name) format!("{}{}", delim, chan_name)
} }
} }
fn process_file(infile: &str, delim : &str, numeric_channel_names : bool) -> Result<(), Error> { fn deinterleave_file<S, R>(
let mut input_file = WaveReader::open(infile)?; mut input_file: WaveReader<R>,
input_format: WaveFmt,
settings: Settings,
) -> Result<(), Error>
where
S: Sample,
R: Read + Seek,
{
let frames_per_read = 4096;
let channel_desc = input_file.channels()?; let channel_desc = input_file.channels()?;
let input_format = input_file.format()?; let channel_count = channel_desc.len();
if channel_desc.len() == 1 { if channel_desc.len() == 1 {
println!("Input file in monoaural, exiting."); println!("Input file in monoaural, exiting.");
return Ok(()); return Ok(());
} }
let infile_path = Path::new(infile); let infile_path = Path::new(&settings.input_path);
let basename = infile_path.file_stem().expect("Unable to extract file basename").to_str().unwrap(); let basename = infile_path
let output_dir = infile_path.parent().expect("Unable to derive parent directory"); .file_stem()
.expect("Unable to extract file basename")
.to_str()
.unwrap();
let output_dir = infile_path
.parent()
.expect("Unable to derive parent directory");
let ouptut_format = WaveFmt::new_pcm_mono(input_format.sample_rate, input_format.bits_per_sample); let output_block_alignment = input_format.bits_per_sample / 8;
let mut input_wave_reader = input_file.audio_frame_reader()?; let output_format = WaveFmt {
channel_count: 1,
for (n, channel) in channel_desc.iter().enumerate() { block_alignment: output_block_alignment,
let suffix = name_suffix(numeric_channel_names, delim, n + 1, channel); bytes_per_second: output_block_alignment as u32 * input_format.sample_rate,
let outfile_name = output_dir.join(format!("{}{}.wav", basename, suffix)) ..input_format
.into_os_string().into_string().unwrap(); };
let mut reader = input_file.audio_frame_reader()?;
let mut writers = channel_desc
.iter()
.enumerate()
.map(|(n, channel)| {
let suffix = name_suffix(
settings.use_numeric_names,
&settings.delimiter,
n + 1,
channel,
);
let outfile_name = output_dir
.join(format!("{}{}.wav", basename, suffix))
.into_os_string()
.into_string()
.unwrap();
println!("Will create file {}", outfile_name); println!("Will create file {}", outfile_name);
let output_file = WaveWriter::create(&outfile_name, ouptut_format).expect("Failed to create new file"); WaveWriter::create(&outfile_name, output_format)
.expect("Failed to create new file")
.audio_frame_writer()
})
.collect::<Result<Vec<_>, _>>()?;
let mut output_wave_writer = output_file.audio_frame_writer()?; let mut input_buffer = vec![S::EQUILIBRIUM; frames_per_read * channel_count];
let mut buffer = input_format.create_frame_buffer(); let mut output_buffer = vec![S::EQUILIBRIUM; frames_per_read];
while input_wave_reader.read_integer_frame(&mut buffer)? > 0 { loop {
output_wave_writer.write_integer_frames(&buffer[n..=n])?; let frames_read = reader.read_frames(&mut input_buffer)? as usize;
if frames_read == 0 {
break;
} }
output_wave_writer.end()?; output_buffer.resize(frames_read, S::EQUILIBRIUM);
input_wave_reader.locate(0)?;
for (n, writer) in writers.iter_mut().enumerate() {
for (output, input) in output_buffer
.iter_mut()
.zip(input_buffer.iter().skip(n).step_by(channel_count))
{
*output = *input;
}
writer.write_frames(&output_buffer)?;
}
}
for writer in writers.drain(..) {
writer.end()?;
} }
Ok(()) Ok(())
} }
fn main() -> io::Result<()> { fn process_file<R>(mut input: WaveReader<R>, settings: Settings) -> Result<(), Error>
where
R: Read + Seek,
{
let format = input.format()?;
use CommonFormat::*;
match (format.common_format(), format.bits_per_sample) {
(IntegerPCM, 8) => deinterleave_file::<u8, R>(input, format, settings),
(IntegerPCM, 16) => deinterleave_file::<i16, R>(input, format, settings),
(IntegerPCM, 24) => deinterleave_file::<I24, R>(input, format, settings),
(IntegerPCM, 32) => deinterleave_file::<i32, R>(input, format, settings),
(IeeeFloatPCM, 32) => deinterleave_file::<f32, R>(input, format, settings),
other => panic!("Unsupported format: {:?}", other),
}
}
struct Settings {
input_path: String,
delimiter: String,
use_numeric_names: bool,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let matches = App::new("wave-deinter") let matches = App::new("wave-deinter")
.version(crate_version!()) .version(crate_version!())
.author(crate_authors!()) .author(crate_authors!())
.about("Extract each channel of a polyphonic wave file as a new monoaural wave file.") .about("Extract each channel of a polyphonic wave file as a new monoaural wave file.")
.arg(Arg::with_name("numeric_names") .arg(
Arg::with_name("numeric_names")
.long("numeric") .long("numeric")
.short("n") .short("n")
.help("Use numeric channel names \"01\" \"02\" etc.") .help("Use numeric channel names \"01\" \"02\" etc.")
.takes_value(false) .takes_value(false),
) )
.arg(Arg::with_name("channel_delimiter") .arg(
Arg::with_name("channel_delimiter")
.long("delim") .long("delim")
.short("d") .short("d")
.help("Channel label delimiter.") .help("Channel label delimiter.")
.default_value(".") .default_value("."),
) )
.arg(Arg::with_name("INPUT") .arg(
Arg::with_name("INPUT")
.help("Input wave file") .help("Input wave file")
.required(true) .required(true)
.multiple(true) .multiple(true),
) )
.get_matches(); .get_matches();
let delimiter = matches.value_of("channel_delimiter").unwrap(); let settings = Settings {
let use_numeric_names = matches.is_present("numeric_names"); input_path: matches.value_of("INPUT").unwrap().into(),
let infile = matches.value_of("INPUT").unwrap(); delimiter: matches.value_of("channel_delimiter").unwrap().into(),
use_numeric_names: matches.is_present("numeric_names"),
};
match process_file(infile, delimiter, use_numeric_names) { process_file(WaveReader::open(&settings.input_path)?, settings)?;
Err(Error::IOError(io)) => Err(io), Ok(())
Err(e) => panic!("Error: {:?}", e),
Ok(()) => Ok(())
}
} }

View File

@@ -10,7 +10,7 @@ extern crate bwavfile;
#[macro_use] #[macro_use]
extern crate clap; extern crate clap;
use clap::{Arg, App}; use clap::{App, Arg};
fn main() -> io::Result<()> { fn main() -> io::Result<()> {
let matches = App::new("wave-inter") let matches = App::new("wave-inter")

View File

@@ -1,9 +1,8 @@
pub type LU = f32; pub type LU = f32;
#[allow(clippy::upper_case_acronyms)]
pub type LUFS = f32; pub type LUFS = f32;
pub type Decibels = f32; pub type Decibels = f32;
/// Broadcast-WAV metadata record. /// Broadcast-WAV metadata record.
/// ///
/// The `bext` record contains information about the original recording of the /// The `bext` record contains information about the original recording of the
@@ -15,25 +14,20 @@ pub type Decibels = f32;
/// a `bext` metadata record. /// a `bext` metadata record.
/// ///
/// ## Resources /// ## Resources
/// - [EBU Tech 3285](https://tech.ebu.ch/docs/tech/tech3285.pdf). /// - [EBU Tech 3285](https://tech.ebu.ch/docs/tech/tech3285.pdf) "Specification of the Broadcast Wave Format (BWF)"
/// - [EBU Tech R098](https://tech.ebu.ch/docs/r/r098.pdf) (1999) "Format for the &lt;CodingHistory&gt; field in Broadcast Wave Format files, BWF" /// - [EBU Tech R098](https://tech.ebu.ch/docs/r/r098.pdf) (1999) "Format for the &lt;CodingHistory&gt; field in Broadcast Wave Format files, BWF"
/// - [EBU Tech R099](https://tech.ebu.ch/docs/r/r099.pdf) (October 2011) "Unique Source Identifier (USID) for use in the /// - [EBU Tech R099](https://tech.ebu.ch/docs/r/r099.pdf) (October 2011) "Unique Source Identifier (USID) for use in the
/// &lt;OriginatorReference&gt; field of the Broadcast Wave Format" /// &lt;OriginatorReference&gt; field of the Broadcast Wave Format"
// Note for me later:
// get env values: https://doc.rust-lang.org/std/macro.option_env.html
// Cargo env values: https://doc.rust-lang.org/cargo/reference/environment-variables.html
#[derive(Debug)] #[derive(Debug)]
pub struct Bext { pub struct Bext {
/// 0..256 ASCII character field with free text.
/// 256 ASCII character field with free text.
pub description: String, pub description: String,
/// Originating application. /// 0..32 ASCII character Originating application.
pub originator: String, pub originator: String,
/// Application-specific UID. /// 0..32 ASCII character application-specific UID or EBU R099-formatted UID.
pub originator_reference: String, pub originator_reference: String,
/// Creation date in format `YYYY-MM-DD`. /// Creation date in format `YYYY-MM-DD`.
@@ -42,7 +36,7 @@ pub struct Bext {
/// Creation time in format `HH:MM:SS`. /// Creation time in format `HH:MM:SS`.
pub origination_time: String, pub origination_time: String,
/// Time of the start of this wave file, expressed as the number of samples /// Start timestamp of this wave file, in number of samples
/// since local midnight. /// since local midnight.
pub time_reference: u64, pub time_reference: u64,
@@ -82,7 +76,6 @@ pub struct Bext {
/// This field is `None` if the version is less than 2. /// This field is `None` if the version is less than 2.
pub max_short_term_loudness: Option<LUFS>, pub max_short_term_loudness: Option<LUFS>,
// 180 bytes of nothing // 180 bytes of nothing
/// Coding History. /// Coding History.
pub coding_history: String pub coding_history: String,
} }

View File

@@ -1,17 +1,17 @@
use std::io::{Read, Write}; use std::io::{Read, Write};
use encoding::{DecoderTrap, EncoderTrap};
use encoding::{Encoding};
use encoding::all::ASCII; use encoding::all::ASCII;
use encoding::Encoding;
use encoding::{DecoderTrap, EncoderTrap};
use byteorder::LittleEndian; use byteorder::LittleEndian;
use byteorder::{ReadBytesExt, WriteBytesExt}; use byteorder::{ReadBytesExt, WriteBytesExt};
use uuid::Uuid; use uuid::Uuid;
use super::bext::Bext;
use super::errors::Error as ParserError; use super::errors::Error as ParserError;
use super::fmt::{WaveFmt, WaveFmtExtended}; use super::fmt::{WaveFmt, WaveFmtExtended};
use super::bext::Bext;
pub trait ReadBWaveChunks: Read { pub trait ReadBWaveChunks: Read {
fn read_bext(&mut self) -> Result<Bext, ParserError>; fn read_bext(&mut self) -> Result<Bext, ParserError>;
@@ -21,31 +21,36 @@ pub trait ReadBWaveChunks: Read {
pub trait WriteBWaveChunks: Write { pub trait WriteBWaveChunks: Write {
fn write_wave_fmt(&mut self, format: &WaveFmt) -> Result<(), ParserError>; fn write_wave_fmt(&mut self, format: &WaveFmt) -> Result<(), ParserError>;
fn write_bext_string_field(&mut self, string: &String, length: usize) -> Result<(),ParserError>; fn write_bext_string_field(&mut self, string: &str, length: usize) -> Result<(), ParserError>;
fn write_bext(&mut self, bext: &Bext) -> Result<(), ParserError>; fn write_bext(&mut self, bext: &Bext) -> Result<(), ParserError>;
} }
impl<T> WriteBWaveChunks for T where T: Write { impl<T> WriteBWaveChunks for T
where
T: Write,
{
fn write_wave_fmt(&mut self, format: &WaveFmt) -> Result<(), ParserError> { fn write_wave_fmt(&mut self, format: &WaveFmt) -> Result<(), ParserError> {
self.write_u16::<LittleEndian>(format.tag as u16 )?; self.write_u16::<LittleEndian>(format.tag)?;
self.write_u16::<LittleEndian>(format.channel_count)?; self.write_u16::<LittleEndian>(format.channel_count)?;
self.write_u32::<LittleEndian>(format.sample_rate)?; self.write_u32::<LittleEndian>(format.sample_rate)?;
self.write_u32::<LittleEndian>(format.bytes_per_second)?; self.write_u32::<LittleEndian>(format.bytes_per_second)?;
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)?;
let uuid = ext.type_guid.as_bytes(); let uuid = ext.type_guid.as_bytes();
self.write(uuid)?; self.write_all(uuid)?;
} }
Ok(()) Ok(())
} }
fn write_bext_string_field(&mut self, string: &String, length: usize) -> Result<(),ParserError> { fn write_bext_string_field(&mut self, string: &str, length: usize) -> Result<(), ParserError> {
let mut buf = ASCII.encode(&string, EncoderTrap::Ignore).expect("Error encoding text"); let mut buf = ASCII
.encode(string, EncoderTrap::Ignore)
.expect("Error encoding text");
buf.truncate(length); buf.truncate(length);
let filler_length = length - buf.len(); let filler_length = length - buf.len();
if filler_length > 0 { if filler_length > 0 {
@@ -69,21 +74,21 @@ impl<T> WriteBWaveChunks for T where T: Write {
let buf = bext.umid.unwrap_or([0u8; 64]); let buf = bext.umid.unwrap_or([0u8; 64]);
self.write_all(&buf)?; self.write_all(&buf)?;
self.write_i16::<LittleEndian>((bext.loudness_value.unwrap_or(0.0) * 100.0) as i16)?;
self.write_i16::<LittleEndian>((bext.loudness_range.unwrap_or(0.0) * 100.0) as i16)?;
self.write_i16::<LittleEndian>((bext.max_true_peak_level.unwrap_or(0.0) * 100.0) as i16)?;
self.write_i16::<LittleEndian>( self.write_i16::<LittleEndian>(
(bext.loudness_value.unwrap_or(0.0) * 100.0) as i16 )?; (bext.max_momentary_loudness.unwrap_or(0.0) * 100.0) as i16,
)?;
self.write_i16::<LittleEndian>( self.write_i16::<LittleEndian>(
(bext.loudness_range.unwrap_or(0.0) * 100.0) as i16 )?; (bext.max_short_term_loudness.unwrap_or(0.0) * 100.0) as i16,
self.write_i16::<LittleEndian>( )?;
(bext.max_true_peak_level.unwrap_or(0.0) * 100.0) as i16 )?;
self.write_i16::<LittleEndian>(
(bext.max_momentary_loudness.unwrap_or(0.0) * 100.0) as i16 )?;
self.write_i16::<LittleEndian>(
(bext.max_short_term_loudness.unwrap_or(0.0) * 100.0) as i16 )?;
let padding = [0u8; 180]; let padding = [0u8; 180];
self.write_all(&padding)?; self.write_all(&padding)?;
let coding = ASCII.encode(&bext.coding_history, EncoderTrap::Ignore) let coding = ASCII
.encode(&bext.coding_history, EncoderTrap::Ignore)
.expect("Error"); .expect("Error");
self.write_all(&coding)?; self.write_all(&coding)?;
@@ -91,8 +96,10 @@ impl<T> WriteBWaveChunks for T where T: Write {
} }
} }
impl<T> ReadBWaveChunks for T where T: Read { impl<T> ReadBWaveChunks for T
where
T: Read,
{
fn read_wave_fmt(&mut self) -> Result<WaveFmt, ParserError> { fn read_wave_fmt(&mut self) -> Result<WaveFmt, ParserError> {
let tag_value: u16; let tag_value: u16;
Ok(WaveFmt { Ok(WaveFmt {
@@ -116,20 +123,22 @@ impl<T> ReadBWaveChunks for T where T: Read {
let mut buf: [u8; 16] = [0; 16]; let mut buf: [u8; 16] = [0; 16];
self.read_exact(&mut buf)?; self.read_exact(&mut buf)?;
Uuid::from_slice(&buf)? Uuid::from_slice(&buf)?
} },
}) })
} else { } else {
None None
} }
} },
}) })
} }
fn read_bext_string_field(&mut self, length: usize) -> Result<String, ParserError> { fn read_bext_string_field(&mut self, length: usize) -> Result<String, ParserError> {
let mut buffer: Vec<u8> = vec![0; length]; let mut buffer: Vec<u8> = vec![0; length];
self.read(&mut buffer)?; self.read_exact(&mut buffer)?;
let trimmed : Vec<u8> = buffer.iter().take_while(|c| **c != 0 as u8).cloned().collect(); let trimmed: Vec<u8> = buffer.iter().take_while(|c| **c != 0_u8).cloned().collect();
Ok(ASCII.decode(&trimmed, DecoderTrap::Ignore).expect("Error decoding text")) Ok(ASCII
.decode(&trimmed, DecoderTrap::Ignore)
.expect("Error decoding text"))
} }
fn read_bext(&mut self) -> Result<Bext, ParserError> { fn read_bext(&mut self) -> Result<Bext, ParserError> {
@@ -147,43 +156,71 @@ impl<T> ReadBWaveChunks for T where T: Read {
}, },
umid: { umid: {
let mut buf = [0u8; 64]; let mut buf = [0u8; 64];
self.read(&mut buf)?; self.read_exact(&mut buf)?;
if version > 0 { Some(buf) } else { None } if version > 0 {
Some(buf)
} else {
None
}
}, },
loudness_value: { loudness_value: {
let val = (self.read_i16::<LittleEndian>()? as f32) / 100f32; let val = (self.read_i16::<LittleEndian>()? as f32) / 100f32;
if version > 1 { Some(val) } else { None } if version > 1 {
Some(val)
} else {
None
}
}, },
loudness_range: { loudness_range: {
let val = self.read_i16::<LittleEndian>()? as f32 / 100f32; let val = self.read_i16::<LittleEndian>()? as f32 / 100f32;
if version > 1 { Some(val) } else { None } if version > 1 {
Some(val)
} else {
None
}
}, },
max_true_peak_level: { max_true_peak_level: {
let val = self.read_i16::<LittleEndian>()? as f32 / 100f32; let val = self.read_i16::<LittleEndian>()? as f32 / 100f32;
if version > 1 { Some(val) } else { None } if version > 1 {
Some(val)
} else {
None
}
}, },
max_momentary_loudness: { max_momentary_loudness: {
let val = self.read_i16::<LittleEndian>()? as f32 / 100f32; let val = self.read_i16::<LittleEndian>()? as f32 / 100f32;
if version > 1 { Some(val) } else { None } if version > 1 {
Some(val)
} else {
None
}
}, },
max_short_term_loudness: { max_short_term_loudness: {
let val = self.read_i16::<LittleEndian>()? as f32 / 100f32; let val = self.read_i16::<LittleEndian>()? as f32 / 100f32;
if version > 1 { Some(val) } else { None } if version > 1 {
Some(val)
} else {
None
}
}, },
coding_history: { coding_history: {
for _ in 0..180 { self.read_u8()?; } for _ in 0..180 {
self.read_u8()?;
}
let mut buf = vec![]; let mut buf = vec![];
self.read_to_end(&mut buf)?; self.read_to_end(&mut buf)?;
ASCII.decode(&buf, DecoderTrap::Ignore).expect("Error decoding text") ASCII
} .decode(&buf, DecoderTrap::Ignore)
.expect("Error decoding text")
},
}) })
} }
} }
#[test] #[test]
fn test_read_51_wav() { fn test_read_51_wav() {
use super::fmt::ChannelMask;
use super::common_format::CommonFormat; use super::common_format::CommonFormat;
use super::fmt::ChannelMask;
let path = "tests/media/pt_24bit_51.wav"; let path = "tests/media/pt_24bit_51.wav";
@@ -198,9 +235,17 @@ fn test_read_51_wav() {
let channels = ChannelMask::channels(extended.channel_mask, format.channel_count); let channels = ChannelMask::channels(extended.channel_mask, format.channel_count);
assert_eq!(channels, [ChannelMask::FrontLeft, ChannelMask::FrontRight, assert_eq!(
ChannelMask::FrontCenter, ChannelMask::LowFrequency, channels,
ChannelMask::BackLeft, ChannelMask::BackRight]); [
ChannelMask::FrontLeft,
ChannelMask::FrontRight,
ChannelMask::FrontCenter,
ChannelMask::LowFrequency,
ChannelMask::BackLeft,
ChannelMask::BackRight
]
);
assert_eq!(format.common_format(), CommonFormat::IntegerPCM); assert_eq!(format.common_format(), CommonFormat::IntegerPCM);
} }

View File

@@ -1,9 +1,17 @@
/// Format tags, UUIDs and utilities
use uuid::Uuid; use uuid::Uuid;
const BASIC_PCM: u16 = 0x0001; /// Format tag for integer LPCM
const BASIC_FLOAT: u16 = 0x0003; pub const WAVE_TAG_PCM: u16 = 0x0001;
const BASIC_MPEG: u16 = 0x0050;
const BASIC_EXTENDED: u16 = 0xFFFE; /// Format tag for float LPCM
pub const WAVE_TAG_FLOAT: u16 = 0x0003;
/// Format tag for MPEG1
pub const WAVE_TAG_MPEG: u16 = 0x0050;
/// Format tag indicating extended format
pub const WAVE_TAG_EXTENDED: u16 = 0xFFFE;
/* RC 2361 §4: /* RC 2361 §4:
@@ -15,30 +23,38 @@ const BASIC_EXTENDED: u16 = 0xFFFE;
*/ */
pub const UUID_PCM: Uuid = Uuid::from_bytes([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, /// Extended format UUID for integer PCM
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71]); 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,
]);
pub const UUID_FLOAT: Uuid = Uuid::from_bytes([0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, /// Extended format UUID for float PCM
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71]); 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,
]);
pub const UUID_MPEG: Uuid = Uuid::from_bytes([0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, /// Extended format UUID for MPEG1 data
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71]); 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,
]);
pub const UUID_BFORMAT_PCM: Uuid = Uuid::from_bytes([0x01, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, /// Extended format for integer Ambisonic B-Format
0x86, 0x44, 0xc8, 0xc1, 0xca, 0x00, 0x00, 0x00]); 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,
pub const UUID_BFORMAT_FLOAT: Uuid = Uuid::from_bytes([0x03, 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([
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
@@ -67,15 +83,17 @@ impl CommonFormat {
/// Resolve a tag and Uuid to a `CommonFormat`. /// Resolve a tag and Uuid to a `CommonFormat`.
pub fn make(basic: u16, uuid: Option<Uuid>) -> Self { pub fn make(basic: u16, uuid: Option<Uuid>) -> Self {
match (basic, uuid) { match (basic, uuid) {
(BASIC_PCM, _) => Self::IntegerPCM, (WAVE_TAG_PCM, _) => Self::IntegerPCM,
(BASIC_FLOAT, _) => Self::IeeeFloatPCM, (WAVE_TAG_FLOAT, _) => Self::IeeeFloatPCM,
(BASIC_MPEG, _) => Self::Mpeg, (WAVE_TAG_MPEG, _) => Self::Mpeg,
(BASIC_EXTENDED, Some(UUID_PCM)) => Self::IntegerPCM, (WAVE_TAG_EXTENDED, Some(WAVE_UUID_PCM)) => Self::IntegerPCM,
(BASIC_EXTENDED, Some(UUID_FLOAT))=> Self::IeeeFloatPCM, (WAVE_TAG_EXTENDED, Some(WAVE_UUID_FLOAT)) => Self::IeeeFloatPCM,
(BASIC_EXTENDED, Some(UUID_BFORMAT_PCM)) => Self::AmbisonicBFormatIntegerPCM, (WAVE_TAG_EXTENDED, Some(WAVE_UUID_BFORMAT_PCM)) => Self::AmbisonicBFormatIntegerPCM,
(BASIC_EXTENDED, Some(UUID_BFORMAT_FLOAT)) => Self::AmbisonicBFormatIeeeFloatPCM, (WAVE_TAG_EXTENDED, Some(WAVE_UUID_BFORMAT_FLOAT)) => {
(BASIC_EXTENDED, Some(x)) => CommonFormat::UnknownExtended(x), Self::AmbisonicBFormatIeeeFloatPCM
(x, _) => CommonFormat::UnknownBasic(x) }
(WAVE_TAG_EXTENDED, Some(x)) => CommonFormat::UnknownExtended(x),
(x, _) => CommonFormat::UnknownBasic(x),
} }
} }
@@ -85,13 +103,13 @@ impl CommonFormat {
/// returned tag will be 0xFFFE and the `Uuid` will describe the format. /// returned tag will be 0xFFFE and the `Uuid` will describe the format.
pub fn take(self) -> (u16, Uuid) { pub fn take(self) -> (u16, Uuid) {
match self { match self {
Self::IntegerPCM => (BASIC_PCM, UUID_PCM), Self::IntegerPCM => (WAVE_TAG_PCM, WAVE_UUID_PCM),
Self::IeeeFloatPCM => (BASIC_FLOAT, UUID_FLOAT), Self::IeeeFloatPCM => (WAVE_TAG_FLOAT, WAVE_UUID_FLOAT),
Self::Mpeg => (BASIC_MPEG, UUID_MPEG), Self::Mpeg => (WAVE_TAG_MPEG, WAVE_UUID_MPEG),
Self::AmbisonicBFormatIntegerPCM => (BASIC_EXTENDED, UUID_BFORMAT_PCM), Self::AmbisonicBFormatIntegerPCM => (WAVE_TAG_EXTENDED, WAVE_UUID_BFORMAT_PCM),
Self::AmbisonicBFormatIeeeFloatPCM => (BASIC_EXTENDED, UUID_BFORMAT_FLOAT), Self::AmbisonicBFormatIeeeFloatPCM => (WAVE_TAG_EXTENDED, WAVE_UUID_BFORMAT_FLOAT),
Self::UnknownBasic(x) => (x, uuid_from_basic_tag(x)), Self::UnknownBasic(x) => (x, uuid_from_basic_tag(x)),
Self::UnknownExtended(x) => ( BASIC_EXTENDED, x) Self::UnknownExtended(x) => (WAVE_TAG_EXTENDED, x),
} }
} }
} }

View File

@@ -1,12 +1,15 @@
use super::fourcc::{FourCC,ReadFourCC, WriteFourCC, LABL_SIG, NOTE_SIG, #![allow(dead_code)]
ADTL_SIG, LTXT_SIG, DATA_SIG}; use super::fourcc::{
FourCC, ReadFourCC, WriteFourCC, ADTL_SIG, DATA_SIG, LABL_SIG, LTXT_SIG, NOTE_SIG,
};
use super::list_form::collect_list_form; use super::list_form::collect_list_form;
use byteorder::{WriteBytesExt, ReadBytesExt, LittleEndian}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use encoding::{DecoderTrap,EncoderTrap};
use encoding::{Encoding};
use encoding::all::ASCII; use encoding::all::ASCII;
use encoding::Encoding;
use encoding::{DecoderTrap, EncoderTrap};
use std::io::{Cursor, Error, Read, Write}; use std::io::{Cursor, Error, Read, Write};
@@ -17,11 +20,10 @@ struct RawCue {
chunk_id: FourCC, chunk_id: FourCC,
chunk_start: u32, chunk_start: u32,
block_start: u32, block_start: u32,
frame_offset : u32 frame_offset: u32,
} }
impl RawCue { impl RawCue {
fn write_to(cues: Vec<Self>) -> Vec<u8> { fn write_to(cues: Vec<Self>) -> Vec<u8> {
let mut writer = Cursor::new(vec![0u8; 0]); let mut writer = Cursor::new(vec![0u8; 0]);
@@ -50,7 +52,7 @@ impl RawCue {
chunk_id: rdr.read_fourcc()?, chunk_id: rdr.read_fourcc()?,
chunk_start: rdr.read_u32::<LittleEndian>()?, chunk_start: rdr.read_u32::<LittleEndian>()?,
block_start: rdr.read_u32::<LittleEndian>()?, block_start: rdr.read_u32::<LittleEndian>()?,
frame_offset : rdr.read_u32::<LittleEndian>()? frame_offset: rdr.read_u32::<LittleEndian>()?,
}) })
} }
@@ -61,15 +63,14 @@ impl RawCue {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct RawLabel { struct RawLabel {
cue_point_id: u32, cue_point_id: u32,
text : Vec<u8> text: Vec<u8>,
} }
impl RawLabel { impl RawLabel {
fn write_to(&self) -> Vec<u8> { fn write_to(&self) -> Vec<u8> {
let mut writer = Cursor::new(vec![0u8; 0]); let mut writer = Cursor::new(vec![0u8; 0]);
writer.write_u32::<LittleEndian>(self.cue_point_id as u32).unwrap(); writer.write_u32::<LittleEndian>(self.cue_point_id).unwrap();
writer.write(&self.text).unwrap(); writer.write_all(&self.text).unwrap();
writer.into_inner() writer.into_inner()
} }
@@ -80,10 +81,10 @@ impl RawLabel {
Ok(Self { Ok(Self {
cue_point_id: rdr.read_u32::<LittleEndian>()?, cue_point_id: rdr.read_u32::<LittleEndian>()?,
text: { text: {
let mut buf = vec![0u8; (length - 4) as usize ]; let mut buf = vec![0u8; length - 4];
rdr.read_exact(&mut buf)?; rdr.read_exact(&mut buf)?;
buf buf
} },
}) })
} }
} }
@@ -91,15 +92,14 @@ impl RawLabel {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct RawNote { struct RawNote {
cue_point_id: u32, cue_point_id: u32,
text : Vec<u8> text: Vec<u8>,
} }
impl RawNote { impl RawNote {
fn write_to(&self) -> Vec<u8> { fn write_to(&self) -> Vec<u8> {
let mut writer = Cursor::new(vec![0u8; 0]); let mut writer = Cursor::new(vec![0u8; 0]);
writer.write_u32::<LittleEndian>(self.cue_point_id).unwrap(); writer.write_u32::<LittleEndian>(self.cue_point_id).unwrap();
writer.write(&self.text).unwrap(); writer.write_all(&self.text).unwrap();
writer.into_inner() writer.into_inner()
} }
@@ -110,10 +110,10 @@ impl RawNote {
Ok(Self { Ok(Self {
cue_point_id: rdr.read_u32::<LittleEndian>()?, cue_point_id: rdr.read_u32::<LittleEndian>()?,
text: { text: {
let mut buf = vec![0u8; (length - 4) as usize ]; let mut buf = vec![0u8; length - 4];
rdr.read_exact(&mut buf)?; rdr.read_exact(&mut buf)?;
buf buf
} },
}) })
} }
} }
@@ -127,11 +127,10 @@ struct RawLtxt {
language: u16, language: u16,
dialect: u16, dialect: u16,
code_page: u16, code_page: u16,
text: Option<Vec<u8>> text: Option<Vec<u8>>,
} }
impl RawLtxt { impl RawLtxt {
fn write_to(&self) -> Vec<u8> { fn write_to(&self) -> Vec<u8> {
let mut writer = Cursor::new(vec![0u8; 0]); let mut writer = Cursor::new(vec![0u8; 0]);
writer.write_u32::<LittleEndian>(self.cue_point_id).unwrap(); writer.write_u32::<LittleEndian>(self.cue_point_id).unwrap();
@@ -142,7 +141,7 @@ impl RawLtxt {
writer.write_u16::<LittleEndian>(self.dialect).unwrap(); writer.write_u16::<LittleEndian>(self.dialect).unwrap();
writer.write_u16::<LittleEndian>(self.code_page).unwrap(); writer.write_u16::<LittleEndian>(self.code_page).unwrap();
if let Some(ext_text) = &self.text { if let Some(ext_text) = &self.text {
writer.write(ext_text).unwrap(); writer.write_all(ext_text).unwrap();
} }
writer.into_inner() writer.into_inner()
} }
@@ -161,13 +160,13 @@ impl RawLtxt {
code_page: rdr.read_u16::<LittleEndian>()?, code_page: rdr.read_u16::<LittleEndian>()?,
text: { text: {
if length - 20 > 0 { if length - 20 > 0 {
let mut buf = vec![0u8; (length - 20) as usize]; let mut buf = vec![0u8; length - 20];
rdr.read_exact(&mut buf)?; rdr.read_exact(&mut buf)?;
Some(buf) Some(buf)
} else { } else {
None None
} }
} },
}) })
} }
} }
@@ -177,7 +176,7 @@ enum RawAdtlMember {
Label(RawLabel), Label(RawLabel),
Note(RawNote), Note(RawNote),
LabeledText(RawLtxt), LabeledText(RawLtxt),
Unrecognized(FourCC) Unrecognized(FourCC),
} }
impl RawAdtlMember { impl RawAdtlMember {
@@ -186,22 +185,26 @@ impl RawAdtlMember {
// It seems like all this casing could be done with traits // It seems like all this casing could be done with traits
for member in members.iter() { for member in members.iter() {
let (fcc, buf) = match member { let (fcc, buf) = match member {
RawAdtlMember::Label(l) => ( (LABL_SIG, l.write_to()) ), RawAdtlMember::Label(l) => (LABL_SIG, l.write_to()),
RawAdtlMember::Note(n) => ( (NOTE_SIG, n.write_to()) ), RawAdtlMember::Note(n) => (NOTE_SIG, n.write_to()),
RawAdtlMember::LabeledText(t) => ( (LTXT_SIG, t.write_to()) ), RawAdtlMember::LabeledText(t) => (LTXT_SIG, t.write_to()),
RawAdtlMember::Unrecognized(f) => (*f, vec![0u8;0] ) // <-- this is a dopey case but here for completeness RawAdtlMember::Unrecognized(f) => (*f, vec![0u8; 0]), // <-- this is a dopey case but here for completeness
}; };
w.write_fourcc(fcc).unwrap(); w.write_fourcc(fcc).unwrap();
w.write_u32::<LittleEndian>(buf.len() as u32).unwrap(); w.write_u32::<LittleEndian>(buf.len() as u32).unwrap();
w.write(&buf).unwrap(); w.write_all(&buf).unwrap();
if buf.len() % 2 == 1 { w.write_u8(0).unwrap(); } if buf.len() % 2 == 1 {
w.write_u8(0).unwrap();
}
} }
let chunk_content = w.into_inner(); let chunk_content = w.into_inner();
let mut writer = Cursor::new(vec![0u8; 0]); let mut writer = Cursor::new(vec![0u8; 0]);
writer.write_fourcc(ADTL_SIG).unwrap(); writer.write_fourcc(ADTL_SIG).unwrap();
writer.write_u32::<LittleEndian>(chunk_content.len() as u32).unwrap(); writer
writer.write(&chunk_content).unwrap(); .write_u32::<LittleEndian>(chunk_content.len() as u32)
.unwrap();
writer.write_all(&chunk_content).unwrap();
writer.into_inner() writer.into_inner()
} }
@@ -210,14 +213,12 @@ impl RawAdtlMember {
let mut retval: Vec<RawAdtlMember> = vec![]; let mut retval: Vec<RawAdtlMember> = vec![];
for chunk in chunks.iter() { for chunk in chunks.iter() {
retval.push( retval.push(match chunk.signature {
match chunk.signature {
LABL_SIG => RawAdtlMember::Label(RawLabel::read_from(&chunk.contents)?), LABL_SIG => RawAdtlMember::Label(RawLabel::read_from(&chunk.contents)?),
NOTE_SIG => RawAdtlMember::Note(RawNote::read_from(&chunk.contents)?), NOTE_SIG => RawAdtlMember::Note(RawNote::read_from(&chunk.contents)?),
LTXT_SIG => RawAdtlMember::LabeledText(RawLtxt::read_from(&chunk.contents)?), LTXT_SIG => RawAdtlMember::LabeledText(RawLtxt::read_from(&chunk.contents)?),
x => RawAdtlMember::Unrecognized(x) x => RawAdtlMember::Unrecognized(x),
} })
)
} }
Ok(retval) Ok(retval)
} }
@@ -230,33 +231,29 @@ trait AdtlMemberSearch {
} }
impl AdtlMemberSearch for Vec<RawAdtlMember> { impl AdtlMemberSearch for Vec<RawAdtlMember> {
fn labels_for_cue_point(&self, id: u32) -> Vec<&RawLabel> { fn labels_for_cue_point(&self, id: u32) -> Vec<&RawLabel> {
self.iter().filter_map(|item| { self.iter()
match item { .filter_map(|item| match item {
RawAdtlMember::Label(x) if x.cue_point_id == id => Some(x), RawAdtlMember::Label(x) if x.cue_point_id == id => Some(x),
_ => None _ => None,
}
}) })
.collect() .collect()
} }
fn notes_for_cue_point(&self, id: u32) -> Vec<&RawNote> { fn notes_for_cue_point(&self, id: u32) -> Vec<&RawNote> {
self.iter().filter_map(|item| { self.iter()
match item { .filter_map(|item| match item {
RawAdtlMember::Note(x) if x.cue_point_id == id => Some(x), RawAdtlMember::Note(x) if x.cue_point_id == id => Some(x),
_ => None _ => None,
}
}) })
.collect() .collect()
} }
fn ltxt_for_cue_point(&self, id: u32) -> Vec<&RawLtxt> { fn ltxt_for_cue_point(&self, id: u32) -> Vec<&RawLtxt> {
self.iter().filter_map(|item| { self.iter()
match item { .filter_map(|item| match item {
RawAdtlMember::LabeledText(x) if x.cue_point_id == id => Some(x), RawAdtlMember::LabeledText(x) if x.cue_point_id == id => Some(x),
_ => None _ => None,
}
}) })
.collect() .collect()
} }
@@ -270,7 +267,6 @@ impl AdtlMemberSearch for Vec<RawAdtlMember> {
/// ### Not Implemented /// ### Not Implemented
/// - [EBU 3285 Supplement 2](https://tech.ebu.ch/docs/tech/tech3285s2.pdf) (July 2001): Quality chunk and cuesheet /// - [EBU 3285 Supplement 2](https://tech.ebu.ch/docs/tech/tech3285s2.pdf) (July 2001): Quality chunk and cuesheet
pub struct Cue { pub struct Cue {
/// The time of this marker /// The time of this marker
pub frame: u32, pub frame: u32,
@@ -281,24 +277,36 @@ pub struct Cue {
pub label: Option<String>, pub label: Option<String>,
/// The text "note"/comment of this marker if provided /// The text "note"/comment of this marker if provided
pub note : Option<String> pub note: Option<String>,
/// The offser of this marker
///
/// **Note:** Applications use the `frame` and `offset` fields
/// in different ways. iZotope RX Audio Editor writes the
/// marker position to *both* fields, while a Sound Devices
/// recorder writes the marker position to *only* the `offset`
/// field.
pub offset: u32,
} }
fn convert_to_cue_string(buffer: &[u8]) -> String { fn convert_to_cue_string(buffer: &[u8]) -> String {
let trimmed : Vec<u8> = buffer.iter().take_while(|c| **c != 0 as u8).cloned().collect(); let trimmed: Vec<u8> = buffer.iter().take_while(|c| **c != 0_u8).cloned().collect();
ASCII.decode(&trimmed, DecoderTrap::Ignore).expect("Error decoding text") ASCII
.decode(&trimmed, DecoderTrap::Ignore)
.expect("Error decoding text")
} }
fn convert_from_cue_string(val: &str) -> Vec<u8> { fn convert_from_cue_string(val: &str) -> Vec<u8> {
ASCII.encode(&val, EncoderTrap::Ignore).expect("Error encoding text") ASCII
.encode(val, EncoderTrap::Ignore)
.expect("Error encoding text")
} }
impl Cue { impl Cue {
/// Take a list of `Cue`s and convert it into `RawCue` and `RawAdtlMember`s /// Take a list of `Cue`s and convert it into `RawCue` and `RawAdtlMember`s
fn compile_to(cues: &[Cue]) -> (Vec<RawCue>, Vec<RawAdtlMember>) { fn compile_to(cues: &[Cue]) -> (Vec<RawCue>, Vec<RawAdtlMember>) {
cues.iter().enumerate() cues.iter()
.enumerate()
.map(|(n, cue)| { .map(|(n, cue)| {
let raw_cue = RawCue { let raw_cue = RawCue {
cue_point_id: n as u32, cue_point_id: n as u32,
@@ -306,25 +314,20 @@ impl Cue {
chunk_id: DATA_SIG, chunk_id: DATA_SIG,
chunk_start: 0, chunk_start: 0,
block_start: 0, block_start: 0,
frame_offset: cue.frame frame_offset: cue.offset,
}; };
let raw_label = cue.label.as_ref().map(|val| { let raw_label = cue.label.as_ref().map(|val| RawLabel {
RawLabel {
cue_point_id: n as u32, cue_point_id: n as u32,
text: convert_from_cue_string(&val) text: convert_from_cue_string(val),
}
}); });
let raw_note = cue.note.as_ref().map(|val| { let raw_note = cue.note.as_ref().map(|val| RawNote {
RawNote {
cue_point_id: n as u32, cue_point_id: n as u32,
text : convert_from_cue_string(&val) text: convert_from_cue_string(val),
}
}); });
let raw_ltxt = cue.length.map(|val| { let raw_ltxt = cue.length.map(|val| RawLtxt {
RawLtxt {
cue_point_id: n as u32, cue_point_id: n as u32,
frame_length: val, frame_length: val,
purpose: FourCC::make(b"rgn "), purpose: FourCC::make(b"rgn "),
@@ -332,20 +335,27 @@ impl Cue {
language: 0, language: 0,
dialect: 0, dialect: 0,
code_page: 0, code_page: 0,
text : None text: None,
}
}); });
(raw_cue, raw_label, raw_note, raw_ltxt) (raw_cue, raw_label, raw_note, raw_ltxt)
}) })
.fold((Vec::<RawCue>::new(), Vec::<RawAdtlMember>::new()), .fold(
(Vec::<RawCue>::new(), Vec::<RawAdtlMember>::new()),
|(mut cues, mut adtls), (cue, label, note, ltxt)| { |(mut cues, mut adtls), (cue, label, note, ltxt)| {
cues.push(cue); cues.push(cue);
label.map(|l| adtls.push( RawAdtlMember::Label(l))); if let Some(l) = label {
note.map(|n| adtls.push(RawAdtlMember::Note(n))); adtls.push(RawAdtlMember::Label(l))
ltxt.map(|m| adtls.push(RawAdtlMember::LabeledText(m))); }
if let Some(n) = note {
adtls.push(RawAdtlMember::Note(n))
}
if let Some(m) = ltxt {
adtls.push(RawAdtlMember::LabeledText(m))
}
(cues, adtls) (cues, adtls)
}) },
)
} }
pub fn collect_from(cue_chunk: &[u8], adtl_chunk: Option<&[u8]>) -> Result<Vec<Cue>, Error> { pub fn collect_from(cue_chunk: &[u8], adtl_chunk: Option<&[u8]>) -> Result<Vec<Cue>, Error> {
@@ -358,32 +368,37 @@ impl Cue {
raw_adtl = vec![]; raw_adtl = vec![];
} }
Ok(raw_cues
Ok( .iter()
raw_cues.iter()
.map(|i| { .map(|i| {
Cue { Cue {
//ident : i.cue_point_id, //ident : i.cue_point_id,
frame: i.frame, frame: i.frame,
length: { length: {
raw_adtl.ltxt_for_cue_point(i.cue_point_id).first() raw_adtl
.ltxt_for_cue_point(i.cue_point_id)
.first()
.filter(|x| x.purpose == FourCC::make(b"rgn ")) .filter(|x| x.purpose == FourCC::make(b"rgn "))
.map(|x| x.frame_length) .map(|x| x.frame_length)
}, },
label: { label: {
raw_adtl.labels_for_cue_point(i.cue_point_id).iter() raw_adtl
.labels_for_cue_point(i.cue_point_id)
.iter()
.map(|s| convert_to_cue_string(&s.text)) .map(|s| convert_to_cue_string(&s.text))
.next() .next()
}, },
note: { note: {
raw_adtl.notes_for_cue_point(i.cue_point_id).iter() raw_adtl
.notes_for_cue_point(i.cue_point_id)
.iter()
//.filter_map(|x| str::from_utf8(&x.text).ok()) //.filter_map(|x| str::from_utf8(&x.text).ok())
.map(|s| convert_to_cue_string(&s.text)) .map(|s| convert_to_cue_string(&s.text))
.next() .next()
},
offset: i.frame_offset,
}
})
.collect())
} }
} }
}).collect()
)
}
}

View File

@@ -1,12 +1,13 @@
use std::io;
use super::fourcc::FourCC; use super::fourcc::FourCC;
use std::error::Error as StdError;
use uuid; use std::{
fmt::{Debug, Display},
io,
};
/// Errors returned by methods in this crate. /// Errors returned by methods in this crate.
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
/// An `io::Error` occurred /// An `io::Error` occurred
IOError(io::Error), IOError(io::Error),
@@ -41,8 +42,20 @@ pub enum Error {
/// The file is not optimized for writing new data /// The file is not optimized for writing new data
DataChunkNotPreparedForAppend, DataChunkNotPreparedForAppend,
/// A buffer with a length that isn't a multiple of channel_count was provided
InvalidBufferSize {
buffer_size: usize,
channel_count: u16,
},
} }
impl StdError for Error {}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Debug::fmt(self, f)
}
}
impl From<io::Error> for Error { impl From<io::Error> for Error {
fn from(error: io::Error) -> Error { fn from(error: io::Error) -> Error {

View File

@@ -1,12 +1,15 @@
use uuid::Uuid; #![allow(dead_code)]
use super::common_format::{CommonFormat, UUID_PCM,UUID_BFORMAT_PCM};
use crate::common_format::{CommonFormat, WAVE_UUID_BFORMAT_PCM, WAVE_UUID_PCM};
use crate::Sample;
use std::io::Cursor; use std::io::Cursor;
use uuid::Uuid;
use byteorder::LittleEndian; use byteorder::LittleEndian;
use byteorder::WriteBytesExt; 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.
/// ///
@@ -18,16 +21,18 @@ use byteorder::WriteBytesExt;
/// `AudioProgramme`. /// `AudioProgramme`.
/// ///
/// See BS.2088-1 § 8, also BS.2094, also blahblahblah... /// See BS.2088-1 § 8, also BS.2094, also blahblahblah...
#[derive(Debug)]
pub struct ADMAudioID { pub struct ADMAudioID {
pub track_uid: [char; 12], pub track_uid: [char; 12],
pub channel_format_ref: [char; 14], pub channel_format_ref: [char; 14],
pub pack_ref: [char; 11] pub pack_ref: [char; 11],
} }
/// Describes a single channel in a WAV file. /// Describes a single channel in a WAV file.
/// ///
/// This information is correlated from the Wave format ChannelMap field and /// This information is correlated from the Wave format ChannelMap field and
/// the `chna` chunk, if present. /// the `chna` chunk, if present.
#[derive(Debug)]
pub struct ChannelDescriptor { pub struct ChannelDescriptor {
/// Index, the offset of this channel's samples in one frame. /// Index, the offset of this channel's samples in one frame.
pub index: u16, pub index: u16,
@@ -42,7 +47,6 @@ pub struct ChannelDescriptor {
pub adm_track_audio_ids: Vec<ADMAudioID>, pub adm_track_audio_ids: Vec<ADMAudioID>,
} }
/// A bitmask indicating which channels are present in /// A bitmask indicating which channels are present in
/// the file. /// the file.
/// ///
@@ -70,7 +74,6 @@ pub enum ChannelMask {
} }
impl From<u32> for ChannelMask { impl From<u32> for ChannelMask {
fn from(value: u32) -> Self { fn from(value: u32) -> Self {
match value { match value {
0x1 => Self::FrontLeft, 0x1 => Self::FrontLeft,
@@ -91,7 +94,7 @@ impl From<u32> for ChannelMask {
0x8000 => Self::TopBackLeft, 0x8000 => Self::TopBackLeft,
0x10000 => Self::TopBackCenter, 0x10000 => Self::TopBackCenter,
0x20000 => Self::TopBackRight, 0x20000 => Self::TopBackRight,
_ => Self::DirectOut _ => Self::DirectOut,
} }
} }
} }
@@ -102,9 +105,10 @@ impl ChannelMask {
if (input_mask & reserved_mask) > 0 { if (input_mask & reserved_mask) > 0 {
vec![ChannelMask::DirectOut; channel_count as usize] vec![ChannelMask::DirectOut; channel_count as usize]
} else { } else {
(0..18).map(|i| 1 << i ) (0..18)
.map(|i| 1 << i)
.filter(|mask| mask & input_mask > 0) .filter(|mask| mask & input_mask > 0)
.map(|mask| Into::<ChannelMask>::into(mask)) .map(ChannelMask::from)
.collect() .collect()
} }
} }
@@ -113,11 +117,11 @@ 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 {
/// Valid bits per sample /// Valid bits per sample
pub valid_bits_per_sample: u16, pub valid_bits_per_sample: u16,
@@ -149,20 +153,18 @@ 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
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct WaveFmt { 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,14 +200,12 @@ 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>,
} }
impl WaveFmt { impl WaveFmt {
pub fn valid_bits_per_sample(&self) -> u16 { pub fn valid_bits_per_sample(&self) -> u16 {
if let Some(ext) = self.extended_format { if let Some(ext) = self.extended_format {
ext.valid_bits_per_sample ext.valid_bits_per_sample
@@ -234,39 +234,55 @@ impl WaveFmt {
tag: 0xFFFE, tag: 0xFFFE,
channel_count, channel_count,
sample_rate, sample_rate,
bytes_per_second: container_bytes_per_sample as u32 * sample_rate * channel_count as u32, bytes_per_second: container_bytes_per_sample as u32
* sample_rate
* channel_count as u32,
block_alignment: container_bytes_per_sample * channel_count, block_alignment: container_bytes_per_sample * channel_count,
bits_per_sample: container_bits_per_sample, bits_per_sample: container_bits_per_sample,
extended_format: Some(WaveFmtExtended { extended_format: Some(WaveFmtExtended {
valid_bits_per_sample: bits_per_sample, valid_bits_per_sample: bits_per_sample,
channel_mask: ChannelMask::DirectOut as u32, channel_mask: ChannelMask::DirectOut as u32,
type_guid: UUID_BFORMAT_PCM type_guid: WAVE_UUID_BFORMAT_PCM,
}) }),
} }
} }
/// 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(sample_rate: u32, bits_per_sample: u16, channel_bitmap: u32) -> Self { pub fn new_pcm_multichannel(
sample_rate: u32,
bits_per_sample: u16,
channel_bitmap: u32,
) -> Self {
let container_bits_per_sample = bits_per_sample + (bits_per_sample % 8); let container_bits_per_sample = bits_per_sample + (bits_per_sample % 8);
let container_bytes_per_sample = container_bits_per_sample / 8; let container_bytes_per_sample = container_bits_per_sample / 8;
let channel_count: u16 = (0..=31).fold(0u16, |accum, n| accum + (0x1 & (channel_bitmap >> n) as u16) ); let channel_count: u16 = (0..=31).fold(0u16, |accum, n| {
accum + (0x1 & (channel_bitmap >> n) as u16)
});
let result: (u16, Option<WaveFmtExtended>) = match channel_bitmap { let result: (u16, Option<WaveFmtExtended>) = match channel_bitmap {
ch if bits_per_sample != container_bits_per_sample => ( ch if bits_per_sample != container_bits_per_sample => (
(0xFFFE, Some(WaveFmtExtended { valid_bits_per_sample: bits_per_sample, channel_mask: ch, 0xFFFE,
type_guid: UUID_PCM }) ) Some(WaveFmtExtended {
valid_bits_per_sample: bits_per_sample,
channel_mask: ch,
type_guid: WAVE_UUID_PCM,
}),
), ),
0b0100 => (0x0001, None), 0b0100 => (0x0001, None),
0b0011 => (0x0001, None), 0b0011 => (0x0001, None),
ch => ( ch => (
(0xFFFE, Some( WaveFmtExtended { valid_bits_per_sample: bits_per_sample, channel_mask: ch, 0xFFFE,
type_guid: UUID_PCM})) Some(WaveFmtExtended {
) valid_bits_per_sample: bits_per_sample,
channel_mask: ch,
type_guid: WAVE_UUID_PCM,
}),
),
}; };
let (tag, extformat) = result; let (tag, extformat) = result;
@@ -275,107 +291,158 @@ impl WaveFmt {
tag, tag,
channel_count, channel_count,
sample_rate, sample_rate,
bytes_per_second: container_bytes_per_sample as u32 * sample_rate * channel_count as u32, bytes_per_second: container_bytes_per_sample as u32
* sample_rate
* channel_count as u32,
block_alignment: container_bytes_per_sample * channel_count, block_alignment: container_bytes_per_sample * channel_count,
bits_per_sample: container_bits_per_sample, bits_per_sample: container_bits_per_sample,
extended_format: extformat extended_format: extformat,
} }
} }
/// 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))
} }
/// Create a frame buffer sized to hold frames for a reader or writer /// Create a frame buffer sized to hold `length` frames for a reader or
/// writer
/// ///
/// This is a conveneince method that creates a `Vec<i32>` with /// This is a conveneince method that creates a `Vec<i32>` with
/// as many elements as there are channels in the underlying stream. /// as many elements as there are channels in the underlying stream.
pub fn create_frame_buffer(&self) -> Vec<i32> { pub fn create_frame_buffer<S: Sample>(&self, length: usize) -> Vec<S> {
vec![0i32; self.channel_count as usize] vec![S::EQUILIBRIUM; self.channel_count as usize * length]
} }
/// Calculate the size of a byte buffer needed to hold audio data of this /// Create a raw byte buffer to hold `length` blocks from a reader or
/// format for a given number of frames /// writer
pub fn buffer_length(&self, frame_count: u64) -> usize { pub fn create_raw_buffer(&self, length: usize) -> Vec<u8> {
(self.block_alignment as u64 * frame_count) as usize vec![0u8; self.block_alignment as usize * length]
} }
// Write frames into a byte vector /// Read bytes into frames
pub fn pack_frames(&self, from_frames: &[i32], into_bytes: &mut Vec<u8>) -> () { pub fn unpack_frames(&self, from_bytes: &[u8], into_frames: &mut [i32]) {
let mut write_cursor = Cursor::new(into_bytes); let mut rdr = Cursor::new(from_bytes);
for frame in into_frames {
assert!(from_frames.len() % self.channel_count as usize == 0, *frame = match (self.valid_bits_per_sample(), self.bits_per_sample) {
"frames buffer does not contain a number of samples % channel_count == 0"); (0..=8,8) => rdr.read_u8().unwrap() as i32 - 0x80_i32, // EBU 3285 §A2.2
(9..=16,16) => rdr.read_i16::<LittleEndian>().unwrap() as i32,
for n in 0..from_frames.len() { (10..=24,24) => rdr.read_i24::<LittleEndian>().unwrap(),
match (self.valid_bits_per_sample(), self.bits_per_sample) { (25..=32,32) => rdr.read_i32::<LittleEndian>().unwrap(),
(0..=8,8) => write_cursor.write_u8((from_frames[n] + 0x80) as u8 ).unwrap(), // EBU 3285 §A2.2
(9..=16,16) => write_cursor.write_i16::<LittleEndian>(from_frames[n] as i16).unwrap(),
(10..=24,24) => write_cursor.write_i24::<LittleEndian>(from_frames[n]).unwrap(),
(25..=32,32) => write_cursor.write_i32::<LittleEndian>(from_frames[n]).unwrap(),
(b,_)=> panic!("Unrecognized integer format, bits per sample {}, channels {}, block_alignment {}", (b,_)=> panic!("Unrecognized integer format, bits per sample {}, channels {}, block_alignment {}",
b, self.channel_count, self.block_alignment) b, self.channel_count, self.block_alignment)
} }
} }
()
} }
/// Read bytes into frames
// pub fn unpack_frames(&self, from_bytes: &[u8], into_frames: &mut Vec<i32>) -> () {
// for n in 0..(from_bytes.len()) {
// buffer[n] = match (self.format.bits_per_sample, framed_bits_per_sample) {
// (0..=8,8) => self.inner.read_u8()? as i32 - 0x80_i32, // EBU 3285 §A2.2
// (9..=16,16) => self.inner.read_i16::<LittleEndian>()? as i32,
// (10..=24,24) => self.inner.read_i24::<LittleEndian>()?,
// (25..=32,32) => self.inner.read_i32::<LittleEndian>()?,
// (b,_)=> panic!("Unrecognized integer format, bits per sample {}, channels {}, block_alignment {}",
// b, self.format.channel_count, self.format.block_alignment)
// }
// }
// }
/// Channel descriptors for each channel. /// Channel descriptors for each channel.
pub fn channels(&self) -> Vec<ChannelDescriptor> { pub fn channels(&self) -> Vec<ChannelDescriptor> {
match self.channel_count { match self.channel_count {
1 => vec![ 1 => vec![ChannelDescriptor {
ChannelDescriptor {
index: 0, index: 0,
speaker: ChannelMask::FrontCenter, speaker: ChannelMask::FrontCenter,
adm_track_audio_ids: vec![] adm_track_audio_ids: vec![],
} }],
],
2 => vec![ 2 => vec![
ChannelDescriptor { ChannelDescriptor {
index: 0, index: 0,
speaker: ChannelMask::FrontLeft, speaker: ChannelMask::FrontLeft,
adm_track_audio_ids: vec![] adm_track_audio_ids: vec![],
}, },
ChannelDescriptor { ChannelDescriptor {
index: 1, index: 1,
speaker: ChannelMask::FrontRight, speaker: ChannelMask::FrontRight,
adm_track_audio_ids: vec![] adm_track_audio_ids: vec![],
} },
], ],
x if x > 2 => { x if x > 2 => {
let channel_mask = self.extended_format.map(|x| x.channel_mask).unwrap_or(0); let channel_mask = self.extended_format.map(|x| x.channel_mask).unwrap_or(0);
let channels = ChannelMask::channels(channel_mask, self.channel_count); let channels = ChannelMask::channels(channel_mask, self.channel_count);
let channels_expanded = channels.iter().chain(std::iter::repeat(&ChannelMask::DirectOut)); let channels_expanded = channels
.iter()
.chain(std::iter::repeat(&ChannelMask::DirectOut));
(0..self.channel_count) (0..self.channel_count)
.zip(channels_expanded) .zip(channels_expanded)
.map(|(n, chan)| ChannelDescriptor { .map(|(n, chan)| ChannelDescriptor {
index: n, index: n,
speaker: *chan, speaker: *chan,
adm_track_audio_ids: vec![] adm_track_audio_ids: vec![],
}).collect() })
}, .collect()
}
x => panic!("Channel count ({}) was illegal!", x), x => panic!("Channel count ({}) was illegal!", x),
} }
} }
} }
pub trait ReadWavAudioData {
/// Read audio data from the receiver as interleaved [i32] samples.
fn read_i32_frames(
&mut self,
format: WaveFmt,
into: &mut [i32],
) -> Result<usize, std::io::Error>;
fn read_f32_frames(
&mut self,
format: WaveFmt,
into: &mut [f32],
) -> Result<usize, std::io::Error>;
}
impl<T> ReadWavAudioData for T
where
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(
&mut self,
format: WaveFmt,
into: &mut [i32],
) -> Result<usize, std::io::Error> {
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) {
(0..=8,8) => self.read_u8().unwrap() as i32 - 0x80_i32, // EBU 3285 §A2.2
(9..=16,16) => self.read_i16::<LittleEndian>().unwrap() as i32,
(10..=24,24) => self.read_i24::<LittleEndian>().unwrap(),
(25..=32,32) => self.read_i32::<LittleEndian>().unwrap(),
(b,_)=> panic!("Unrecognized integer format, bits per sample {}, channels {}, block_alignment {}",
b, format.channel_count, format.block_alignment)
}
}
todo!()
}
fn read_f32_frames(
&mut self,
format: WaveFmt,
into: &mut [f32],
) -> Result<usize, std::io::Error> {
assert!(into.len().is_multiple_of(format.channel_count as usize));
todo!()
}
}
trait WriteWavAudioData {
fn write_i32_frames(&mut self, format: WaveFmt, from: &[i32]) -> Result<usize, std::io::Error>;
fn write_f32_frames(&mut self, format: WaveFmt, from: &[f32]) -> Result<usize, std::io::Error>;
}
impl<T> WriteWavAudioData for T
where
T: std::io::Write,
{
fn write_i32_frames(&mut self, _format: WaveFmt, _: &[i32]) -> Result<usize, std::io::Error> {
todo!()
}
fn write_f32_frames(&mut self, _format: WaveFmt, _: &[f32]) -> Result<usize, std::io::Error> {
todo!()
}
}

View File

@@ -16,7 +16,12 @@ impl FourCC {
impl From<[char; 4]> for FourCC { impl From<[char; 4]> for FourCC {
fn from(chars: [char; 4]) -> Self { fn from(chars: [char; 4]) -> Self {
Self([chars[0] as u8 , chars[1] as u8, chars[2] as u8, chars[3] as u8]) Self([
chars[0] as u8,
chars[1] as u8,
chars[2] as u8,
chars[3] as u8,
])
} }
} }
@@ -32,20 +37,28 @@ impl From<FourCC> for [u8; 4] {
} }
} }
impl From<&FourCC> for [char; 4] { impl From<&FourCC> for [char; 4] {
fn from(f: &FourCC) -> Self { fn from(f: &FourCC) -> Self {
[f.0[0] as char, f.0[1] as char, f.0[2] as char, f.0[3] as char,] [
f.0[0] as char,
f.0[1] as char,
f.0[2] as char,
f.0[3] as char,
]
} }
} }
impl From<FourCC> for [char; 4] { impl From<FourCC> for [char; 4] {
fn from(f: FourCC) -> Self { fn from(f: FourCC) -> Self {
[f.0[0] as char, f.0[1] as char, f.0[2] as char, f.0[3] as char,] [
f.0[0] as char,
f.0[1] as char,
f.0[2] as char,
f.0[3] as char,
]
} }
} }
impl From<&FourCC> for String { impl From<&FourCC> for String {
fn from(f: &FourCC) -> Self { fn from(f: &FourCC) -> Self {
let chars: [char; 4] = f.into(); let chars: [char; 4] = f.into();
@@ -75,7 +88,10 @@ pub trait WriteFourCC: io::Write {
fn write_fourcc(&mut self, fourcc: FourCC) -> Result<(), io::Error>; fn write_fourcc(&mut self, fourcc: FourCC) -> Result<(), io::Error>;
} }
impl<T> ReadFourCC for T where T: io::Read { impl<T> ReadFourCC for T
where
T: io::Read,
{
fn read_fourcc(&mut self) -> Result<FourCC, io::Error> { fn read_fourcc(&mut self) -> Result<FourCC, io::Error> {
let mut buf: [u8; 4] = [0; 4]; let mut buf: [u8; 4] = [0; 4];
self.read_exact(&mut buf)?; self.read_exact(&mut buf)?;
@@ -83,7 +99,10 @@ impl<T> ReadFourCC for T where T: io::Read {
} }
} }
impl<T> WriteFourCC for T where T: io::Write { impl<T> WriteFourCC for T
where
T: io::Write,
{
fn write_fourcc(&mut self, fourcc: FourCC) -> Result<(), io::Error> { fn write_fourcc(&mut self, fourcc: FourCC) -> Result<(), io::Error> {
let buf: [u8; 4] = fourcc.into(); let buf: [u8; 4] = fourcc.into();
self.write_all(&buf)?; self.write_all(&buf)?;
@@ -91,7 +110,6 @@ impl<T> WriteFourCC for T where T: io::Write {
} }
} }
pub const RIFF_SIG: FourCC = FourCC::make(b"RIFF"); pub const RIFF_SIG: FourCC = FourCC::make(b"RIFF");
pub const WAVE_SIG: FourCC = FourCC::make(b"WAVE"); pub const WAVE_SIG: FourCC = FourCC::make(b"WAVE");
pub const RF64_SIG: FourCC = FourCC::make(b"RF64"); pub const RF64_SIG: FourCC = FourCC::make(b"RF64");
@@ -117,7 +135,6 @@ pub const LABL_SIG: FourCC = FourCC::make(b"labl");
pub const NOTE_SIG: FourCC = FourCC::make(b"note"); pub const NOTE_SIG: FourCC = FourCC::make(b"note");
pub const LTXT_SIG: FourCC = FourCC::make(b"ltxt"); pub const LTXT_SIG: FourCC = FourCC::make(b"ltxt");
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

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
@@ -31,39 +17,42 @@ Apps we test against:
- iZotope RX Audio Editor - iZotope RX Audio Editor
- FFMpeg - FFMpeg
- Audacity - Audacity
- Sound Devices field recorders: 702T, MixPre-10 II
[github]: https://github.com/iluvcapra/bwavfile [github]: https://github.com/iluvcapra/bwavfile
*/ */
// #![feature(external_doc)]
// #[doc(include="../README.md")]
// #[cfg(doctest)]
// pub struct ReadmeDoctests;
extern crate encoding;
extern crate byteorder; extern crate byteorder;
extern crate encoding;
extern crate uuid; extern crate uuid;
mod fourcc;
mod errors;
mod common_format; mod common_format;
mod errors;
mod fourcc;
mod parser;
mod list_form; mod list_form;
mod parser;
mod bext;
mod chunks; mod chunks;
mod cue; mod cue;
mod bext;
mod fmt; mod fmt;
mod sample;
mod wavereader; mod wavereader;
mod wavewriter; mod wavewriter;
pub use errors::Error;
pub use wavereader::{WaveReader, AudioFrameReader};
pub use wavewriter::{WaveWriter, AudioFrameWriter};
pub use bext::Bext; pub use bext::Bext;
pub use fmt::{WaveFmt, WaveFmtExtended, ChannelDescriptor, ChannelMask, ADMAudioID}; pub use common_format::{
pub use common_format::CommonFormat; CommonFormat, WAVE_TAG_EXTENDED, WAVE_TAG_FLOAT, WAVE_TAG_MPEG, WAVE_TAG_PCM,
WAVE_UUID_BFORMAT_FLOAT, WAVE_UUID_BFORMAT_PCM, WAVE_UUID_FLOAT, WAVE_UUID_MPEG, WAVE_UUID_PCM,
};
pub use cue::Cue; pub use cue::Cue;
pub use errors::Error;
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

@@ -1,10 +1,10 @@
use super::fourcc::{FourCC, ReadFourCC}; use super::fourcc::{FourCC, ReadFourCC};
use byteorder::{ReadBytesExt, LittleEndian}; use byteorder::{LittleEndian, ReadBytesExt};
use std::io::{Cursor, Error, Read}; use std::io::{Cursor, Error, Read};
pub struct ListFormItem { pub struct ListFormItem {
pub signature: FourCC, pub signature: FourCC,
pub contents : Vec<u8> pub contents: Vec<u8>,
} }
/// A helper that will accept a LIST chunk as a [u8] /// A helper that will accept a LIST chunk as a [u8]
@@ -26,7 +26,10 @@ pub fn collect_list_form(list_contents :& [u8]) -> Result<Vec<ListFormItem>, Err
cursor.read_exact(&mut content_buf)?; cursor.read_exact(&mut content_buf)?;
remain -= this_size; remain -= this_size;
retval.push( ListFormItem { signature : this_sig, contents : content_buf } ); retval.push(ListFormItem {
signature: this_sig,
contents: content_buf,
});
if this_size % 2 == 1 { if this_size % 2 == 1 {
cursor.read_u8()?; cursor.read_u8()?;
@@ -35,6 +38,5 @@ pub fn collect_list_form(list_contents :& [u8]) -> Result<Vec<ListFormItem>, Err
} }
} }
Ok(retval) Ok(retval)
} }

View File

@@ -1,15 +1,14 @@
use std::collections::HashMap;
use std::io; use std::io;
use std::io::SeekFrom::{Current, Start}; use std::io::SeekFrom::{Current, Start};
use std::io::{Seek, Read}; use std::io::{Read, Seek};
use std::collections::HashMap;
use byteorder::LittleEndian; use byteorder::LittleEndian;
use byteorder::ReadBytesExt; use byteorder::ReadBytesExt;
use super::errors::Error; use super::errors::Error;
use super::fourcc::{FourCC, ReadFourCC}; use super::fourcc::{FourCC, ReadFourCC};
use super::fourcc::{RIFF_SIG, RF64_SIG, BW64_SIG, WAVE_SIG, DS64_SIG, DATA_SIG}; use super::fourcc::{BW64_SIG, DATA_SIG, DS64_SIG, RF64_SIG, RIFF_SIG, WAVE_SIG};
// just for your reference... // just for your reference...
// RF64 documentation https://www.itu.int/dms_pubrec/itu-r/rec/bs/R-REC-BS.2088-1-201910-I!!PDF-E.pdf // RF64 documentation https://www.itu.int/dms_pubrec/itu-r/rec/bs/R-REC-BS.2088-1-201910-I!!PDF-E.pdf
@@ -19,14 +18,29 @@ use super::fourcc::{RIFF_SIG, RF64_SIG, BW64_SIG, WAVE_SIG, DS64_SIG, DATA_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 { signature: FourCC, length_field: u32 }, ReadHeader {
ReadRF64Header { signature: FourCC }, signature: FourCC,
ReadDS64 {file_size: u64, long_sizes: HashMap<FourCC,u64> }, length_field: u32,
BeginChunk { signature: FourCC, content_start: u64, content_length: u64 }, },
Failed { error: Error }, ReadRF64Header {
FinishParse signature: FourCC,
},
ReadDS64 {
file_size: u64,
long_sizes: HashMap<FourCC, u64>,
},
BeginChunk {
signature: FourCC,
content_start: u64,
content_length: u64,
},
Failed {
error: Error,
},
FinishParse,
} }
#[derive(Debug)] #[derive(Debug)]
@@ -36,30 +50,29 @@ enum State {
ReadyForDS64, ReadyForDS64,
ReadyForChunk { at: u64, remaining: u64 }, ReadyForChunk { at: u64, remaining: u64 },
Error, Error,
Complete Complete,
} }
pub struct Parser<R: Read + Seek> { pub struct Parser<R: Read + Seek> {
stream: R, stream: R,
state: State, state: State,
ds64state: HashMap<FourCC,u64> ds64state: HashMap<FourCC, u64>,
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct ChunkIteratorItem { pub struct ChunkIteratorItem {
pub signature: FourCC, pub signature: FourCC,
pub start: u64, pub start: u64,
pub length: u64 pub length: u64,
} }
impl<R: Read + Seek> Parser<R> { impl<R: Read + Seek> Parser<R> {
// wraps a stream // wraps a stream
pub fn make(stream: R) -> Result<Self, Error> { pub fn make(stream: R) -> Result<Self, Error> {
let newmap: HashMap<FourCC, u64> = HashMap::new(); let newmap: HashMap<FourCC, u64> = HashMap::new();
let mut the_stream = stream; let mut the_stream = stream;
the_stream.seek(Start(0))?; the_stream.seek(Start(0))?;
return Ok(Parser { Ok(Parser {
stream: the_stream, stream: the_stream,
state: State::New, state: State::New,
ds64state: newmap, ds64state: newmap,
@@ -71,24 +84,39 @@ impl<R: Read + Seek> Parser<R> {
// } // }
pub fn into_chunk_iterator(self) -> impl Iterator<Item = Result<ChunkIteratorItem, Error>> { pub fn into_chunk_iterator(self) -> impl Iterator<Item = Result<ChunkIteratorItem, Error>> {
self.filter_map({|event| self.filter_map({
if let Event::BeginChunk {signature , content_start, content_length } = event { |event| {
Some(Ok(ChunkIteratorItem {signature, start: content_start, length: content_length })) if let Event::BeginChunk {
signature,
content_start,
content_length,
} = event
{
Some(Ok(ChunkIteratorItem {
signature,
start: content_start,
length: content_length,
}))
} else if let Event::Failed { error } = event { } else if let Event::Failed { error } = event {
Some(Err(error)) Some(Err(error))
} else { } else {
None None
} }
}
}) })
} }
pub fn into_chunk_list(self) -> Result<Vec<ChunkIteratorItem>, Error> { pub fn into_chunk_list(self) -> Result<Vec<ChunkIteratorItem>, Error> {
let mut error = Ok(()); let mut error = Ok(());
let chunks = self.into_chunk_iterator() let chunks = self
.into_chunk_iterator()
.scan(&mut error, |err, res| match res { .scan(&mut error, |err, res| match res {
Ok(ok) => Some(ok), Ok(ok) => Some(ok),
Err(e) => { **err = Err(e); None } Err(e) => {
**err = Err(e);
None
}
}) })
.collect(); .collect();
@@ -96,7 +124,6 @@ impl<R: Read + Seek> Parser<R> {
Ok(chunks) Ok(chunks)
} }
} }
impl<R: Read + Seek> Iterator for Parser<R> { impl<R: Read + Seek> Iterator for Parser<R> {
@@ -105,12 +132,11 @@ impl<R: Read + Seek> Iterator for Parser<R> {
fn next(&mut self) -> Option<Event> { fn next(&mut self) -> Option<Event> {
let (event, next_state) = self.advance(); let (event, next_state) = self.advance();
self.state = next_state; self.state = next_state;
return event; event
} }
} }
impl<R: Read + Seek> Parser<R> { impl<R: Read + Seek> Parser<R> {
fn parse_header(&mut self) -> Result<(Event, State), io::Error> { fn parse_header(&mut self) -> Result<(Event, State), io::Error> {
let file_sig = self.stream.read_fourcc()?; let file_sig = self.stream.read_fourcc()?;
let length = self.stream.read_u32::<LittleEndian>()?; let length = self.stream.read_u32::<LittleEndian>()?;
@@ -123,30 +149,30 @@ impl<R: Read + Seek> Parser<R> {
(RIFF_SIG, size, WAVE_SIG) => { (RIFF_SIG, size, WAVE_SIG) => {
event = Event::ReadHeader { event = Event::ReadHeader {
signature: file_sig, signature: file_sig,
length_field: size length_field: size,
}; };
next_state = State::ReadyForChunk { next_state = State::ReadyForChunk {
at: 12, at: 12,
remaining: (length - 4) as u64, remaining: (length - 4) as u64,
}; };
}, }
(RF64_SIG, RF64_SIZE_MARKER, WAVE_SIG) | (BW64_SIG, RF64_SIZE_MARKER, WAVE_SIG) => { (RF64_SIG, RF64_SIZE_MARKER, WAVE_SIG) | (BW64_SIG, RF64_SIZE_MARKER, WAVE_SIG) => {
event = Event::ReadRF64Header { event = Event::ReadRF64Header {
signature: file_sig signature: file_sig,
}; };
next_state = State::ReadyForDS64; next_state = State::ReadyForDS64;
}, }
_ => { _ => {
event = Event::Failed { event = Event::Failed {
error: Error::HeaderNotRecognized error: Error::HeaderNotRecognized,
}; };
next_state = State::Error; next_state = State::Error;
} }
} }
return Ok( (event, next_state) ); Ok((event, next_state))
} }
fn parse_ds64(&mut self) -> Result<(Event, State), Error> { fn parse_ds64(&mut self) -> Result<(Event, State), Error> {
@@ -157,8 +183,7 @@ impl<R: Read + Seek> Parser<R> {
let mut read: u64 = 0; let mut read: u64 = 0;
if ds64_sig != DS64_SIG { if ds64_sig != DS64_SIG {
return Err(Error::MissingRequiredDS64); Err(Error::MissingRequiredDS64)
} else { } else {
let long_file_size = self.stream.read_u64::<LittleEndian>()?; let long_file_size = self.stream.read_u64::<LittleEndian>()?;
let long_data_size = self.stream.read_u64::<LittleEndian>()?; let long_data_size = self.stream.read_u64::<LittleEndian>()?;
@@ -197,19 +222,17 @@ impl<R: Read + Seek> Parser<R> {
remaining: long_file_size - (4 + 8 + ds64_size), remaining: long_file_size - (4 + 8 + ds64_size),
}; };
return Ok( (event, state) ); Ok((event, state))
} }
} }
fn enter_chunk(&mut self, at: u64, remaining: u64) -> Result<(Event, State), io::Error> { fn enter_chunk(&mut self, at: u64, remaining: u64) -> Result<(Event, State), io::Error> {
let event; let event;
let state; let state;
if remaining == 0 { if remaining == 0 {
event = Event::FinishParse; event = Event::FinishParse;
state = State::Complete; state = State::Complete;
} else { } else {
let this_fourcc = self.stream.read_fourcc()?; let this_fourcc = self.stream.read_fourcc()?;
let this_size: u64; let this_size: u64;
@@ -221,59 +244,84 @@ impl<R: Read + Seek> Parser<R> {
this_size = self.stream.read_u32::<LittleEndian>()? as u64; this_size = self.stream.read_u32::<LittleEndian>()? as u64;
} }
let this_displacement :u64 = if this_size % 2 == 1 { this_size + 1 } else { this_size }; let this_displacement: u64 = if this_size % 2 == 1 {
this_size + 1
} else {
this_size
};
self.stream.seek(Current(this_displacement as i64))?; self.stream.seek(Current(this_displacement as i64))?;
event = Event::BeginChunk { event = Event::BeginChunk {
signature: this_fourcc, signature: this_fourcc,
content_start: at + 8, content_start: at + 8,
content_length: this_size content_length: this_size,
}; };
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),
} }
} }
return Ok( (event, state) ); Ok((event, state))
} }
fn handle_state(&mut self) -> Result<(Option<Event>, State), Error> { fn handle_state(&mut self) -> Result<(Option<Event>, State), Error> {
match self.state { match self.state {
State::New => { State::New => Ok((Some(Event::StartParse), State::ReadyForHeader)),
return Ok( ( Some(Event::StartParse) , State::ReadyForHeader) );
},
State::ReadyForHeader => { State::ReadyForHeader => {
let (event, state) = self.parse_header()?; let (event, state) = self.parse_header()?;
return Ok( ( Some(event), state ) ); Ok((Some(event), state))
}, }
State::ReadyForDS64 => { State::ReadyForDS64 => {
let (event, state) = self.parse_ds64()?; let (event, state) = self.parse_ds64()?;
return Ok( ( Some(event), state ) ); Ok((Some(event), state))
}, }
State::ReadyForChunk { at, remaining } => { State::ReadyForChunk { at, remaining } => {
let (event, state) = self.enter_chunk(at, remaining)?; let (event, state) = self.enter_chunk(at, remaining)?;
return Ok( ( Some(event), state ) ); Ok((Some(event), state))
},
State::Error => {
return Ok( ( Some(Event::FinishParse) , State::Complete ) );
},
State::Complete => {
return Ok( ( None, State::Complete ) );
} }
State::Error => Ok((Some(Event::FinishParse), State::Complete)),
State::Complete => Ok((None, State::Complete)),
} }
} }
fn advance(&mut self) -> (Option<Event>, State) { fn advance(&mut self) -> (Option<Event>, State) {
match self.handle_state() { match self.handle_state() {
Ok(( event , state) ) => { Ok((event, state)) => (event, state),
return (event, state); Err(error) => (Some(Event::Failed { error }), State::Error),
},
Err(error) => {
return (Some(Event::Failed { error: error.into() } ), State::Error );
}
} }
} }
} }
#[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);
}
}

14
src/sample.rs Normal file
View File

@@ -0,0 +1,14 @@
pub use dasp_sample::I24;
use dasp_sample::Duplex;
pub trait Sample:
dasp_sample::Sample + Duplex<u8> + Duplex<i16> + Duplex<I24> + Duplex<i32> + Duplex<f32>
{
}
impl Sample for u8 {}
impl Sample for i16 {}
impl Sample for I24 {}
impl Sample for i32 {}
impl Sample for f32 {}

View File

@@ -1,26 +1,29 @@
use std::fs::File; use std::fs::File;
use std::io::SeekFrom; use std::path::Path;
use std::io::Cursor;
use std::io::{Read, Seek, BufReader}; use std::io::Cursor;
use std::io::SeekFrom::{Start,Current,}; use std::io::SeekFrom;
use std::io::SeekFrom::Start;
use std::io::{BufReader, Read, Seek};
use super::parser::Parser;
use super::fourcc::{FourCC, ReadFourCC, FMT__SIG, DATA_SIG, BEXT_SIG, LIST_SIG,
JUNK_SIG, FLLR_SIG, CUE__SIG, ADTL_SIG, AXML_SIG, IXML_SIG};
use super::errors::Error as ParserError;
use super::fmt::{WaveFmt, ChannelDescriptor, ChannelMask};
use super::bext::Bext; use super::bext::Bext;
use super::chunks::ReadBWaveChunks; use super::chunks::ReadBWaveChunks;
use super::cue::Cue; use super::cue::Cue;
use super::errors::Error as ParserError;
use super::errors::Error; use super::errors::Error;
use super::CommonFormat; use super::fmt::{ChannelDescriptor, ChannelMask, WaveFmt};
use super::fourcc::{
FourCC, ReadFourCC, ADTL_SIG, AXML_SIG, BEXT_SIG, CUE__SIG, DATA_SIG, FLLR_SIG, FMT__SIG,
IXML_SIG, JUNK_SIG, LIST_SIG,
};
use super::parser::Parser;
use super::{CommonFormat, Sample, I24};
use byteorder::LittleEndian; use byteorder::LittleEndian;
use byteorder::ReadBytesExt; use byteorder::ReadBytesExt;
use dasp_sample::Sample as _; // Expose to_sample()
/// Read audio frames /// Read audio frames
/// ///
@@ -32,11 +35,10 @@ pub struct AudioFrameReader<R: Read + Seek> {
inner: R, inner: R,
format: WaveFmt, format: WaveFmt,
start: u64, start: u64,
length: u64 length: u64,
} }
impl<R: Read + Seek> AudioFrameReader<R> { impl<R: Read + Seek> AudioFrameReader<R> {
/// Create a new `AudioFrameReader` /// Create a new `AudioFrameReader`
/// ///
/// ### Panics /// ### Panics
@@ -46,15 +48,27 @@ impl<R: Read + Seek> AudioFrameReader<R> {
/// and the format tag is readable by this implementation (only /// and the format tag is readable by this implementation (only
/// format 0x01 is supported at this time.) /// format 0x01 is supported at this time.)
pub fn new(mut inner: R, format: WaveFmt, start: u64, length: u64) -> Result<Self, Error> { pub fn new(mut inner: R, format: WaveFmt, start: u64, length: u64) -> Result<Self, Error> {
assert!(format.block_alignment * 8 == format.bits_per_sample * format.channel_count, assert!(
format.block_alignment * 8 == format.bits_per_sample * format.channel_count,
"Unable to read audio frames from packed formats: block alignment is {}, should be {}", "Unable to read audio frames from packed formats: block alignment is {}, should be {}",
format.block_alignment, (format.bits_per_sample / 8 ) * format.channel_count); format.block_alignment,
(format.bits_per_sample / 8) * format.channel_count
);
assert!(format.common_format() == CommonFormat::IntegerPCM , assert!(
"Unsupported format tag {:?}", format.tag); format.common_format() == CommonFormat::IntegerPCM
|| format.common_format() == CommonFormat::IeeeFloatPCM,
"Unsupported format tag {:?}",
format.tag
);
inner.seek(Start(start))?; inner.seek(Start(start))?;
Ok( AudioFrameReader { inner , format , start, length} ) Ok(AudioFrameReader {
inner,
format,
start,
length,
})
} }
/// Unwrap the inner reader. /// Unwrap the inner reader.
@@ -76,50 +90,79 @@ impl<R: Read + Seek> AudioFrameReader<R> {
Ok((seek_result - self.start) / self.format.block_alignment as u64) Ok((seek_result - self.start) / self.format.block_alignment as u64)
} }
/// Reads frames from the file into the provided buffer
///
/// The function will attempt to fill the buffer, but will stop without error when the end of
/// the file is reached.
///
/// The reader will convert from the file's sample type into the buffer's sample type.
/// Note that no dithering will be applied during sample type conversion,
/// if dithering is required then it will need to be applied manually.
///
/// The return value is the number of frames read into the buffer.
pub fn read_frames<S>(&mut self, buffer: &mut [S]) -> Result<u64, Error>
where
S: Sample,
{
use CommonFormat::*;
/// Read a frame let channel_count = self.format.channel_count as usize;
/// let common_format = self.format.common_format();
/// A single frame is read from the audio stream and the read location let bits_per_sample = self.format.bits_per_sample;
/// is advanced one frame.
///
/// Regardless of the number of bits in the audio sample, this method
/// always writes `i32` samples back to the buffer. These samples are
/// written back "right-aligned" so samples that are shorter than i32
/// will leave the MSB bits empty.
///
/// For example: A full-code sample in 16 bit (0xFFFF) will be written
/// back to the buffer as 0x0000FFFF.
///
///
/// ### Panics
///
/// The `buffer` must have a number of elements equal to the number of
/// channels and this method will panic if this is not the case.
pub fn read_integer_frame(&mut self, buffer:&mut [i32]) -> Result<u64,Error> {
assert!(buffer.len() as u16 == self.format.channel_count,
"read_integer_frame was called with a mis-sized buffer, expected {}, was {}",
self.format.channel_count, buffer.len());
let framed_bits_per_sample = self.format.block_alignment * 8 / self.format.channel_count; if !buffer.len().is_multiple_of(channel_count) {
return Err(Error::InvalidBufferSize {
buffer_size: buffer.len(),
channel_count: self.format.channel_count,
});
}
let tell = self.inner.seek(Current(0))?; let position = self.inner.stream_position()? - self.start;
let frames_requested = (buffer.len() / channel_count) as u64;
let bytes_per_frame = self.format.block_alignment as u64;
let frames_remaining = (self.length - position) / bytes_per_frame;
let frames_to_read = frames_requested.min(frames_remaining);
let samples_to_read = frames_to_read as usize * channel_count;
if (tell - self.start) < self.length { match (common_format, bits_per_sample) {
for n in 0..(self.format.channel_count as usize) { (IntegerPCM, 8) => read_into_buffer(samples_to_read, buffer, || {
buffer[n] = match (self.format.bits_per_sample, framed_bits_per_sample) { Ok(self.inner.read_u8()?.to_sample())
(0..=8,8) => self.inner.read_u8()? as i32 - 0x80_i32, // EBU 3285 §A2.2 }),
(9..=16,16) => self.inner.read_i16::<LittleEndian>()? as i32, (IntegerPCM, 16) => read_into_buffer(samples_to_read, buffer, || {
(10..=24,24) => self.inner.read_i24::<LittleEndian>()?, Ok(self.inner.read_i16::<LittleEndian>()?.to_sample())
(25..=32,32) => self.inner.read_i32::<LittleEndian>()?, }),
(b,_)=> panic!("Unrecognized integer format, bits per sample {}, channels {}, block_alignment {}", (IntegerPCM, 24) => read_into_buffer(samples_to_read, buffer, || {
b, self.format.channel_count, self.format.block_alignment) Ok(I24::from(self.inner.read_i24::<LittleEndian>()?).to_sample())
}),
(IntegerPCM, 32) => read_into_buffer(samples_to_read, buffer, || {
Ok(self.inner.read_i32::<LittleEndian>()?.to_sample())
}),
(IeeeFloatPCM, 32) => read_into_buffer(samples_to_read, buffer, || {
Ok(self.inner.read_f32::<LittleEndian>()?.to_sample())
}),
(_, _) => panic!(
"Unsupported format, bits per sample {}, channels {}, sample format: {:?}",
bits_per_sample, channel_count, common_format
),
}?;
Ok(frames_to_read)
} }
} }
Ok( 1 )
} else { fn read_into_buffer<S, F>(
Ok( 0 ) sample_count: usize,
} buffer: &mut [S],
mut read_fn: F,
) -> Result<(), Error>
where
F: FnMut() -> Result<S, Error>,
{
for output in buffer.iter_mut().take(sample_count) {
*output = read_fn()?;
} }
Ok(())
} }
/// Wave, Broadcast-WAV and RF64/BW64 parser/reader. /// Wave, Broadcast-WAV and RF64/BW64 parser/reader.
@@ -133,9 +176,9 @@ impl<R: Read + Seek> AudioFrameReader<R> {
/// assert_eq!(format.channel_count, 1); /// assert_eq!(format.channel_count, 1);
/// ///
/// let mut frame_reader = r.audio_frame_reader().unwrap(); /// let mut frame_reader = r.audio_frame_reader().unwrap();
/// let mut buffer = format.create_frame_buffer(); /// let mut buffer = format.create_frame_buffer::<i32>(1);
/// ///
/// let read = frame_reader.read_integer_frame(&mut buffer).unwrap(); /// let read = frame_reader.read_frames(&mut buffer).unwrap();
/// ///
/// assert_eq!(buffer, [0i32]); /// assert_eq!(buffer, [0i32]);
/// assert_eq!(read, 1); /// assert_eq!(read, 1);
@@ -167,35 +210,30 @@ impl<R: Read + Seek> AudioFrameReader<R> {
/// [itu2088]: https://www.itu.int/dms_pubrec/itu-r/rec/bs/R-REC-BS.2088-1-201910-I!!PDF-E.pdf /// [itu2088]: https://www.itu.int/dms_pubrec/itu-r/rec/bs/R-REC-BS.2088-1-201910-I!!PDF-E.pdf
/// [rfc3261]: https://tools.ietf.org/html/rfc2361 /// [rfc3261]: https://tools.ietf.org/html/rfc2361
#[derive(Debug)] #[derive(Debug)]
pub struct WaveReader<R: Read + Seek> { pub struct WaveReader<R: Read + Seek> {
pub inner: R, pub inner: R,
} }
impl WaveReader<BufReader<File>> { impl WaveReader<BufReader<File>> {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, ParserError> {
pub fn open(path: &str) -> Result<Self, ParserError> {
let f = File::open(path)?; let f = File::open(path)?;
let inner = BufReader::new(f); let inner = BufReader::new(f);
Ok( Self::new(inner)? ) Self::new(inner)
} }
} }
impl WaveReader<File> { impl WaveReader<File> {
/// Open a file for reading with unbuffered IO. /// Open a file for reading with unbuffered IO.
/// ///
/// A convenience that opens `path` and calls `Self::new()` /// A convenience that opens `path` and calls `Self::new()`
pub fn open_unbuffered<P: AsRef<Path>>(path: P) -> Result<Self, ParserError> {
pub fn open_unbuffered(path: &str) -> Result<Self, ParserError> {
let inner = File::open(path)?; let inner = File::open(path)?;
return Ok( Self::new(inner)? ) Self::new(inner)
} }
} }
impl<R: Read + Seek> WaveReader<R> { impl<R: Read + Seek> WaveReader<R> {
/// Wrap a `Read` struct in a new `WaveReader`. /// Wrap a `Read` struct in a new `WaveReader`.
/// ///
/// This is the primary entry point into the `WaveReader` interface. The /// This is the primary entry point into the `WaveReader` interface. The
@@ -207,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};
@@ -232,10 +270,9 @@ impl<R: Read + Seek> WaveReader<R> {
Ok(retval) Ok(retval)
} }
/// Unwrap the inner reader. /// Unwrap the inner reader.
pub fn into_inner(self) -> R { pub fn into_inner(self) -> R {
return self.inner; self.inner
} }
/// ///
@@ -244,10 +281,14 @@ impl<R: Read + Seek> WaveReader<R> {
pub fn audio_frame_reader(mut self) -> Result<AudioFrameReader<R>, ParserError> { pub fn audio_frame_reader(mut self) -> Result<AudioFrameReader<R>, ParserError> {
let format = self.format()?; let format = self.format()?;
let audio_chunk_reader = self.get_chunk_extent_at_index(DATA_SIG, 0)?; let audio_chunk_reader = self.get_chunk_extent_at_index(DATA_SIG, 0)?;
Ok(AudioFrameReader::new(self.inner, format, audio_chunk_reader.0, audio_chunk_reader.1)?) AudioFrameReader::new(
self.inner,
format,
audio_chunk_reader.0,
audio_chunk_reader.1,
)
} }
/// The count of audio frames in the file. /// The count of audio frames in the file.
pub fn frame_length(&mut self) -> Result<u64, ParserError> { pub fn frame_length(&mut self) -> Result<u64, ParserError> {
let (_, data_length) = self.get_chunk_extent_at_index(DATA_SIG, 0)?; let (_, data_length) = self.get_chunk_extent_at_index(DATA_SIG, 0)?;
@@ -255,7 +296,6 @@ impl<R: Read + Seek> WaveReader<R> {
Ok(data_length / (format.block_alignment as u64)) Ok(data_length / (format.block_alignment as u64))
} }
/// Sample and frame format of this wave file. /// Sample and frame format of this wave file.
/// ///
pub fn format(&mut self) -> Result<WaveFmt, ParserError> { pub fn format(&mut self) -> Result<WaveFmt, ParserError> {
@@ -275,7 +315,6 @@ impl<R: Read + Seek> WaveReader<R> {
} else { } else {
Ok(None) Ok(None)
} }
} }
/// Describe the channels in this file /// Describe the channels in this file
@@ -296,17 +335,21 @@ impl<R: Read + Seek> WaveReader<R> {
/// assert_eq!(chans[4].speaker, ChannelMask::BackLeft); /// assert_eq!(chans[4].speaker, ChannelMask::BackLeft);
/// ``` /// ```
pub fn channels(&mut self) -> Result<Vec<ChannelDescriptor>, ParserError> { pub fn channels(&mut self) -> Result<Vec<ChannelDescriptor>, ParserError> {
let format = self.format()?; let format = self.format()?;
let channel_masks: Vec<ChannelMask> = match (format.channel_count, format.extended_format) { let channel_masks: Vec<ChannelMask> = match (format.channel_count, format.extended_format) {
(1, _) => vec![ChannelMask::FrontCenter], (1, _) => vec![ChannelMask::FrontCenter],
(2, _) => vec![ChannelMask::FrontLeft, ChannelMask::FrontRight], (2, _) => vec![ChannelMask::FrontLeft, ChannelMask::FrontRight],
(n, Some(x)) => ChannelMask::channels(x.channel_mask, n), (n, Some(x)) => ChannelMask::channels(x.channel_mask, n),
(n,_) => vec![ChannelMask::DirectOut; n as usize] (n, _) => vec![ChannelMask::DirectOut; n as usize],
}; };
Ok( (0..format.channel_count).zip(channel_masks) Ok((0..format.channel_count)
.map(|(i,m)| ChannelDescriptor { index: i, speaker:m, adm_track_audio_ids: vec![] } ) .zip(channel_masks)
.map(|(i, m)| ChannelDescriptor {
index: i,
speaker: m,
adm_track_audio_ids: vec![],
})
.collect()) .collect())
} }
@@ -346,7 +389,7 @@ impl<R: Read + Seek> WaveReader<R> {
match (cue_read, adtl_read) { match (cue_read, adtl_read) {
(0, _) => Ok(vec![]), (0, _) => Ok(vec![]),
(_, 0) => Ok(Cue::collect_from(&cue_buffer, None)?), (_, 0) => Ok(Cue::collect_from(&cue_buffer, None)?),
(_,_) => Ok( Cue::collect_from(&cue_buffer, Some(&adtl_buffer) )? ) (_, _) => Ok(Cue::collect_from(&cue_buffer, Some(&adtl_buffer))?),
} }
} }
@@ -370,7 +413,6 @@ impl<R: Read + Seek> WaveReader<R> {
self.read_chunk(AXML_SIG, 0, buffer) self.read_chunk(AXML_SIG, 0, buffer)
} }
/** /**
* Validate file is readable. * Validate file is readable.
* *
@@ -421,7 +463,10 @@ impl<R: Read + Seek> WaveReader<R> {
self.validate_readable()?; self.validate_readable()?;
let chunk_fourccs: Vec<FourCC> = Parser::make(&mut self.inner)? let chunk_fourccs: Vec<FourCC> = Parser::make(&mut self.inner)?
.into_chunk_list()?.iter().map(|c| c.signature ).collect(); .into_chunk_list()?
.iter()
.map(|c| c.signature)
.collect();
if chunk_fourccs == vec![FMT__SIG, DATA_SIG] { if chunk_fourccs == vec![FMT__SIG, DATA_SIG] {
Ok(()) /* FIXME: finish implementation */ Ok(()) /* FIXME: finish implementation */
@@ -484,31 +529,40 @@ impl<R: Read + Seek> WaveReader<R> {
let chunks = Parser::make(&mut self.inner)?.into_chunk_list()?; let chunks = Parser::make(&mut self.inner)?.into_chunk_list()?;
let ds64_space_required = 92; let ds64_space_required = 92;
let eligible_filler_chunks = chunks.iter() let eligible_filler_chunks = chunks
.iter()
.take_while(|c| c.signature == JUNK_SIG || c.signature == FLLR_SIG); .take_while(|c| c.signature == JUNK_SIG || c.signature == FLLR_SIG);
let filler = eligible_filler_chunks let filler = eligible_filler_chunks
.enumerate() .enumerate()
.fold(0, |accum, (n, item)| if n == 0 { accum + item.length } else {accum + item.length + 8}); .fold(0, |accum, (n, item)| {
if n == 0 {
accum + item.length
} else {
accum + item.length + 8
}
});
if filler < ds64_space_required { if filler < ds64_space_required {
Err(ParserError::InsufficientDS64Reservation {expected: ds64_space_required, actual: filler}) Err(ParserError::InsufficientDS64Reservation {
expected: ds64_space_required,
actual: filler,
})
} else { } else {
let data_pos = chunks.iter().position(|c| c.signature == DATA_SIG); let data_pos = chunks.iter().position(|c| c.signature == DATA_SIG);
match data_pos { match data_pos {
Some(p) if p == chunks.len() - 1 => Ok(()), Some(p) if p == chunks.len() - 1 => Ok(()),
_ => Err(ParserError::DataChunkNotPreparedForAppend) _ => Err(ParserError::DataChunkNotPreparedForAppend),
} }
} }
} }
} }
impl<R: Read + Seek> WaveReader<R> { impl<R: Read + Seek> WaveReader<R> {
// Private implementation // Private implementation
// //
// As time passes thi get smore obnoxious because I haven't implemented recursive chunk // As time passes this get smore obnoxious because I haven't implemented recursive chunk
// parsing in the raw parser and I'm working around it // parsing in the raw parser and I'm working around it
// fn chunk_reader(&mut self, signature: FourCC, at_index: u32) -> Result<RawChunkReader<R>, ParserError> { // fn chunk_reader(&mut self, signature: FourCC, at_index: u32) -> Result<RawChunkReader<R>, ParserError> {
@@ -524,17 +578,20 @@ impl<R:Read+Seek> WaveReader<R> {
} }
} }
fn read_chunk(
fn read_chunk(&mut self, ident: FourCC, at: u32, mut buffer: &mut Vec<u8>) -> Result<usize, ParserError> { &mut self,
ident: FourCC,
at: u32,
buffer: &mut Vec<u8>,
) -> Result<usize, ParserError> {
match self.get_chunk_extent_at_index(ident, at) { match self.get_chunk_extent_at_index(ident, at) {
Ok((start, length)) => { Ok((start, length)) => {
buffer.resize(length as usize, 0x0); buffer.resize(length as usize, 0x0);
self.inner.seek(SeekFrom::Start(start))?; self.inner.seek(SeekFrom::Start(start))?;
self.inner.read(&mut buffer).map_err(|e| ParserError::IOError(e)) self.inner.read(buffer).map_err(ParserError::IOError)
}, }
Err(ParserError::ChunkMissing { signature: _ }) => Ok(0), Err(ParserError::ChunkMissing { signature: _ }) => Ok(0),
Err( any ) => Err(any.into()) Err(any) => Err(any),
} }
} }
@@ -542,14 +599,16 @@ impl<R:Read+Seek> WaveReader<R> {
fn get_chunks_extents(&mut self, fourcc: FourCC) -> Result<Vec<(u64, u64)>, ParserError> { fn get_chunks_extents(&mut self, fourcc: FourCC) -> Result<Vec<(u64, u64)>, ParserError> {
let p = Parser::make(&mut self.inner)?.into_chunk_list()?; let p = Parser::make(&mut self.inner)?.into_chunk_list()?;
Ok( p.iter().filter(|item| item.signature == fourcc) Ok(p.iter()
.map(|item| (item.start, item.length)).collect() ) .filter(|item| item.signature == fourcc)
.map(|item| (item.start, item.length))
.collect())
} }
/// Index of first LIST for with the given FORM fourcc /// Index of first LIST for with the given FORM fourcc
fn get_list_form(&mut self, fourcc: FourCC) -> Result<Option<u32>, ParserError> { fn get_list_form(&mut self, fourcc: FourCC) -> Result<Option<u32>, ParserError> {
for (n, (start, _)) in self.get_chunks_extents(LIST_SIG)?.iter().enumerate() { for (n, (start, _)) in self.get_chunks_extents(LIST_SIG)?.iter().enumerate() {
self.inner.seek(SeekFrom::Start(*start as u64))?; self.inner.seek(SeekFrom::Start(*start))?;
let this_fourcc = self.inner.read_fourcc()?; let this_fourcc = self.inner.read_fourcc()?;
if this_fourcc == fourcc { if this_fourcc == fourcc {
return Ok(Some(n as u32)); return Ok(Some(n as u32));
@@ -559,8 +618,12 @@ impl<R:Read+Seek> WaveReader<R> {
Ok(None) Ok(None)
} }
fn get_chunk_extent_at_index(&mut self, fourcc: FourCC, index: u32) -> Result<(u64,u64), ParserError> { fn get_chunk_extent_at_index(
if let Some((start, length)) = self.get_chunks_extents(fourcc)?.iter().nth(index as usize) { &mut self,
fourcc: FourCC,
index: u32,
) -> Result<(u64, u64), ParserError> {
if let Some((start, length)) = self.get_chunks_extents(fourcc)?.get(index as usize) {
Ok((*start, *length)) Ok((*start, *length))
} else { } else {
Err(ParserError::ChunkMissing { signature: fourcc }) Err(ParserError::ChunkMissing { signature: fourcc })
@@ -576,5 +639,4 @@ fn test_list_form() {
f.read_list(ADTL_SIG, &mut buf).unwrap(); f.read_list(ADTL_SIG, &mut buf).unwrap();
assert_ne!(buf.len(), 0); assert_ne!(buf.len(), 0);
} }

View File

@@ -1,14 +1,18 @@
use std::fs::File; use std::fs::File;
use std::io::{Write,Seek,SeekFrom,Cursor,BufWriter}; use std::io::{BufWriter, Cursor, Seek, SeekFrom, Write};
use std::path::Path;
use crate::CommonFormat;
use super::Error;
use super::fourcc::{FourCC, WriteFourCC, RIFF_SIG, RF64_SIG, DS64_SIG,
WAVE_SIG, FMT__SIG, DATA_SIG, ELM1_SIG, JUNK_SIG, BEXT_SIG,AXML_SIG,
IXML_SIG};
use super::fmt::WaveFmt; use super::fmt::WaveFmt;
use super::fourcc::{
FourCC, WriteFourCC, AXML_SIG, BEXT_SIG, DATA_SIG, DS64_SIG, ELM1_SIG, FMT__SIG, IXML_SIG,
JUNK_SIG, RF64_SIG, RIFF_SIG, WAVE_SIG,
};
use super::{Error, Sample, I24};
//use super::common_format::CommonFormat; //use super::common_format::CommonFormat;
use super::chunks::WriteBWaveChunks;
use super::bext::Bext; use super::bext::Bext;
use super::chunks::WriteBWaveChunks;
use byteorder::LittleEndian; use byteorder::LittleEndian;
use byteorder::WriteBytesExt; use byteorder::WriteBytesExt;
@@ -16,37 +20,87 @@ use byteorder::WriteBytesExt;
/// Write audio frames to a `WaveWriter`. /// Write audio frames to a `WaveWriter`.
/// ///
/// ///
pub struct AudioFrameWriter<W> where W: Write + Seek { pub struct AudioFrameWriter<W>
inner : WaveChunkWriter<W> where
W: Write + Seek,
{
inner: WaveChunkWriter<W>,
write_buffer: Vec<u8>,
} }
impl<W> AudioFrameWriter<W> where W: Write + Seek { impl<W> AudioFrameWriter<W>
where
W: Write + Seek,
{
fn new(inner: WaveChunkWriter<W>) -> Self { fn new(inner: WaveChunkWriter<W>) -> Self {
AudioFrameWriter { inner } AudioFrameWriter {
inner,
write_buffer: Vec::new(),
} }
fn write_integer_frames_to_buffer(&self, from_frames :&[i32], to_buffer : &mut Vec<u8>) -> () {
assert!(from_frames.len() % self.inner.inner.format.channel_count as usize == 0,
"frames buffer does not contain a number of samples % channel_count == 0");
self.inner.inner.format.pack_frames(&from_frames, to_buffer);
()
} }
/// Write interleaved samples in `buffer` /// Write interleaved samples in `buffer`
/// ///
/// # Panics /// The writer will convert from the buffer's sample type into the file's sample type.
/// /// Note that no dithering will be applied during sample type conversion,
/// This function will panic if `buffer.len()` modulo the Wave file's channel count /// if dithering is required then it will need to be applied manually.
/// is not zero. pub fn write_frames<S>(&mut self, buffer: &[S]) -> Result<(), Error>
pub fn write_integer_frames(&mut self, buffer: &[i32]) -> Result<u64,Error> { where
let mut write_buffer = vec![0u8; 0]; S: Sample,
{
let format = &self.inner.inner.format;
let channel_count = format.channel_count as usize;
self.write_integer_frames_to_buffer(&buffer, &mut write_buffer); if !buffer.len().is_multiple_of(channel_count) {
return Err(Error::InvalidBufferSize {
buffer_size: buffer.len(),
channel_count: format.channel_count,
});
}
self.inner.write(&write_buffer)?; let frame_count = buffer.len() / channel_count;
self.inner.flush()?; let write_buffer_size = format.block_alignment as usize * frame_count;
Ok(write_buffer.len() as u64 / self.inner.inner.format.channel_count as u64) self.write_buffer.resize(write_buffer_size, 0);
let mut write_cursor = Cursor::new(&mut self.write_buffer);
let common_format = format.common_format();
let bits_per_sample = format.bits_per_sample;
match (common_format, bits_per_sample) {
(_, 8) => {
for sample in buffer {
write_cursor.write_u8(sample.to_sample())?
}
}
(_, 16) => {
for sample in buffer {
write_cursor.write_i16::<LittleEndian>(sample.to_sample())?
}
}
(_, 24) => {
for sample in buffer {
write_cursor.write_i24::<LittleEndian>(sample.to_sample::<I24>().inner())?
}
}
(CommonFormat::IntegerPCM, 32) => {
for sample in buffer {
write_cursor.write_i32::<LittleEndian>(sample.to_sample())?
}
}
(CommonFormat::IeeeFloatPCM, 32) => {
for sample in buffer {
write_cursor.write_f32::<LittleEndian>(sample.to_sample())?
}
}
(_, _) => panic!(
"Unrecognized format, bits per sample {}, channels {}, sample format {:?}",
bits_per_sample, channel_count, common_format
),
}
self.inner.write_all(&self.write_buffer)?;
Ok(())
} }
/// Finish writing audio frames and unwrap the inner `WaveWriter`. /// Finish writing audio frames and unwrap the inner `WaveWriter`.
@@ -66,59 +120,76 @@ impl<W> AudioFrameWriter<W> where W: Write + Seek {
/// ///
/// When you are done writing to a chunk you must call `end()` in order to /// When you are done writing to a chunk you must call `end()` in order to
/// finalize the chunk for storage. /// finalize the chunk for storage.
pub struct WaveChunkWriter<W> where W: Write + Seek { pub struct WaveChunkWriter<W>
where
W: Write + Seek,
{
ident: FourCC, ident: FourCC,
inner: WaveWriter<W>, inner: WaveWriter<W>,
content_start_pos: u64, content_start_pos: u64,
length : u64 length: u64,
} }
impl<W> WaveChunkWriter<W> where W: Write + Seek { impl<W> WaveChunkWriter<W>
where
W: Write + Seek,
{
fn begin(mut inner: WaveWriter<W>, ident: FourCC) -> Result<Self, Error> { fn begin(mut inner: WaveWriter<W>, ident: FourCC) -> Result<Self, Error> {
let length: u64 = 0; let length: u64 = 0;
inner.inner.write_fourcc(ident)?; inner.inner.write_fourcc(ident)?;
inner.inner.write_u32::<LittleEndian>(length as u32)?; inner.inner.write_u32::<LittleEndian>(length as u32)?;
inner.increment_form_length(8)?; inner.increment_form_length(8)?;
let content_start_pos = inner.inner.seek(SeekFrom::End(0))?; let content_start_pos = inner.inner.seek(SeekFrom::End(0))?;
Ok( WaveChunkWriter { ident, inner , content_start_pos, length } ) Ok(WaveChunkWriter {
ident,
inner,
content_start_pos,
length,
})
} }
fn end(mut self) -> Result<WaveWriter<W>, Error> { fn end(mut self) -> Result<WaveWriter<W>, Error> {
if self.length % 2 == 1 { if self.length % 2 == 1 {
self.inner.inner.seek(SeekFrom::End(0))?; self.inner.inner.seek(SeekFrom::End(0))?;
self.inner.inner.write(&[0u8])?; self.inner.inner.write_u8(0)?;
self.inner.increment_form_length(1)?; self.inner.increment_form_length(1)?;
} }
Ok(self.inner) Ok(self.inner)
} }
fn increment_chunk_length(&mut self, amount: u64) -> Result<(), std::io::Error> { fn increment_chunk_length(&mut self, amount: u64) -> Result<(), std::io::Error> {
self.length = self.length + amount; self.length += amount;
if !self.inner.is_rf64 { if !self.inner.is_rf64 {
self.inner.inner.seek(SeekFrom::Start(self.content_start_pos - 4))?; self.inner
self.inner.inner.write_u32::<LittleEndian>(self.length as u32)?; .inner
} else { .seek(SeekFrom::Start(self.content_start_pos - 4))?;
if self.ident == DATA_SIG { self.inner
.inner
.write_u32::<LittleEndian>(self.length as u32)?;
} else if self.ident == DATA_SIG {
let data_chunk_64bit_field_offset = 8 + 4 + 8 + 8; let data_chunk_64bit_field_offset = 8 + 4 + 8 + 8;
self.inner.inner.seek(SeekFrom::Start(self.content_start_pos - 4))?; self.inner
.inner
.seek(SeekFrom::Start(self.content_start_pos - 4))?;
self.inner.inner.write_u32::<LittleEndian>(0xFFFF_FFFF)?; self.inner.inner.write_u32::<LittleEndian>(0xFFFF_FFFF)?;
// this only need to happen once, not every time we increment // this only need to happen once, not every time we increment
self.inner.inner.seek(SeekFrom::Start(data_chunk_64bit_field_offset))?; self.inner
.inner
.seek(SeekFrom::Start(data_chunk_64bit_field_offset))?;
self.inner.inner.write_u64::<LittleEndian>(self.length)?; self.inner.inner.write_u64::<LittleEndian>(self.length)?;
} else { } else {
todo!("FIXME RF64 wave writing is not yet supported for chunks other than `data`") todo!("FIXME RF64 wave writing is not yet supported for chunks other than `data`")
} }
}
Ok(()) Ok(())
} }
} }
impl<W> Write for WaveChunkWriter<W> where W: Write + Seek { impl<W> Write for WaveChunkWriter<W>
where
W: Write + Seek,
{
fn write(&mut self, buffer: &[u8]) -> Result<usize, std::io::Error> { fn write(&mut self, buffer: &[u8]) -> Result<usize, std::io::Error> {
self.inner.inner.seek(SeekFrom::End(0))?; self.inner.inner.seek(SeekFrom::End(0))?;
let written = self.inner.inner.write(buffer)?; let written = self.inner.inner.write(buffer)?;
@@ -166,9 +237,9 @@ impl<W> Write for WaveChunkWriter<W> where W: Write + Seek {
/// ///
/// let mut frame_writer = w.audio_frame_writer().unwrap(); /// let mut frame_writer = w.audio_frame_writer().unwrap();
/// ///
/// frame_writer.write_integer_frames(&[0i32]).unwrap(); /// frame_writer.write_frames(&[0i32]).unwrap();
/// frame_writer.write_integer_frames(&[0i32]).unwrap(); /// frame_writer.write_frames(&[0i32]).unwrap();
/// frame_writer.write_integer_frames(&[0i32]).unwrap(); /// frame_writer.write_frames(&[0i32]).unwrap();
/// frame_writer.end().unwrap(); /// frame_writer.end().unwrap();
/// ``` /// ```
/// ///
@@ -196,7 +267,10 @@ impl<W> Write for WaveChunkWriter<W> where W: Write + Seek {
/// [ebu3306v2]: https://tech.ebu.ch/docs/tech/tech3306.pdf /// [ebu3306v2]: https://tech.ebu.ch/docs/tech/tech3306.pdf
/// [itu2088]: https://www.itu.int/dms_pubrec/itu-r/rec/bs/R-REC-BS.2088-1-201910-I!!PDF-E.pdf /// [itu2088]: https://www.itu.int/dms_pubrec/itu-r/rec/bs/R-REC-BS.2088-1-201910-I!!PDF-E.pdf
/// [rfc3261]: https://tools.ietf.org/html/rfc2361 /// [rfc3261]: https://tools.ietf.org/html/rfc2361
pub struct WaveWriter<W> where W: Write + Seek { pub struct WaveWriter<W>
where
W: Write + Seek,
{
inner: W, inner: W,
form_length: u64, form_length: u64,
@@ -204,31 +278,32 @@ pub struct WaveWriter<W> where W: Write + Seek {
pub is_rf64: bool, pub is_rf64: bool,
/// Format of the wave file. /// Format of the wave file.
pub format: WaveFmt pub format: WaveFmt,
} }
const DS64_RESERVATION_LENGTH: u32 = 96; const DS64_RESERVATION_LENGTH: u32 = 96;
impl WaveWriter<BufWriter<File>> { impl WaveWriter<BufWriter<File>> {
/// Create a new Wave file at `path`. /// Create a new Wave file at `path`.
pub fn create(path : &str, format : WaveFmt) -> Result<Self, Error> { pub fn create<P: AsRef<Path>>(path: P, format: WaveFmt) -> Result<Self, Error> {
let f = File::create(path)?; let f = File::create(path)?;
let b = BufWriter::new(f); let b = BufWriter::new(f);
Ok( Self::new(b, format)? ) Self::new(b, format)
} }
} }
impl WaveWriter<File> { impl WaveWriter<File> {
/// Creare a new Wave file with unbuffered IO at `path` /// Creare a new Wave file with unbuffered IO at `path`
pub fn create_unbuffered(path : &str, format : WaveFmt) -> Result<Self, Error> { pub fn create_unbuffered<P: AsRef<Path>>(path: P, format: WaveFmt) -> Result<Self, Error> {
let f = File::create(path)?; let f = File::create(path)?;
Ok( Self::new(f, format)? ) Self::new(f, format)
} }
} }
impl<W> WaveWriter<W> where W: Write + Seek { impl<W> WaveWriter<W>
where
W: Write + Seek,
{
/// Wrap a writer in a Wave writer. /// Wrap a writer in a Wave writer.
/// ///
/// The inner writer will immediately have a RIFF WAVE file header /// The inner writer will immediately have a RIFF WAVE file header
@@ -239,7 +314,12 @@ impl<W> WaveWriter<W> where W: Write + Seek {
inner.write_u32::<LittleEndian>(0)?; inner.write_u32::<LittleEndian>(0)?;
inner.write_fourcc(WAVE_SIG)?; inner.write_fourcc(WAVE_SIG)?;
let mut retval = WaveWriter { inner, form_length: 0, is_rf64: false, format}; let mut retval = WaveWriter {
inner,
form_length: 0,
is_rf64: false,
format,
};
retval.increment_form_length(4)?; retval.increment_form_length(4)?;
@@ -258,11 +338,11 @@ impl<W> WaveWriter<W> where W: Write + Seek {
self.inner.write_fourcc(ident)?; self.inner.write_fourcc(ident)?;
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(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(&[0u8])?; self.inner.write_u8(0)?;
self.increment_form_length(8 + data.len() as u64 + 1)?; self.increment_form_length(8 + data.len() as u64 + 1)?;
} }
Ok(()) Ok(())
@@ -276,7 +356,7 @@ impl<W> WaveWriter<W> where W: Write + Seek {
pub fn write_broadcast_metadata(&mut self, bext: &Bext) -> Result<(), Error> { pub fn write_broadcast_metadata(&mut self, bext: &Bext) -> Result<(), Error> {
//FIXME Implement re-writing //FIXME Implement re-writing
let mut c = Cursor::new(vec![0u8; 0]); let mut c = Cursor::new(vec![0u8; 0]);
c.write_bext(&bext)?; c.write_bext(bext)?;
let buf = c.into_inner(); let buf = c.into_inner();
self.write_chunk(BEXT_SIG, &buf)?; self.write_chunk(BEXT_SIG, &buf)?;
Ok(()) Ok(())
@@ -285,13 +365,13 @@ impl<W> WaveWriter<W> where W: Write + Seek {
/// Write iXML metadata /// Write iXML metadata
pub fn write_ixml(&mut self, ixml: &[u8]) -> Result<(), Error> { pub fn write_ixml(&mut self, ixml: &[u8]) -> Result<(), Error> {
//FIXME Implement re-writing //FIXME Implement re-writing
self.write_chunk(IXML_SIG, &ixml) self.write_chunk(IXML_SIG, ixml)
} }
/// Write axml/ADM metadata /// Write axml/ADM metadata
pub fn write_axml(&mut self, axml: &[u8]) -> Result<(), Error> { pub fn write_axml(&mut self, axml: &[u8]) -> Result<(), Error> {
//FIXME Implement re-writing //FIXME Implement re-writing
self.write_chunk(AXML_SIG, &axml) self.write_chunk(AXML_SIG, axml)
} }
/// Write a `JUNK` filler chunk /// Write a `JUNK` filler chunk
@@ -312,7 +392,7 @@ impl<W> WaveWriter<W> where W: Write + Seek {
let to_add = framing - (lip % framing) - 16; let to_add = framing - (lip % framing) - 16;
let mut chunk = self.chunk(ELM1_SIG)?; let mut chunk = self.chunk(ELM1_SIG)?;
let buf = vec![0u8; to_add as usize]; let buf = vec![0u8; to_add as usize];
chunk.write(&buf)?; chunk.write_all(&buf)?;
let closed = chunk.end()?; let closed = chunk.end()?;
let inner = closed.chunk(DATA_SIG)?; let inner = closed.chunk(DATA_SIG)?;
Ok(AudioFrameWriter::new(inner)) Ok(AudioFrameWriter::new(inner))
@@ -342,16 +422,16 @@ impl<W> WaveWriter<W> where W: Write + Seek {
/// Add `amount` to the RIFF/RF64 form length /// Add `amount` to the RIFF/RF64 form length
fn increment_form_length(&mut self, amount: u64) -> Result<(), std::io::Error> { fn increment_form_length(&mut self, amount: u64) -> Result<(), std::io::Error> {
self.form_length = self.form_length + amount; self.form_length += amount;
if self.is_rf64 { if self.is_rf64 {
self.inner.seek(SeekFrom::Start(8 + 4 + 8))?; self.inner.seek(SeekFrom::Start(8 + 4 + 8))?;
self.inner.write_u64::<LittleEndian>(self.form_length)?; self.inner.write_u64::<LittleEndian>(self.form_length)?;
} else if self.form_length < u32::MAX as u64 { } else if self.form_length < u32::MAX as u64 {
self.inner.seek(SeekFrom::Start(4))?; self.inner.seek(SeekFrom::Start(4))?;
self.inner.write_u32::<LittleEndian>(self.form_length as u32)?; self.inner
.write_u32::<LittleEndian>(self.form_length as u32)?;
} else { } else {
self.promote_to_rf64()?; self.promote_to_rf64()?;
} }
Ok(()) Ok(())
} }
@@ -359,9 +439,9 @@ impl<W> WaveWriter<W> where W: Write + Seek {
#[test] #[test]
fn test_new() { fn test_new() {
use std::io::Cursor;
use super::fourcc::ReadFourCC; use super::fourcc::ReadFourCC;
use byteorder::ReadBytesExt; use byteorder::ReadBytesExt;
use std::io::Cursor;
let mut cursor = Cursor::new(vec![0u8; 0]); let mut cursor = Cursor::new(vec![0u8; 0]);
let format = WaveFmt::new_pcm_mono(4800, 24); let format = WaveFmt::new_pcm_mono(4800, 24);
@@ -385,9 +465,9 @@ fn test_new() {
#[test] #[test]
fn test_write_audio() { fn test_write_audio() {
use std::io::Cursor;
use super::fourcc::ReadFourCC; use super::fourcc::ReadFourCC;
use byteorder::ReadBytesExt; use byteorder::ReadBytesExt;
use std::io::Cursor;
let mut cursor = Cursor::new(vec![0u8; 0]); let mut cursor = Cursor::new(vec![0u8; 0]);
let format = WaveFmt::new_pcm_mono(48000, 24); let format = WaveFmt::new_pcm_mono(48000, 24);
@@ -395,9 +475,9 @@ fn test_write_audio() {
let mut frame_writer = w.audio_frame_writer().unwrap(); let mut frame_writer = w.audio_frame_writer().unwrap();
frame_writer.write_integer_frames(&[0i32]).unwrap(); frame_writer.write_frames(&[0i32]).unwrap();
frame_writer.write_integer_frames(&[0i32]).unwrap(); frame_writer.write_frames(&[0i32]).unwrap();
frame_writer.write_integer_frames(&[0i32]).unwrap(); frame_writer.write_frames(&[0i32]).unwrap();
frame_writer.end().unwrap(); frame_writer.end().unwrap();
@@ -428,7 +508,10 @@ fn test_write_audio() {
let tell = cursor.seek(SeekFrom::Current(0)).unwrap(); let tell = cursor.seek(SeekFrom::Current(0)).unwrap();
assert!(tell % 0x4000 == 0); assert!(tell % 0x4000 == 0);
assert_eq!(form_size, 4 + 8 + junk_size + 8 + fmt_size + 8 + elm1_size + 8 + data_size + data_size % 2) assert_eq!(
form_size,
4 + 8 + junk_size + 8 + fmt_size + 8 + elm1_size + 8 + data_size + data_size % 2
)
} }
#[test] #[test]
@@ -460,15 +543,14 @@ fn test_write_bext() {
let mut frame_writer = w.audio_frame_writer().unwrap(); let mut frame_writer = w.audio_frame_writer().unwrap();
frame_writer.write_integer_frames(&[0i32]).unwrap(); frame_writer.write_frames(&[0i32]).unwrap();
frame_writer.write_integer_frames(&[0i32]).unwrap(); frame_writer.write_frames(&[0i32]).unwrap();
frame_writer.write_integer_frames(&[0i32]).unwrap(); frame_writer.write_frames(&[0i32]).unwrap();
frame_writer.end().unwrap(); frame_writer.end().unwrap();
} }
// NOTE! This test of RF64 writing takes several minutes to complete in debug builds
// NOTE! This test of RF64 writing takes several minutes to complete.
#[test] #[test]
fn test_create_rf64() { fn test_create_rf64() {
use super::fourcc::ReadFourCC; use super::fourcc::ReadFourCC;
@@ -487,11 +569,14 @@ fn test_create_rf64() {
let mut af = w.audio_frame_writer().unwrap(); let mut af = w.audio_frame_writer().unwrap();
for _ in 0..(four_and_a_half_hours_of_frames * format.channel_count as u64 / buflen) { for _ in 0..(four_and_a_half_hours_of_frames * format.channel_count as u64 / buflen) {
af.write_integer_frames(&buf).unwrap(); af.write_frames(&buf).unwrap();
} }
af.end().unwrap(); af.end().unwrap();
assert!(cursor.seek(SeekFrom::End(0)).unwrap() > 0xFFFF_FFFFu64, "internal test error, Created file is not long enough to be RF64" ); assert!(
cursor.seek(SeekFrom::End(0)).unwrap() > 0xFFFF_FFFFu64,
"internal test error, Created file is not long enough to be RF64"
);
let expected_data_length = four_and_a_half_hours_of_frames * format.block_alignment as u64; let expected_data_length = four_and_a_half_hours_of_frames * format.block_alignment as u64;
cursor.seek(SeekFrom::Start(0)).unwrap(); cursor.seek(SeekFrom::Start(0)).unwrap();
@@ -504,20 +589,33 @@ fn test_create_rf64() {
let form_size = cursor.read_u64::<LittleEndian>().unwrap(); let form_size = cursor.read_u64::<LittleEndian>().unwrap();
let data_size = cursor.read_u64::<LittleEndian>().unwrap(); let data_size = cursor.read_u64::<LittleEndian>().unwrap();
assert_eq!(data_size, expected_data_length); assert_eq!(data_size, expected_data_length);
cursor.seek(SeekFrom::Current(ds64_size as i64 - 16)).unwrap(); cursor
.seek(SeekFrom::Current(ds64_size as i64 - 16))
.unwrap();
assert_eq!(cursor.read_fourcc().unwrap(), FMT__SIG); assert_eq!(cursor.read_fourcc().unwrap(), FMT__SIG);
let fmt_size = cursor.read_u32::<LittleEndian>().unwrap(); let fmt_size = cursor.read_u32::<LittleEndian>().unwrap();
cursor.seek(SeekFrom::Current((fmt_size + fmt_size % 2) as i64)).unwrap(); cursor
.seek(SeekFrom::Current((fmt_size + fmt_size % 2) as i64))
.unwrap();
assert_eq!(cursor.read_fourcc().unwrap(), ELM1_SIG); assert_eq!(cursor.read_fourcc().unwrap(), ELM1_SIG);
let elm1_size = cursor.read_u32::<LittleEndian>().unwrap(); let elm1_size = cursor.read_u32::<LittleEndian>().unwrap();
let data_start = cursor.seek(SeekFrom::Current((elm1_size + elm1_size % 2) as i64)).unwrap(); let data_start = cursor
.seek(SeekFrom::Current((elm1_size + elm1_size % 2) as i64))
.unwrap();
assert!((data_start + 8) % 0x4000 == 0, "data content start is not aligned, starts at {}", data_start + 8); assert!(
(data_start + 8) % 0x4000 == 0,
"data content start is not aligned, starts at {}",
data_start + 8
);
assert_eq!(cursor.read_fourcc().unwrap(), DATA_SIG); assert_eq!(cursor.read_fourcc().unwrap(), DATA_SIG);
assert_eq!(cursor.read_u32::<LittleEndian>().unwrap(), 0xFFFF_FFFF); assert_eq!(cursor.read_u32::<LittleEndian>().unwrap(), 0xFFFF_FFFF);
cursor.seek(SeekFrom::Current(data_size as i64)).unwrap(); cursor.seek(SeekFrom::Current(data_size as i64)).unwrap();
assert_eq!(4 + 8 + ds64_size as u64 + 8 + data_size + 8 + fmt_size as u64 + 8 + elm1_size as u64, form_size) assert_eq!(
4 + 8 + ds64_size as u64 + 8 + data_size + 8 + fmt_size as u64 + 8 + elm1_size as u64,
form_size
)
} }

View File

@@ -1,7 +1,6 @@
extern crate serde_json; extern crate serde_json;
use core::fmt::Debug; use core::fmt::Debug;
use serde_json::{Value, from_str}; use serde_json::{from_str, Value};
use std::fs::File; use std::fs::File;
use std::io::Read; use std::io::Read;
@@ -13,20 +12,18 @@ use bwavfile::WaveReader;
// as read by `WaveReader`. // as read by `WaveReader`.
// This is rickety but we're going with it // This is rickety but we're going with it
fn assert_match_stream<T>(stream_key: &str, fn assert_match_stream<T>(stream_key: &str, other: impl Fn(&mut WaveReader<File>) -> T)
other: impl Fn(&mut WaveReader<File>) -> T) where
where T: PartialEq + Debug, T: PartialEq + Debug,
T: Into<Value> T: Into<Value>,
{ {
let mut json_file = File::open("tests/ffprobe_media_tests.json").unwrap(); let mut json_file = File::open("tests/ffprobe_media_tests.json").unwrap();
let mut s = String::new(); let mut s = String::new();
json_file.read_to_string(&mut s).unwrap(); json_file.read_to_string(&mut s).unwrap();
if let Value::Array(v) = from_str(&mut s).unwrap() { /* */ if let Value::Array(v) = from_str(&mut s).unwrap() {
/* */
v.iter() v.iter()
.filter(|value| { .filter(|value| !value["format"]["filename"].is_null())
!value["format"]["filename"].is_null()
})
.for_each(|value| { .for_each(|value| {
let filen: &str = value["format"]["filename"].as_str().unwrap(); let filen: &str = value["format"]["filename"].as_str().unwrap();
let json_value: &Value = &value["streams"][0][stream_key]; let json_value: &Value = &value["streams"][0][stream_key];
@@ -34,7 +31,6 @@ fn assert_match_stream<T>(stream_key: &str,
let wavfile_value: T = other(&mut wavfile); let wavfile_value: T = other(&mut wavfile);
println!("asserting {} for {}", stream_key, filen); println!("asserting {} for {}", stream_key, filen);
assert_eq!(Into::<Value>::into(wavfile_value), *json_value); assert_eq!(Into::<Value>::into(wavfile_value), *json_value);
}) })
} }
} }
@@ -46,7 +42,9 @@ fn test_frame_count() {
#[test] #[test]
fn test_sample_rate() { fn test_sample_rate() {
assert_match_stream("sample_rate", |w| format!("{}", w.format().unwrap().sample_rate) ); assert_match_stream("sample_rate", |w| {
format!("{}", w.format().unwrap().sample_rate)
});
} }
#[test] #[test]

View File

@@ -1,17 +1,16 @@
extern crate bwavfile; extern crate bwavfile;
use bwavfile::WaveReader; use bwavfile::ChannelMask;
use bwavfile::Error; use bwavfile::Error;
use bwavfile::{ ChannelMask}; use bwavfile::WaveReader;
use bwavfile::I24;
#[test] #[test]
fn test_open() { fn test_open() {
let path = "tests/media/ff_silence.wav"; let path = "tests/media/ff_silence.wav";
match WaveReader::open(path) { match WaveReader::open(path) {
Ok(_) => { Ok(_) => (),
()
},
Err(x) => { Err(x) => {
assert!(false, "Opened error.wav with unexpected error {:?}", x) assert!(false, "Opened error.wav with unexpected error {:?}", x)
} }
@@ -82,18 +81,16 @@ fn test_read() {
let path = "tests/media/audacity_16bit.wav"; let path = "tests/media/audacity_16bit.wav";
let mut w = WaveReader::open(path).expect("Failure opening test file"); let mut w = WaveReader::open(path).expect("Failure opening test file");
let mut buffer = w.format().unwrap().create_frame_buffer(); let mut buffer = w.format().unwrap().create_frame_buffer::<i16>(1);
let mut reader = w.audio_frame_reader().unwrap(); let mut reader = w.audio_frame_reader().unwrap();
assert_eq!(reader.read_frames(&mut buffer).unwrap(), 1);
assert_eq!(buffer[0], -2823_i16);
assert_eq!(reader.read_integer_frame(&mut buffer).unwrap(), 1); assert_eq!(reader.read_frames(&mut buffer).unwrap(), 1);
assert_eq!(buffer[0], -2823_i32); assert_eq!(buffer[0], 2012_i16);
assert_eq!(reader.read_integer_frame(&mut buffer).unwrap(), 1); assert_eq!(reader.read_frames(&mut buffer).unwrap(), 1);
assert_eq!(buffer[0], 2012_i32); assert_eq!(buffer[0], 4524_i16);
assert_eq!(reader.read_integer_frame(&mut buffer).unwrap(), 1);
assert_eq!(buffer[0], 4524_i32);
} }
#[test] #[test]
@@ -101,21 +98,21 @@ fn test_locate_multichannel_read() {
let path = "tests/media/ff_pink.wav"; let path = "tests/media/ff_pink.wav";
let mut w = WaveReader::open(path).expect("Failure opening test file"); let mut w = WaveReader::open(path).expect("Failure opening test file");
let mut buffer = w.format().unwrap().create_frame_buffer(); let mut buffer = w.format().unwrap().create_frame_buffer::<I24>(1);
let mut reader = w.audio_frame_reader().unwrap(); let mut reader = w.audio_frame_reader().unwrap();
assert_eq!(reader.read_integer_frame(&mut buffer).unwrap(), 1); assert_eq!(reader.read_frames(&mut buffer).unwrap(), 1);
assert_eq!(buffer[0], 332702_i32); assert_eq!(buffer[0], I24::from(332702));
assert_eq!(buffer[1], 3258791_i32); assert_eq!(buffer[1], I24::from(3258791));
assert_eq!(reader.read_integer_frame(&mut buffer).unwrap(), 1); assert_eq!(reader.read_frames(&mut buffer).unwrap(), 1);
assert_eq!(buffer[0], -258742_i32); // 0x800000 = 8388608 // 8129866 - 8388608 assert_eq!(buffer[0], I24::from(-258742)); // 0x800000 = 8388608 // 8129866 - 8388608
assert_eq!(buffer[1], 0x0D7EF9_i32); assert_eq!(buffer[1], I24::from(0x0D7EF9));
assert_eq!(reader.locate(100).unwrap(), 100); assert_eq!(reader.locate(100).unwrap(), 100);
assert_eq!(reader.read_integer_frame(&mut buffer).unwrap(), 1); assert_eq!(reader.read_frames(&mut buffer).unwrap(), 1);
assert_eq!(buffer[0], 0x109422_i32); assert_eq!(buffer[0], I24::from(0x109422));
assert_eq!(buffer[1], -698901_i32); // 7689707 - 8388608 assert_eq!(buffer[1], I24::from(-698901)); // 7689707 - 8388608
} }
#[test] #[test]
@@ -158,13 +155,15 @@ fn test_channels_stereo_no_fmt_extended() {
assert_eq!(channels[1].speaker, ChannelMask::FrontRight); assert_eq!(channels[1].speaker, ChannelMask::FrontRight);
} }
//See issue 6 and 7 ///See issue 6 and 7
#[test] #[test]
fn test_frame_reader_consumes_reader() { fn test_frame_reader_consumes_reader() {
// Issue #6 // Issue #6
use bwavfile::{WaveFmt, AudioFrameReader}; use bwavfile::{AudioFrameReader, WaveFmt};
use std::fs::File; use std::fs::File;
fn from_wav_filename(wav_filename: &str) -> Result<(WaveFmt, AudioFrameReader<std::io::BufReader<File>>), ()> { fn from_wav_filename(
wav_filename: &str,
) -> Result<(WaveFmt, AudioFrameReader<std::io::BufReader<File>>), ()> {
if let Ok(mut r) = WaveReader::open(&wav_filename) { if let Ok(mut r) = WaveReader::open(&wav_filename) {
let format = r.format().unwrap(); let format = r.format().unwrap();
let frame_reader = r.audio_frame_reader().unwrap(); let frame_reader = r.audio_frame_reader().unwrap();
@@ -176,3 +175,47 @@ fn test_frame_reader_consumes_reader() {
let _result = from_wav_filename("tests/media/pt_24bit_stereo.wav").unwrap(); let _result = from_wav_filename("tests/media/pt_24bit_stereo.wav").unwrap();
} }
///See to PR#10
#[test]
fn test_cue_read_sounddevices() {
let mut f = WaveReader::open("tests/media/sounddevices_6_cue_points.wav").unwrap();
let cue_points = f.cue_points().unwrap();
assert_eq!(cue_points.len(), 6);
assert_eq!(cue_points[0].frame, 0);
assert_eq!(cue_points[0].length, None);
assert_eq!(cue_points[0].label, None);
assert_eq!(cue_points[0].note, None);
assert_eq!(cue_points[0].offset, 90112);
assert_eq!(cue_points[1].frame, 0);
assert_eq!(cue_points[1].length, None);
assert_eq!(cue_points[1].label, None);
assert_eq!(cue_points[1].note, None);
assert_eq!(cue_points[1].offset, 176128);
assert_eq!(cue_points[2].frame, 0);
assert_eq!(cue_points[2].length, None);
assert_eq!(cue_points[2].label, None);
assert_eq!(cue_points[2].note, None);
assert_eq!(cue_points[2].offset, 237568);
assert_eq!(cue_points[3].frame, 0);
assert_eq!(cue_points[3].length, None);
assert_eq!(cue_points[3].label, None);
assert_eq!(cue_points[3].note, None);
assert_eq!(cue_points[3].offset, 294912);
assert_eq!(cue_points[4].frame, 0);
assert_eq!(cue_points[4].length, None);
assert_eq!(cue_points[4].label, None);
assert_eq!(cue_points[4].note, None);
assert_eq!(cue_points[4].offset, 380928);
assert_eq!(cue_points[5].frame, 0);
assert_eq!(cue_points[5].length, None);
assert_eq!(cue_points[5].label, None);
assert_eq!(cue_points[5].note, None);
assert_eq!(cue_points[5].offset, 385024);
}

Binary file not shown.