├── package.json ├── lib ├── get-voltage.js └── augmented-on-ended.js ├── demo ├── index.html ├── main.js └── bundle.js ├── readme.md └── index.js /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "snare", 3 | "version": "2.8.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "noise-buffer": "^1.0.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/get-voltage.js: -------------------------------------------------------------------------------- 1 | module.exports = function getVoltage(context) { 2 | var buffer = context.createBuffer(1, 2, 44100); 3 | var data = buffer.getChannelData(0); 4 | data[0] = 1; 5 | data[1] = 1; 6 | var source = context.createBufferSource(); 7 | source.buffer = buffer; 8 | source.loop = true; 9 | return source; 10 | } 11 | -------------------------------------------------------------------------------- /lib/augmented-on-ended.js: -------------------------------------------------------------------------------- 1 | module.exports = function augmentedOnEnded(triggerNode, fn) { 2 | var context = triggerNode.context; 3 | if (context instanceof (window.OfflineAudioContext || window.webkitOfflineAudioContext)) { 4 | context.suspend().then(function() { 5 | fn(); 6 | context.resume(); 7 | }); 8 | } else if (context instanceof (window.AudioContext || window.webkitAudioContext)) { 9 | triggerNode.onended = fn; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 |

Snare

16 | 17 | 18 |
19 | 20 | 21 |
22 | 23 | 24 |
25 | 26 | 27 |
28 |
29 | 30 |
31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ##Usage 2 | 3 | `npm install --save snare` 4 | 5 | ```javascript 6 | var Snare = require('snare'); 7 | 8 | // Initialize AudioContext 9 | var context = new AudioContext(); 10 | 11 | // Initialize instrument 12 | var snare = Snare(context); 13 | 14 | // Create snare audio node (one time use only) 15 | var snareNode = snare(); 16 | 17 | // Connect to target node 18 | snareNode.connect(context.destination); 19 | 20 | /* 21 | * detune is connected to the oscillators' detune 22 | */ 23 | snareNode.detune instanceof AudioParam 24 | // -> true 25 | snareNode.detune.value = 1200; 26 | 27 | /* 28 | * snappy controls the gain of the noise 29 | */ 30 | snareNode.snappy instanceof AudioParam 31 | // -> true 32 | snareNode.snappy.value = 0.75 33 | 34 | /* 35 | * tone controls the mix between low and high tones 36 | */ 37 | snareNode.tone instanceof AudioParam 38 | // -> true 39 | snareNode.tone.value = 0.5 40 | 41 | /* 42 | * duration defaults to 0.3 43 | */ 44 | snareNode.duration = 0.2 45 | 46 | // Start 47 | snareNode.start(context.currentTime); 48 | ``` 49 | -------------------------------------------------------------------------------- /demo/main.js: -------------------------------------------------------------------------------- 1 | var Snare = require('../index'); 2 | var snare; 3 | 4 | var ct = 0; 5 | 6 | window.reset = function reset() { 7 | if (window.context) { 8 | window.context.close(); 9 | } 10 | var context = new AudioContext(); 11 | window.context = context; 12 | snare = Snare(context); 13 | } 14 | reset(); 15 | 16 | document.getElementById('play').addEventListener('click', function(e) { 17 | if (ct % 100 === 0) { 18 | console.log(ct); 19 | } 20 | ct++; 21 | 22 | /* 23 | var osc = context.createOscillator(); 24 | osc.connect(context.destination); 25 | osc.start(context.currentTime); 26 | osc.stop(context.currentTime + 0.03); 27 | */ 28 | var snareNode = snare(); 29 | 30 | snareNode.detune.value = parseInt(document.getElementById('detune').value); 31 | snareNode.duration = parseFloat(document.getElementById('duration').value); 32 | snareNode.snappy.value = parseFloat(document.getElementById('snappy').value); 33 | snareNode.tone.value = parseFloat(document.getElementById('tone').value); 34 | 35 | snareNode.connect(context.destination); 36 | snareNode.start(context.currentTime); 37 | }); 38 | 39 | var interval = false; 40 | document.getElementById('loop').addEventListener('click', function(e) { 41 | if (typeof interval === 'number') { 42 | clearInterval(interval); 43 | interval = false; 44 | } else { 45 | interval = setInterval(function() { 46 | document.getElementById('play').click(); 47 | }, 70); 48 | document.getElementById('play').click(); 49 | } 50 | }); 51 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var NoiseBuffer = require('noise-buffer'); 2 | var augmentedOnEnded = require('./lib/augmented-on-ended'); 3 | var getVoltage = require('./lib/get-voltage'); 4 | 5 | 6 | module.exports = function(context, parameters) { 7 | 8 | var playingNodes = []; 9 | 10 | parameters = parameters || {}; 11 | parameters.tone = typeof parameters.tone === 'number' ? parameters.tone : 64; 12 | parameters.snappy = typeof parameters.snappy === 'number' ? parameters.snappy : 64; 13 | 14 | var noiseBuffer = NoiseBuffer(1); 15 | 16 | return function() { 17 | 18 | var masterHighBump = context.createBiquadFilter(); 19 | masterHighBump.frequency.value = 4000; 20 | masterHighBump.gain.value = 6; 21 | masterHighBump.type = "peaking"; 22 | var masterLowBump = context.createBiquadFilter(); 23 | masterLowBump.frequency.value = 200; 24 | masterLowBump.gain.value = 12; 25 | masterLowBump.type = "peaking"; 26 | masterHighBump.connect(masterLowBump); 27 | 28 | 29 | var masterBus = context.createGain(); 30 | masterBus.gain.value = 0.4; 31 | masterBus.connect(masterHighBump); 32 | 33 | 34 | var noiseHighpass = context.createBiquadFilter(); 35 | noiseHighpass.type = "highpass"; 36 | noiseHighpass.frequency.value = 1200; 37 | noiseHighpass.connect(masterBus); 38 | 39 | 40 | var oscsHighpass = context.createBiquadFilter(); 41 | oscsHighpass.type = "highpass"; 42 | oscsHighpass.frequency.value = 400; 43 | oscsHighpass.connect(masterBus); 44 | 45 | var audioNode = context.createGain(); 46 | 47 | 48 | var noise = context.createBufferSource(); 49 | noise.buffer = noiseBuffer; 50 | noise.loop = true; 51 | 52 | 53 | var snappyGainNode = context.createGain(); 54 | snappyGainNode.gain.value = 1; 55 | audioNode.snappy = snappyGainNode.gain; 56 | 57 | var oscsPreChoke = context.createGain(); 58 | oscsPreChoke.gain.value = 0; 59 | var noisePreChoke = context.createGain(); 60 | noisePreChoke.gain.value = 0; 61 | 62 | 63 | var noiseGain = context.createGain(); 64 | noise.connect(snappyGainNode); 65 | snappyGainNode.connect(noiseGain); 66 | noiseGain.connect(noisePreChoke); 67 | noisePreChoke.connect(noiseHighpass); 68 | 69 | 70 | 71 | var oscsGain = context.createGain(); 72 | oscsGain.connect(oscsPreChoke); 73 | oscsPreChoke.connect(oscsHighpass); 74 | 75 | var voltage = getVoltage(context); 76 | var detuneGainNode = context.createGain(); 77 | detuneGainNode.gain.value = 0; 78 | audioNode.detune = detuneGainNode.gain; 79 | voltage.connect(detuneGainNode); 80 | 81 | var postChoke = context.createGain(); 82 | postChoke.gain.value = 0; 83 | 84 | 85 | var oscs = [87.307, 329.628].map(function(frequency) { 86 | var osc = context.createOscillator(); 87 | osc.frequency.value = frequency; 88 | detuneGainNode.connect(osc.detune); 89 | return osc; 90 | }); 91 | 92 | var toneNode = context.createGain(); 93 | toneNode.gain.value = 0.5; 94 | voltage.connect(toneNode); 95 | 96 | audioNode.tone = toneNode.gain; 97 | 98 | var oscAGainNode = context.createGain(); 99 | var oscBGainNode = context.createGain(); 100 | 101 | oscAGainNode.gain.value = -1; 102 | oscBGainNode.gain.value = 0; 103 | 104 | oscs[0].connect(oscAGainNode); 105 | oscs[1].connect(oscBGainNode); 106 | 107 | toneNode.connect(oscAGainNode.gain); 108 | toneNode.connect(oscBGainNode.gain); 109 | 110 | oscAGainNode.connect(oscsGain); 111 | oscBGainNode.connect(oscsGain); 112 | 113 | 114 | masterLowBump.connect(postChoke); 115 | postChoke.connect(audioNode); 116 | 117 | 118 | augmentedOnEnded(noise, function() { 119 | masterLowBump.disconnect(postChoke); 120 | }); 121 | 122 | 123 | audioNode.duration = 0.3; 124 | 125 | audioNode.start = function(when) { 126 | 127 | oscsPreChoke.gain.setValueAtTime(0, when + 0.0001); 128 | oscsPreChoke.gain.linearRampToValueAtTime(1, when + 0.0002); 129 | noisePreChoke.gain.setValueAtTime(0, when + 0.0001); 130 | noisePreChoke.gain.linearRampToValueAtTime(1, when + 0.0002); 131 | postChoke.gain.setValueAtTime(0, when + 0.0001); 132 | postChoke.gain.linearRampToValueAtTime(1, when + 0.0002); 133 | 134 | // each Snare instance is monophonic 135 | while (playingNodes.length) { 136 | playingNodes.pop().stop(when); 137 | } 138 | playingNodes.push(audioNode); 139 | 140 | if (typeof when !== "number") { 141 | when = context.currentTime; 142 | } 143 | 144 | noiseGain.gain.setValueAtTime(0.0001, when); 145 | noiseGain.gain.exponentialRampToValueAtTime(Math.max(0.0001, 1), 146 | when + Math.min(audioNode.duration * (0.01 / 0.3), 0.01)); 147 | noiseGain.gain.exponentialRampToValueAtTime(0.0001, 148 | when + audioNode.duration); 149 | 150 | oscsGain.gain.setValueAtTime(0.0001, when); 151 | oscsGain.gain.exponentialRampToValueAtTime(1, 152 | when + Math.min(0.01, audioNode.duration * (0.01 / 0.3))); 153 | oscsGain.gain.exponentialRampToValueAtTime(0.00001, 154 | when + audioNode.duration * 2/3); 155 | 156 | oscs.forEach(function(osc) { 157 | osc.start(when); 158 | osc.stop(when + audioNode.duration); 159 | }); 160 | 161 | noise.start(when); 162 | noise.stop(when + audioNode.duration); 163 | 164 | voltage.start(when); 165 | voltage.stop(when + audioNode.duration); 166 | 167 | 168 | }; 169 | audioNode.stop = function(when) { 170 | 171 | oscsPreChoke.gain.setValueAtTime(1, when); 172 | oscsPreChoke.gain.linearRampToValueAtTime(0, when + 0.0001); 173 | noisePreChoke.gain.setValueAtTime(1, when); 174 | noisePreChoke.gain.linearRampToValueAtTime(0, when + 0.0001); 175 | postChoke.gain.setValueAtTime(1, when); 176 | postChoke.gain.linearRampToValueAtTime(0, when + 0.0001); 177 | 178 | // Do not stop twice 179 | audioNode.stop = function() {}; 180 | audioNode.gain.setValueAtTime(1, when); 181 | audioNode.gain.linearRampToValueAtTime(0, when + 0.01); 182 | try { 183 | oscs.forEach(function(osc) { 184 | osc.stop(when + 0.01); 185 | }); 186 | noise.stop(when + 0.01); 187 | voltage.stop(when + 0.01); 188 | } catch (e) { 189 | // likely already stopped 190 | } 191 | }; 192 | return audioNode; 193 | }; 194 | }; 195 | -------------------------------------------------------------------------------- /demo/bundle.js: -------------------------------------------------------------------------------- 1 | (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o