Merge branch 'dev' of https://github.com/Tonejs/Tone.js into dev

This commit is contained in:
tambien 2018-05-17 11:11:04 -04:00
commit ba1e40ddd3
2 changed files with 193 additions and 47 deletions

View file

@ -3,13 +3,14 @@ define(["Tone/core/Tone", "Tone/component/Analyser", "Tone/core/AudioNode"], fun
"use strict";
/**
* @class Tone.Meter gets the [RMS](https://en.wikipedia.org/wiki/Root_mean_square)
* @class Tone.Meter gets the Peak or [RMS](https://en.wikipedia.org/wiki/Root_mean_square)
* of an input signal with some averaging applied. It can also get the raw
* value of the input signal.
*
* @constructor
* @extends {Tone.AudioNode}
* @param {Number} smoothing The amount of smoothing applied between frames.
* @param {'rms' | 'peak'} type Calculation method of dB value, defaults to RMS
* @example
* var meter = new Tone.Meter();
* var mic = new Tone.UserMedia().open();
@ -32,10 +33,25 @@ define(["Tone/core/Tone", "Tone/component/Analyser", "Tone/core/AudioNode"], fun
//set the smoothing initially
this.smoothing = options.smoothing;
/**
* Calculation method used to get the dB value
* @type {'rms' | 'peak'}
*/
this.type = options.type;
};
Tone.extend(Tone.Meter, Tone.AudioNode);
/**
* Calculation methods available for dB value, default is RMS
* @enum {String}
*/
Tone.Meter.Type = {
RMS : "rms",
Peak : "peak"
};
/**
* The defaults
* @type {Object}
@ -43,7 +59,8 @@ define(["Tone/core/Tone", "Tone/component/Analyser", "Tone/core/AudioNode"], fun
* @const
*/
Tone.Meter.defaults = {
"smoothing" : 0.8
"smoothing" : 0.8,
"type" : Tone.Meter.Type.RMS
};
/**
@ -51,11 +68,19 @@ define(["Tone/core/Tone", "Tone/component/Analyser", "Tone/core/AudioNode"], fun
* @returns {Decibels}
*/
Tone.Meter.prototype.getLevel = function(){
this._analyser.type = "fft";
var values = this._analyser.getValue();
var offset = 28; // normalizes most signal levels
// TODO: compute loudness from FFT
return Math.max.apply(this, values) + offset;
switch (this.type){
case Tone.Meter.Type.RMS:
var rmsFloatValue = this.getRmsFloatValue(values);
return Tone.gainToDb(rmsFloatValue);
case Tone.Meter.Type.Peak:
var peakFloatValue = this.getPeakFloatValue(values);
return Tone.gainToDb(peakFloatValue);
default:
// Sanity check, should have thrown while setting type
throw new TypeError("Tone.Meter: invalid type: " + this.type);
}
};
/**
@ -63,11 +88,43 @@ define(["Tone/core/Tone", "Tone/component/Analyser", "Tone/core/AudioNode"], fun
* @returns {Number}
*/
Tone.Meter.prototype.getValue = function(){
this._analyser.type = "waveform";
var value = this._analyser.getValue();
return value[0];
};
/**
* Gets the peak value from a Float32Array, uses absolute values so
* negative values are counted towards the peak.
*
* @param {Float32Array} values Float32Array with amplitude ratio readings
* @returns {Number}
*/
Tone.Meter.prototype.getPeakFloatValue = function(values){
var peak = 0;
for (var i = 0; i < values.length; i++){
var value = Math.abs(values[i]);
if (value > peak){
peak = value;
}
}
return peak;
};
/**
* Gets the [RMS](https://en.wikipedia.org/wiki/Root_mean_square) value from a Float32Array
*
* @param {Float32Array} values Float32Array with amplitude ratio readings
* @returns {Number}
*/
Tone.Meter.prototype.getRmsFloatValue = function(values){
var totalSquared = 0;
for (var i = 0; i < values.length; i++){
var value = values[i];
totalSquared += value * value;
}
return Math.sqrt(totalSquared / values.length);
};
/**
* A value from 0 -> 1 where 0 represents no time averaging with the last analysis frame.
* @memberOf Tone.Meter#
@ -81,7 +138,25 @@ define(["Tone/core/Tone", "Tone/component/Analyser", "Tone/core/AudioNode"], fun
},
set : function(val){
this._analyser.smoothing = val;
}
});
/**
* Either 'rms' or 'peak', determines calculation method of getValue
* @memberOf Tone.Meter#
* @type {'rms' | 'peak'}
* @name type
*/
Object.defineProperty(Tone.Meter.prototype, "type", {
get : function(){
return this._type;
},
set : function(type){
if (type !== Tone.Meter.Type.RMS && type !== Tone.Meter.Type.Peak){
throw new TypeError("Tone.Meter: invalid type: " + type);
}
this._type = type;
}
});
/**

View file

@ -7,44 +7,93 @@ function (Meter, Basic, Offline, Test, Signal, PassAudio, Tone, Merge, Oscillato
Basic(Meter);
context("Metering", function(){
it("handles input and output connections", function(){
var meter = new Meter();
Test.connect(meter);
meter.connect(Test);
meter.dispose();
});
it("handles getter/setter as Object", function(){
var meter = new Meter();
var values = {
"smoothing" : 0.2
};
meter.set(values);
expect(meter.get().smoothing).to.equal(0.2);
meter.dispose();
});
it("can be constructed with an object", function(){
var meter = new Meter({
"smoothing" : 0.3
});
expect(meter.smoothing).to.equal(0.3);
meter.dispose();
});
it("passes the audio through", function(){
var meter;
return PassAudio(function(input){
meter = new Meter();
input.chain(meter, Tone.Master);
});
});
if (Supports.ONLINE_TESTING){
it("measures the incoming signal", function(done){
if (Supports.ONLINE_TESTING) {
it("handles input and output connections", function(){
var meter = new Meter();
Test.connect(meter);
meter.connect(Test);
meter.dispose();
});
it("handles getter/setter as Object", function(){
var meter = new Meter();
var values = {
"smoothing" : 0.2
};
meter.set(values);
expect(meter.get().smoothing).to.equal(0.2);
meter.dispose();
});
it("can be constructed with an object", function(){
var meter = new Meter({
"smoothing" : 0.3
});
expect(meter.smoothing).to.equal(0.3);
meter.dispose();
});
it("passes the audio through", function(){
var meter;
return PassAudio(function(input){
meter = new Meter();
input.chain(meter, Tone.Master);
});
});
it("defaults to rms level of incoming signal", function(){
var meter = new Meter();
expect(meter.type).to.equal("rms");
meter.dispose();
});
it("throws when invalid type is set", function(){
var meter = new Meter();
expect(function() {
meter.type = "shouldThrow";
}).to.throw("Tone.Meter: invalid type: shouldThrow");
meter.dispose();
expect(function(){
new Meter({ type: "shouldThrow" });
}).to.throw("Tone.Meter: invalid type: shouldThrow");
});
it("can change type", function(){
var meter = new Meter();
expect(meter.type).to.equal("rms");
meter.type = "peak";
expect(meter.type).to.equal("peak");
meter.type = "rms";
expect(meter.type).to.equal("rms");
meter.dispose();
});
it("gets peak value correctly", function(){
var values = [1.0, -2.0, 3.0, -4.0, 5.0, -6.0, 7.0, -8.0, 9.0, -10.0];
var meter = new Meter();
var peak = meter.getPeakFloatValue(values);
expect(peak).to.equal(10);
meter.dispose();
});
// Based on https://rosettacode.org/wiki/Averages/Root_mean_square#JavaScript
it("gets rms value correctly", function(){
var values = [-1.0, 2.0, -3.0, 4.0, -5.0, 6.0, -7.0, 8.0, -9.0, 10.0];
var meter = new Meter();
var peak = meter.getRmsFloatValue(values);
expect(peak).to.be.closeTo(6.2048, 0.0001);
meter.dispose();
});
it("measures the peak incoming signal", function(done){
var meter = new Meter({ type: "peak" });
var signal = new Signal(1).connect(meter);
setTimeout(function(){
expect(meter.getValue()).to.be.closeTo(1, 0.05);
@ -54,8 +103,19 @@ function (Meter, Basic, Offline, Test, Signal, PassAudio, Tone, Merge, Oscillato
}, 400);
});
it("can get the level of the incoming signal", function(done){
var meter = new Meter();
it("measures the rms incoming signal", function(done){
var meter = new Meter({ type: "rms" });
var signal = new Signal(1).connect(meter);
setTimeout(function(){
expect(meter.getValue()).to.be.closeTo(1, 0.05);
meter.dispose();
signal.dispose();
done();
}, 400);
});
it("can get the peak level of the incoming signal", function(done){
var meter = new Meter({ type: "peak" });
var osc = new Oscillator().connect(meter).start();
osc.volume.value = -6;
setTimeout(function(){
@ -65,8 +125,19 @@ function (Meter, Basic, Offline, Test, Signal, PassAudio, Tone, Merge, Oscillato
done();
}, 400);
});
}
it("can get the rms level of the incoming signal", function(done){
var meter = new Meter({ type: "rms" });
var osc = new Oscillator().connect(meter).start();
osc.volume.value = -6;
setTimeout(function(){
expect(meter.getLevel()).to.be.closeTo(-9, 1);
meter.dispose();
osc.dispose();
done();
}, 400);
});
}
});
});
});