UNPKG

sono

Version:

A simple yet powerful JavaScript library for working with Web Audio

256 lines (208 loc) 11.3 kB
'use strict'; exports.__esModule = true; var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); var _createClass2 = require('babel-runtime/helpers/createClass'); var _createClass3 = _interopRequireDefault(_createClass2); var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); var _inherits2 = require('babel-runtime/helpers/inherits'); var _inherits3 = _interopRequireDefault(_inherits2); var _abstractDirectEffect = require('./abstract-direct-effect'); var _abstractDirectEffect2 = _interopRequireDefault(_abstractDirectEffect); var _isSafeNumber = require('../core/utils/isSafeNumber'); var _isSafeNumber2 = _interopRequireDefault(_isSafeNumber); var _sono = require('../core/sono'); var _sono2 = _interopRequireDefault(_sono); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function noteFromPitch(frequency) { var noteNum = 12 * (Math.log(frequency / 440) * Math.LOG2E); return Math.round(noteNum) + 69; } function frequencyFromNoteNumber(note) { return 440 * Math.pow(2, (note - 69) / 12); } function centsOffFromPitch(frequency, note) { return Math.floor(1200 * Math.log(frequency / frequencyFromNoteNumber(note)) * Math.LOG2E); } var Analyser = function (_AbstractDirectEffect) { (0, _inherits3.default)(Analyser, _AbstractDirectEffect); function Analyser() { var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, _ref$fftSize = _ref.fftSize, fftSize = _ref$fftSize === undefined ? 2048 : _ref$fftSize, _ref$minDecibels = _ref.minDecibels, minDecibels = _ref$minDecibels === undefined ? -100 : _ref$minDecibels, _ref$maxDecibels = _ref.maxDecibels, maxDecibels = _ref$maxDecibels === undefined ? -30 : _ref$maxDecibels, _ref$smoothing = _ref.smoothing, smoothing = _ref$smoothing === undefined ? 0.9 : _ref$smoothing, _ref$useFloats = _ref.useFloats, useFloats = _ref$useFloats === undefined ? false : _ref$useFloats; (0, _classCallCheck3.default)(this, Analyser); var _this = (0, _possibleConstructorReturn3.default)(this, _AbstractDirectEffect.call(this, _sono2.default.context.createAnalyser())); _this._useFloats = !!useFloats; _this._waveform = null; _this._frequencies = null; _this._node.fftSize = fftSize; _this.update({ minDecibels: minDecibels, maxDecibels: maxDecibels, smoothing: smoothing }); return _this; } Analyser.prototype.update = function update(_ref2) { var minDecibels = _ref2.minDecibels, maxDecibels = _ref2.maxDecibels, smoothing = _ref2.smoothing; if ((0, _isSafeNumber2.default)(smoothing)) { this._node.smoothingTimeConstant = smoothing; } if ((0, _isSafeNumber2.default)(minDecibels)) { this._node.minDecibels = minDecibels; } if ((0, _isSafeNumber2.default)(maxDecibels)) { this._node.maxDecibels = maxDecibels; } }; Analyser.prototype.getWaveform = function getWaveform() { var useFloats = this._useFloats && this._node.getFloatTimeDomainData; if (!this._waveform) { this._waveform = this._createArray(useFloats, this._node.fftSize); } if (useFloats) { this._node.getFloatTimeDomainData(this._waveform); } else { this._node.getByteTimeDomainData(this._waveform); } return this._waveform; }; Analyser.prototype.getFrequencies = function getFrequencies() { var useFloats = this._useFloats && this._node.getFloatFrequencyData; if (!this._frequencies) { this._frequencies = this._createArray(useFloats, this._node.frequencyBinCount); } if (useFloats) { this._node.getFloatFrequencyData(this._frequencies); } else { this._node.getByteFrequencyData(this._frequencies); } return this._frequencies; }; Analyser.prototype.getAmplitude = function getAmplitude(callback) { if (!this._amplitudeWorker) { this._createAmplitudeAnalyser(); } this._amplitudeCallback = callback || this._amplitudeCallback; var f = new Float32Array(this._node.fftSize); f.set(this.getFrequencies(true)); this._amplitudeWorker.postMessage({ sum: 0, length: f.byteLength, numSamples: this._node.fftSize / 2, b: f.buffer }, [f.buffer]); }; Analyser.prototype.getPitch = function getPitch(callback) { if (!this._pitchWorker) { this._createPitchAnalyser(); } this._pitchCallback = callback || this._pitchCallback; var f = new Float32Array(this._node.fftSize); f.set(this.getWaveform(true)); this._pitchWorker.postMessage({ sampleRate: _sono2.default.context.sampleRate, b: f.buffer }, [f.buffer]); }; Analyser.prototype._createArray = function _createArray(useFloats, length) { return useFloats ? new Float32Array(length) : new Uint8Array(length); }; Analyser.prototype._createAmplitudeAnalyser = function _createAmplitudeAnalyser() { var _this2 = this; //the worker returns a normalized value //first a sum of all magnitudes devided by the byteLength, then devide by half the fft (1channel) var amplitudeBlob = new Blob(['onmessage = function(e) {\n var data = e.data;\n var f = new Float32Array(data.b);\n for (var i = 0; i < f.length; i++) {\n data.sum += f[i];\n }\n data.sum /= f.length;\n postMessage(Math.max(1.0 - (data.sum / data.numSamples * -1.0), 0));\n };']); var amplitudeBlobURL = URL.createObjectURL(amplitudeBlob); this._amplitudeWorker = new Worker(amplitudeBlobURL); this._amplitudeWorker.onmessage = function (event) { if (!_this2._amplitudeCallback) { return; } _this2._amplitudeCallback(event.data); }; }; Analyser.prototype._createPitchAnalyser = function _createPitchAnalyser() { var _this3 = this; var pitchBlob = new Blob(['onmessage = function(e) {\n var data = e.data;\n var sampleRate = data.sampleRate;\n var buf = new Float32Array(data.b);\n var SIZE = buf.length;\n var MAX_SAMPLES = Math.floor(SIZE / 2);\n var bestOffset = -1;\n var bestCorrel = 0;\n var rms = 0;\n var foundGoodCorrelation = false;\n var correls = new Array(MAX_SAMPLES);\n for (var i = 0; i < SIZE; i++) {\n var val = buf[i];\n rms += val * val;\n }\n rms = Math.sqrt(rms / SIZE);\n if (rms < 0.01) {\n postMessage(-1);\n } else {\n var lastCorrelation = 1;\n for (var offset = 0; offset < MAX_SAMPLES; offset++) {\n var correl = 0;\n for (var i = 0; i < MAX_SAMPLES; i++) {\n correl += Math.abs(buf[i] - buf[i + offset]);\n }\n correl = 1 - correl / MAX_SAMPLES;\n correls[offset] = correl;\n if (correl > 0.9 && correl > lastCorrelation) {\n foundGoodCorrelation = true;\n if (correl > bestCorrel) {\n bestCorrel = correl;\n bestOffset = offset;\n }\n } else if (foundGoodCorrelation) {\n var shift = (correls[bestOffset + 1] - correls[bestOffset - 1]) / correls[bestOffset];\n postMessage(sampleRate / (bestOffset + 8 * shift));\n }\n lastCorrelation = correl;\n }\n if (bestCorrel > 0.01) {\n postMessage(sampleRate / bestOffset);\n } else {\n postMessage(-1);\n }\n }\n };']); var pitchBlobURL = URL.createObjectURL(pitchBlob); this._pitchWorker = new Worker(pitchBlobURL); var noteStrings = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']; var pitchCallbackObject = { hertz: 0, note: '', noteIndex: 0, detuneCents: 0, detune: '' }; this._pitchWorker.onmessage = function (event) { if (!_this3._pitchCallback) { return; } var hz = event.data; if (hz !== -1) { var note = noteFromPitch(hz); var detune = centsOffFromPitch(hz, note); pitchCallbackObject.hertz = hz; pitchCallbackObject.noteIndex = note % 12; pitchCallbackObject.note = noteStrings[note % 12]; pitchCallbackObject.detuneCents = detune; if (detune === 0) { pitchCallbackObject.detune = ''; } else if (detune < 0) { pitchCallbackObject.detune = 'flat'; } else { pitchCallbackObject.detune = 'sharp'; } } _this3._pitchCallback(pitchCallbackObject); }; }; (0, _createClass3.default)(Analyser, [{ key: 'frequencyBinCount', get: function get() { return this._node.frequencyBinCount; } }, { key: 'maxDecibels', get: function get() { return this._node.maxDecibels; }, set: function set(value) { if ((0, _isSafeNumber2.default)(value)) { this._node.maxDecibels = value; } } }, { key: 'minDecibels', get: function get() { return this._node.minDecibels; }, set: function set(value) { if ((0, _isSafeNumber2.default)(value)) { this._node.minDecibels = value; } } }, { key: 'smoothing', get: function get() { return this._node.smoothingTimeConstant; }, set: function set(value) { if ((0, _isSafeNumber2.default)(value)) { this._node.smoothingTimeConstant = value; } } }]); return Analyser; }(_abstractDirectEffect2.default); exports.default = _sono2.default.register('analyser', function (opts) { return new Analyser(opts); });