tone-stream
Version:
A simple audio tone stream library
179 lines (143 loc) • 4.53 kB
JavaScript
const { Readable } = require("stream");
const SpecReadStream = require("spec-read-stream");
const DTMF = require("./lib/dtmf");
const EventEmitter = require('events');
class ToneStream extends Readable {
constructor(format) {
super();
if (format) {
this.sampleRate = format.sampleRate;
this.bitDepth = format.bitDepth;
this.channels = format.channels;
} else {
// default
this.sampleRate = 8000;
this.bitDepth = 16;
this.channels = 1;
}
this.amplitude = 2 ** this.bitDepth / 2 - 1;
this.specReadStream = new SpecReadStream();
this.currentSample = 0;
this.eventEmitter = new EventEmitter();
}
add(spec) {
this.specReadStream.add(spec);
var num_samples = spec[0]
return num_samples
}
concat(specs) {
var num_samples = 0
specs.forEach((spec) => {
this.specReadStream.add(spec);
num_samples += spec[0]
});
return num_samples
}
on(evt, cb) {
super.on(evt, cb);
if (evt == "empty") {
this.specReadStream.on(evt, cb);
} else {
this.eventEmitter.on(evt, cb);
}
}
_read(n) {
//console.log(`_read(${n})`)
let sampleSize = this.bitDepth / 8;
let blockAlign = sampleSize * this.channels;
let numSamples = Math.floor(n / blockAlign);
let end = this.currentSample + numSamples;
let buf = Buffer.alloc(numSamples * blockAlign);
let setter = buf["writeInt" + this.bitDepth + "LE"].bind(buf);
var specs = this.specReadStream.read(numSamples);
//console.log(`specs=${specs}`)
var buf_idx = 0;
if (!specs) {
/*
for (var j = 0; j < numSamples * this.channels; j++) {
let offset = j * sampleSize * this.channels;
setter(0, offset);
}
this.push(buf);
*/
this.push(null)
return;
}
let actualSamples = specs.reduce((total, spec) => {
return total + spec[0];
}, 0);
//console.log(`actualSamples=${actualSamples}`)
for (var i = 0; i < specs.length; i++) {
var spec = specs[i];
//console.log(`spec=${spec}`)
var nSamples = spec[0];
var spec_val = spec[1];
var val_calculator = (() => {
if (spec_val == "s") {
// silence
return () => {
return 0;
};
} else if (typeof spec_val == "number") {
var freq = spec_val;
let t = (Math.PI * 2 * freq) / this.sampleRate;
return (amplitude, currentSample) => {
return Math.round(amplitude * Math.sin(t * currentSample)); // sine wave
};
} else if (
typeof spec_val == "string" &&
spec_val.startsWith("DTMF:")
) {
var tone = DTMF[spec_val.split(":")[1]];
var lo = tone[0];
var hi = tone[1];
let t_lo = (Math.PI * 2 * lo) / this.sampleRate;
let t_hi = (Math.PI * 2 * hi) / this.sampleRate;
return (amplitude, currentSample) => {
return (
(Math.round(amplitude * Math.sin(t_lo * currentSample)) +
Math.round(amplitude * Math.sin(t_hi * currentSample))) /
2
);
};
} else {
throw `invalid spec ${spec}`;
}
})();
for (var j = 0; j < nSamples; j++) {
for (let channel = 0; channel < this.channels; channel++) {
let val = val_calculator(this.amplitude, this.currentSample) / 2; // halve amplitude
let offset =
buf_idx * sampleSize * this.channels + channel * sampleSize;
setter(val, offset);
buf_idx++;
this.currentSample++;
}
}
}
if (typeof this.filler == "number") {
while (numSamples > actualSamples) {
//console.log("adding filler")
for (let channel = 0; channel < this.channels; channel++) {
let offset = (actualSamples - 1) * sampleSize * this.channels;
setter(this.filler, offset);
this.currentSample++;
actualSamples++;
}
}
}
if (numSamples > actualSamples) {
//console.log(`slicing buf from ${numSamples} to ${actualSamples}`)
//console.log(`${actualSamples * sampleSize * this.channels}`)
buf = buf.slice(0, actualSamples * sampleSize * this.channels);
}
//console.log("pushing")
//console.log(buf.length)
this.push(buf);
}
}
module.exports = {
ToneStream,
utils: require("./lib/utils.js"),
ssml: require("./lib/ssml.js"),
};