Tone.js/Tone/source/oscillator/OmniOscillator.ts

449 lines
14 KiB
TypeScript
Raw Normal View History

import { Cents, Degrees, Frequency, Seconds, Time } from "../../core/type/Units";
2019-09-16 03:32:40 +00:00
import { optionsFromArguments } from "../../core/util/Defaults";
2019-07-17 16:55:34 +00:00
import { readOnly } from "../../core/util/Interface";
import { isNumber, isString } from "../../core/util/TypeCheck";
import { Signal } from "../../signal/Signal";
import { Source } from "../Source";
import { AMOscillator } from "./AMOscillator";
import { FatOscillator } from "./FatOscillator";
import { FMOscillator } from "./FMOscillator";
import { Oscillator } from "./Oscillator";
import { generateWaveform,
OmniOscillatorConstructorOptions, OmniOscillatorOptions,
OmniOscillatorType, ToneOscillatorInterface, ToneOscillatorType } from "./OscillatorInterface";
import { PulseOscillator } from "./PulseOscillator";
import { PWMOscillator } from "./PWMOscillator";
2019-07-17 16:55:34 +00:00
2019-09-04 22:34:42 +00:00
export { OmniOscillatorOptions } from "./OscillatorInterface";
2019-07-17 16:55:34 +00:00
/**
* All of the oscillator types that OmniOscillator can take on
*/
type AnyOscillator = Oscillator | PWMOscillator | PulseOscillator | FatOscillator | AMOscillator | FMOscillator;
/**
* All of the Oscillator constructor types mapped to their name.
*/
interface OmniOscillatorSource {
"fm": FMOscillator;
"am": AMOscillator;
"pwm": PWMOscillator;
"pulse": PulseOscillator;
"oscillator": Oscillator;
"fat": FatOscillator;
}
/**
* The available oscillator types.
*/
export type OmniOscSourceType = keyof OmniOscillatorSource;
// Conditional Types
type IsAmOrFmOscillator<Osc, Ret> = Osc extends AMOscillator ? Ret : Osc extends FMOscillator ? Ret : undefined;
type IsFatOscillator<Osc, Ret> = Osc extends FatOscillator ? Ret : undefined;
type IsPWMOscillator<Osc, Ret> = Osc extends PWMOscillator ? Ret : undefined;
type IsPulseOscillator<Osc, Ret> = Osc extends PulseOscillator ? Ret : undefined;
type IsFMOscillator<Osc, Ret> = Osc extends FMOscillator ? Ret : undefined;
2019-08-10 15:51:35 +00:00
type AnyOscillatorConstructor = new (...args: any[]) => AnyOscillator;
2019-07-17 16:55:34 +00:00
const OmniOscillatorSourceMap: {
2019-09-16 03:32:40 +00:00
[key in OmniOscSourceType]: AnyOscillatorConstructor
2019-07-17 16:55:34 +00:00
} = {
am: AMOscillator,
fat: FatOscillator,
fm: FMOscillator,
oscillator: Oscillator,
pulse: PulseOscillator,
pwm: PWMOscillator,
};
/**
* OmniOscillator aggregates Tone.Oscillator, Tone.PulseOscillator,
* Tone.PWMOscillator, Tone.FMOscillator, Tone.AMOscillator, and Tone.FatOscillator
* into one class. The oscillator class can be changed by setting the `type`.
* `omniOsc.type = "pwm"` will set it to the Tone.PWMOscillator. Prefixing
* any of the basic types ("sine", "square4", etc.) with "fm", "am", or "fat"
* will use the FMOscillator, AMOscillator or FatOscillator respectively.
* For example: `omniOsc.type = "fatsawtooth"` will create set the oscillator
* to a FatOscillator of type "sawtooth".
* @example
2019-10-25 20:54:33 +00:00
* import { OmniOscillator } from "tone";
* const omniOsc = new OmniOscillator("C#4", "pwm");
2019-09-16 14:15:23 +00:00
* @category Source
2019-07-17 16:55:34 +00:00
*/
export class OmniOscillator<OscType extends AnyOscillator>
2019-09-16 03:32:40 +00:00
extends Source<OmniOscillatorConstructorOptions>
implements Omit<ToneOscillatorInterface, "type"> {
2019-07-17 16:55:34 +00:00
2019-09-04 23:18:44 +00:00
readonly name: string = "OmniOscillator";
2019-07-17 16:55:34 +00:00
readonly frequency: Signal<"frequency">;
readonly detune: Signal<"cents">;
2019-07-17 16:55:34 +00:00
/**
* The oscillator that can switch types
*/
private _oscillator!: AnyOscillator;
/**
2019-09-14 20:39:18 +00:00
* the type of the oscillator source
2019-07-17 16:55:34 +00:00
*/
private _sourceType!: OmniOscSourceType;
2019-08-27 15:47:52 +00:00
/**
* @param frequency The initial frequency of the oscillator.
* @param type The type of the oscillator.
*/
2019-07-17 16:55:34 +00:00
constructor(frequency?: Frequency, type?: OmniOscillatorType);
2019-08-27 15:47:52 +00:00
constructor(options?: Partial<OmniOscillatorConstructorOptions>);
2019-07-17 16:55:34 +00:00
constructor() {
super(optionsFromArguments(OmniOscillator.getDefaults(), arguments, ["frequency", "type"]));
const options = optionsFromArguments(OmniOscillator.getDefaults(), arguments, ["frequency", "type"]);
this.frequency = new Signal({
context: this.context,
units: "frequency",
2019-08-08 18:17:41 +00:00
value: options.frequency,
});
this.detune = new Signal({
context: this.context,
units: "cents",
2019-08-08 18:17:41 +00:00
value: options.detune,
});
2019-07-17 16:55:34 +00:00
readOnly(this, ["frequency", "detune"]);
// set the options
this.set(options);
2019-07-17 16:55:34 +00:00
}
static getDefaults(): OmniOscillatorOptions {
return Object.assign(
Oscillator.getDefaults(),
FMOscillator.getDefaults(),
AMOscillator.getDefaults(),
FatOscillator.getDefaults(),
PulseOscillator.getDefaults(),
PWMOscillator.getDefaults(),
);
2019-07-17 16:55:34 +00:00
}
/**
2019-09-14 20:39:18 +00:00
* start the oscillator
2019-07-17 16:55:34 +00:00
*/
protected _start(time: Time): void {
this._oscillator.start(time);
}
/**
2019-09-14 20:39:18 +00:00
* start the oscillator
2019-07-17 16:55:34 +00:00
*/
protected _stop(time: Time): void {
this._oscillator.stop(time);
}
protected _restart(time: Seconds): this {
2019-07-17 16:55:34 +00:00
this._oscillator.restart(time);
return this;
}
/**
* The type of the oscillator. Can be any of the basic types: sine, square, triangle, sawtooth. Or
* prefix the basic types with "fm", "am", or "fat" to use the FMOscillator, AMOscillator or FatOscillator
* types. The oscillator could also be set to "pwm" or "pulse". All of the parameters of the
* oscillator's class are accessible when the oscillator is set to that type, but throws an error
2019-10-25 20:54:33 +00:00
* when it's not.
2019-07-17 16:55:34 +00:00
* @example
2019-10-25 20:54:33 +00:00
* import { OmniOscillator } from "tone";
* const omniOsc = new OmniOscillator().toDestination().start();
2019-07-17 16:55:34 +00:00
* omniOsc.type = "pwm";
2019-10-25 20:54:33 +00:00
* // modulationFrequency is parameter which is available
* // only when the type is "pwm".
2019-07-17 16:55:34 +00:00
* omniOsc.modulationFrequency.value = 0.5;
* @example
2019-10-25 20:54:33 +00:00
* import { OmniOscillator } from "tone";
* const omniOsc = new OmniOscillator().toDestination().start();
* // an square wave frequency modulated by a sawtooth
2019-07-17 16:55:34 +00:00
* omniOsc.type = "fmsquare";
* omniOsc.modulationType = "sawtooth";
*/
get type(): OmniOscillatorType {
let prefix = "";
if (["am", "fm", "fat"].some(p => this._sourceType === p)) {
prefix = this._sourceType;
}
return prefix + this._oscillator.type as OmniOscillatorType;
2019-07-17 16:55:34 +00:00
}
set type(type) {
if (type.substr(0, 2) === "fm") {
this._createNewOscillator("fm");
this._oscillator = this._oscillator as FMOscillator;
this._oscillator.type = type.substr(2) as ToneOscillatorType;
2019-07-17 16:55:34 +00:00
} else if (type.substr(0, 2) === "am") {
this._createNewOscillator("am");
this._oscillator = this._oscillator as AMOscillator;
2019-09-16 03:32:40 +00:00
this._oscillator.type = type.substr(2) as ToneOscillatorType;
2019-07-17 16:55:34 +00:00
} else if (type.substr(0, 3) === "fat") {
this._createNewOscillator("fat");
this._oscillator = this._oscillator as FatOscillator;
2019-09-16 03:32:40 +00:00
this._oscillator.type = type.substr(3) as ToneOscillatorType;
2019-07-17 16:55:34 +00:00
} else if (type === "pwm") {
this._createNewOscillator("pwm");
this._oscillator = this._oscillator as PWMOscillator;
} else if (type === "pulse") {
this._createNewOscillator("pulse");
} else {
this._createNewOscillator("oscillator");
this._oscillator = this._oscillator as Oscillator;
this._oscillator.type = (type as ToneOscillatorType);
2019-07-17 16:55:34 +00:00
}
}
/**
* The value is an empty array when the type is not "custom".
* This is not available on "pwm" and "pulse" oscillator types.
2019-10-25 20:54:33 +00:00
* See [[Oscillator.partials]]
2019-07-17 16:55:34 +00:00
*/
get partials(): number[] {
return this._oscillator.partials;
}
set partials(partials) {
if (!this._getOscType(this._oscillator, "pulse") && !this._getOscType(this._oscillator, "pwm")) {
this._oscillator.partials = partials;
}
}
get partialCount(): number {
return this._oscillator.partialCount;
}
set partialCount(partialCount) {
if (!this._getOscType(this._oscillator, "pulse") && !this._getOscType(this._oscillator, "pwm")) {
this._oscillator.partialCount = partialCount;
}
}
set(props: Partial<OmniOscillatorConstructorOptions>): this {
2019-07-17 16:55:34 +00:00
// make sure the type is set first
if (Reflect.has(props, "type") && props.type) {
this.type = props.type;
}
// then set the rest
super.set(props);
return this;
}
/**
2019-09-14 20:39:18 +00:00
* connect the oscillator to the frequency and detune signals
2019-07-17 16:55:34 +00:00
*/
private _createNewOscillator(oscType: OmniOscSourceType): void {
if (oscType !== this._sourceType) {
this._sourceType = oscType;
2019-08-10 15:51:35 +00:00
const OscConstructor = OmniOscillatorSourceMap[oscType];
2019-07-17 16:55:34 +00:00
// short delay to avoid clicks on the change
const now = this.now();
if (this._oscillator) {
const oldOsc = this._oscillator;
oldOsc.stop(now);
// dispose the old one
this.context.setTimeout(() => oldOsc.dispose(), this.blockTime);
}
2019-08-10 15:51:35 +00:00
this._oscillator = new OscConstructor({
2019-09-16 03:32:40 +00:00
context: this.context,
2019-07-17 16:55:34 +00:00
});
this.frequency.connect(this._oscillator.frequency);
this.detune.connect(this._oscillator.detune);
this._oscillator.connect(this.output);
2019-08-10 15:51:35 +00:00
this._oscillator.onstop = () => this.onstop(this);
2019-07-17 16:55:34 +00:00
if (this.state === "started") {
this._oscillator.start(now);
}
}
}
get phase(): Degrees {
return this._oscillator.phase;
}
set phase(phase) {
this._oscillator.phase = phase;
}
/**
* The source type of the oscillator.
* @example
2019-10-25 20:54:33 +00:00
* import { OmniOscillator } from "tone";
* const omniOsc = new OmniOscillator(440, "fmsquare");
* console.log(omniOsc.sourceType); // 'fm'
2019-07-17 16:55:34 +00:00
*/
get sourceType(): OmniOscSourceType {
return this._sourceType;
}
set sourceType(sType) {
// the basetype defaults to sine
let baseType = "sine";
if (this._oscillator.type !== "pwm" && this._oscillator.type !== "pulse") {
baseType = this._oscillator.type;
}
// set the type
if (sType === "fm") {
this.type = "fm" + baseType as OmniOscillatorType;
2019-07-17 16:55:34 +00:00
} else if (sType === "am") {
this.type = "am" + baseType as OmniOscillatorType;
2019-07-17 16:55:34 +00:00
} else if (sType === "fat") {
this.type = "fat" + baseType as OmniOscillatorType;
2019-07-17 16:55:34 +00:00
} else if (sType === "oscillator") {
this.type = baseType as OmniOscillatorType;
2019-07-17 16:55:34 +00:00
} else if (sType === "pulse") {
this.type = "pulse";
} else if (sType === "pwm") {
this.type = "pwm";
}
}
private _getOscType<SourceType extends OmniOscSourceType>(
osc: AnyOscillator,
sourceType: SourceType,
): osc is OmniOscillatorSource[SourceType] {
return osc instanceof OmniOscillatorSourceMap[sourceType];
}
/**
2019-10-25 20:54:33 +00:00
* The base type of the oscillator. See [[Oscillator.baseType]]
2019-07-17 16:55:34 +00:00
* @example
2019-10-25 20:54:33 +00:00
* import { OmniOscillator } from "tone";
* const omniOsc = new OmniOscillator(440, "fmsquare4");
* omniOsc.sourceType; // 'fm'
* omniOsc.baseType; // 'square'
* omniOsc.partialCount; // 4
2019-07-17 16:55:34 +00:00
*/
get baseType(): OscillatorType | "pwm" | "pulse" {
2019-07-17 16:55:34 +00:00
return this._oscillator.baseType;
}
set baseType(baseType) {
if (!this._getOscType(this._oscillator, "pulse") &&
!this._getOscType(this._oscillator, "pwm") &&
baseType !== "pulse" && baseType !== "pwm") {
this._oscillator.baseType = baseType;
}
}
/**
* The width of the oscillator when sourceType === "pulse".
2019-10-25 20:54:33 +00:00
* See [[PWMOscillator.width]]
2019-07-17 16:55:34 +00:00
* @example
2019-10-25 20:54:33 +00:00
* import { OmniOscillator } from "tone";
* const omniOsc = new OmniOscillator(440, "pulse");
* // can access the width attribute only if type === "pulse"
2019-07-17 16:55:34 +00:00
* omniOsc.width.value = 0.2;
*/
get width(): IsPulseOscillator<OscType, Signal<"audioRange">> {
2019-07-17 16:55:34 +00:00
if (this._getOscType(this._oscillator, "pulse")) {
return this._oscillator.width as IsPulseOscillator<OscType, Signal<"audioRange">> ;
2019-07-17 16:55:34 +00:00
} else {
return undefined as IsPulseOscillator<OscType, Signal<"audioRange">> ;
2019-07-17 16:55:34 +00:00
}
}
/**
2019-10-25 20:54:33 +00:00
* The number of detuned oscillators when sourceType === "fat".
* See [[FatOscillator.count]]
2019-07-17 16:55:34 +00:00
*/
get count(): IsFatOscillator<OscType, number> {
if (this._getOscType(this._oscillator, "fat")) {
return this._oscillator.count as IsFatOscillator<OscType, number>;
} else {
return undefined as IsFatOscillator<OscType, number>;
}
}
set count(count) {
if (this._getOscType(this._oscillator, "fat") && isNumber(count)) {
this._oscillator.count = count;
}
}
/**
2019-10-25 20:54:33 +00:00
* The detune spread between the oscillators when sourceType === "fat".
* See [[FatOscillator.count]]
2019-07-17 16:55:34 +00:00
*/
get spread(): IsFatOscillator<OscType, Cents> {
if (this._getOscType(this._oscillator, "fat")) {
return this._oscillator.spread as IsFatOscillator<OscType, Cents>;
} else {
return undefined as IsFatOscillator<OscType, Cents>;
}
}
set spread(spread) {
if (this._getOscType(this._oscillator, "fat") && isNumber(spread)) {
this._oscillator.spread = spread;
}
}
/**
2019-10-25 20:54:33 +00:00
* The type of the modulator oscillator. Only if the oscillator is set to "am" or "fm" types.
* See [[AMOscillator]] or [[FMOscillator]]
2019-07-17 16:55:34 +00:00
*/
get modulationType(): IsAmOrFmOscillator<OscType, ToneOscillatorType> {
if (this._getOscType(this._oscillator, "fm") || this._getOscType(this._oscillator, "am")) {
return this._oscillator.modulationType as IsAmOrFmOscillator<OscType, ToneOscillatorType>;
} else {
return undefined as IsAmOrFmOscillator<OscType, ToneOscillatorType>;
}
}
set modulationType(mType) {
if ((this._getOscType(this._oscillator, "fm") || this._getOscType(this._oscillator, "am")) && isString(mType)) {
this._oscillator.modulationType = mType;
}
}
/**
2019-10-25 20:54:33 +00:00
* The modulation index when the sourceType === "fm"
* See [[FMOscillator]].
2019-07-17 16:55:34 +00:00
*/
get modulationIndex(): IsFMOscillator<OscType, Signal<"positive">> {
2019-07-17 16:55:34 +00:00
if (this._getOscType(this._oscillator, "fm")) {
return this._oscillator.modulationIndex as IsFMOscillator<OscType, Signal<"positive">>;
2019-07-17 16:55:34 +00:00
} else {
return undefined as IsFMOscillator<OscType, Signal<"positive">>;
2019-07-17 16:55:34 +00:00
}
}
/**
2019-09-14 20:39:18 +00:00
* Harmonicity is the frequency ratio between the carrier and the modulator oscillators.
2019-10-25 20:54:33 +00:00
* See [[AMOscillator]] or [[FMOscillator]]
2019-07-17 16:55:34 +00:00
*/
get harmonicity(): IsAmOrFmOscillator<OscType, Signal<"positive">> {
2019-07-17 16:55:34 +00:00
if (this._getOscType(this._oscillator, "fm") || this._getOscType(this._oscillator, "am")) {
return this._oscillator.harmonicity as IsAmOrFmOscillator<OscType, Signal<"positive">>;
2019-07-17 16:55:34 +00:00
} else {
return undefined as IsAmOrFmOscillator<OscType, Signal<"positive">>;
2019-07-17 16:55:34 +00:00
}
}
/**
2019-10-25 20:54:33 +00:00
* The modulationFrequency Signal of the oscillator when sourceType === "pwm"
* see [[PWMOscillator]]
2019-09-24 21:21:59 +00:00
* @min 0.1
* @max 5
2019-07-17 16:55:34 +00:00
*/
get modulationFrequency(): IsPWMOscillator<OscType, Signal<"frequency">> {
2019-07-17 16:55:34 +00:00
if (this._getOscType(this._oscillator, "pwm")) {
return this._oscillator.modulationFrequency as IsPWMOscillator<OscType, Signal<"frequency">>;
2019-07-17 16:55:34 +00:00
} else {
return undefined as IsPWMOscillator<OscType, Signal<"frequency">>;
2019-07-17 16:55:34 +00:00
}
}
2019-11-17 18:09:19 +00:00
async asArray(length = 1024): Promise<Float32Array> {
return generateWaveform(this, length);
}
2019-07-17 16:55:34 +00:00
dispose(): this {
super.dispose();
this.detune.dispose();
this.frequency.dispose();
this._oscillator.dispose();
return this;
}
}