
ãã©ãŠã¶ãŒã·ã³ã»ãµã€ã¶ãŒãäœæãããšããã¢ã€ãã¢ã¯ãAudio APIããŸã åæ段éã«ããããã©ãŠã¶ãŒããé³å£°ãæœåºããã»ãšãã©ã®æ©äŒïŒæ¢è£œã®ãã¡ã€ã«ãåçããå Žåãé€ãïŒãbase64ã§ã®ãšã³ã³ãŒããšãªãŒãã£ãªã¿ã°ã§ã®èšé²ã䌎ãWAVã®çæã§ãã£ãå Žåã§ããããªãåã«æãã€ãããã®ã§ãã ãããŠãåæãšã³ãŒãã£ã³ã°ãåé¡ãªãæ©èœããå ŽåïŒWAV圢åŒã¯éåžžã«åçŽã§ãïŒããªã¢ã«ã¿ã€ã ã§é³æ¥œãåçããããã®ãªãŒãã£ãªã®ã¹ããªãŒãã³ã°ã§ã¯ããã¹ãŠãæªåããã·ãŒã ã¬ã¹ãªãããã¡ãªã³ã°ãéæã§ããããªãã¯ã¯ãªãã£ããããã¢ã€ãã¢ã¯çãŸããåã«æ¶æ» ããŸããã é·å¹Žã«ããã£ãŠãAudio APIããµããŒããããã©ãŠã¶ãŒãå€§å¹ ã«è¿œå ããããã®çµæããã®åéã§ã®æ°ããå®éšã«è§ŠçºãããŸããã ãã®èšäºã§ã¯ãHTML5ã䜿çšããŠãã©ãŠã¶ãŒã·ã³ã»ãµã€ã¶ãŒãäœæããæé ã説æããŸããåçŽãªæ£åŒŠæ³¢ã®çæããå§ãŸããä¿¡å·ã®åãæ¿ããšå€èª¿ããªãŒãã£ãªãšãã§ã¯ãã§çµãããŸãã
趣å³ã®ãã¥ãŒãžã·ã£ã³ã§ãããããã«ã¿ã€ã ã®ããã°ã©ããŒã§ããç§ã¯ãå®è£ ãèŠç©ãããçæ³çã«ã¯é²é³ãã楜åšãæå ã«ãªããšãã«ãä»äºäžã«é³æ¥œã®ã¢ã€ãã¢ããã°ãã°è¿œãè¶ããŸãã ãããã£ãŠãæåã¯ãªã³ã©ã€ã³MIDIã·ãŒã±ã³ãµãŒã®ã¢ã€ãã¢ãçãŸããŸãããããã«ãããã»ãšãã©ã®ã¢ã€ãã¢ãã¹ã±ããããŠä¿åããããšãã§ããŸãã ããããå°ãªããšãããŒããŒãä»ãã®ããŠã¹ã䜿çšããŠãããã£ãã·ã¥ãã¹ã¯ãé¢ããããšãªãããªã¢ã«ã¿ã€ã ã§æãæµ®ããã ã¡ããã£ãåçããã³é²é³ããæ©èœãæããªããã©ã®ãããªã·ãŒã±ã³ãµãŒã§ããããã ãã®çµæãæãåçŽãªã·ã³ã»ãµã€ã¶ãŒãéçºããéçšã§ãå°ãèããå¿ã³èŸŒãã§ããŸããŸããã倧ããªãã®ã«æºããã¹ããã©ããã§ã¯ãããŸããã ãã¡ãããJavaScriptã·ã³ã»ãµã€ã¶ãŒã®ã¢ã€ãã¢ã¯æ°ãããã®ã§ã¯ãªããããŸããŸãªèª¬åŸã¬ãã«ã®å®è£ ãææãã¡ãã¡ã§çºçããŸããããå°ãªããšãããã§ã¯ãããã«é¢ããŠããªãŒãã£ãªAPIã®ãããã¯ã«é¢ããèšäºã¯æ°ä»¶ããèŠã€ãããŸããã§ããããããŠãã®ããã¹ãã®ããã«åº§ãããã«ä¿ããŸããã
åºæã§ç°¡åã«è¿°ã¹ãããã«ãç§ãæåã«èããã®ã¯ããµãŠã³ããå®å šã«åæçã«åŠçããŠããWAV圢åŒã«çŽæ¥ãšã³ã³ãŒãããããšã§ãããããã®åœ¢åŒã®ã¹ããªãŒãã³ã°åçãšé¢é£ãããã¯ã®ããã¥ã¡ã³ããçµã¿åãããŠèŠåŽããéçšã§ãç§ã¯çªç¶ãå®è£ ãè¯ããšæããŸããMDNã§èª¬æãããŠãããªãŒãã£ãªAPIãã©ãŠã¶ã äžèŠãªããªãã¯ãæå°éã®æ段ã䜿çšããªãAudio APIã«ãããæ©èœãããã¯ããä»®æ³ãªãŒãã£ãªãã¹ãäœæãããããã奜ã¿ã«åãããŠèª¿æŽããã³åãæ¿ããããšãã§ããŸãã APIã«ã¯ãã»ãŒãã¹ãŠã®å¿ èŠãªåºæ¬èŠçŽ ïŒãªã·ã¬ãŒã¿ãŒãã¢ã³ããã¹ããªãã¿ãŒãªã©ïŒãèšèŒãããŠããŸããå®å šãªãªã¹ããšäœ¿çšäŸã«ã€ããŠã¯ã MDNã®å¯Ÿå¿ããã»ã¯ã·ã§ã³ãåç §ããããšããå§ãããŸãããã®ãããäž»ãªåé¡ã¯ãå¹æçã§é©åãªã¹ã€ããã³ã°ãšäœæãšç®¡çã§ãã
æåã®ããŒãžã§ã³ã§ã¯ãå®è£ ããæå°éã®æ©èœã決å®ããå¿ èŠããããŸãã ç§ã«é¢ããŠã¯ã波圢ïŒæ£åŒŠæ³¢ãã®ãããæ³¢ãèè¡ïŒãããã³ããã©ãŒãïŒããŒã³ã®å€åïŒããã¬ã¢ãïŒããªã¥ãŒã ã®å€åïŒããšã³ãŒãªã©ã®ãšãã§ã¯ããéžæããããšã絶察ã«å¿ èŠã§ãã ããã«ãå€å žçãªæ©èœãããADSRãšã³ãããŒãèšå®ãèŠãããšæããŸããããã¯ãå®éã®æ¥œåšã§æŒå¥ãããé³ã®é³ã®åŒ·ãã®äœçžã®åçŽåãããè¿äŒŒã§ãã
ãã¬ã¢ããšããã©ãŒããå€èª¿å¹æïŒããããæ¯å¹ ãšãããïŒã§ããããšãèãããšãã·ã¹ãã ãã©ã¡ãŒã¿ãŒã®ããããã«å¯ŸãããŠãããŒãµã«ã¢ãžã¥ã¬ãŒã¿ãŒã®å®è£ ã¯ãæãèªç¶ã§æè»ãªãœãªã¥ãŒã·ã§ã³ã®ããã§ãã ã¢ãžã¥ã¬ãŒã¿ãŒã®å šäœçãªç¹åŸŽã¯ãä¿¡å·ãã©ã¡ãŒã¿ãŒã ãã§ãªããä»ã®èªå·±çžäŒŒã¢ãžã¥ã¬ãŒã¿ãŒã®ãã©ã¡ãŒã¿ãŒã«ãçŽæ¥åœ±é¿ãäžããæ³ååã«ãã£ãŠã®ã¿å¶éãããé³ã®åœ¢æã®å¯èœæ§ãå¶éã§ããããšã§ãã ãã€ãŠããã®ã¢ã€ãã¢ã¯ã¢ããã°ã·ã³ã»ãµã€ã¶ãŒã®åºç€ã«ããããŸããããåãæ¿ãã¯æ¬¡ã®ããã«ãªããŸããã

