UNPKG

tone

Version:

A Web Audio framework for making interactive music in the browser.

198 lines 7.04 kB
import { Envelope } from "../component/envelope/Envelope"; import { Filter } from "../component/filter/Filter"; import { Gain } from "../core/context/Gain"; import { ToneAudioNode } from "../core/context/ToneAudioNode"; import { deepMerge, omitFromObject, optionsFromArguments } from "../core/util/Defaults"; import { noOp } from "../core/util/Interface"; import { Multiply } from "../signal/Multiply"; import { Scale } from "../signal/Scale"; import { Signal } from "../signal/Signal"; import { FMOscillator } from "../source/oscillator/FMOscillator"; import { Monophonic } from "./Monophonic"; /** * Inharmonic ratio of frequencies based on the Roland TR-808 * Taken from https://ccrma.stanford.edu/papers/tr-808-cymbal-physically-informed-circuit-bendable-digital-model */ const inharmRatios = [1.0, 1.483, 1.932, 2.546, 2.630, 3.897]; /** * A highly inharmonic and spectrally complex source with a highpass filter * and amplitude envelope which is good for making metallophone sounds. * Based on CymbalSynth by [@polyrhythmatic](https://github.com/polyrhythmatic). * Inspiration from [Sound on Sound](https://shorturl.at/rSZ12). * @category Instrument */ export class MetalSynth extends Monophonic { constructor() { super(optionsFromArguments(MetalSynth.getDefaults(), arguments)); this.name = "MetalSynth"; /** * The array of FMOscillators */ this._oscillators = []; /** * The frequency multipliers */ this._freqMultipliers = []; const options = optionsFromArguments(MetalSynth.getDefaults(), arguments); this.detune = new Signal({ context: this.context, units: "cents", value: options.detune, }); this.frequency = new Signal({ context: this.context, units: "frequency", }); this._amplitude = new Gain({ context: this.context, gain: 0, }).connect(this.output); this._highpass = new Filter({ // Q: -3.0102999566398125, Q: 0, context: this.context, type: "highpass", }).connect(this._amplitude); for (let i = 0; i < inharmRatios.length; i++) { const osc = new FMOscillator({ context: this.context, harmonicity: options.harmonicity, modulationIndex: options.modulationIndex, modulationType: "square", onstop: i === 0 ? () => this.onsilence(this) : noOp, type: "square", }); osc.connect(this._highpass); this._oscillators[i] = osc; const mult = new Multiply({ context: this.context, value: inharmRatios[i], }); this._freqMultipliers[i] = mult; this.frequency.chain(mult, osc.frequency); this.detune.connect(osc.detune); } this._filterFreqScaler = new Scale({ context: this.context, max: 7000, min: this.toFrequency(options.resonance), }); this.envelope = new Envelope({ attack: options.envelope.attack, attackCurve: "linear", context: this.context, decay: options.envelope.decay, release: options.envelope.release, sustain: 0, }); this.envelope.chain(this._filterFreqScaler, this._highpass.frequency); this.envelope.connect(this._amplitude.gain); // set the octaves this._octaves = options.octaves; this.octaves = options.octaves; } static getDefaults() { return deepMerge(Monophonic.getDefaults(), { envelope: Object.assign(omitFromObject(Envelope.getDefaults(), Object.keys(ToneAudioNode.getDefaults())), { attack: 0.001, decay: 1.4, release: 0.2, }), harmonicity: 5.1, modulationIndex: 32, octaves: 1.5, resonance: 4000, }); } /** * Trigger the attack. * @param time When the attack should be triggered. * @param velocity The velocity that the envelope should be triggered at. */ _triggerEnvelopeAttack(time, velocity = 1) { this.envelope.triggerAttack(time, velocity); this._oscillators.forEach(osc => osc.start(time)); if (this.envelope.sustain === 0) { this._oscillators.forEach(osc => { osc.stop(time + this.toSeconds(this.envelope.attack) + this.toSeconds(this.envelope.decay)); }); } return this; } /** * Trigger the release of the envelope. * @param time When the release should be triggered. */ _triggerEnvelopeRelease(time) { this.envelope.triggerRelease(time); this._oscillators.forEach(osc => osc.stop(time + this.toSeconds(this.envelope.release))); return this; } getLevelAtTime(time) { time = this.toSeconds(time); return this.envelope.getValueAtTime(time); } /** * The modulationIndex of the oscillators which make up the source. * see [[FMOscillator.modulationIndex]] * @min 1 * @max 100 */ get modulationIndex() { return this._oscillators[0].modulationIndex.value; } set modulationIndex(val) { this._oscillators.forEach(osc => (osc.modulationIndex.value = val)); } /** * The harmonicity of the oscillators which make up the source. * see Tone.FMOscillator.harmonicity * @min 0.1 * @max 10 */ get harmonicity() { return this._oscillators[0].harmonicity.value; } set harmonicity(val) { this._oscillators.forEach(osc => (osc.harmonicity.value = val)); } /** * The lower level of the highpass filter which is attached to the envelope. * This value should be between [0, 7000] * @min 0 * @max 7000 */ get resonance() { return this._filterFreqScaler.min; } set resonance(val) { this._filterFreqScaler.min = this.toFrequency(val); this.octaves = this._octaves; } /** * The number of octaves above the "resonance" frequency * that the filter ramps during the attack/decay envelope * @min 0 * @max 8 */ get octaves() { return this._octaves; } set octaves(val) { this._octaves = val; this._filterFreqScaler.max = this._filterFreqScaler.min * Math.pow(2, val); } dispose() { super.dispose(); this._oscillators.forEach(osc => osc.dispose()); this._freqMultipliers.forEach(freqMult => freqMult.dispose()); this.frequency.dispose(); this.detune.dispose(); this._filterFreqScaler.dispose(); this._amplitude.dispose(); this.envelope.dispose(); this._highpass.dispose(); return this; } } //# sourceMappingURL=MetalSynth.js.map