tonegenerator
Advanced tools
Comparing version 0.2.0 to 0.2.1
@@ -10,3 +10,3 @@ /* | ||
for(var i = 0; i < cycle; i++) { | ||
tmp = volume * Math.sin((i/cycle) * Math.PI * 2); | ||
tmp = Math.min(volume * Math.sin((i/cycle) * Math.PI * 2), volume - 1) | ||
data[i] = Math.round(tmp); | ||
@@ -34,1 +34,4 @@ } | ||
}; | ||
module.exports.MAX_16 = 32768; | ||
module.exports.MAX_8 = 128; |
{ | ||
"name": "tonegenerator", | ||
"description": "Generates a tone as raw PCM WAV data, so you can do operations on it", | ||
"version": "0.2.0", | ||
"version": "0.2.1", | ||
"scripts": { | ||
@@ -6,0 +6,0 @@ "test": "node test.js", |
156
README.md
@@ -7,4 +7,6 @@ ToneGenerator for node.js | ||
## Generating Tones: | ||
```javascript | ||
tone(frequency, lengthInSeconds, volume = 30, sampleRate, = 44100) | ||
tone(frequency, lengthInSeconds, volume = 30, sampleRate = 44100) | ||
``` | ||
@@ -17,3 +19,3 @@ | ||
```javascript | ||
var tone = require("tonegenerator"); | ||
var tone = require('tonegenerator'); | ||
var A440 = tone(440, 20, 30); // get PCM data for a 440hz A, 20 seconds, volume 30 | ||
@@ -23,19 +25,11 @@ var A440_low_sample = tone(440, 20, 30, 22050); // this array has lower sample rate and will only be half as long | ||
I'm really unsure what the 'volume' value means, but you can use it to create | ||
different tones with different volumes, let me know how it works for you! | ||
The data is returned as a normal array, so you can do operations on it. | ||
Before writing to a file, you need to convert it to a buffer: | ||
## Combining notes | ||
```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"); | ||
// An A-major chord | ||
var tone1 = tone(440, 2, 60); | ||
var tone2 = tone(554.37, 2, 30); | ||
var tone3 = tone(659.26, 2, 30); | ||
var tone1 = tone(440, 2, 60) | ||
var tone2 = tone(554.37, 2, 30) | ||
var tone3 = tone(659.26, 2, 30) | ||
@@ -46,5 +40,3 @@ // "playing" one tone at the time | ||
// we can play them in a sequence | ||
var res = [].concat(tone1); | ||
res = res.concat(tone2); | ||
res = res.concat(tone3); | ||
var res = tone1.concat(tone2, tone3) | ||
@@ -54,10 +46,126 @@ // By adding values of the tones for each sample, | ||
for(var i = 0; i < tone1.length; i++) { | ||
res.push(tone1[i] + tone2[i] + tone3[i]); | ||
res.push(tone1[i] + tone2[i] + tone3[i]) | ||
} | ||
``` | ||
// write to file (note conversion to buffer!) | ||
var writer = new fs.createWriteStream("A-major.wav"); | ||
writer.write(header( 44100 * 8 )); // 44100 Hz * 8 seconds | ||
writer.write(new Buffer(res)); | ||
writer.end(); | ||
## 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(440, 2, 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(440, 2, 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(440, 2, tone.MAX_16) | ||
// A not so loud C for channel 2 | ||
var channel2 = tone(554.37, 2, 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) |
@@ -13,5 +13,8 @@ var assert = require('assert') | ||
assert.strictEqual(Math.max.apply(Math, tone1), 10, 'takes the volume argument as max') | ||
assert.strictEqual(Math.min.apply(Math, tone1), -10, 'takes the volume argument as max') | ||
assert.strictEqual(Math.max.apply(Math, tone2), 30, 'takes the volume argument as max') | ||
// takes the volume argument - 1 as max | ||
assert.strictEqual(Math.max.apply(Math, tone1), 9) | ||
// takes the volume argument as min | ||
assert.strictEqual(Math.min.apply(Math, tone1), -10) | ||
// takes the volume argument as max | ||
assert.strictEqual(Math.max.apply(Math, tone2), 29) | ||
@@ -18,0 +21,0 @@ assert.equal(tone1.length/2, tonefrequency.length, 'when halving audio sampling rate, the array length should be half of default') |
7087
45
167