import Tone from "../core/Tone"; import "../instrument/Instrument"; import "../source/FMOscillator"; import "../component/Filter"; import "../component/FrequencyEnvelope"; import "../component/AmplitudeEnvelope"; import "../core/Gain"; import "../signal/Scale"; import "../signal/Multiply"; /** * 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 * @private * @static * @type {Array} */ var inharmRatios = [1.0, 1.483, 1.932, 2.546, 2.630, 3.897]; /** * @class A highly inharmonic and spectrally complex source with a highpass filter * and amplitude envelope which is good for making metalophone sounds. Based * on CymbalSynth by [@polyrhythmatic](https://github.com/polyrhythmatic). * Inspiration from [Sound on Sound](https://web.archive.org/web/20160610143924/https://www.soundonsound.com/sos/jul02/articles/synthsecrets0702.asp). * * @constructor * @extends {Tone.Instrument} * @param {Object} [options] The options availble for the synth * see defaults below */ Tone.MetalSynth = function(options){ options = Tone.defaultArg(options, Tone.MetalSynth.defaults); Tone.Instrument.call(this, options); /** * The frequency of the cymbal * @type {Frequency} * @signal */ this.frequency = new Tone.Signal(options.frequency, Tone.Type.Frequency); /** * The array of FMOscillators * @type {Array} * @private */ this._oscillators = []; /** * The frequency multipliers * @type {Array} * @private */ this._freqMultipliers = []; /** * The amplitude for the body * @type {Tone.Gain} * @private */ this._amplitue = new Tone.Gain(0).connect(this.output); /** * highpass the output * @type {Tone.Filter} * @private */ this._highpass = new Tone.Filter({ "type" : "highpass", "Q" : -3.0102999566398125 }).connect(this._amplitue); /** * The number of octaves the highpass * filter frequency ramps * @type {Number} * @private */ this._octaves = options.octaves; /** * Scale the body envelope * for the bandpass * @type {Tone.Scale} * @private */ this._filterFreqScaler = new Tone.Scale(options.resonance, 7000); /** * The envelope which is connected both to the * amplitude and highpass filter's cutoff frequency * @type {Tone.Envelope} */ this.envelope = new Tone.Envelope({ "attack" : options.envelope.attack, "attackCurve" : "linear", "decay" : options.envelope.decay, "sustain" : 0, "release" : options.envelope.release, }).chain(this._filterFreqScaler, this._highpass.frequency); this.envelope.connect(this._amplitue.gain); for (var i = 0; i < inharmRatios.length; i++){ var osc = new Tone.FMOscillator({ "type" : "square", "modulationType" : "square", "harmonicity" : options.harmonicity, "modulationIndex" : options.modulationIndex }); osc.connect(this._highpass); this._oscillators[i] = osc; var mult = new Tone.Multiply(inharmRatios[i]); this._freqMultipliers[i] = mult; this.frequency.chain(mult, osc.frequency); } //set the octaves this.octaves = options.octaves; }; Tone.extend(Tone.MetalSynth, Tone.Instrument); /** * default values * @static * @const * @type {Object} */ Tone.MetalSynth.defaults = { "frequency" : 200, "envelope" : { "attack" : 0.001, "decay" : 1.4, "release" : 0.2 }, "harmonicity" : 5.1, "modulationIndex" : 32, "resonance" : 4000, "octaves" : 1.5 }; /** * Trigger the attack. * @param {Time} time When the attack should be triggered. * @param {NormalRange} [velocity=1] The velocity that the envelope should be triggered at. * @return {Tone.MetalSynth} this */ Tone.MetalSynth.prototype.triggerAttack = function(time, vel){ time = this.toSeconds(time); vel = Tone.defaultArg(vel, 1); this.envelope.triggerAttack(time, vel); this._oscillators.forEach(function(osc){ osc.start(time); }); //if the sustain is 0, stop the oscillator as well if (this.envelope.sustain === 0){ this._oscillators.forEach(function(osc){ osc.stop(time + this.envelope.attack + this.envelope.decay); }.bind(this)); } return this; }; /** * Trigger the release of the envelope. * @param {Time} time When the release should be triggered. * @return {Tone.MetalSynth} this */ Tone.MetalSynth.prototype.triggerRelease = function(time){ time = this.toSeconds(time); this.envelope.triggerRelease(time); this._oscillators.forEach(function(osc){ osc.stop(time + this.envelope.release); }.bind(this)); return this; }; /** * Sync the instrument to the Transport. All subsequent calls of * [triggerAttack](#triggerattack) and [triggerRelease](#triggerrelease) * will be scheduled along the transport. * @example * synth.sync() * //schedule 3 notes when the transport first starts * synth.triggerAttackRelease('8n', 0) * synth.triggerAttackRelease('8n', '8n') * synth.triggerAttackRelease('8n', '4n') * //start the transport to hear the notes * Transport.start() * @returns {Tone.Instrument} this */ Tone.MetalSynth.prototype.sync = function(){ this._syncMethod("triggerAttack", 0); this._syncMethod("triggerRelease", 0); return this; }; /** * Trigger the attack and release of the envelope after the given * duration. * @param {Time} duration The duration before triggering the release * @param {Time} time When the attack should be triggered. * @param {NormalRange} [velocity=1] The velocity that the envelope should be triggered at. * @return {Tone.MetalSynth} this */ Tone.MetalSynth.prototype.triggerAttackRelease = function(duration, time, velocity){ time = this.toSeconds(time); duration = this.toSeconds(duration); this.triggerAttack(time, velocity); this.triggerRelease(time + duration); return this; }; /** * The modulationIndex of the oscillators which make up the source. * see Tone.FMOscillator.modulationIndex * @memberOf Tone.MetalSynth# * @type {Positive} * @name modulationIndex */ Object.defineProperty(Tone.MetalSynth.prototype, "modulationIndex", { get : function(){ return this._oscillators[0].modulationIndex.value; }, set : function(val){ for (var i = 0; i < this._oscillators.length; i++){ this._oscillators[i].modulationIndex.value = val; } } }); /** * The harmonicity of the oscillators which make up the source. * see Tone.FMOscillator.harmonicity * @memberOf Tone.MetalSynth# * @type {Positive} * @name harmonicity */ Object.defineProperty(Tone.MetalSynth.prototype, "harmonicity", { get : function(){ return this._oscillators[0].harmonicity.value; }, set : function(val){ for (var i = 0; i < this._oscillators.length; i++){ this._oscillators[i].harmonicity.value = val; } } }); /** * The frequency of the highpass filter attached to the envelope * @memberOf Tone.MetalSynth# * @type {Frequency} * @name resonance */ Object.defineProperty(Tone.MetalSynth.prototype, "resonance", { get : function(){ return this._filterFreqScaler.min; }, set : function(val){ this._filterFreqScaler.min = val; this.octaves = this._octaves; } }); /** * The number of octaves above the "resonance" frequency * that the filter ramps during the attack/decay envelope * @memberOf Tone.MetalSynth# * @type {Number} * @name octaves */ Object.defineProperty(Tone.MetalSynth.prototype, "octaves", { get : function(){ return this._octaves; }, set : function(octs){ this._octaves = octs; this._filterFreqScaler.max = this._filterFreqScaler.min * Math.pow(2, octs); } }); /** * Clean up * @returns {Tone.MetalSynth} this */ Tone.MetalSynth.prototype.dispose = function(){ Tone.Instrument.prototype.dispose.call(this); for (var i = 0; i < this._oscillators.length; i++){ this._oscillators[i].dispose(); this._freqMultipliers[i].dispose(); } this._oscillators = null; this._freqMultipliers = null; this.frequency.dispose(); this.frequency = null; this._filterFreqScaler.dispose(); this._filterFreqScaler = null; this._amplitue.dispose(); this._amplitue = null; this.envelope.dispose(); this.envelope = null; this._highpass.dispose(); this._highpass = null; }; export default Tone.MetalSynth;