tonegenerator
Version:
Generates a tone as raw PCM WAV data, so you can do operations on it
193 lines (147 loc) • 6.39 kB
Markdown
ToneGenerator for node.js
====
This thing generates an array of numbers in waveforms. This waveform can be written into a wavefile as raw PCM data.
It does not play the sounds. If you want to play sounds, make sure to read the examples on how to write wave files below.
## Generating Tones:
```javascript
var tone = require('tonegenerator')
// New Interface! More options!!
var tonedata = tone({
freq: 440,
lengthInSecs: 2.0,
volume: 30,
rate: 44100,
shape: 'triangle',
Int16Array: true // use this mode when generating very long notes
})
// The old interface, still available for compatibility
var tonedata = tone(frequency, lengthInSeconds, volume = 30, rate = 44100)
```
#### Using the new interface
- **freq** frequency in hertz. *defaults to 440*
- **lengthInSecs** controls the length of the array output together with the samplerate *defaults to 2.0*
- **volume** controls max/min for the array values. If you intend to write 8-bit it should be less than or equal to tone.MAX_8, if 16 bit it should be less than or equal to tone.MAX_16. *defaults to 30*
- **rate** sample rate, number of samples per second. Together with lengthInSecs, this define the length of the output array (lengthInSeccs * rate). *defaults to 44100*
- **shape** controls the wave shape. Options are *'triangle', 'square', 'sine', 'saw'*. You can also pass in a custom function, see the tests for an example of this. *defaults to 'sine'*
#### Using the old interface
The old interface takes four arguments: *freq, lengthInSecs, volume, rate*.
**volume** and **rate** are optional, the default is shown above.
**Shape is not available in the old interface. If you want to specify rate, you have to specify volume!**
#### Useful constants
```javascript
tone.MAX_8 // max volume to be used with 8-bit sound
tone.MAX_16 // max volume for 16 bit sound
```javascript
var tone = require('tonegenerator');
var A440 = tone({ freq: 440, lengthInSeconds: 20, volume: 30 }); // get PCM data for a 440hz A, 20 seconds, volume 30
var A440_low_sample = tone(440, 20, 30, 22050); // (old interface) this array has lower sample rate and will only be half as long
```
The data is returned as a normal array, so you can do operations on it.
## Combining notes
```javascript
// An A-major chord
var tone1 = tone({ freq: 440, lengthInSecs: 2, volume: 60 })
var tone2 = tone({ freq: 554.37, lengthInSecs: 2, volume: 30 })
var tone3 = tone({ freq: 659.26, lengthInSecs: 2, volume: 30 })
// "playing" one tone at the time
// note that at this time, our sound is just an array
// of gain values. By appending the raw PCM data for one after another,
// we can play them in a sequence
var res = tone1.concat(tone2, tone3)
// By adding values of the tones for each sample,
// we play them simultaneously, as a chord
for(var i = 0; i < tone1.length; i++) {
res.push(tone1[i] + tone2[i] + tone3[i])
}
```
## Volume on 8-bit and 16-bit PCM data
The meaning of the 'volume' value depends on whether you're creating 8-bit or 16-bit data. For 8-bit data, the max volume to avoid distortion is 128. For 16-bit data, the max volume is 32768. Those values are available as **require('tonegenerator').MAX_8** and **require('tonegenerator').MAX_16** respectively.
## Writing 8-bit data to a Wave File
Before writing your PCM data to a file, you need to convert it to a buffer of UInt8 values. 8-bit wave data goes from 0-255, so we need to add 128 to each value:
```javascript
var tone = require('tonegenerator');
// Use this package to write a header for the wave file
// https://www.npmjs.org/package/waveheader
var header = require('waveheader');
var fs = require('fs');
var file = fs.createWriteStream('8bit-example.wav')
var samples = tone({ freq: 440, lengthInSecs: 2, volume: tone.MAX_8 })
file.write(header(samples.length, {
bitDepth: 8
}))
// Convert -128 -> 127 range into 0 -> 255
var data = Uint8Array.from(samples, function (val) {
return val + 128
})
if (Buffer.from) { // Node 5+
buffer = Buffer.from(data)
} else {
buffer = new Buffer(data)
}
file.write(buffer)
file.end()
```
## Writing 16-bit data to a Wave file
16-bit data requires a little bit more work, since we need to take Endianess into account. Unlike 8-bit data, the volumes does not start at 0, but at -32768.
All the references to data length need to be doubled.
```javascript
var tone = require('tonegenerator');
var header = require('waveheader');
var fs = require('fs');
var file = fs.createWriteStream('16bit-example.wav')
var samples = tone({ freq: 440, lengthInSecs: 2, volume: tone.MAX_16 })
file.write(header(samples.length * 2, {
bitDepth: 16
}))
var data = Int16Array.from(samples)
var size = data.length * 2 // 2 bytes per sample
if (Buffer.allocUnsafe) { // Node 5+
buffer = Buffer.allocUnsafe(size)
} else {
buffer = new Buffer(size)
}
data.forEach(function (value, index) {
buffer.writeInt16LE(value, index * 2)
})
file.write(buffer)
file.end()
```
## Writing stereo sounds
In stereo wave data, the sample for each channel comes right after each other.
The principle looks like `[sample0-1 sample0-2 sample1-1 sample1-2]`. So we need to
first generate the data for each channel, then interleave them.
```javascript
var tone = require('tonegenerator');
var header = require('waveheader');
var fs = require('fs');
var file = fs.createWriteStream('16bit-stereo.wav')
// A loud A for channel 1
var channel1 = tone({ freq: 440, lengthInSecs: 2, volume: tone.MAX_16 })
// A not so loud C for channel 2
var channel2 = tone({ freq: 554.37, lengthInSecs: 2, volume: tone.MAX_16 / 4 })
// create an array where the 2 channels are interleaved:
var samples = []
for (var i = 0; i < channel1.length; i++) {
samples.push(channel1[i])
samples.push(channel2[i])
}
file.write(header(samples.length * 2, {
channels: 2,
bitDepth: 16
}))
var data = Int16Array.from(samples)
var size = data.length * 2 // 2 bytes per sample
if (Buffer.allocUnsafe) { // Node 5+
buffer = Buffer.allocUnsafe(size)
} else {
buffer = new Buffer(size)
}
data.forEach(function (value, index) {
buffer.writeInt16LE(value, index * 2)
})
file.write(buffer)
file.end()
```
## Reading:
* [Wave PCM SoundFile format](http://soundfile.sapp.org/doc/WaveFormat/) - make sure to read the 'Notes' section
* [ABC of Uncompressed digital audio](http://blog.bjornroche.com/2013/05/the-abcs-of-pcm-uncompressed-digital.html)