ç§ãã¡ã«ãšã£ãŠã¯ã次ã®ããã«ãªããŸãã
synth.connect(volume); volume.connect(delay.input); delay.connect(pan); pan.connect(audioCtx.destination); var vibrato = new SineModulator(); vibrato.modulate(synth, 'pitchShift');
ãŸããåçŽãªãµãŠã³ãåæããå§ããŸãããããã®ãªããžã§ã¯ãã§ã¯ãAudioContextãªããžã§ã¯ããäœæãããã®äžã§ãªã·ã¬ãŒã¿ãŒãäœæãããã®åšæ³¢æ°ãèšå®ãããªãŒãã£ãªåºåã«æ¥ç¶ããŠããçºæ¯ããããµãŠã³ããçæããŸãã
audioContext = new AudioContext(); var oscillator = audioContext.createOscillator() ; oscillator.frequency.value = 440; oscillator.connect(audioContext.destination); oscillator.start(0);
ãã¹ãŠãæ£ããè¡ãããå Žåãé»è©±ã®ããŒãé³ã®åšæ³¢æ°ã«ç²ŸéããŠãããã¹ãŠã®äººïŒãŸãããŸãã¯é³å-ããã¯èª°ã«ã§ãè¿ãïŒã«ã¹ããŒã«ãŒ/ããããã©ã³ã§å¿å°ããé³ãèãããã¯ãã§ãã ãã®ããã»ã¹ã¯ãåããªããžã§ã¯ãã®å¯Ÿå¿ãã.stopïŒïŒãããŒãåŒã³åºãããšã§åæ¢ã§ããŸãã ã¡ãªã¿ã«ãéå§é¢æ°ãšåæ¢é¢æ°ã«æž¡ããããã©ã¡ãŒã¿ãŒã¯ç§åäœã®æéã§ãããã®åŸãã·ã°ãã«ã«é¢ããŠæå®ãããã¢ã¯ã·ã§ã³ãå®è¡ããå¿ èŠããããŸãã ãŸã å¿ èŠãªãã®ã§ããã©ã¡ãŒã¿ãŒã0ã«èšå®ããŸããããŸã£ããçç¥ã§ããŸãã .connectïŒïŒããã³.disconnectïŒïŒã¡ãœããã«ã泚æãæãå¿ èŠããããŸãããããã®ã¡ãœããã¯ããã¹ãŠã®ããŒãã«å ±éã®AudioNodeã€ã³ã¿ãŒãã§ãŒã¹ã®äžéšã§ãããå ¥åãšåºåãåãæ¿ããã®ã«åœ¹ç«ã¡ãŸãã ãªã·ã¬ãŒã¿ãŒã®.connectïŒïŒãåŒã³åºãããšã«ãããçµæã®ãªãŒãã£ãªä¿¡å·ããã©ã¡ãŒã¿ãŒãšããŠæž¡ãããããŒãããã®å Žåã¯audioContext.destinationã«éä¿¡ããããã«æ瀺ããŸãããã®å Žåãããã°ã©ã ã®èŠ³ç¹ããã¯ãããã«åçããããã«ãªãã¬ãŒãã£ã³ã°ã·ã¹ãã ã«ãµãŠã³ããéä¿¡ããæçµå®å ã§ãã
ã·ã³ã»ãµã€ã¶ãŒã®æåã®æ©èœãšããŠã波圢ã®éžæãå®è£ ããŸãã æãäžè¬çãªæ³¢åœ¢ïŒéžãèè¡ãªã©ïŒã¯APIã®äžéšãšããŠå©çšã§ãããªã·ã¬ãŒã¿ãŒã«é©åãªãã©ã¡ãŒã¿ãŒãæå®ããããšã§äœ¿çšã§ããŸãïŒäŸïŒaudioContext.createOscillatorïŒ 'square'ïŒïŒã ããã«èå³æ·±ãã±ãŒã¹ãšããŠãä»»æ波圢ãæå®ã§ããPeriodicWaveã€ã³ã¿ãŒãã§ãŒã¹ããããŸãã ããŒãªãšä¿æ°ãæã€2ã€ã®é åãé¢æ°ã®å ¥åã«éä¿¡ãããŸããããã¯ãä»»æã®æ³¢åœ¢ã«å¯ŸããŠç°¡åã«èšç®ã§ããããã»ã¹ãæç®ã§èŠã€ããããšãã§ããŸãïŒããšãã°ã ããã§ç°¡åã«èª¬æããŸã ïŒã ãããã£ãŠãæ¯å¹ ãæ¯äŸçã«æžå°ããä¿¡å·ã®ãã¹ãŠã®é«èª¿æ³¢ã®åèšã§ããåãã®ãããæ³¢ã«ã€ããŠãäœåŒŠïŒè€çŽ è¡šèšã®å®æ°ïŒã®ä¿æ°ã¯0ã§ãããæ£åŒŠïŒèæ°ïŒ1 / nnã®å Žåãé¢æ°ã¯æ¬¡ã®ããã«ãªããŸããã®ããã«èŠããïŒ

