A data structure for real-time reading, editing, seeking, and encoding of mono
WAV audio files via hypercore v10. Version control, branches, and peering come
for free thanks to the hypercore@next
branch.
$ npm install git+ssh://[email protected]:sacreddata/hyperwav.git
WIP, expect breaking changes
This library's intent is to enable real-time peer-to-peer recording, editing, and remixing of audio content without sacrificing fidelity, privacy, nor speed.
The WAV audio can be sourced from any valid instance of
random-access-storage
and can be therefore stored in memory as a
buffer, on S3 as a remote cloud URI, or as a file on the local file system. This
means it is functional on local offline-first client applications, server-side
applications, and web apps all from one codebase.
ANYTHING YOU CAN DO IN HYPERCORE, YOU CAN DO IN WAVECORE. Start here!
A Hyperwav can be created from a number of inputs and sources; with no arguments provided at all, a new Hyperwav with sane defaults is provided.
- Create new Hyperwav with no inputs
- Create new Hyperwav with
Source
input - Create new Hyperwav with hypercore input
- Create new Hyperwav from other Wavecore
- Create new Hyperwav with
random-access-storage
input
- Append
- Pad tail (Add blank data to tail to extend duration)
Hyperwav audio editing tasks mimic the methods offered by JavaScript Arrays
- SoX
stat
- SoX
stats
- SoX
spectrogram
-
audiowaveform
peaks data -
aubioonset
onset timing -
fpcalc
fingerprint
- RIFF tags
- BWF tags
- Cue points (in progress)
- Replication between Hyperwavs
There were several factors influencing the decision to support only mono WAV
data. Most notably, the Web Audio API's AudioBuffer.getChannelData()
method,
as its name suggests, loads audio data one channel at a time. Hyperwav audio
data can therefore be easily loaded inline with that method, querying it for
specific data ranges before having to allocate buffer resources.
By forcing all Hyperwavs to be mono-first, we also enable different processing to occur on each channel of audio without worrying about data interleaving.
const mr = new MediaRecorder(stream)
const wave = new Hyperwav()
wave.recStream(mr.stream)
const core = new Hypercore(ram)
mediaRecorder.onstop = function() {
const wave = new Hyperwav({ core })
}
mediaRecorder.ondataavailable = function(d) {
core.append(d.data)
}
const wavecore = new HyperwavSox({ source })
await wavecore.open()
wavecore.play({start: 5, end: 13}) // Play indeces 5 through 13
Listen to the audio being inserted in the Hyperwav in real-time! Yes, you read that correctly: REAL-TIME!
const wavecore = new Hyperwav({ source })
await wavecore.open()
const waveStream = wavecore._liveStream()
// Consume this ReadableStream to listen to the audio as it gets recorded into
// the Hyperwav!
Here we make a snapshot of our Hyperwav so we can test out an edit to the WAV audio. By using our new session to test the edit we leave the original Hyperwav audio in-tact, enabling non-destructive editing with no additional memory allocation necessary!
const Hyperwav = require('@storyboard-fm/wavecore')
const wave = new Hyperwav({ source })
await wave.open()
const snapshot = wave.snapshot()
await wave.truncate(10)
console.log(wave.core.length) // 10
console.log(snapshot.core.length) // 58
The following trims a Hyperwav to start on the 20th index.
const Hyperwav = require('@storyboard-fm/wavecore')
const wave = new Hyperwav({ source })
await wave.open()
console.log(wave.core.length) // 58
const shiftedCore = await Promise.resolve(wave.shift(20))
console.log(shiftedCore.length) // 38
The following truncates a Hyperwav to the first 20 indeces.
const Hyperwav = require('@storyboard-fm/wavecore')
const wave = new Hyperwav({ source })
await wave.open()
console.log(wave.core.length) // 58
await wave.truncate(20)
console.log(wave.core.length) // 20
Execute this script at example2.js
.
const Hyperwav = require('.')
const w = new Hyperwav({ source })
async function main() {
await w.open()
console.log('splitting Hyperwav at index 22....')
const [head, tail] = await w.split(22)
console.log('done!', head, tail)
}
main()
Increase gain by +6dBFS and add a limiter to prevent clipping
const wave = new HyperwavSox({ source })
await wave.open()
const gainUp = await wave.gain('+6', {limiter:true})
const wave = new Hyperwav({ source })
await wave.open()
const audioBufferNorm = await wave.audioBuffer({ normalize: true })
Increase gain so that the peak dBFS value = 0
const wave = new HyperwavSox({ source })
await wave.open()
const normCore = await wave.norm()
Change the "tempo" of the audio without changing the pitch
const wave = new HyperwavSox({ source })
await wave.open()
const snap = wave.snapshot() // save copy of original audio
const slowerWave = await Promise.resolve(wave.tempo(0.8)) // 20% slower
const fasterWave = await Promise.resolve(wave.tempo(1.1)) // 10% faster
Certain operations in Hyperwav provide RIFF tags which, when converting from PCM to WAV file format, will be written to the resultant WAV file. They are detailed in the table below.
Tag String | Description |
---|---|
PRT1 |
Part number |
PRT2 |
Total parts |
TCOD |
Start time (ms) |
TCDO |
End time (ms) |
STAT |
"0" = Clipping; "1" = Normal |
Statements | Branches | Functions | Lines |
---|---|---|---|
We use mocha
, with nyc
for test coverage reporting.
$ npm run test