diff --git a/piano/synth/AudioPlayer.mjs b/piano/synth/AudioPlayer.mjs new file mode 100644 index 0000000..2867b0c --- /dev/null +++ b/piano/synth/AudioPlayer.mjs @@ -0,0 +1,59 @@ +import Timbre from "./Timbre.mjs"; + +export default class AudioPlayer { + /** @type {Timbre} */ + timbre = null; + frequency = null; + oscillatorNode = null; + gainNode = null; + + constructor(timbre, frequency) { + this.timbre = timbre; + this.frequency = frequency; + + this.oscillatorNode = timbre.audioCtx.createOscillator(); + if (this.timbre.type === 'custom') { + this.oscillatorNode.setPeriodicWave(timbre.wave); + } else { + this.oscillatorNode.type = timbre.type; + } + this.oscillatorNode.frequency.value = frequency; + + this.gainNode = timbre.audioCtx.createGain(); + this.gainNode.gain.value = 0; + + this.oscillatorNode + .connect(this.gainNode) + .connect(timbre.audioCtx.destination); + this.oscillatorNode.start(); + } + + start() { + const adsr = this.timbre.adsr; + let time = this.timbre.audioCtx.currentTime; + this.gainNode.gain.cancelScheduledValues(time); + + this.gainNode.gain.setValueAtTime(0, time); + + time += adsr.attackDuration; + this.gainNode.gain.linearRampToValueAtTime(adsr.attackAmplitude, time); + + time += adsr.decayDuration; + this.gainNode.gain.linearRampToValueAtTime(adsr.decayAmplitude, time); + + if (adsr.sustainDuration) { + time += adsr.sustainDuration; + this.gainNode.gain.linearRampToValueAtTime(0, time); + } + } + + stop() { + const adsr = this.timbre.adsr; + let time = this.timbre.audioCtx.currentTime; + this.gainNode.gain.cancelScheduledValues(time); + + time += adsr.releaseDuration; + this.gainNode.gain.linearRampToValueAtTime(0, time); + } + +}; diff --git a/piano/synth/Timbre.mjs b/piano/synth/Timbre.mjs new file mode 100644 index 0000000..0aab00f --- /dev/null +++ b/piano/synth/Timbre.mjs @@ -0,0 +1,27 @@ +export default class Timbre { + audioCtx = null; + type = 'sine'; + wave = null; + adsr = { + attackAmplitude: 1, + attackDuration: 0.1, + decayAmplitude: 0.5, + decayDuration: 0.2, + sustainDuration: 3, + releaseDuration: 1, + }; + + constructor() { + this.audioCtx = new (window.AudioContext || window.webkitAudioContext)(); + } + + setWithHarmonics(harmonics) { + this.harmonics = harmonics; + + this.type = 'custom'; + this.wave = new PeriodicWave(this.audioCtx, { + real: new Float32Array(harmonics), + imag: new Float32Array(harmonics.length), + }); + } +}; diff --git a/piano/synth/index.html b/piano/synth/index.html index d24e62f..f3e2614 100644 --- a/piano/synth/index.html +++ b/piano/synth/index.html @@ -28,6 +28,8 @@
+ +

@@ -35,44 +37,37 @@
diff --git a/piano/synth/synth.mjs b/piano/synth/synth.mjs deleted file mode 100644 index d18fbf3..0000000 --- a/piano/synth/synth.mjs +++ /dev/null @@ -1,77 +0,0 @@ -export default { - - audioCtx: null, - - start: function() { - if (!this.audioCtx) - this.audioCtx = new (window.AudioContext || window.webkitAudioContext)(); - }, - - makeHarmonicNode: function(frequency, harmonics) { - - const wave = new PeriodicWave(this.audioCtx, { - real: new Float32Array(harmonics), - imag: new Float32Array(harmonics.length), - }); - - const oscillator = new OscillatorNode(this.audioCtx, { - frequency, - type: "custom", - periodicWave: wave - }); - return oscillator; - }, - - makeEnvelopeNode: function(adsr, time) { - if (!time) time = this.audioCtx.currentTime; - - const env = new GainNode(this.audioCtx); - env.gain.cancelScheduledValues(time); - env.gain.setValueAtTime(0, time); - env.gain.linearRampToValueAtTime(adsr.attackAmplitude, time + adsr.attackDuration); - env.gain.linearRampToValueAtTime( - adsr.decayAmplitude, - time + adsr.attackDuration + adsr.decayDuration - ); - if (adsr.sustainDuration) { - env.gain.linearRampToValueAtTime( - 0, - time + adsr.attackDuration + adsr.decayDuration + adsr.sustainDuration - ); - } - return env; - }, - - releaseEnvelope: function(env, adsr, time) { - if (!time) time = this.audioCtx.currentTime; - - env.gain.linearRampToValueAtTime( - 0, - time + adsr.releaseDuration - ); - }, - - createAudioPlayer: function(freq, harmonics, adsr) { - const audioPlayer = {}; - audioPlayer.start = () => { - if (audioPlayer.oscillator) { - audioPlayer.oscillator.stop(); - } - - audioPlayer.oscillator = this.makeHarmonicNode(freq, harmonics); - audioPlayer.envelope = this.makeEnvelopeNode(adsr); - - audioPlayer.oscillator - .connect(audioPlayer.envelope) - .connect(this.audioCtx.destination); - - audioPlayer.oscillator.start(); - }; - audioPlayer.stop = () => { - this.releaseEnvelope(audioPlayer.envelope, adsr); - audioPlayer.oscillator.stop(this.audioCtx.currentTime + adsr.releaseDuration); - }; - return audioPlayer; - }, - -}