åœç¶ããã³ã®ãªã®åãå³ãè¯ãããã«ã¯ãè¿œå ããé«èª¿æ³¢ã®æ°ãå¢ããå¿ èŠããããŸãã ãã®ããã»ã¹ã¯ã¢ãã¡ãŒã·ã§ã³ã«ç€ºãããŠããŸãã

ãããã£ãŠããã®ä¿¡å·ã«å¯Ÿããä¿æ°ã®èšç®ãšPeriodicWaveã®éä¿¡ã¯æ¬¡ã®ããã«ãªããŸãã
var context = new global.AudioContext(); var steps = 128; var imag = new global.Float32Array(steps); var real = new global.Float32Array(steps); for (var i = 1; i < steps; i++) { imag[i] = 1 / (i * Math.PI); } var wave = context.createPeriodicWave(real, imag); module.exports = wave;
次ã®ã¹ãããã¯ãããããéžæããæ©èœã§ãã å®éã®ã·ã³ã»ãµã€ã¶ãŒã§ã¯ãããã¯ããŒããŒãã䜿çšããŠè¡ãããŸããããŸãã¯ã¢ãã¿ãŒç»é¢ã«ersatzããŒããŒããäœæããŸãã ç§ã¯ãã¶ã€ããŒã§ã¯ãªããé«å質ã®ãã¶ã€ã³ãäœæã§ããŸããã§ããããã®ããããã¹ãŠã®ã°ã©ãã£ãã¯ãæã§æããŠããã¶ã€ã³æèã®äžè¶³ãé ããŠããŸãã ãã®çµæãåºæ¬æ©èœãåããããŒããŒããšã次ã®3ã€ã®æ³¢åœ¢ããéžæã§ããŸãã

