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 { Time } from "../core/type/Units"; import { Signal } from "../signal/Signal"; import { OutputNode, 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 })); 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(Synth.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: 6, rolloff: -24, type: "lowpass", }, ), filterEnvelope: Object.assign( omitFromObject(FrequencyEnvelope.getDefaults(), Object.keys(ToneAudioNode.getDefaults())), { attack: 0.06, 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: "square", }, ), }); } /** * 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?: Time, velocity: number = 1): void { const computedTime = this.toSeconds(time); this.envelope.triggerAttack(computedTime, velocity); this.filterEnvelope.triggerAttack(computedTime); this.oscillator.start(computedTime); if (this.envelope.sustain === 0) { const computedAttack = this.toSeconds(this.envelope.attack); const computedDecay = this.toSeconds(this.envelope.decay); this.oscillator.stop(computedTime + computedAttack + computedDecay); } } /** * start the release portion of the envelope * @param time the time the release should start */ protected _triggerEnvelopeRelease(time: Time): void { time = this.toSeconds(time); this.envelope.triggerRelease(time); this.filterEnvelope.triggerRelease(time); this.oscillator.stop(time + this.toSeconds(this.envelope.release)); } dispose(): this { super.dispose(); this.oscillator.dispose(); this.envelope.dispose(); this.filterEnvelope.dispose(); this.filter.dispose(); return this; } }