UNPKG

7.03 kBJavaScriptView Raw
1import { Envelope } from "../component/envelope/Envelope.js";
2import { Filter } from "../component/filter/Filter.js";
3import { Gain } from "../core/context/Gain.js";
4import { ToneAudioNode, } from "../core/context/ToneAudioNode.js";
5import { deepMerge, omitFromObject, optionsFromArguments, } from "../core/util/Defaults.js";
6import { noOp } from "../core/util/Interface.js";
7import { Multiply } from "../signal/Multiply.js";
8import { Scale } from "../signal/Scale.js";
9import { Signal } from "../signal/Signal.js";
10import { FMOscillator } from "../source/oscillator/FMOscillator.js";
11import { Monophonic } from "./Monophonic.js";
12/**
13 * Inharmonic ratio of frequencies based on the Roland TR-808
14 * Taken from https://ccrma.stanford.edu/papers/tr-808-cymbal-physically-informed-circuit-bendable-digital-model
15 */
16const inharmRatios = [1.0, 1.483, 1.932, 2.546, 2.63, 3.897];
17/**
18 * A highly inharmonic and spectrally complex source with a highpass filter
19 * and amplitude envelope which is good for making metallophone sounds.
20 * Based on CymbalSynth by [@polyrhythmatic](https://github.com/polyrhythmatic).
21 * @category Instrument
22 */
23export class MetalSynth extends Monophonic {
24 constructor() {
25 const options = optionsFromArguments(MetalSynth.getDefaults(), arguments);
26 super(options);
27 this.name = "MetalSynth";
28 /**
29 * The array of FMOscillators
30 */
31 this._oscillators = [];
32 /**
33 * The frequency multipliers
34 */
35 this._freqMultipliers = [];
36 this.detune = new Signal({
37 context: this.context,
38 units: "cents",
39 value: options.detune,
40 });
41 this.frequency = new Signal({
42 context: this.context,
43 units: "frequency",
44 });
45 this._amplitude = new Gain({
46 context: this.context,
47 gain: 0,
48 }).connect(this.output);
49 this._highpass = new Filter({
50 // Q: -3.0102999566398125,
51 Q: 0,
52 context: this.context,
53 type: "highpass",
54 }).connect(this._amplitude);
55 for (let i = 0; i < inharmRatios.length; i++) {
56 const osc = new FMOscillator({
57 context: this.context,
58 harmonicity: options.harmonicity,
59 modulationIndex: options.modulationIndex,
60 modulationType: "square",
61 onstop: i === 0 ? () => this.onsilence(this) : noOp,
62 type: "square",
63 });
64 osc.connect(this._highpass);
65 this._oscillators[i] = osc;
66 const mult = new Multiply({
67 context: this.context,
68 value: inharmRatios[i],
69 });
70 this._freqMultipliers[i] = mult;
71 this.frequency.chain(mult, osc.frequency);
72 this.detune.connect(osc.detune);
73 }
74 this._filterFreqScaler = new Scale({
75 context: this.context,
76 max: 7000,
77 min: this.toFrequency(options.resonance),
78 });
79 this.envelope = new Envelope({
80 attack: options.envelope.attack,
81 attackCurve: "linear",
82 context: this.context,
83 decay: options.envelope.decay,
84 release: options.envelope.release,
85 sustain: 0,
86 });
87 this.envelope.chain(this._filterFreqScaler, this._highpass.frequency);
88 this.envelope.connect(this._amplitude.gain);
89 // set the octaves
90 this._octaves = options.octaves;
91 this.octaves = options.octaves;
92 }
93 static getDefaults() {
94 return deepMerge(Monophonic.getDefaults(), {
95 envelope: Object.assign(omitFromObject(Envelope.getDefaults(), Object.keys(ToneAudioNode.getDefaults())), {
96 attack: 0.001,
97 decay: 1.4,
98 release: 0.2,
99 }),
100 harmonicity: 5.1,
101 modulationIndex: 32,
102 octaves: 1.5,
103 resonance: 4000,
104 });
105 }
106 /**
107 * Trigger the attack.
108 * @param time When the attack should be triggered.
109 * @param velocity The velocity that the envelope should be triggered at.
110 */
111 _triggerEnvelopeAttack(time, velocity = 1) {
112 this.envelope.triggerAttack(time, velocity);
113 this._oscillators.forEach((osc) => osc.start(time));
114 if (this.envelope.sustain === 0) {
115 this._oscillators.forEach((osc) => {
116 osc.stop(time +
117 this.toSeconds(this.envelope.attack) +
118 this.toSeconds(this.envelope.decay));
119 });
120 }
121 return this;
122 }
123 /**
124 * Trigger the release of the envelope.
125 * @param time When the release should be triggered.
126 */
127 _triggerEnvelopeRelease(time) {
128 this.envelope.triggerRelease(time);
129 this._oscillators.forEach((osc) => osc.stop(time + this.toSeconds(this.envelope.release)));
130 return this;
131 }
132 getLevelAtTime(time) {
133 time = this.toSeconds(time);
134 return this.envelope.getValueAtTime(time);
135 }
136 /**
137 * The modulationIndex of the oscillators which make up the source.
138 * see {@link FMOscillator.modulationIndex}
139 * @min 1
140 * @max 100
141 */
142 get modulationIndex() {
143 return this._oscillators[0].modulationIndex.value;
144 }
145 set modulationIndex(val) {
146 this._oscillators.forEach((osc) => (osc.modulationIndex.value = val));
147 }
148 /**
149 * The harmonicity of the oscillators which make up the source.
150 * see Tone.FMOscillator.harmonicity
151 * @min 0.1
152 * @max 10
153 */
154 get harmonicity() {
155 return this._oscillators[0].harmonicity.value;
156 }
157 set harmonicity(val) {
158 this._oscillators.forEach((osc) => (osc.harmonicity.value = val));
159 }
160 /**
161 * The lower level of the highpass filter which is attached to the envelope.
162 * This value should be between [0, 7000]
163 * @min 0
164 * @max 7000
165 */
166 get resonance() {
167 return this._filterFreqScaler.min;
168 }
169 set resonance(val) {
170 this._filterFreqScaler.min = this.toFrequency(val);
171 this.octaves = this._octaves;
172 }
173 /**
174 * The number of octaves above the "resonance" frequency
175 * that the filter ramps during the attack/decay envelope
176 * @min 0
177 * @max 8
178 */
179 get octaves() {
180 return this._octaves;
181 }
182 set octaves(val) {
183 this._octaves = val;
184 this._filterFreqScaler.max =
185 this._filterFreqScaler.min * Math.pow(2, val);
186 }
187 dispose() {
188 super.dispose();
189 this._oscillators.forEach((osc) => osc.dispose());
190 this._freqMultipliers.forEach((freqMult) => freqMult.dispose());
191 this.frequency.dispose();
192 this.detune.dispose();
193 this._filterFreqScaler.dispose();
194 this._amplitude.dispose();
195 this.envelope.dispose();
196 this._highpass.dispose();
197 return this;
198 }
199}
200//# sourceMappingURL=MetalSynth.js.map
\No newline at end of file