åããŒã¹ãããŒã¯ã¯ãåçãããããŒãã®ã·ãªã¢ã«çªå·ã«é¢ããæ å ±ãå«ãä¿¡å·ãã·ã³ã»ãµã€ã¶ãŒã«éä¿¡ããå¿ èŠããããŸãã åãããšãããŒãªãªãŒã¹ã€ãã³ãã«ãåœãŠã¯ãŸããŸãã å€å žçãªã·ã³ã»ãµã€ã¶ãŒã¯ãããŒãæŒãããšã§æåã«1ã€ã®ãªã·ã¬ãŒã¿ãŒããããã§å€èª¿ããŠããŸããããæè¡çãªå¶éããªããããç§ã¯äººçãè€éã«ãããåæã«ä»»æã®æ°ã®ããŒããæŒå¥ããã®ã«å¿ èŠãªæ°ã®ãªã·ã¬ãŒã¿ãŒãäœãããšã«ããŸãã åŸç¶ã®ã¢ãžã¥ãŒã«ãšã®äŸ¿å©ãªåãæ¿ãã®ããã«ããã¹ãŠã®ãªã·ã¬ãŒã¿ãŒãæ¥ç¶ããããã¹ãŠã®ä¿¡å·ã1ã€ã®ã¹ããªãŒã ã«ããã·ã³ã°ãã1ã€ã®å ±éåºåããŒããããããšã«åæããŸãã 1ã€ã®ãšã³ããªãã€ã³ããš1ã€ã®åºå£ãã€ã³ãã®ã«ãŒã«ã¯ãä»åŸãã¹ãŠã®åŸç¶ã¢ãžã¥ãŒã«ã«é©çšãããŸãã ãã®ãããªãã€ã³ããšããŠãã²ã€ã³ã1ïŒããã©ã«ãïŒã®æãåçŽãªGainNodeããŒãã䜿çšãããŸãã æŠç¥çã«ã¯ã次ã®ããã«ãªããŸãã

ãããŠããã®ãããªã³ãŒãã§ã¯ïŒ
function Synth(context) { this.audioContext = context; this.output = context.createGain(); this._oscillators = {}; } Synth.prototype.play = function(note) { var oscillator; oscillator = this._oscillators[note.pitch] = this.audioContext.createOscillator(); oscillator.frequency.value = note.frequency; oscillator.connect(this.output); oscillator.start(0); return oscillator; }; Synth.prototype.stop = function(note) { this._oscillators[note.pitch].stop(0); };
åºæ¬æ©èœã®1ã€ã¯ãé³å£°ãçæããããã«èšèšãããããã€ã¹ãæ³åããããšã¯äžå¯èœã§ããããªãŒãã£ãªAPIã¯ããããGainNodeããã³StereoPannerã€ã³ã¿ãŒãã§ã€ã¹ãæäŸããããããã£ã³ãã«ã®é³éãšãã©ã³ã¹ã調æŽããããšã§ãã ããããåè·¯ã«è¿œå ããåãæ¥ç¶æ¹æ³ã䜿çšããŠéå€ããŸãïŒ
var audioContext = new AudioContext(); var volume = audioContext.createGain(); var pan = audioContext.createStereoPanner(); volume.gain.value = 1; pan.pan.value = 0; synth.output.connect(volume); volume.connect(pan); pan.connect(audioContext.destination);
ãã©ã¡ãŒã¿ãŒã調æŽããã«ã¯ã2ã€ã®å ¥åãã£ãŒã«ããäœæãã察å¿ããããŒãã«çŽæ¥æ¥ç¶ããŸãã å€ãèªã¿åã£ãŠæž¡ãããã«ãã¡ãã£ãšãŒã¿ãŒãã¿ãŒã³ãå®è£ ããåçŽãªã³ã³ãããŒã«ãªããžã§ã¯ããäœæããå€æŽæã«é¢å¿ã®ãã人ã«ãã£ãŒã«ãå€ãéä¿¡ããŸãã ãã®å®è£ ã«å°å¿µããæå³ã¯ãããŸãããã©ã®ãã£ãŒã«ãã§ãå€ãå€æŽããããšãã«äœãèµ·ãããã«éäžããŸãã
controls.on('volume-change', function(value) { volume.gain.value = value; }); controls.on('pan-change', function(value) { pan.pan.value = value; });
ããã©ãŒããšãã§ã¯ãã®éçºãããã³MIDIããã€ã¹ã®ç©ççãªã¬ããŒã«ãããããã®å¶åŸ¡ã«ã€ããŠäºåã«èããSynthã¢ãžã¥ãŒã«ã«ãã£ãŠçæããããã¹ãŠã®ãµãŠã³ãã®ããããå€æŽããæ©èœãè¿œå ããŸãã ãã®é¢æ°ã¯ãå ã®ã¢ãžã¥ãŒã«ãžã®æ··åç©ãšããŠå®è£ ãããŸãã
PitchShifterã³ãŒã
function PitchShifter() { this._pitchShift = 0; var oscillators = {}; Object.defineProperty(this, "pitchShift", { set: function (ps) { this._pitchShift = ps; for(var pitch in oscillators) { oscillators[pitch].frequency.value = oscillators[pitch].baseFrequency * Math.pow(2, this._pitchShift/1200); } }, get: function() { return this._pitchShift; } }); var old = { play: this.play, stop: this.stop }; this.play = function(note) { var osc = oscillators[note.pitch] = old.play.call(this, note); osc.baseFrequency = note.frequency; osc.frequency.value = osc.baseFrequency * Math.pow(2, this._pitchShift/1200); return osc; }; this.stop = function(note) { delete oscillators[note.pitch]; old.stop.apply(this, arguments); }; }
ã§ããããæåã®å¹æã§ããããã©ãŒããã€ãŸã ãããã®åšæçãªãããå€åã ãããã®ç®çã®ããã«ãåè¿°ã®ããã«ãå€èª¿åšãäœæããèšç»ã©ãããå€èª¿åšã¯ãæ¯å¹ ãåšæ³¢æ°ãªã©ã®ä»ã®å€èª¿åšã®ç¹æ§ãå«ãã·ã¹ãã ã®ãã©ã¡ãŒã¿ãŒãå€èª¿ã§ããããã«å®è£ ããå¿ èŠããããŸãã
ïŒ UPD ïŒã³ã¡ã³ãã§ã圌ãã¯ééã§ã¢ãããŒãã®éæé©æ§ãæ£ãã瀺åããŸãããç¹ã«çºæ¯åšã¯ãã©ã¡ãŒã¿ãçŽæ¥å¶åŸ¡ã§ãããããå·çæç¹ã§ã¯ããããŸããã§ããã®ã§ãã¢ãžã¥ã¬ãŒã¿ã«é¢ããéšåã¯å®çšçãšãããããçè«çã§ãïŒ
åšæ³¢æ°ã®å€åãšãæ£é¢ãããè¡çªãããªã¹ã¯ãããæåã®åé¡ã¯ãä¿¡å·ã¬ãã«éã®ãžã£ã³ãã§ããããã¯ãæ®å¿µãªããã次ã®å³ã«èŠãããããã«ã¯ã£ãããšèãããŸãã

