1 | import { Envelope } from "../component/envelope/Envelope.js";
|
2 | import { Filter } from "../component/filter/Filter.js";
|
3 | import { Gain } from "../core/context/Gain.js";
|
4 | import { ToneAudioNode, } from "../core/context/ToneAudioNode.js";
|
5 | import { deepMerge, omitFromObject, optionsFromArguments, } from "../core/util/Defaults.js";
|
6 | import { noOp } from "../core/util/Interface.js";
|
7 | import { Multiply } from "../signal/Multiply.js";
|
8 | import { Scale } from "../signal/Scale.js";
|
9 | import { Signal } from "../signal/Signal.js";
|
10 | import { FMOscillator } from "../source/oscillator/FMOscillator.js";
|
11 | import { Monophonic } from "./Monophonic.js";
|
12 |
|
13 |
|
14 |
|
15 |
|
16 | const inharmRatios = [1.0, 1.483, 1.932, 2.546, 2.63, 3.897];
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 | export class MetalSynth extends Monophonic {
|
24 | constructor() {
|
25 | const options = optionsFromArguments(MetalSynth.getDefaults(), arguments);
|
26 | super(options);
|
27 | this.name = "MetalSynth";
|
28 | |
29 |
|
30 |
|
31 | this._oscillators = [];
|
32 | |
33 |
|
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 |
|
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 |
|
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 |
|
108 |
|
109 |
|
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 |
|
125 |
|
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 |
|
138 |
|
139 |
|
140 |
|
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 |
|
150 |
|
151 |
|
152 |
|
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 |
|
162 |
|
163 |
|
164 |
|
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 |
|
175 |
|
176 |
|
177 |
|
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 |
|
\ | No newline at end of file |