2019-07-15 13:46:10 +00:00
|
|
|
import { Gain } from "../../core/context/Gain";
|
2019-12-14 22:07:13 +00:00
|
|
|
import { Degrees, Frequency, Seconds } from "../../core/type/Units";
|
2019-07-15 13:46:10 +00:00
|
|
|
import { optionsFromArguments } from "../../core/util/Defaults";
|
|
|
|
import { readOnly } from "../../core/util/Interface";
|
|
|
|
import { AudioToGain } from "../../signal/AudioToGain";
|
|
|
|
import { Multiply } from "../../signal/Multiply";
|
|
|
|
import { Signal } from "../../signal/Signal";
|
|
|
|
import { Source } from "../Source";
|
2019-07-19 16:32:17 +00:00
|
|
|
import { Oscillator } from "./Oscillator";
|
|
|
|
import { AMConstructorOptions, AMOscillatorOptions,
|
2019-10-08 23:22:59 +00:00
|
|
|
generateWaveform, NonCustomOscillatorType,
|
|
|
|
ToneOscillatorInterface,
|
2019-07-19 16:32:17 +00:00
|
|
|
ToneOscillatorType } from "./OscillatorInterface";
|
2019-07-15 13:46:10 +00:00
|
|
|
|
2019-09-04 22:34:42 +00:00
|
|
|
export { AMOscillatorOptions } from "./OscillatorInterface";
|
|
|
|
|
2019-07-15 13:46:10 +00:00
|
|
|
/**
|
|
|
|
* An amplitude modulated oscillator node. It is implemented with
|
|
|
|
* two oscillators, one which modulators the other's amplitude
|
|
|
|
* through a gain node.
|
2019-07-17 21:40:47 +00:00
|
|
|
* ```
|
2019-07-15 13:46:10 +00:00
|
|
|
* +-------------+ +----------+
|
|
|
|
* | Carrier Osc +>------> GainNode |
|
|
|
|
* +-------------+ | +--->Output
|
|
|
|
* +---> gain |
|
|
|
|
* +---------------+ | +----------+
|
|
|
|
* | Modulator Osc +>---+
|
|
|
|
* +---------------+
|
2019-07-17 21:40:47 +00:00
|
|
|
* ```
|
2019-07-15 13:46:10 +00:00
|
|
|
*
|
|
|
|
* @example
|
2019-10-25 20:54:33 +00:00
|
|
|
* import { AMOscillator } from "tone";
|
2019-10-23 03:04:52 +00:00
|
|
|
* // a sine oscillator amplitude-modulated by a square wave
|
2019-11-16 22:13:26 +00:00
|
|
|
* const amOsc = new AMOscillator("Ab3", "sine", "square").toDestination().start().stop("+5");
|
2019-10-29 18:51:29 +00:00
|
|
|
* // schedule a series of notes
|
|
|
|
* amOsc.frequency.setValueAtTime("F3", "+0.25");
|
|
|
|
* amOsc.frequency.setValueAtTime("C4", "+0.5");
|
|
|
|
* amOsc.frequency.setValueAtTime("Bb3", "+1");
|
|
|
|
* // schedule harmonicity changes along with those notes
|
|
|
|
* amOsc.harmonicity.setValueAtTime(0.5, "+0.25");
|
2019-11-16 22:13:26 +00:00
|
|
|
* amOsc.harmonicity.setValueAtTime(1, "+1");
|
|
|
|
* amOsc.harmonicity.linearRampTo(1.1, 2, "+1");
|
2019-10-29 18:51:29 +00:00
|
|
|
* // fade it out all the way at the end
|
2019-11-16 22:13:26 +00:00
|
|
|
* amOsc.volume.exponentialRampTo(-Infinity, 3, "+2,");
|
2019-09-16 14:15:23 +00:00
|
|
|
* @category Source
|
2019-07-15 13:46:10 +00:00
|
|
|
*/
|
2019-07-19 16:32:17 +00:00
|
|
|
export class AMOscillator extends Source<AMOscillatorOptions> implements ToneOscillatorInterface {
|
2019-07-15 13:46:10 +00:00
|
|
|
|
2019-09-04 23:18:44 +00:00
|
|
|
readonly name: string = "AMOscillator";
|
2019-07-15 13:46:10 +00:00
|
|
|
|
|
|
|
/**
|
2019-09-14 20:39:18 +00:00
|
|
|
* The carrier oscillator
|
2019-07-15 13:46:10 +00:00
|
|
|
*/
|
2019-08-08 18:15:56 +00:00
|
|
|
private _carrier: Oscillator;
|
2019-07-15 13:46:10 +00:00
|
|
|
|
2019-10-28 15:37:53 +00:00
|
|
|
readonly frequency: Signal<"frequency">;
|
|
|
|
readonly detune: Signal<"cents">;
|
2019-07-15 13:46:10 +00:00
|
|
|
|
|
|
|
/**
|
2019-09-14 20:39:18 +00:00
|
|
|
* The modulating oscillator
|
2019-07-15 13:46:10 +00:00
|
|
|
*/
|
2019-08-08 18:15:56 +00:00
|
|
|
private _modulator: Oscillator;
|
2019-07-15 13:46:10 +00:00
|
|
|
|
|
|
|
/**
|
2019-09-14 20:39:18 +00:00
|
|
|
* convert the -1,1 output to 0,1
|
2019-07-15 13:46:10 +00:00
|
|
|
*/
|
|
|
|
private _modulationScale = new AudioToGain({ context: this.context });
|
|
|
|
|
|
|
|
/**
|
2019-09-14 20:39:18 +00:00
|
|
|
* Harmonicity is the frequency ratio between the carrier and the modulator oscillators.
|
|
|
|
* A harmonicity of 1 gives both oscillators the same frequency.
|
|
|
|
* Harmonicity = 2 means a change of an octave.
|
2019-08-30 16:06:38 +00:00
|
|
|
* @example
|
2019-10-29 18:30:25 +00:00
|
|
|
* import { AMOscillator, Transport } from "tone";
|
2019-10-25 20:54:33 +00:00
|
|
|
* const amOsc = new AMOscillator("D2").toDestination().start();
|
2019-10-29 18:30:25 +00:00
|
|
|
* Transport.scheduleRepeat(time => {
|
|
|
|
* amOsc.harmonicity.setValueAtTime(1, time);
|
|
|
|
* amOsc.harmonicity.setValueAtTime(0.5, time + 0.5);
|
|
|
|
* amOsc.harmonicity.setValueAtTime(1.5, time + 1);
|
|
|
|
* amOsc.harmonicity.setValueAtTime(1, time + 2);
|
|
|
|
* amOsc.harmonicity.linearRampToValueAtTime(2, time + 4);
|
|
|
|
* }, 4);
|
|
|
|
* Transport.start();
|
2019-07-15 13:46:10 +00:00
|
|
|
*/
|
2019-10-28 15:37:53 +00:00
|
|
|
readonly harmonicity: Signal<"positive">;
|
2019-07-15 13:46:10 +00:00
|
|
|
|
|
|
|
/**
|
2019-09-14 20:39:18 +00:00
|
|
|
* the node where the modulation happens
|
2019-07-15 13:46:10 +00:00
|
|
|
*/
|
|
|
|
private _modulationNode = new Gain({
|
|
|
|
context: this.context,
|
|
|
|
});
|
|
|
|
|
2019-08-27 15:47:52 +00:00
|
|
|
/**
|
|
|
|
* @param frequency The starting frequency of the oscillator.
|
|
|
|
* @param type The type of the carrier oscillator.
|
|
|
|
* @param modulationType The type of the modulator oscillator.
|
|
|
|
*/
|
2019-07-15 13:46:10 +00:00
|
|
|
constructor(frequency?: Frequency, type?: ToneOscillatorType, modulationType?: ToneOscillatorType);
|
2019-08-27 15:47:52 +00:00
|
|
|
constructor(options?: Partial<AMConstructorOptions>);
|
2019-07-15 13:46:10 +00:00
|
|
|
constructor() {
|
|
|
|
|
|
|
|
super(optionsFromArguments(AMOscillator.getDefaults(), arguments, ["frequency", "type", "modulationType"]));
|
|
|
|
const options = optionsFromArguments(AMOscillator.getDefaults(), arguments, ["frequency", "type", "modulationType"]);
|
|
|
|
|
2019-09-16 03:32:40 +00:00
|
|
|
this._carrier = new Oscillator({
|
|
|
|
context: this.context,
|
2019-08-08 18:15:56 +00:00
|
|
|
detune: options.detune,
|
|
|
|
frequency: options.frequency,
|
2019-08-10 15:51:35 +00:00
|
|
|
onstop: () => this.onstop(this),
|
2019-08-08 18:15:56 +00:00
|
|
|
phase: options.phase,
|
|
|
|
type: options.type,
|
|
|
|
} as OscillatorOptions);
|
|
|
|
this.frequency = this._carrier.frequency,
|
|
|
|
this.detune = this._carrier.detune;
|
|
|
|
|
|
|
|
this._modulator = new Oscillator({
|
2019-09-16 03:32:40 +00:00
|
|
|
context: this.context,
|
2019-08-08 18:15:56 +00:00
|
|
|
phase: options.phase,
|
|
|
|
type: options.modulationType,
|
|
|
|
} as OscillatorOptions);
|
|
|
|
|
|
|
|
this.harmonicity = new Multiply({
|
|
|
|
context: this.context,
|
|
|
|
units: "positive",
|
|
|
|
value: options.harmonicity,
|
|
|
|
});
|
2019-07-15 13:46:10 +00:00
|
|
|
|
|
|
|
// connections
|
|
|
|
this.frequency.chain(this.harmonicity, this._modulator.frequency);
|
|
|
|
this._modulator.chain(this._modulationScale, this._modulationNode.gain);
|
|
|
|
this._carrier.chain(this._modulationNode, this.output);
|
|
|
|
|
|
|
|
readOnly(this, ["frequency", "detune", "harmonicity"]);
|
|
|
|
}
|
|
|
|
|
|
|
|
static getDefaults(): AMOscillatorOptions {
|
|
|
|
return Object.assign(Oscillator.getDefaults(), {
|
|
|
|
harmonicity: 1,
|
2019-07-19 16:32:17 +00:00
|
|
|
modulationType: "square" as NonCustomOscillatorType,
|
2019-07-15 13:46:10 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-09-14 20:39:18 +00:00
|
|
|
* start the oscillator
|
2019-07-15 13:46:10 +00:00
|
|
|
*/
|
|
|
|
protected _start(time: Seconds): void {
|
|
|
|
this._modulator.start(time);
|
|
|
|
this._carrier.start(time);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-09-14 20:39:18 +00:00
|
|
|
* stop the oscillator
|
2019-07-15 13:46:10 +00:00
|
|
|
*/
|
|
|
|
protected _stop(time: Seconds): void {
|
|
|
|
this._modulator.stop(time);
|
|
|
|
this._carrier.stop(time);
|
|
|
|
}
|
|
|
|
|
2019-12-14 21:09:24 +00:00
|
|
|
protected _restart(time: Seconds): void {
|
2019-07-15 13:46:10 +00:00
|
|
|
this._modulator.restart(time);
|
|
|
|
this._carrier.restart(time);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The type of the carrier oscillator
|
|
|
|
*/
|
|
|
|
get type(): ToneOscillatorType {
|
|
|
|
return this._carrier.type;
|
|
|
|
}
|
|
|
|
set type(type: ToneOscillatorType) {
|
|
|
|
this._carrier.type = type;
|
|
|
|
}
|
|
|
|
|
|
|
|
get baseType(): OscillatorType {
|
|
|
|
return this._carrier.baseType;
|
|
|
|
}
|
|
|
|
set baseType(baseType: OscillatorType) {
|
|
|
|
this._carrier.baseType = baseType;
|
|
|
|
}
|
|
|
|
|
|
|
|
get partialCount(): number {
|
|
|
|
return this._carrier.partialCount;
|
|
|
|
}
|
|
|
|
set partialCount(partialCount: number) {
|
|
|
|
this._carrier.partialCount = partialCount;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The type of the modulator oscillator
|
|
|
|
*/
|
|
|
|
get modulationType(): ToneOscillatorType {
|
|
|
|
return this._modulator.type;
|
|
|
|
}
|
|
|
|
set modulationType(type: ToneOscillatorType) {
|
|
|
|
this._modulator.type = type;
|
|
|
|
}
|
|
|
|
|
|
|
|
get phase(): Degrees {
|
|
|
|
return this._carrier.phase;
|
|
|
|
}
|
|
|
|
set phase(phase: Degrees) {
|
|
|
|
this._carrier.phase = phase;
|
|
|
|
this._modulator.phase = phase;
|
|
|
|
}
|
|
|
|
|
|
|
|
get partials(): number[] {
|
|
|
|
return this._carrier.partials;
|
|
|
|
}
|
|
|
|
set partials(partials: number[]) {
|
|
|
|
this._carrier.partials = partials;
|
|
|
|
}
|
|
|
|
|
2019-11-17 18:09:19 +00:00
|
|
|
async asArray(length = 1024): Promise<Float32Array> {
|
2019-10-08 23:22:59 +00:00
|
|
|
return generateWaveform(this, length);
|
|
|
|
}
|
|
|
|
|
2019-07-15 13:46:10 +00:00
|
|
|
/**
|
2019-09-14 20:39:18 +00:00
|
|
|
* Clean up.
|
2019-07-15 13:46:10 +00:00
|
|
|
*/
|
|
|
|
dispose(): this {
|
|
|
|
super.dispose();
|
|
|
|
this.frequency.dispose();
|
|
|
|
this.detune.dispose();
|
|
|
|
this.harmonicity.dispose();
|
|
|
|
this._carrier.dispose();
|
|
|
|
this._modulator.dispose();
|
|
|
|
this._modulationNode.dispose();
|
|
|
|
this._modulationScale.dispose();
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
}
|