ããã«ãåã®æ£åŒŠæ³¢ãšåŸç¶ã®æ£åŒŠæ³¢ã®äœçžã®éãã¯ãçŸåšã®æç¹ã«äŸåãããããéåžžã«èŠèŠãããªããŸãã ãã®æãŸãããªãå¹æãåé¿ããã«ã¯ãåšæ³¢æ°ãå€æŽãããšãã«ããŒãã§ã¯ãªãçŸåšã®X軞ã«æ²¿ã£ãç¹ã«å¯ŸããŠæ£åŒŠæ³¢ãå§çž®ãŸãã¯ã¹ãã¬ããããå¿ èŠããããŸããããã«ãããä¿¡å·äœçžã¯ãªããžã§ã¯ãã«åžžã«ä¿åããããããªãæ¯å¹ å€ãèšç®ããéã®åºæºç¹ãšããŠäœ¿çšãããŸãã

å€èª¿åšã®å®è£ ã«ããã2çªç®ã®éèŠãªç¹ã¯ãå æ³æ§ã§ãã çè«çã«ã¯ãä»»æã®æ°ã®æ£åŒŠæ³¢ãè¿œå ããçµæãšããŠåŸããã波圢ã«ã¯å¶éããªããå€èª¿åšãå ç®æ§æ¡ä»¶ãæºãããããåµé æ§ã®ç¯å²ãåºãããŸãã
äžèšã®æ¡ä»¶ãèæ ®ããŠãå¿ èŠãªå€èª¿åšãå®è£ ããŸãã
å€èª¿åšã³ãŒã
function SineModulator (options) { options = options || {}; this._frequency = options.frequency || 0; this._phaseOffset = 0; this._startedAt = 0; this._interval = null; this._prevValue = 0; this.depth = options.depth || 0; Object.defineProperty(this, "frequency", { set: function (frequency) { frequency = parseFloat(frequency); this._phaseOffset = this._phaseNow(); this._startedAt = Date.now(); this._frequency = frequency; }, get: function() { return this._frequency; } }); } SineModulator.prototype.modulate = function(object, property) { this._objToModulate = object; this._propertyToModulate = property; }; SineModulator.prototype.start = function() { this._startedAt = Date.now(); var this_ = this; this._interval = setInterval(function() { var value = this_._modValueNow(); var diff = value - this_._prevValue; this_._objToModulate[this_._propertyToModulate] += diff; this_._prevValue = value; }, 10); }; SineModulator.prototype._phaseNow = function() { var timeDiff = (Date.now() - this._startedAt) / 1000; var phase = this._phaseOffset + timeDiff * this.frequency % 1; return phase; }; SineModulator.prototype._modValueNow = function() { var phase = this._phaseNow(); return Math.sin((phase) * 2 * Math.PI) * this.depth; }; SineModulator.prototype.stop = function() { clearInterval(this._interval); } module.exports = SineModulator;
次ã«ãã¢ãžã¥ã¬ãŒã¿ãŒã䜿çšããŠãããããåšæçã«å€æŽãããšåæã«ããšãã§ã¯ããã©ã¡ãŒã¿ãŒã®å ¥åãã£ãŒã«ããã€ã³ã¿ãŒãã§ã€ã¹ã«è¿œå ããã¢ãžã¥ã¬ãŒã¿ãŒã®å¯Ÿå¿ããããããã£ã«ãªã³ã¯ããŸãã
var vibrato = new SineModulator(); vibrato.modulate(synth, 'pitchShift'); controls.on('vibrato-on-change', function(value) { parseInt(value) ? vibrato.start() : vibrato.stop(); }); controls.on('vibrato-depth-change', function(value) { vibrato.depth = value; }); controls.on('vibrato-freq-change', function(value) { vibrato.frequency = value; });
çµæã®ä¿¡å·ãéå§ããŠåæããæ¯åã®ååšã«æ³šæããŸãã éæãããå¹æïŒ

