mirror of
https://github.com/Tonejs/Tone.js
synced 2024-11-16 00:27:58 +00:00
converting Solo to ts
This commit is contained in:
parent
daf340ba69
commit
3d26c3adba
3 changed files with 284 additions and 158 deletions
|
@ -1,158 +0,0 @@
|
|||
import Tone from "../../core/Tone";
|
||||
import "../core/Gain";
|
||||
import "../core/AudioNode";
|
||||
|
||||
/**
|
||||
* @class Tone.Solo lets you isolate a specific audio stream. When
|
||||
* an instance is set to `solo=true`, it will mute all other instances.
|
||||
* @extends {Tone.AudioNode}
|
||||
* @example
|
||||
* var soloA = new Tone.Solo()
|
||||
* var soloB = new Tone.Solo()
|
||||
* soloA.solo = true
|
||||
* //no audio will pass through soloB
|
||||
*/
|
||||
Tone.Solo = function(){
|
||||
|
||||
var options = Tone.defaults(arguments, ["solo"], Tone.Solo);
|
||||
Tone.AudioNode.call(this);
|
||||
|
||||
/**
|
||||
* The input and output node
|
||||
* @type {Tone.Gain}
|
||||
*/
|
||||
this.input = this.output = new Tone.Gain();
|
||||
|
||||
/**
|
||||
* A bound _soloed method
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
this._soloBind = this._soloed.bind(this);
|
||||
|
||||
//listen for solo events class-wide.
|
||||
this.context.on("solo", this._soloBind);
|
||||
//set initially
|
||||
this.solo = options.solo;
|
||||
};
|
||||
|
||||
Tone.extend(Tone.Solo, Tone.AudioNode);
|
||||
|
||||
/**
|
||||
* The defaults
|
||||
* @type {Object}
|
||||
* @static
|
||||
*/
|
||||
Tone.Solo.defaults = {
|
||||
solo : false,
|
||||
};
|
||||
|
||||
/**
|
||||
* Isolates this instance and mutes all other instances of Tone.Solo.
|
||||
* Only one instance can be soloed at a time. A soloed
|
||||
* instance will report `solo=false` when another instance is soloed.
|
||||
* @memberOf Tone.Solo#
|
||||
* @type {Boolean}
|
||||
* @name solo
|
||||
*/
|
||||
Object.defineProperty(Tone.Solo.prototype, "solo", {
|
||||
get : function(){
|
||||
return this._isSoloed();
|
||||
},
|
||||
set : function(solo){
|
||||
if (solo){
|
||||
this._addSolo();
|
||||
} else {
|
||||
this._removeSolo();
|
||||
}
|
||||
this.context.emit("solo", this);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* If the current instance is muted, i.e. another instance is soloed
|
||||
* @memberOf Tone.Solo#
|
||||
* @type {Boolean}
|
||||
* @name muted
|
||||
* @readOnly
|
||||
*/
|
||||
Object.defineProperty(Tone.Solo.prototype, "muted", {
|
||||
get : function(){
|
||||
return this.input.gain.value === 0;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Add this to the soloed array
|
||||
* @private
|
||||
*/
|
||||
Tone.Solo.prototype._addSolo = function(){
|
||||
if (!Tone.isArray(this.context._currentSolo)){
|
||||
this.context._currentSolo = [];
|
||||
}
|
||||
if (!this._isSoloed()){
|
||||
this.context._currentSolo.push(this);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove this from the soloed array
|
||||
* @private
|
||||
*/
|
||||
Tone.Solo.prototype._removeSolo = function(){
|
||||
if (this._isSoloed()){
|
||||
var index = this.context._currentSolo.indexOf(this);
|
||||
this.context._currentSolo.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {Boolean} Is this on the soloed array
|
||||
* @private
|
||||
*/
|
||||
Tone.Solo.prototype._isSoloed = function(){
|
||||
if (Tone.isArray(this.context._currentSolo)){
|
||||
return this.context._currentSolo.length !== 0 && this.context._currentSolo.indexOf(this) !== -1;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {Boolean} Returns true if no one is soloed
|
||||
* @private
|
||||
*/
|
||||
Tone.Solo.prototype._noSolos = function(){
|
||||
return !Tone.isArray(this.context._currentSolo) || this.context._currentSolo.length === 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Solo the current instance and unsolo all other instances.
|
||||
* @param {Tone.Solo} instance The instance which is being soloed/unsoloed.
|
||||
* @private
|
||||
*/
|
||||
Tone.Solo.prototype._soloed = function(){
|
||||
if (this._isSoloed()){
|
||||
this.input.gain.value = 1;
|
||||
} else if (this._noSolos()){
|
||||
//no one is soloed
|
||||
this.input.gain.value = 1;
|
||||
} else {
|
||||
this.input.gain.value = 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clean up
|
||||
* @return {Tone.Solo} this
|
||||
*/
|
||||
Tone.Solo.prototype.dispose = function(){
|
||||
this.context.off("solo", this._soloBind);
|
||||
this._removeSolo();
|
||||
this._soloBind = null;
|
||||
Tone.AudioNode.prototype.dispose.call(this);
|
||||
return this;
|
||||
};
|
||||
|
||||
export default Tone.Solo;
|
||||
|
138
Tone/component/channel/Solo.test.ts
Normal file
138
Tone/component/channel/Solo.test.ts
Normal file
|
@ -0,0 +1,138 @@
|
|||
import { expect } from "chai";
|
||||
import { BasicTests } from "test/helper/Basic";
|
||||
import { ConstantOutput } from "test/helper/ConstantOutput";
|
||||
import { Signal } from "Tone/signal/Signal";
|
||||
import { Solo } from "./Solo";
|
||||
|
||||
describe("Solo", () => {
|
||||
|
||||
BasicTests(Solo);
|
||||
|
||||
context("Soloing", () => {
|
||||
|
||||
it("can be soloed an unsoloed", () => {
|
||||
const sol = new Solo();
|
||||
sol.solo = true;
|
||||
expect(sol.solo).to.be.true;
|
||||
sol.solo = false;
|
||||
expect(sol.solo).to.be.false;
|
||||
sol.dispose();
|
||||
});
|
||||
|
||||
it("can be passed into the constructor", () => {
|
||||
const sol = new Solo(true);
|
||||
expect(sol.solo).to.be.true;
|
||||
sol.dispose();
|
||||
});
|
||||
|
||||
it("can be passed into the constructor with an object", () => {
|
||||
const sol = new Solo({ solo : true });
|
||||
expect(sol.solo).to.be.true;
|
||||
sol.dispose();
|
||||
});
|
||||
|
||||
it("other instances are unsoloed when one is soloed", () => {
|
||||
const solA = new Solo();
|
||||
const solB = new Solo();
|
||||
solA.solo = true;
|
||||
solB.solo = false;
|
||||
expect(solA.solo).to.be.true;
|
||||
expect(solB.solo).to.be.false;
|
||||
solB.solo = true;
|
||||
expect(solA.solo).to.be.true;
|
||||
expect(solB.solo).to.be.true;
|
||||
solA.solo = false;
|
||||
expect(solA.solo).to.be.false;
|
||||
expect(solB.solo).to.be.true;
|
||||
solA.dispose();
|
||||
solB.dispose();
|
||||
});
|
||||
|
||||
it("other instances report themselves as muted", () => {
|
||||
const solA = new Solo();
|
||||
const solB = new Solo();
|
||||
solA.solo = true;
|
||||
solB.solo = false;
|
||||
expect(solA.muted).to.be.false;
|
||||
expect(solB.muted).to.be.true;
|
||||
solA.dispose();
|
||||
solB.dispose();
|
||||
});
|
||||
|
||||
it("all instances are unmuted when there is no solo", () => {
|
||||
const solA = new Solo();
|
||||
const solB = new Solo();
|
||||
solA.solo = true;
|
||||
solB.solo = false;
|
||||
solA.solo = false;
|
||||
expect(solA.muted).to.be.false;
|
||||
expect(solB.muted).to.be.false;
|
||||
solA.dispose();
|
||||
solB.dispose();
|
||||
});
|
||||
|
||||
it("a newly created instance will be muted if there is already a soloed instance", () => {
|
||||
const solA = new Solo(true);
|
||||
const solB = new Solo();
|
||||
expect(solA.muted).to.be.false;
|
||||
expect(solB.muted).to.be.true;
|
||||
solA.dispose();
|
||||
solB.dispose();
|
||||
});
|
||||
|
||||
it("passes both signals when nothing is soloed", () => {
|
||||
return ConstantOutput(() => {
|
||||
const soloA = new Solo().toDestination();
|
||||
const soloB = new Solo().toDestination();
|
||||
new Signal(10).connect(soloA);
|
||||
new Signal(20).connect(soloB);
|
||||
}, 30, 0.01);
|
||||
});
|
||||
|
||||
it("passes one signal when it is soloed", () => {
|
||||
return ConstantOutput(() => {
|
||||
const soloA = new Solo().toDestination();
|
||||
const soloB = new Solo().toDestination();
|
||||
new Signal(10).connect(soloA);
|
||||
new Signal(20).connect(soloB);
|
||||
soloA.solo = true;
|
||||
}, 10, 0.01);
|
||||
});
|
||||
|
||||
it("can solo multiple at once", () => {
|
||||
return ConstantOutput(() => {
|
||||
const soloA = new Solo().toDestination();
|
||||
const soloB = new Solo().toDestination();
|
||||
new Signal(10).connect(soloA);
|
||||
new Signal(20).connect(soloB);
|
||||
soloA.solo = true;
|
||||
soloB.solo = true;
|
||||
}, 30, 0.01);
|
||||
});
|
||||
|
||||
it("can unsolo all", () => {
|
||||
return ConstantOutput(() => {
|
||||
const soloA = new Solo().toDestination();
|
||||
const soloB = new Solo().toDestination();
|
||||
new Signal(10).connect(soloA);
|
||||
new Signal(20).connect(soloB);
|
||||
soloA.solo = true;
|
||||
soloB.solo = true;
|
||||
soloA.solo = false;
|
||||
soloB.solo = false;
|
||||
}, 30, 0.01);
|
||||
});
|
||||
|
||||
it("can solo and unsolo while keeping previous soloed", () => {
|
||||
return ConstantOutput(() => {
|
||||
const soloA = new Solo().toDestination();
|
||||
const soloB = new Solo().toDestination();
|
||||
new Signal(10).connect(soloA);
|
||||
new Signal(20).connect(soloB);
|
||||
soloA.solo = true;
|
||||
soloB.solo = true;
|
||||
soloB.solo = false;
|
||||
}, 10, 0.01);
|
||||
});
|
||||
});
|
||||
});
|
146
Tone/component/channel/Solo.ts
Normal file
146
Tone/component/channel/Solo.ts
Normal file
|
@ -0,0 +1,146 @@
|
|||
import { Context } from "Tone/core";
|
||||
import { Gain } from "../../core/context/Gain";
|
||||
import { ToneAudioNode, ToneAudioNodeOptions } from "../../core/context/ToneAudioNode";
|
||||
import { optionsFromArguments } from "../../core/util/Defaults";
|
||||
|
||||
export interface SoloOptions extends ToneAudioNodeOptions {
|
||||
solo: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Solo lets you isolate a specific audio stream. When an instance is set to `solo=true`,
|
||||
* it will mute all other instances of Solo.
|
||||
* @example
|
||||
* var soloA = new Solo()
|
||||
* var soloB = new Solo()
|
||||
* soloA.solo = true
|
||||
* //no audio will pass through soloB
|
||||
*/
|
||||
export class Solo extends ToneAudioNode<SoloOptions> {
|
||||
|
||||
readonly name: string = "Solo";
|
||||
|
||||
readonly input: Gain;
|
||||
readonly output: Gain;
|
||||
|
||||
/**
|
||||
* @param solo If the connection should be initially solo'ed.
|
||||
*/
|
||||
constructor(solo?: boolean);
|
||||
// tslint:disable-next-line: unified-signatures
|
||||
constructor(options?: Partial<SoloOptions>);
|
||||
constructor() {
|
||||
|
||||
super(optionsFromArguments(Solo.getDefaults(), arguments, ["solo"]));
|
||||
const options = optionsFromArguments(Solo.getDefaults(), arguments, ["solo"]);
|
||||
|
||||
this.input = this.output = new Gain({
|
||||
context: this.context,
|
||||
});
|
||||
|
||||
if (!Solo._allSolos.has(this.context)) {
|
||||
Solo._allSolos.set(this.context, new Set());
|
||||
}
|
||||
(Solo._allSolos.get(this.context) as Set<Solo>).add(this);
|
||||
|
||||
// set initially
|
||||
this.solo = options.solo;
|
||||
}
|
||||
|
||||
static getDefaults(): SoloOptions {
|
||||
return Object.assign(ToneAudioNode.getDefaults(), {
|
||||
solo: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hold all of the solo'ed tracks belonging to a specific context
|
||||
*/
|
||||
private static _allSolos: Map<Context, Set<Solo>> = new Map();
|
||||
|
||||
/**
|
||||
* Hold the currently solo'ed instance(s)
|
||||
*/
|
||||
private static _soloed: Map<Context, Set<Solo>> = new Map();
|
||||
|
||||
/**
|
||||
* Isolates this instance and mutes all other instances of Solo.
|
||||
* Only one instance can be soloed at a time. A soloed
|
||||
* instance will report `solo=false` when another instance is soloed.
|
||||
*/
|
||||
get solo(): boolean {
|
||||
return this._isSoloed();
|
||||
}
|
||||
set solo(solo) {
|
||||
if (solo) {
|
||||
this._addSolo();
|
||||
} else {
|
||||
this._removeSolo();
|
||||
}
|
||||
(Solo._allSolos.get(this.context) as Set<Solo>).forEach(instance => instance._updateSolo());
|
||||
}
|
||||
|
||||
/**
|
||||
* If the current instance is muted, i.e. another instance is soloed
|
||||
*/
|
||||
get muted(): boolean {
|
||||
return this.input.gain.value === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add this to the soloed array
|
||||
*/
|
||||
private _addSolo(): void {
|
||||
if (!Solo._soloed.has(this.context)) {
|
||||
Solo._soloed.set(this.context, new Set());
|
||||
}
|
||||
(Solo._soloed.get(this.context) as Set<Solo>).add(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove this from the soloed array
|
||||
*/
|
||||
private _removeSolo(): void {
|
||||
if (Solo._soloed.has(this.context)) {
|
||||
(Solo._soloed.get(this.context) as Set<Solo>).delete(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this on the soloed array
|
||||
*/
|
||||
private _isSoloed(): boolean {
|
||||
return Solo._soloed.has(this.context) && (Solo._soloed.get(this.context) as Set<Solo>).has(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if no one is soloed
|
||||
*/
|
||||
private _noSolos(): boolean {
|
||||
// either does not have any soloed added
|
||||
return !Solo._soloed.has(this.context) ||
|
||||
// or has a solo set but doesn't include any items
|
||||
(Solo._soloed.has(this.context) && (Solo._soloed.get(this.context) as Set<Solo>).size === 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Solo the current instance and unsolo all other instances.
|
||||
*/
|
||||
private _updateSolo(): void {
|
||||
if (this._isSoloed()) {
|
||||
this.input.gain.value = 1;
|
||||
} else if (this._noSolos()) {
|
||||
// no one is soloed
|
||||
this.input.gain.value = 1;
|
||||
} else {
|
||||
this.input.gain.value = 0;
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): this {
|
||||
super.dispose();
|
||||
(Solo._allSolos.get(this.context) as Set<Solo>).delete(this);
|
||||
this._removeSolo();
|
||||
return this;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue