UNPKG

5.71 kBPlain TextView Raw
1import { Monophonic, MonophonicOptions } from "./Monophonic.js";
2import { MonoSynth, MonoSynthOptions } from "./MonoSynth.js";
3import { Signal } from "../signal/Signal.js";
4import { readOnly, RecursivePartial } from "../core/util/Interface.js";
5import { LFO } from "../source/oscillator/LFO.js";
6import { Gain } from "../core/context/Gain.js";
7import { Multiply } from "../signal/Multiply.js";
8import {
9 Frequency,
10 NormalRange,
11 Positive,
12 Seconds,
13 Time,
14} from "../core/type/Units.js";
15import {
16 deepMerge,
17 omitFromObject,
18 optionsFromArguments,
19} from "../core/util/Defaults.js";
20import { Param } from "../core/context/Param.js";
21
22export interface DuoSynthOptions extends MonophonicOptions {
23 voice0: Omit<MonoSynthOptions, keyof MonophonicOptions>;
24 voice1: Omit<MonoSynthOptions, keyof MonophonicOptions>;
25 harmonicity: Positive;
26 vibratoRate: Frequency;
27 vibratoAmount: Positive;
28}
29
30/**
31 * DuoSynth is a monophonic synth composed of two {@link MonoSynth}s run in parallel with control over the
32 * frequency ratio between the two voices and vibrato effect.
33 * @example
34 * const duoSynth = new Tone.DuoSynth().toDestination();
35 * duoSynth.triggerAttackRelease("C4", "2n");
36 * @category Instrument
37 */
38export class DuoSynth extends Monophonic<DuoSynthOptions> {
39 readonly name: string = "DuoSynth";
40
41 readonly frequency: Signal<"frequency">;
42 readonly detune: Signal<"cents">;
43
44 /**
45 * the first voice
46 */
47 readonly voice0: MonoSynth;
48
49 /**
50 * the second voice
51 */
52 readonly voice1: MonoSynth;
53
54 /**
55 * The amount of vibrato
56 */
57 public vibratoAmount: Param<"normalRange">;
58
59 /**
60 * the vibrato frequency
61 */
62 public vibratoRate: Signal<"frequency">;
63
64 /**
65 * Harmonicity is the ratio between the two voices. A harmonicity of
66 * 1 is no change. Harmonicity = 2 means a change of an octave.
67 * @example
68 * const duoSynth = new Tone.DuoSynth().toDestination();
69 * duoSynth.triggerAttackRelease("C4", "2n");
70 * // pitch voice1 an octave below voice0
71 * duoSynth.harmonicity.value = 0.5;
72 */
73 public harmonicity: Signal<"positive">;
74
75 /**
76 * The vibrato LFO.
77 */
78 private _vibrato: LFO;
79
80 /**
81 * the vibrato gain
82 */
83 private _vibratoGain: Gain<"normalRange">;
84
85 constructor(options?: RecursivePartial<DuoSynthOptions>);
86 constructor() {
87 const options = optionsFromArguments(DuoSynth.getDefaults(), arguments);
88 super(options);
89
90 this.voice0 = new MonoSynth(
91 Object.assign(options.voice0, {
92 context: this.context,
93 onsilence: () => this.onsilence(this),
94 })
95 );
96 this.voice1 = new MonoSynth(
97 Object.assign(options.voice1, {
98 context: this.context,
99 })
100 );
101
102 this.harmonicity = new Multiply({
103 context: this.context,
104 units: "positive",
105 value: options.harmonicity,
106 });
107
108 this._vibrato = new LFO({
109 frequency: options.vibratoRate,
110 context: this.context,
111 min: -50,
112 max: 50,
113 });
114 // start the vibrato immediately
115 this._vibrato.start();
116 this.vibratoRate = this._vibrato.frequency;
117 this._vibratoGain = new Gain({
118 context: this.context,
119 units: "normalRange",
120 gain: options.vibratoAmount,
121 });
122 this.vibratoAmount = this._vibratoGain.gain;
123
124 this.frequency = new Signal({
125 context: this.context,
126 units: "frequency",
127 value: 440,
128 });
129 this.detune = new Signal({
130 context: this.context,
131 units: "cents",
132 value: options.detune,
133 });
134
135 // control the two voices frequency
136 this.frequency.connect(this.voice0.frequency);
137 this.frequency.chain(this.harmonicity, this.voice1.frequency);
138
139 this._vibrato.connect(this._vibratoGain);
140 this._vibratoGain.fan(this.voice0.detune, this.voice1.detune);
141
142 this.detune.fan(this.voice0.detune, this.voice1.detune);
143
144 this.voice0.connect(this.output);
145 this.voice1.connect(this.output);
146
147 readOnly(this, [
148 "voice0",
149 "voice1",
150 "frequency",
151 "vibratoAmount",
152 "vibratoRate",
153 ]);
154 }
155
156 getLevelAtTime(time: Time): NormalRange {
157 time = this.toSeconds(time);
158 return (
159 this.voice0.envelope.getValueAtTime(time) +
160 this.voice1.envelope.getValueAtTime(time)
161 );
162 }
163
164 static getDefaults(): DuoSynthOptions {
165 return deepMerge(Monophonic.getDefaults(), {
166 vibratoAmount: 0.5,
167 vibratoRate: 5,
168 harmonicity: 1.5,
169 voice0: deepMerge(
170 omitFromObject(
171 MonoSynth.getDefaults(),
172 Object.keys(Monophonic.getDefaults())
173 ),
174 {
175 filterEnvelope: {
176 attack: 0.01,
177 decay: 0.0,
178 sustain: 1,
179 release: 0.5,
180 },
181 envelope: {
182 attack: 0.01,
183 decay: 0.0,
184 sustain: 1,
185 release: 0.5,
186 },
187 }
188 ),
189 voice1: deepMerge(
190 omitFromObject(
191 MonoSynth.getDefaults(),
192 Object.keys(Monophonic.getDefaults())
193 ),
194 {
195 filterEnvelope: {
196 attack: 0.01,
197 decay: 0.0,
198 sustain: 1,
199 release: 0.5,
200 },
201 envelope: {
202 attack: 0.01,
203 decay: 0.0,
204 sustain: 1,
205 release: 0.5,
206 },
207 }
208 ),
209 }) as unknown as DuoSynthOptions;
210 }
211 /**
212 * Trigger the attack portion of the note
213 */
214 protected _triggerEnvelopeAttack(time: Seconds, velocity: number): void {
215 // @ts-ignore
216 this.voice0._triggerEnvelopeAttack(time, velocity);
217 // @ts-ignore
218 this.voice1._triggerEnvelopeAttack(time, velocity);
219 }
220
221 /**
222 * Trigger the release portion of the note
223 */
224 protected _triggerEnvelopeRelease(time: Seconds) {
225 // @ts-ignore
226 this.voice0._triggerEnvelopeRelease(time);
227 // @ts-ignore
228 this.voice1._triggerEnvelopeRelease(time);
229 return this;
230 }
231
232 dispose(): this {
233 super.dispose();
234 this.voice0.dispose();
235 this.voice1.dispose();
236 this.frequency.dispose();
237 this.detune.dispose();
238 this._vibrato.dispose();
239 this.vibratoRate.dispose();
240 this._vibratoGain.dispose();
241 this.harmonicity.dispose();
242 return this;
243 }
244}