ãªã¹ãã®æ¬¡ã®é ç®-ãã¬ã¢ã-ã¯ååçã«åæ§ã§ãå¯äžã®éãã¯å€èª¿ããããã©ã¡ãŒã¿ãŒãä»åã¯é³éã§ãã ãã®å¹æã®ã³ãŒãã¯ãæå°éã«æããããã»ãšãã©åãã§ãã
var tremolo = new SineModulator(); tremolo.modulate(volume.gain, 'value'); controls.on('tremolo-on-change', function(value) { parseInt(value) ? tremolo.start() : tremolo.stop(); }); controls.on('tremolo-depth-change', function(value) { tremolo.depth = value; }); controls.on('tremolo-freq-change', function(value) { tremolo.frequency = value; });
2ã€ã®å€èª¿ããŒã¿ãæã¡ããããã®åšæ³¢æ°ãçµã¿åããããããŸãã¯éã«ç¹æ®ãããããããšã§ããµãŠã³ãã§éåžžã«èå³æ·±ãå¹æãæ¢ã«åŸãããšãã§ããŸãããŸããåšæãçžäºã«åçŽãã€æ¯å¹ ãåºãããããšã§ãã«ãªã¹ãšäºæž¬äžèœã®å¹æã«ãã£ãŠæºåã®ã§ããŠããªããªã¹ããŒããã£ããããããšããã§ããŸãïŒ
次ã®è«ççãªã¹ãããã¯ãã¬ãã«ãäžããŠãããã©ãŒãåšæ³¢æ°ãªã©ã®å©çšå¯èœãªã¢ãžã¥ã¬ãŒã¿ãŒã®ãã©ã¡ãŒã¿ãŒã®1ã€ãå€èª¿ããããšããããšã§ãããã®çµæãã²ãŒã ã®å¹æã¯æ¯åã®é床ã«ãªããŸãã ãããå®çŸããããã«ã2ã€ã®å€èª¿åšãäœæãããã®ãã¡ã®1ã€ãå€èª¿ãã©ã¡ãŒã¿ãŒãšããŠ2çªç®ã®åšæ³¢æ°ç¹æ§ã«å²ãåœãŠãŸãã
var vibrato = new SineModulator(); vibrato2.modulate(synth, 'pitchShift') vibrato2.frequency = 5; vibrato2.depth = 50; vibrato.modulate(synth, 'pitchShift'); vibrato.start(); vibrato2 = new SineModulator(); vibrato2.modulate(vibrato, 'frequency'); vibrato2.frequency = 0.2; vibrato2.depth = 3; vibrato2.start();
ããŒã³ã®å€åã®åšæ³¢æ°ã®åšæçãªå€åã裞ã®è³ã§èããããªãŒãã£ãªãšãã£ã¿ãŒã§èšé²ãããŠéãããçµæã®ä¿¡å·ãé©åã«èŠããŸãïŒå¯åºŠã®é«ãé åã§ã¯ãåšæ³¢æ°ã®é«ãé³ããããŸãïŒã

ããã«ãããå€èª¿ã䜿çšããå®éšã¯æåãããšèŠãªãããå¹æãå®çŸãããŸãã ä»»æã®ä¿¡å·ã«ããå€èª¿ã¯æ£åŒŠæ³¢ãè¿œå ããããšã§å®çŸã§ããŸãããå°æ¥çã«ã¯ãå°ãªããšãæãäžè¬çã«äœ¿çšããã波圢ïŒã®ãããæ³¢ãæ¹åœ¢æ³¢ïŒã«å¯ŸããŠãæºåãããå€èª¿åšã®ã»ãããçšæããæ¹ãã¯ããã«äŸ¿å©ã§ãããã®ã·ããªãªã§ã¯ãæ¢åã®å€èª¿åšãšã®é¡æšã«ãã£ãŠã³ã³ã¹ãã©ã¯ã¿ãŒã®ã»ãããäœæã§ããŸãSineModulatorãããã³ãªã·ã¬ãŒã¿ãŒã®æ³¢åœ¢ãæå®ãããšãã«äœ¿çšããããŒãªãšä¿æ°ãä»ããŠæ³¢åœ¢ãèšå®ããã¡ã«ããºã ã䜿çšããŸãã ãã®ã¿ã¹ã¯ã¯Audio APIã«çŽæ¥é¢ä¿ããªããªã£ããããããã§ã¯ãã®ãããã¯ãå®æãããæåã®éå€èª¿å¹æãã€ãŸããšã³ãŒã®å®è£ ã«é²ãããšãææ¡ããŸãã
ã»ãšãã©ã®å Žåããã®å¹æã¯å¿çã®æ°ãå¿çæéãæžè¡°ä¿æ°ã®3ã€ã®ãã©ã¡ãŒã¿ãŒã«ãã£ãŠç¹åŸŽä»ããããŸãã ãŒããš1以å€ã®å¿çæ°ãåŠçããã«ã¯ãä¿¡å·ãåå²ããååå²ã®é 延ç·ãäœæããå¿ èŠããããŸãã é 延ç·ã¯ãä¿¡å·ã®ééãäžå®æéé ãããããŒãã§ãã Audio APIã«ãã£ãŠæäŸãããAudioContext.createDelayé¢æ°ã«ãã£ãŠäœæãããé 延ç·ã䜿çšããŸãã æžè¡°ä¿æ°ã®ååšã«ãããååå²ãé 延ç·åè·¯ïŒå¢å¹ åšïŒã«å€ãããŸãã ããã«ãçŽç²ãªä¿¡å·ãšå¹æã®ããä¿¡å·ãåãæ¿ããå¿ èŠããããŸãããŸãããã¹ã®ååŸã®ãªã³ã¯ãšã®åçŽãªåãæ¿ãã®å¯èœæ§ãæäŸããå¿ èŠããããŸãïŒ1ã€ã®å ¥åãš1ã€ã®åºåãæã€ããšã«åæããŠãã ããïŒã

