2019-09-17 17:37:43 +00:00
|
|
|
import { Frequency, NormalRange, Time } from "../core/type/Units";
|
2019-09-01 11:17:32 +00:00
|
|
|
import { LowpassCombFilter } from "../component/filter/LowpassCombFilter";
|
|
|
|
import { deepMerge } from "../core/util/Defaults";
|
|
|
|
import { optionsFromArguments } from "../core/util/Defaults";
|
|
|
|
import { RecursivePartial } from "../core/util/Interface";
|
|
|
|
import { Noise } from "../source/Noise";
|
2019-09-17 17:40:07 +00:00
|
|
|
import { Instrument, InstrumentOptions } from "./Instrument";
|
2019-09-01 11:17:32 +00:00
|
|
|
|
2019-09-17 17:37:43 +00:00
|
|
|
export interface PluckSynthOptions extends InstrumentOptions {
|
2019-09-01 11:17:32 +00:00
|
|
|
attackNoise: number;
|
|
|
|
dampening: Frequency;
|
|
|
|
resonance: NormalRange;
|
2019-09-29 21:25:31 +00:00
|
|
|
release: Time;
|
2019-09-01 11:17:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-05-11 18:09:05 +00:00
|
|
|
* Karplus-Strong string synthesis.
|
2019-09-01 11:17:32 +00:00
|
|
|
* @example
|
2020-04-17 02:24:18 +00:00
|
|
|
* const plucky = new Tone.PluckSynth().toDestination();
|
2019-10-28 21:31:32 +00:00
|
|
|
* plucky.triggerAttack("C4", "+0.5");
|
|
|
|
* plucky.triggerAttack("C3", "+1");
|
|
|
|
* plucky.triggerAttack("C2", "+1.5");
|
|
|
|
* plucky.triggerAttack("C1", "+2");
|
2019-09-17 17:55:51 +00:00
|
|
|
* @category Instrument
|
2019-09-01 11:17:32 +00:00
|
|
|
*/
|
2019-09-17 17:37:43 +00:00
|
|
|
export class PluckSynth extends Instrument<PluckSynthOptions> {
|
2019-09-01 11:17:32 +00:00
|
|
|
|
|
|
|
readonly name = "PluckSynth";
|
|
|
|
|
2019-09-17 17:37:43 +00:00
|
|
|
/**
|
|
|
|
* Noise burst at the beginning
|
|
|
|
*/
|
2019-09-01 11:17:32 +00:00
|
|
|
private _noise: Noise;
|
|
|
|
private _lfcf: LowpassCombFilter;
|
|
|
|
|
|
|
|
/**
|
2019-09-17 17:37:43 +00:00
|
|
|
* The amount of noise at the attack.
|
|
|
|
* Nominal range of [0.1, 20]
|
|
|
|
* @min 0.1
|
|
|
|
* @max 20
|
2019-09-01 11:17:32 +00:00
|
|
|
*/
|
|
|
|
attackNoise: number;
|
|
|
|
|
2019-09-17 17:37:43 +00:00
|
|
|
/**
|
2019-09-29 21:25:31 +00:00
|
|
|
* The amount of resonance of the pluck. Also correlates to the sustain duration.
|
2019-09-17 17:37:43 +00:00
|
|
|
*/
|
2019-09-29 21:25:31 +00:00
|
|
|
resonance: NormalRange;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The release time which corresponds to a resonance ramp down to 0
|
|
|
|
*/
|
|
|
|
release: Time;
|
2019-09-17 17:37:43 +00:00
|
|
|
|
2019-09-01 11:17:32 +00:00
|
|
|
constructor(options?: RecursivePartial<PluckSynthOptions>)
|
|
|
|
constructor() {
|
|
|
|
|
|
|
|
super(optionsFromArguments(PluckSynth.getDefaults(), arguments));
|
|
|
|
const options = optionsFromArguments(PluckSynth.getDefaults(), arguments);
|
|
|
|
|
2019-09-17 17:37:43 +00:00
|
|
|
this._noise = new Noise({
|
|
|
|
context: this.context,
|
|
|
|
type: "pink"
|
|
|
|
});
|
2019-09-01 11:17:32 +00:00
|
|
|
|
|
|
|
this.attackNoise = options.attackNoise;
|
|
|
|
|
|
|
|
this._lfcf = new LowpassCombFilter({
|
2019-09-17 17:37:43 +00:00
|
|
|
context: this.context,
|
2019-09-01 11:17:32 +00:00
|
|
|
dampening: options.dampening,
|
|
|
|
resonance: options.resonance,
|
|
|
|
});
|
|
|
|
|
2019-09-29 21:25:31 +00:00
|
|
|
this.resonance = options.resonance;
|
|
|
|
this.release = options.release;
|
2019-09-01 11:17:32 +00:00
|
|
|
|
|
|
|
this._noise.connect(this._lfcf);
|
|
|
|
this._lfcf.connect(this.output);
|
|
|
|
}
|
|
|
|
|
|
|
|
static getDefaults(): PluckSynthOptions {
|
2019-09-17 17:37:43 +00:00
|
|
|
return deepMerge(Instrument.getDefaults(), {
|
2019-09-17 17:40:07 +00:00
|
|
|
attackNoise: 1,
|
|
|
|
dampening: 4000,
|
|
|
|
resonance: 0.7,
|
2019-09-29 21:25:31 +00:00
|
|
|
release: 1,
|
2019-09-01 11:17:32 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-09-25 02:41:58 +00:00
|
|
|
/**
|
|
|
|
* The dampening control. i.e. the lowpass filter frequency of the comb filter
|
|
|
|
* @min 0
|
|
|
|
* @max 7000
|
|
|
|
*/
|
|
|
|
get dampening(): Frequency {
|
|
|
|
return this._lfcf.dampening;
|
|
|
|
}
|
|
|
|
set dampening(fq) {
|
|
|
|
this._lfcf.dampening = fq;
|
|
|
|
}
|
|
|
|
|
2019-09-17 17:37:43 +00:00
|
|
|
triggerAttack(note: Frequency, time?: Time): this {
|
2019-09-01 11:17:32 +00:00
|
|
|
const freq = this.toFrequency(note);
|
|
|
|
time = this.toSeconds(time);
|
|
|
|
const delayAmount = 1 / freq;
|
|
|
|
this._lfcf.delayTime.setValueAtTime(delayAmount, time);
|
|
|
|
this._noise.start(time);
|
|
|
|
this._noise.stop(time + delayAmount * this.attackNoise);
|
2019-09-29 21:25:31 +00:00
|
|
|
this._lfcf.resonance.cancelScheduledValues(time);
|
|
|
|
this._lfcf.resonance.setValueAtTime(this.resonance, time);
|
2019-09-01 11:17:32 +00:00
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2019-09-17 17:37:43 +00:00
|
|
|
/**
|
2019-09-29 21:25:31 +00:00
|
|
|
* Ramp down the [[resonance]] to 0 over the duration of the release time.
|
2019-09-17 17:37:43 +00:00
|
|
|
*/
|
2020-04-17 02:24:18 +00:00
|
|
|
triggerRelease(time?: Time): this {
|
2019-09-29 21:25:31 +00:00
|
|
|
this._lfcf.resonance.linearRampTo(0, this.release, time);
|
2019-09-17 17:37:43 +00:00
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2019-09-01 11:17:32 +00:00
|
|
|
dispose(): this {
|
|
|
|
super.dispose();
|
|
|
|
this._noise.dispose();
|
|
|
|
this._lfcf.dispose();
|
|
|
|
return this;
|
|
|
|
}
|
2019-09-17 17:40:07 +00:00
|
|
|
}
|