Tone.js/Tone/source/oscillator/FatOscillator.ts
2019-09-16 10:15:23 -04:00

286 lines
7.2 KiB
TypeScript

import { AudioRange, Cents, Degrees, Frequency, Positive, Time } from "../../core/type/Units";
import { optionsFromArguments } from "../../core/util/Defaults";
import { noOp, readOnly } from "../../core/util/Interface";
import { AudioToGain } from "../../signal/AudioToGain";
import { Multiply } from "../../signal/Multiply";
import { Signal } from "../../signal/Signal";
import { Source } from "../Source";
import { Oscillator } from "./Oscillator";
import { FatConstructorOptions, FatOscillatorOptions,
ToneOscillatorInterface, ToneOscillatorType } from "./OscillatorInterface";
export { FatOscillatorOptions } from "./OscillatorInterface";
/**
* FatOscillator is an array of oscillators with detune spread between the oscillators
* @param frequency The oscillator's frequency.
* @param type The type of the oscillator.
* @param spread The detune spread between the oscillators.
* @example
* var fatOsc = new FatOscillator("Ab3", "sine", 40).toDestination().start();
* @category Source
*/
export class FatOscillator extends Source<FatOscillatorOptions> implements ToneOscillatorInterface {
readonly name: string = "FatOscillator";
/**
* The oscillator's frequency
*/
readonly frequency: Signal<Frequency>;
/**
* The detune control signal.
*/
readonly detune: Signal<Cents>;
/**
* The array of oscillators
*/
private _oscillators: Oscillator[] = [];
/**
* The total spread of the oscillators
*/
private _spread: Cents;
/**
* The type of the oscillator
*/
private _type: ToneOscillatorType;
/**
* The phase of the oscillators
*/
private _phase: Degrees;
/**
* The partials array
*/
private _partials: number[];
/**
* The number of partials to use
*/
private _partialCount: number;
constructor(options?: Partial<FatConstructorOptions>);
constructor(frequency?: Frequency, type?: ToneOscillatorType, modulationType?: ToneOscillatorType);
constructor() {
super(optionsFromArguments(FatOscillator.getDefaults(), arguments, ["frequency", "type", "spread"]));
const options = optionsFromArguments(FatOscillator.getDefaults(), arguments, ["frequency", "type", "spread"]);
this.frequency = new Signal({
context: this.context,
units: "frequency",
value: options.frequency,
});
this.detune = new Signal({
context: this.context,
units: "cents",
value: options.detune,
});
this._spread = options.spread;
this._type = options.type;
this._phase = options.phase;
this._partials = options.partials;
this._partialCount = options.partialCount;
// set the count initially
this.count = options.count;
readOnly(this, ["frequency", "detune"]);
}
static getDefaults(): FatOscillatorOptions {
return Object.assign(Oscillator.getDefaults(), {
count: 3,
spread: 20,
type: "sawtooth",
});
}
/**
* start the oscillator
*/
protected _start(time: Time): void {
time = this.toSeconds(time);
this._forEach(osc => osc.start(time));
}
/**
* stop the oscillator
*/
protected _stop(time: Time): void {
time = this.toSeconds(time);
this._forEach(osc => osc.stop(time));
}
/**
* restart the oscillator
*/
restart(time): this {
time = this.toSeconds(time);
this._forEach(osc => osc.restart(time));
return this;
}
/**
* Iterate over all of the oscillators
*/
private _forEach(iterator: (osc: Oscillator, index: number) => void): void {
for (let i = 0; i < this._oscillators.length; i++) {
iterator(this._oscillators[i], i);
}
}
/**
* The type of the oscillator
*/
get type(): ToneOscillatorType {
return this._type;
}
set type(type: ToneOscillatorType) {
this._type = type;
this._forEach(osc => osc.type = type);
}
/**
* The detune spread between the oscillators. If "count" is
* set to 3 oscillators and the "spread" is set to 40,
* the three oscillators would be detuned like this: [-20, 0, 20]
* for a total detune spread of 40 cents.
*/
get spread(): Cents {
return this._spread;
}
set spread(spread: Cents) {
this._spread = spread;
if (this._oscillators.length > 1) {
const start = -spread / 2;
const step = spread / (this._oscillators.length - 1);
this._forEach((osc, i) => osc.detune.value = start + step * i);
}
}
/**
* The number of detuned oscillators. Should be an integer greater than 1.
*/
get count(): number {
return this._oscillators.length;
}
set count(count: number) {
count = Math.max(count, 1);
if (this._oscillators.length !== count) {
// dispose the previous oscillators
this._forEach(osc => osc.dispose());
this._oscillators = [];
for (let i = 0; i < count; i++) {
const osc = new Oscillator({
context: this.context,
onstop: i === 0 ? () => this.onstop(this) : noOp,
});
if (this.type === "custom") {
osc.partials = this._partials;
} else {
osc.type = this._type;
}
osc.partialCount = this._partialCount;
osc.phase = this._phase + (i / count) * 360;
osc.volume.value = -6 - count * 1.1;
this.frequency.connect(osc.frequency);
this.detune.connect(osc.detune);
osc.connect(this.output);
this._oscillators[i] = osc;
}
// set the spread
this.spread = this._spread;
if (this.state === "started") {
this._forEach(osc => osc.start());
}
}
}
/**
* The phase of the oscillator in degrees.
*/
get phase(): Degrees {
return this._phase;
}
set phase(phase: Degrees) {
this._phase = phase;
this._forEach(osc => osc.phase = phase);
}
/**
* The oscillator type without the partialsCount appended to the end
* @example
* osc.type = 'sine2'
* osc.baseType //'sine'
* osc.partialCount = 2
*/
get baseType(): OscillatorType {
return this._oscillators[0].baseType;
}
set baseType(baseType: OscillatorType) {
this._forEach(osc => osc.baseType = baseType);
this._type = this._oscillators[0].type;
}
/**
* The partials of the carrier waveform. A partial represents
* the amplitude at a harmonic. The first harmonic is the
* fundamental frequency, the second is the octave and so on
* following the harmonic series.
* Setting this value will automatically set the type to "custom".
* The value is an empty array when the type is not "custom".
* @memberOf FatOscillator#
* @type {Array}
* @name partials
* @example
* osc.partials = [1, 0.2, 0.01];
*/
get partials(): number[] {
return this._oscillators[0].partials;
}
set partials(partials: number[]) {
this._partials = partials;
if (partials.length) {
this._type = "custom";
this._forEach(osc => osc.partials = partials);
}
}
/**
* 'partialCount' offers an alternative way to set the number of used partials.
* When partialCount is 0, the maximum number of partials are used when representing
* the waveform using the periodicWave. When 'partials' is set, this value is
* not settable, but equals the length of the partials array.
* @memberOf FatOscillator#
* @type {Number}
* @name partialCount
*/
get partialCount(): number {
return this._oscillators[0].partialCount;
}
set partialCount(partialCount: number) {
this._partialCount = partialCount;
this._forEach(osc => osc.partialCount = partialCount);
this._type = this._oscillators[0].type;
}
/**
* Clean up.
*/
dispose(): this {
super.dispose();
this.frequency.dispose();
this.detune.dispose();
this._forEach(osc => osc.dispose());
return this;
}
}