UNPKG

4.38 kBPlain TextView Raw
1import { AmplitudeEnvelope } from "../component/envelope/AmplitudeEnvelope.js";
2import { Envelope, EnvelopeOptions } from "../component/envelope/Envelope.js";
3import {
4 ToneAudioNode,
5 ToneAudioNodeOptions,
6} from "../core/context/ToneAudioNode.js";
7import { NormalRange, Seconds, Time } from "../core/type/Units.js";
8import { omitFromObject, optionsFromArguments } from "../core/util/Defaults.js";
9import { readOnly } from "../core/util/Interface.js";
10import { RecursivePartial } from "../core/util/Interface.js";
11import { Signal } from "../signal/Signal.js";
12import { OmniOscillator } from "../source/oscillator/OmniOscillator.js";
13import {
14 OmniOscillatorOptions,
15 OmniOscillatorSynthOptions,
16} from "../source/oscillator/OscillatorInterface.js";
17import { Source } from "../source/Source.js";
18import { Monophonic, MonophonicOptions } from "./Monophonic.js";
19
20export interface SynthOptions extends MonophonicOptions {
21 oscillator: OmniOscillatorSynthOptions;
22 envelope: Omit<EnvelopeOptions, keyof ToneAudioNodeOptions>;
23}
24
25/**
26 * Synth is composed simply of a {@link OmniOscillator} routed through an {@link AmplitudeEnvelope}.
27 * ```
28 * +----------------+ +-------------------+
29 * | OmniOscillator +>--> AmplitudeEnvelope +>--> Output
30 * +----------------+ +-------------------+
31 * ```
32 * @example
33 * const synth = new Tone.Synth().toDestination();
34 * synth.triggerAttackRelease("C4", "8n");
35 * @category Instrument
36 */
37export class Synth<
38 Options extends SynthOptions = SynthOptions,
39> extends Monophonic<Options> {
40 readonly name: string = "Synth";
41
42 /**
43 * The oscillator.
44 */
45 readonly oscillator: OmniOscillator<any>;
46
47 /**
48 * The frequency signal
49 */
50 readonly frequency: Signal<"frequency">;
51
52 /**
53 * The detune signal
54 */
55 readonly detune: Signal<"cents">;
56
57 /**
58 * The envelope
59 */
60 readonly envelope: AmplitudeEnvelope;
61
62 /**
63 * @param options the options available for the synth.
64 */
65 constructor(options?: RecursivePartial<SynthOptions>);
66 constructor() {
67 const options = optionsFromArguments(Synth.getDefaults(), arguments);
68 super(options);
69
70 this.oscillator = new OmniOscillator(
71 Object.assign(
72 {
73 context: this.context,
74 detune: options.detune,
75 onstop: () => this.onsilence(this),
76 },
77 options.oscillator
78 )
79 );
80
81 this.frequency = this.oscillator.frequency;
82 this.detune = this.oscillator.detune;
83
84 this.envelope = new AmplitudeEnvelope(
85 Object.assign(
86 {
87 context: this.context,
88 },
89 options.envelope
90 )
91 );
92
93 // connect the oscillators to the output
94 this.oscillator.chain(this.envelope, this.output);
95 readOnly(this, ["oscillator", "frequency", "detune", "envelope"]);
96 }
97
98 static getDefaults(): SynthOptions {
99 return Object.assign(Monophonic.getDefaults(), {
100 envelope: Object.assign(
101 omitFromObject(
102 Envelope.getDefaults(),
103 Object.keys(ToneAudioNode.getDefaults())
104 ),
105 {
106 attack: 0.005,
107 decay: 0.1,
108 release: 1,
109 sustain: 0.3,
110 }
111 ),
112 oscillator: Object.assign(
113 omitFromObject(OmniOscillator.getDefaults(), [
114 ...Object.keys(Source.getDefaults()),
115 "frequency",
116 "detune",
117 ]),
118 {
119 type: "triangle",
120 }
121 ) as OmniOscillatorOptions,
122 });
123 }
124
125 /**
126 * start the attack portion of the envelope
127 * @param time the time the attack should start
128 * @param velocity the velocity of the note (0-1)
129 */
130 protected _triggerEnvelopeAttack(time: Seconds, velocity: number): void {
131 // the envelopes
132 this.envelope.triggerAttack(time, velocity);
133 this.oscillator.start(time);
134 // if there is no release portion, stop the oscillator
135 if (this.envelope.sustain === 0) {
136 const computedAttack = this.toSeconds(this.envelope.attack);
137 const computedDecay = this.toSeconds(this.envelope.decay);
138 this.oscillator.stop(time + computedAttack + computedDecay);
139 }
140 }
141
142 /**
143 * start the release portion of the envelope
144 * @param time the time the release should start
145 */
146 protected _triggerEnvelopeRelease(time: Seconds): void {
147 this.envelope.triggerRelease(time);
148 this.oscillator.stop(time + this.toSeconds(this.envelope.release));
149 }
150
151 getLevelAtTime(time: Time): NormalRange {
152 time = this.toSeconds(time);
153 return this.envelope.getValueAtTime(time);
154 }
155
156 /**
157 * clean up
158 */
159 dispose(): this {
160 super.dispose();
161 this.oscillator.dispose();
162 this.envelope.dispose();
163 return this;
164 }
165}