æ®å¿µãªãããAudioNodeã€ã³ã¿ãŒãã§ãŒã¹ãå®å šã«å®è£ ããä»ã®ããŒãã®æ¥ç¶ã¡ãœããã®ãã©ã¡ãŒã¿ãŒãšããŠçŽæ¥äœ¿çšã§ããèŠçŽ ãäœæããæ¹æ³ãèŠã€ããããšãã§ããŸããã§ããã ã€ã³ã¿ãŒãããã§ã®æ€çŽ¢ã§ãçµæãåŸãããªãã£ããããæçµçã«ã¯ãã€ã³ã¿ãŒãããäžã®ããããç¥èã®ãã人ã ããäžããããã¢ããã€ã¹ã«åŸããŸããããã®æ¬è³ªã¯ããªããžã§ã¯ããäžé£ã®æšæºããŒãã®ã³ã³ããã§ãããå ¥åãžã®æ¥ç¶ã¯çŽæ¥ã§ã¯ãªããå ¥åããããã£ãä»ããŠè¡ãããã GainNodeã®ããŒã¹ããŒãã§ããã
ãã¹ããå®è£ ãå®è¡ã 次ã®åœ¢åŒã®ãŠã§ãŒãããããŸãã

ãã®ã³ãŒãã§ããã«å°éããŸããïŒ
ãšã³ãŒå®è£
function Delay(audioCtx) { this._audioCtx = audioCtx; this.input = audioCtx.createGain(); this._delayLines = []; this._gainNodes = []; this._delayLinesInput = audioCtx.createGain(); this._output = audioCtx.createGain(); this._taps = 0; this._latency = 0; this._feedback = 0; Object.defineProperty(this, "feedback", { set: function (freq) { this._feedback = freq; this._applyParams(); }, get: function() { return this._feedback; } }); Object.defineProperty(this, "latency", { set: function (freq) { this._latency = freq; this._applyParams(); }, get: function() { return this._latency; } }); Object.defineProperty(this, "taps", { set: function (value) { var prevTaps = this._taps; var diff = value - this._taps; for(var i = 0; i < diff; i++) { diff < 0 ? this._popTap() : this._pushTap(); } this._taps = value; }, get: function() { return this._taps; } }); this.input.connect(this._output); } Delay.prototype._applyParams = function() { for(var i = 0; i < this._delayLines.length; i++) { this._delayLines[i].delayTime.value = this._latency / 1000 * (i + 1); this._gainNodes[i].gain.value = Math.pow(this._feedback, (1 + i)) } }; Delay.prototype._pushTap = function() { var delay = this._audioCtx.createDelay(10.0); this._delayLines.push(delay); var gainNode = this._audioCtx.createGain(); this._gainNodes.push(gainNode); gainNode.connect(this._output); delay.connect(gainNode); this._delayLinesInput.connect(delay); }; Delay.prototype._popTap = function() { var lastDelayLine = this._delayLines.pop(); var lastGainNode = this._gainNodes.pop(); lastDelayLine.disconnect(lastGainNode); lastGainNode.disconnect(this._output); this._delayLinesInput.disconnect(lastDelayLine); }; Delay.prototype.start = function() { if (!this._started) { this.input.connect(this._delayLinesInput); this._started = true; } } Delay.prototype.stop = function() { if (this._started) { this.input.disconnect(this._delayLinesInput); this._started = false; } }; Delay.prototype.connect = function(target) { this._output.connect(target); }; module.exports = Delay;
é²æ©ã¯æããã§ãïŒå¹æã®å€§éšåã®é©çšããçããé³
ç¹ã«ãã®å¹æãå æããã«ã¯ããã®äž»ãªæ©èœã«ãã€ããã¯ã¹ãäžããããã«ãããããADSRãšã³ãããŒãïŒAttack-Decay-Sustain-ReleaseïŒããããŸããããã¯ãåæãããæ³¢ã®åœ¢ç¶ãæéçã«ç¹åŸŽä»ããå®éã®æ¥œåšã§åãããé³ã®åäœãè¿äŒŒçã«èª¬æããŸãã

