import { AmplitudeEnvelope } from "../component/envelope/AmplitudeEnvelope"; import { Envelope, EnvelopeOptions } from "../component/envelope/Envelope"; import { Filter, FilterOptions } from "../component/filter/Filter"; import { omitFromObject, optionsFromArguments } from "../core/util/Defaults"; import { readOnly, RecursivePartial } from "../core/util/Interface"; import { Monophonic, MonophonicOptions } from "../instrument/Monophonic"; import { OmniOscillator } from "../source/oscillator/OmniOscillator"; import { Source } from "../source/Source"; import { Synth, SynthOptions } from "./Synth"; import { FrequencyEnvelope, FrequencyEnvelopeOptions } from "../component/envelope/FrequencyEnvelope"; import { NormalRange, Seconds, Time } from "../core/type/Units"; import { Signal } from "../signal/Signal"; import { ToneAudioNode, ToneAudioNodeOptions } from "../core/context/ToneAudioNode"; import { OmniOscillatorSynthOptions } from "../source/oscillator/OscillatorInterface"; export interface MonoSynthOptions extends MonophonicOptions { oscillator: OmniOscillatorSynthOptions; envelope: Omit; filterEnvelope: Omit; filter: Omit; } /** * MonoSynth is composed of one `oscillator`, one `filter`, and two `envelopes`. * The amplitude of the Oscillator and the cutoff frequency of the * Filter are controlled by Envelopes. * * @example * import { MonoSynth } from "tone"; * const synth = new MonoSynth({ * oscillator: { * type: "square" * }, * envelope: { * attack: 0.1 * } * }).toDestination(); * synth.triggerAttackRelease("C4", "8n"); */ export class MonoSynth extends Monophonic { readonly name = "MonoSynth"; /** * The oscillator. */ readonly oscillator: OmniOscillator; /** * The frequency control. */ readonly frequency: Signal<"frequency">; /** * The detune control. */ readonly detune: Signal<"cents">; /** * The filter. */ readonly filter: Filter; /** * The filter envelope. */ readonly filterEnvelope: FrequencyEnvelope; /** * The amplitude envelope. */ readonly envelope: AmplitudeEnvelope; constructor(options?: RecursivePartial); constructor() { super(optionsFromArguments(MonoSynth.getDefaults(), arguments)); const options = optionsFromArguments(MonoSynth.getDefaults(), arguments); this.oscillator = new OmniOscillator(Object.assign(options.oscillator, { context: this.context, detune: options.detune, onstop: () => this.onsilence(this), })); this.frequency = this.oscillator.frequency; this.detune = this.oscillator.detune; this.filter = new Filter(Object.assign(options.filter, { context: this.context })); this.filterEnvelope = new FrequencyEnvelope(Object.assign(options.filterEnvelope, { context: this.context })); this.envelope = new AmplitudeEnvelope(Object.assign(options.envelope, { context: this.context })); // connect the oscillators to the output this.oscillator.chain(this.filter, this.envelope, this.output); // connect the filter envelope this.filterEnvelope.connect(this.filter.frequency); readOnly(this, ["oscillator", "frequency", "detune", "filter", "filterEnvelope", "envelope"]); } static getDefaults(): MonoSynthOptions { return Object.assign(Monophonic.getDefaults(), { envelope: Object.assign( omitFromObject(Envelope.getDefaults(), Object.keys(ToneAudioNode.getDefaults())), { attack: 0.005, decay: 0.1, release: 1, sustain: 0.9, }, ), filter: Object.assign( omitFromObject(Filter.getDefaults(), Object.keys(ToneAudioNode.getDefaults())), { Q: 1, rolloff: -12, type: "lowpass", }, ), filterEnvelope: Object.assign( omitFromObject(FrequencyEnvelope.getDefaults(), Object.keys(ToneAudioNode.getDefaults())), { attack: 0.6, baseFrequency: 200, decay: 0.2, exponent: 2, octaves: 3, release: 2, sustain: 0.5, } ), oscillator: Object.assign( omitFromObject(OmniOscillator.getDefaults(), Object.keys(Source.getDefaults())), { type: "sawtooth", }, ), }); } /** * start the attack portion of the envelope * @param time the time the attack should start * @param velocity the velocity of the note (0-1) */ protected _triggerEnvelopeAttack(time: Seconds, velocity: number = 1): void { this.envelope.triggerAttack(time, velocity); this.filterEnvelope.triggerAttack(time); this.oscillator.start(time); if (this.envelope.sustain === 0) { const computedAttack = this.toSeconds(this.envelope.attack); const computedDecay = this.toSeconds(this.envelope.decay); this.oscillator.stop(time + computedAttack + computedDecay); } } /** * start the release portion of the envelope * @param time the time the release should start */ protected _triggerEnvelopeRelease(time: Seconds): void { this.envelope.triggerRelease(time); this.filterEnvelope.triggerRelease(time); this.oscillator.stop(time + this.toSeconds(this.envelope.release)); } getLevelAtTime(time: Time): NormalRange { time = this.toSeconds(time); return this.envelope.getValueAtTime(time); } dispose(): this { super.dispose(); this.oscillator.dispose(); this.envelope.dispose(); this.filterEnvelope.dispose(); this.filter.dispose(); return this; } }