ããªã¥ãŒã ã®ã¹ã ãŒãºãªå¢æžã®çµæãšããŠããã®ãããªãšã³ãããŒããåçãããåããŒãã«é©çšãããšãåçŽããã³ãã®ãžã£ã³ãã®ãããªå€±éãåé€ãããŸããããã¯ãé«åšæ³¢æ°ã¯ãªãã¯ãšããŠè³ã§ç¥èŠãããŸãã ãããã·ããã®å Žåã®ããã«ãå®è£ ã¯ã·ã³ã»ãµã€ã¶ãŒã«çŽæ¥å ããããŸãã åãªã·ã¬ãŒã¿ãŒãäœæãããšããã¢ã³ããšåºåããŒãã®éã«ã¢ã³ããé 眮ããADSRãšã³ãããŒãã®æå®ããããã©ã¡ãŒã¿ãŒã«åŸã£ãŠã²ã€ã³ãå¶åŸ¡ããŸãã
ã³ãŒãã®äžéšã¯ããæ¬ç©ã§ã
function ADSR() { this.ADSR = { A: null, D: null, S: null, R: null }; var oscillators = {}; var gainNodes = {}; var old = { play: this.play, stop: this.stop }; this.play = function(note) { var osc = oscillators[note.pitch] = old.play.call(this, note); var gain = gainNodes[note.pitch] = this.audioContext.createGain(); osc.disconnect(this.output); osc.connect(gain); gain.connect(this.output); gain.gain.value = 0; this.ADSR.A = parseInt(this.ADSR.A); this.ADSR.D = parseInt(this.ADSR.D); this.ADSR.S = parseFloat(this.ADSR.S); this.ADSR.R = parseInt(this.ADSR.R); var this_ = this; var startedAt = Date.now(); var interval = setInterval(function() { var diff = Date.now() - startedAt; if (diff < this_.ADSR.A) { gain.gain.value = diff / this_.ADSR.A; } else if (diff < this_.ADSR.A + this_.ADSR.D) { gain.gain.value = 1 - (diff - this_.ADSR.A) / (this_.ADSR.D / (1 - this_.ADSR.S)); } else { gain.gain.value = this_.ADSR.S; clearInterval(interval); } }, 10); return osc; }; this.stop = function(note) { var releasedAt = Date.now(); var this_ = this; var arguments_ = arguments; var gain = gainNodes[note.pitch]; var osc = oscillators[note.pitch]; var gainOnRelease = gain.gain.value; var interval = setInterval(function() { var diff = Date.now() - releasedAt; if (diff < this_.ADSR.R) { gain.gain.value = gainOnRelease * (1 - diff / this_.ADSR.R); } else { clearInterval(interval); gain.gain.value = 0; old.stop.apply(this_, arguments_); osc.disconnect(gainNodes[note.pitch]); gain.disconnect(this.output); delete oscillators[note.pitch]; delete gain[note.pitch]; } }, 20); }; } module.exports = ADSR;

ããã§ããã®ã¹ãããŒã¯ãå°å ¥ããŠã人éã®ãªã¹ãã³ã°ã«é©ãããµãŠã³ããçæããå®å šã«æ©èœããåºæ¬çãªã·ã³ã»ãµã€ã¶ãŒãæã«å ¥ããŸããã ãããªãæ¹åã¹ãããã¯ãäŸãã°ããªã·ã¬ãŒã¿ãŒãšã¢ãžã¥ã¬ãŒã¿ãŒã®èŠèŠçãªäœæããã¥ãŒãã³ã°ãã¹ã€ããã³ã°ãªã©ã®ã€ã³ã¿ãŒãã§ãŒã¹ã®å€æŽãããã³åæèªäœã«é¢ããŠããã£ã«ã¿ãŒã®è¿œå ãé«èª¿æ³¢ãéç·åœ¢æªã¿ãªã©ã®å°å ¥ãªã©ã§ãããã ããããã¯ãããªãç 究ã®ãããã¯ã§ãã 次ã®èšäºã§ã¯ãMIDIã€ã³ã¹ãã¥ã«ã¡ã³ããç¹ã«ããŒããŒããšã®ã¿ãŒãçµæã®ã·ã³ã»ãµã€ã¶ãŒã«æ¥ç¶ãããµãŠã³ããšå®éã®ãªãŒãã£ãªãšãã§ã¯ãã®é²é³ã«åãæ¿ããäºå®ã§ãã ãã¡ãããããã¯ãã¹ãŠãã©ãŠã¶ãŒã§ïŒ
ããã°ã©ã ã®ãã¢ã¯ã次ã®ãªã³ã¯ããå ¥æã§ããŸããmiroshko.github.io / Synzer
ãã¹ãŠã®ãœãŒã¹ã³ãŒãã¯githubïŒ github.com/miroshko/Synzerã§å ¥æã§ããŸããã¹ã¿ãŒããã©ãŒã¯ããã«ãªã¯ãšã¹ãã«æºè¶³ããŸãã