├── style.css ├── audio ├── beat.mp3 ├── click.mp3 ├── piano-f3.mp3 └── symmetry.mp3 ├── images ├── demo.png ├── demo2.png ├── demo3.png ├── graph1.png ├── graph2.png └── graph3.png ├── package.json ├── test3.html ├── test2.html ├── test4.html ├── waveform.html ├── index.html ├── test.html ├── index-experiment2.html ├── index-experiment.html ├── README.md ├── js └── bt.js ├── WAM.js └── WAM2.js /style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #000 3 | } 4 | -------------------------------------------------------------------------------- /audio/beat.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taylorbf/WAM/HEAD/audio/beat.mp3 -------------------------------------------------------------------------------- /audio/click.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taylorbf/WAM/HEAD/audio/click.mp3 -------------------------------------------------------------------------------- /images/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taylorbf/WAM/HEAD/images/demo.png -------------------------------------------------------------------------------- /images/demo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taylorbf/WAM/HEAD/images/demo2.png -------------------------------------------------------------------------------- /images/demo3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taylorbf/WAM/HEAD/images/demo3.png -------------------------------------------------------------------------------- /images/graph1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taylorbf/WAM/HEAD/images/graph1.png -------------------------------------------------------------------------------- /images/graph2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taylorbf/WAM/HEAD/images/graph2.png -------------------------------------------------------------------------------- /images/graph3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taylorbf/WAM/HEAD/images/graph3.png -------------------------------------------------------------------------------- /audio/piano-f3.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taylorbf/WAM/HEAD/audio/piano-f3.mp3 -------------------------------------------------------------------------------- /audio/symmetry.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taylorbf/WAM/HEAD/audio/symmetry.mp3 -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wam", 3 | "description": "Web Audio Modules", 4 | "author": "Ben Taylor ", 5 | "repository": { 6 | "type": "git", 7 | "url": "http://github.com/taylorbf/WAM/" 8 | }, 9 | "engine": "node >= 0.10.0", 10 | "version": "0.0.2", 11 | "license": "New BSD License" 12 | } 13 | -------------------------------------------------------------------------------- /test3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 22 | -------------------------------------------------------------------------------- /test2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 23 | -------------------------------------------------------------------------------- /test4.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 25 | -------------------------------------------------------------------------------- /waveform.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 24 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 23 | -------------------------------------------------------------------------------- /test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 54 | -------------------------------------------------------------------------------- /index-experiment2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 80 | 81 | -------------------------------------------------------------------------------- /index-experiment.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 86 | 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #WAM: Web Audio Modules 2 | 3 | ![Demo Image](images/demo3.png) 4 | 5 | **WAM** is a collection of open-source modular computer music components built by the web audio community. 6 | 7 | Each module includes a web audio engine & HTML5 GUI. Modules are written in js object literal notation, according to the WAM spec. 8 | 9 | By encapsulating and sharing our common (& uncommon) computer music devices, WAM is designed to assist building expressive musical instruments in the browser. 10 | 11 | [**Demo**](http://taylorbf.github.io/WAM) 12 | 13 | [**Video: "How to contribute a module"**](https://youtu.be/HTw1pRo5HtE) 14 | 15 | ## Current Modules 16 | 17 | Generators: **sine**, **FMSeq**, **microphone**, **btlooper**, **scratch** 18 | 19 | Effects: **delay**, **enveloop** 20 | 21 | Output/Analysis: **meter**, **mixer4ch**, **out** 22 | 23 | 24 | ## Building an Instrument 25 | 26 | A complete WAM instrument looks something like this: 27 | 28 | 29 | ```js 30 | WAM.setContext( Tone.context ) 31 | 32 | var rack1 = WAM.route([ 33 | WAM.microphone(), 34 | WAM.delay(), 35 | WAM.mixer4ch(), 36 | WAM.out() 37 | ]) 38 | ``` 39 | 40 | The parts... 41 | 42 | #### Assigning Context 43 | 44 | Tell WAM about your current audio context: 45 | 46 | ```js 47 | WAM.setContext( ctx ) 48 | ``` 49 | 50 | #### Adding Modules 1-by-1 (not recommended) 51 | 52 | Individual modules can be added to your project using **WAM.*moduleName*()** and connected to each other (or to any web audio node) using `.connect()`. 53 | 54 | ```js 55 | var mySine = WAM.sine() 56 | var myDelay = WAM.delay() 57 | mySine.connect(myDelay) 58 | myDelay.connect( WAM.out() ) 59 | ``` 60 | 61 | #### Chaining Modules (recommended) 62 | 63 | Groups of modules can be created and connected simultaneously using `WAM.route()`. This way you don't need to `.connect()` every module. 64 | 65 | ```js 66 | var rack1 = WAM.route([ 67 | WAM.sine(), 68 | WAM.delay(), 69 | WAM.out() 70 | ]) 71 | ``` 72 | 73 | ...creates the audiograph: 74 | 75 | ![AudioGraph](images/graph1.png) 76 | 77 | More complex audiographs can be created using `WAM.join()` along with `WAM.route()`: 78 | 79 | ```js 80 | var rack1 = WAM.route([ 81 | WAM.join( WAM.sine(), WAM.sine(), WAM.sine() ), 82 | WAM.delay(), 83 | WAM.out() 84 | ]) 85 | ``` 86 | 87 | ...creates the audiograph: 88 | 89 | ![AudioGraph](images/graph2.png) 90 | 91 | and this... 92 | 93 | ```js 94 | var rack1 = WAM.route([ 95 | WAM.join( WAM.sine(), WAM.sine(), WAM.sine() ), 96 | WAM.join( WAM.delay(), WAM.delay() ), 97 | WAM.out() 98 | ]) 99 | ``` 100 | 101 | ...creates the audiograph: 102 | 103 | ![AudioGraph](images/graph3.png) 104 | 105 | You can also connect groups: 106 | 107 | ```js 108 | var rack1 = WAM.route([ 109 | WAM.join( WAM.sine(), WAM.sine(), WAM.sine() ), 110 | WAM.delay() 111 | ]) 112 | 113 | var rack2 = WAM.route([ 114 | WAM.join( WAM.sine(), WAM.sine(), WAM.sine() ), 115 | WAM.delay() 116 | ]) 117 | 118 | var rack3 = WAM.route([ 119 | WAM.reverb(), 120 | WAM.out() 121 | ]) 122 | 123 | rack1.connect(rack3) 124 | rack2.connect(rack3) 125 | ``` 126 | 127 | #### Positioning Modules on the Page 128 | 129 | By default, modules are positioned on the page relative to each other, within the flow of the document. 130 | 131 | To place a module precisely on the page, specify x/y when creating the module. Example: 132 | 133 | ``` 134 | WAM.sine(100,200) 135 | ``` 136 | 137 | ... creates a sine module 100 px from the left and 200 px from the top of the document. 138 | 139 | ## Contributing Modules 140 | 141 | **All users are encouraged to add modules to WAM! I am accepting all pull requests of working modules. These can be audio effects, signal generators, granular synths... the sky's the limit.** 142 | 143 | Modules are written as JS object literals within WAM.js. Each object follows the same pattern of properties and methods. 144 | 145 | ##### Example Module: "Sine" (sine oscillator with frequency and volume controls) 146 | 147 | ```js 148 | Modules.sine = { 149 | dependencies: [ "Tone" ], 150 | size: { 151 | w: 80, 152 | h: 52 153 | }, 154 | audio: function() { 155 | this.toneosc = new Tone.Oscillator(440, "sine").start(); 156 | this.toneosc.connect(this.output) 157 | }, 158 | interface: [ 159 | { 160 | type: "dial", 161 | label: "freq", 162 | action: function(data) { 163 | this.toneosc.frequency.value = data.value * 1000 164 | }, 165 | size: { 166 | w: 40, 167 | h: 40 168 | }, 169 | location: { 170 | x: 0, 171 | y: 0 172 | } 173 | }, 174 | { 175 | type: "dial", 176 | label: "vol", 177 | action: function(data) { 178 | this.toneosc.volume.value = -100 + data.value*100 179 | }, 180 | size: { 181 | w: 40, 182 | h: 40 183 | }, 184 | location: { 185 | x: 40, 186 | y: 0 187 | } 188 | } 189 | ] 190 | } 191 | ``` 192 | 193 | ##### But what does it all mean? 194 | 195 | The core of a module is: 196 | 197 | - A bit of web audio code (held in the `audio` function) which is executed once when the module loads. 198 | - A list of NexusUI interface components which have actions that affect the audio code depending on data from the interface. Interface actions are executed whenever the interface is interacted with. 199 | 200 | This is the same "sine" module with all size and location details removed. It should show a clearer picture of the module's core components. 201 | 202 | ```js 203 | Modules.sine = { 204 | audio: function() { 205 | this.toneosc = new Tone.Oscillator(440, "sine").start(); 206 | this.toneosc.connect(this.output) 207 | }, 208 | interface: [ 209 | { 210 | type: "dial", 211 | label: "freq", 212 | action: function(data) { 213 | this.toneosc.frequency.value = data.value * 1000 214 | } 215 | }, 216 | { 217 | type: "dial", 218 | label: "vol", 219 | action: function(data) { 220 | this.toneosc.volume.value = -100 + data.value*100 221 | } 222 | } 223 | ] 224 | } 225 | ``` 226 | 227 | 228 | ## Anatomy of a Module 229 | 230 | Each module also has the following built-in properties, which may be useful during development: 231 | 232 | ##### this.input 233 | 234 | Gain node forming the audio input to the current module. This is connected to by other modules. 235 | 236 | ##### this.output 237 | 238 | Gain node forming the audio output of the current module. This connects to other modules. 239 | 240 | ##### this.shell 241 | 242 | The <div> containing the module. 243 | 244 | ##### this.components 245 | 246 | An array of the NexusUI interface components in this module. 247 | 248 | ##### this.type 249 | 250 | The type of this module (i.e. "sine"). 251 | 252 | 253 | ## Anatomy of WAM 254 | 255 | 256 | ##### WAM.context 257 | 258 | AudioContext, set with `WAM.setContext()` 259 | 260 | ##### WAM.modules 261 | 262 | Array of modules created within the current project. 263 | 264 | -------------------------------------------------------------------------------- /js/bt.js: -------------------------------------------------------------------------------- 1 | mt = {} 2 | 3 | 4 | /* @method toPolar 5 | @description Receives cartesian coordinates and returns polar coordinates as an object with 'radius' and 'angle' properties. 6 | @param {float} [x] 7 | @param {float} [y] 8 | */ 9 | mt.toPolar = function(x,y) { 10 | var r = Math.sqrt(x*x + y*y); 11 | 12 | var theta = Math.atan2(y,x); 13 | if (theta < 0.) { 14 | theta = theta + (2 * Math.PI); 15 | } 16 | return {radius: r, angle: theta}; 17 | } 18 | 19 | /* @method toCartesian 20 | @description Receives polar coordinates and returns cartesian coordinates as an object with 'x' and 'y' properties. 21 | @param {float} [radius] 22 | @param {float} [angle] 23 | */ 24 | mt.toCartesian = function(radius, angle){ 25 | var cos = Math.cos(angle); 26 | var sin = Math.sin(angle); 27 | return {x: radius*cos, y: radius*sin*-1}; 28 | } 29 | 30 | 31 | /* @method clip 32 | @description Limits a number to within low and high values. 33 | @param {float} [input value] 34 | @param {float} [low limit] 35 | @param {float} [high limit] 36 | */ 37 | mt.clip = function(value, low, high) { 38 | return Math.min(high, Math.max(low, value)); 39 | } 40 | 41 | /* @method prune 42 | @description Limits a float to within a certain number of decimal places 43 | @param {float} [input value] 44 | @param {integer} [max decimal places] 45 | */ 46 | 47 | mt.prune = function(data, scale) { 48 | if (typeof data === "number") { 49 | data = parseFloat(data.toFixed(scale)); 50 | } else if (data instanceof Array) { 51 | for (var i=0;i newrate) { 147 | clearTimeout(this.timeout) 148 | this.pulse(); 149 | } else if (newrate < oldrate) { 150 | clearTimeout(this.timeout) 151 | var delay = this.rate - (this.time.cur - this.time.last); 152 | if (delay < 0 ) { delay = 0 } 153 | this.timeout = setTimeout(this.pulse.bind(this),delay) 154 | } 155 | } 156 | this.start(); 157 | } 158 | 159 | 160 | /* 161 | * @method interval 162 | * @description interval with controllable speed / interval time 163 | * @param {number} [rate] 164 | * @param {function} [callback] 165 | */ 166 | mt.interval = function(rate,func) { 167 | var _int = new mt.VariableSpeedInterval(rate,func) 168 | return _int; 169 | } 170 | 171 | /* use like this: 172 | // func is optional 173 | var x = interval(50, function() { bla ... }) 174 | x.ms(100); 175 | x.stop() 176 | // later 177 | x.start() 178 | //can change function midway 179 | x.event = function() { ... } 180 | 181 | */ 182 | 183 | 184 | /** 185 | * Returns a random entry from the arguments 186 | */ 187 | mt.pick = function() { 188 | 189 | return arguments[mt.random(arguments.length)] 190 | 191 | } 192 | 193 | /** 194 | * A major scale 195 | * @type {Array} 196 | */ 197 | mt.major = [1/1,9/8,5/4,4/3,3/2,5/3,15/8] 198 | 199 | /** 200 | * returns an octave multiplier (i.e. octave(0) return 1, octave (-1) returns 0.5) 201 | * @param {integer} num Octave 202 | */ 203 | mt.octave = function(num) { 204 | return Math.pow(2,num) 205 | } 206 | 207 | 208 | 209 | mt.getCol = function(index,limit) { 210 | return index%limit; 211 | } 212 | 213 | mt.getRow = function(index,limit) { 214 | return Math.floor(index/limit); 215 | } 216 | 217 | mt.pick = function(array) { 218 | return array[mt.random(array.length)]; 219 | } 220 | 221 | 222 | /** @method r 223 | @description Returns a random integer between two given scale parameters. If only one argument, uses 0 as the minimum. 224 | @param {float} [min] Lower limit of random range. 225 | @param {float} [max] Upper limit of random range. 226 | */ 227 | mt.r = mt.random = function(bound1,bound2) { 228 | if (!bound2) { 229 | bound2 = bound1 230 | bound1 = 0 231 | } 232 | var low = Math.min(bound1,bound2) 233 | var high = Math.max(bound1,bound2) 234 | return Math.floor(Math.random()*(high-low)+low) 235 | } 236 | /** @method rf 237 | @description Returns a random float between 0 and a given scale parameter. If only one argument, uses 0 as the minimum. 238 | @param {float} [min] Lower limit of random range. 239 | @param {float} [max] Upper limit of random range. 240 | */ 241 | mt.rf = function(bound1,bound2) { 242 | if (!bound2) { 243 | bound2 = bound1 244 | bound1 = 0 245 | } 246 | var low = Math.min(bound1,bound2) 247 | var high = Math.max(bound1,bound2) 248 | return Math.random()*(high-low)+low 249 | } 250 | 251 | /** @method cycle 252 | @description Count a number upwards until it reaches a maximum, then send it back to a minimum value.
253 | I.e. cycle(x,0,5) will output 0,1,2,3,4,5,0,1,2... if called many times in succession 254 | @param {float} [min] Lower limit of random range. 255 | @param {float} [min] Lower limit of random range. 256 | @param {float} [max] Upper limit of random range. 257 | */ 258 | mt.cycle = function(input,min,max) { 259 | input++; 260 | if (input >= max) { 261 | input = min; 262 | } 263 | return input; 264 | } 265 | 266 | mt.loadScript = function (url, callback) { 267 | var head = document.getElementsByTagName('head')[0]; 268 | var script = document.createElement('script'); 269 | script.type = 'text/javascript'; 270 | script.src = url; 271 | 272 | script.onreadystatechange = callback; 273 | script.onload = callback; 274 | 275 | head.appendChild(script); 276 | } 277 | 278 | 279 | window.SmartMatrix = function(cols,rows) { 280 | 281 | this.rows = rows; 282 | this.cols = cols; 283 | 284 | this.row = 0; 285 | this.col = 0; 286 | 287 | this.index = 0; 288 | this.max = this.rows * this.cols; 289 | 290 | this.advance = function() { 291 | 292 | this.index++; 293 | 294 | if (this.index >= this.max ) { 295 | this.index = 0; 296 | } 297 | 298 | this.getCoordinate(this.index); 299 | 300 | } 301 | 302 | this.getCoordinate = function(index) { 303 | this.index = index; 304 | this.row = Math.floor(index/this.cols) 305 | this.col = index - (this.row * this.cols) 306 | this.ping(); 307 | } 308 | 309 | this.ping = function() { 310 | return { 311 | row: this.row, 312 | col: this.col 313 | } 314 | } 315 | 316 | this.advanceRow = function() { 317 | this.row++ 318 | if (this.row >= this.rows) { 319 | this.row = 0 320 | } 321 | this.setIndex() 322 | this.ping() 323 | } 324 | 325 | this.advanceCol = function() { 326 | this.col++ 327 | if (this.col >= this.cols) { 328 | this.col = 0 329 | } 330 | this.setIndex() 331 | this.ping() 332 | } 333 | 334 | this.setIndex = function() { 335 | this.index = this.col + (this.row * this.cols) 336 | } 337 | 338 | //have a 'translate index to row' func, same for col 339 | //have a 'step through' function that returns an object with next row/col 340 | //have a 'step row' that goes to next in row, then cycles around 341 | //have a 'steo col' that goes through col and cycles around 342 | 343 | } 344 | -------------------------------------------------------------------------------- /WAM.js: -------------------------------------------------------------------------------- 1 | 2 | var Wam = function(Modules) { 3 | 4 | this.context; 5 | this.modules = [] 6 | 7 | nx.onload = function() { 8 | nx.colorize("black") 9 | nx.colorize("fill","#e2e2e2") 10 | } 11 | 12 | for (var key in Modules) { 13 | this[key] = this.make.bind(this,key) 14 | } 15 | 16 | } 17 | 18 | 19 | Wam.prototype.setContext = function(context) { 20 | this.context = context 21 | } 22 | 23 | Wam.prototype.out = function() { 24 | return { input: this.context.destination } 25 | } 26 | 27 | Wam.prototype.route = function(path) { 28 | for (var i=0;i= 0 && this.player.reverse) { 480 | this.player.reverse = false; 481 | } 482 | data.speed = Math.abs(data.speed) 483 | this.player.playbackRate = data.speed 484 | } 485 | }, 486 | size: { 487 | w: 100, 488 | h: 100 489 | }, 490 | loc: { 491 | x: 0, 492 | y: 0 493 | } 494 | } 495 | ]}, 496 | "mixer4ch": { 497 | size: { 498 | w: 200, 499 | h: 100 500 | }, 501 | audio: function() { 502 | this.input = WAM.context.createChannelMerger(4); 503 | this.splitter = WAM.context.createChannelSplitter(4); 504 | 505 | this.input.connect(this.splitter) 506 | 507 | this.panvol1 = new Tone.PanVol(0.5, -2); 508 | this.panvol2 = new Tone.PanVol(0.5, -2); 509 | this.panvol3 = new Tone.PanVol(0.5, -2); 510 | this.panvol4 = new Tone.PanVol(0.5, -2); 511 | this.components[8].setup(WAM.context,this.panvol1.output.output); 512 | this.components[9].setup(WAM.context,this.panvol2.output.output); 513 | this.components[10].setup(WAM.context,this.panvol3.output.output); 514 | this.components[11].setup(WAM.context,this.panvol4.output.output); 515 | this.splitter.connect(this.panvol1,0) 516 | this.splitter.connect(this.panvol2,1) 517 | this.splitter.connect(this.panvol3,2) 518 | this.splitter.connect(this.panvol4,3) 519 | 520 | 521 | this.panvol1.output.connect(this.output) 522 | this.panvol2.output.connect(this.output) 523 | this.panvol3.output.connect(this.output) 524 | this.panvol4.output.connect(this.output) 525 | 526 | }, 527 | interface: [ 528 | { 529 | type: "slider", 530 | label: "1", 531 | action: function(data) { 532 | this.panvol1.volume.value = -50 + data.value*50 533 | }, 534 | size: { 535 | w: 20, 536 | h: 90 537 | }, 538 | loc: { 539 | x: 0, 540 | y: 0 541 | } 542 | }, 543 | { 544 | type: "slider", 545 | label: "2", 546 | action: function(data) { 547 | this.panvol2.volume.value = -50 + data.value*50 548 | }, 549 | size: { 550 | w: 20, 551 | h: 90 552 | }, 553 | loc: { 554 | x: 50, 555 | y: 0 556 | } 557 | }, 558 | { 559 | type: "slider", 560 | label: "3", 561 | action: function(data) { 562 | this.panvol3.volume.value = -50 + data.value*50 563 | }, 564 | size: { 565 | w: 20, 566 | h: 90 567 | }, 568 | loc: { 569 | x: 100, 570 | y: 0 571 | } 572 | }, 573 | { 574 | type: "slider", 575 | label: "4", 576 | action: function(data) { 577 | this.panvol4.volume.value = -50 + data.value*50 578 | }, 579 | size: { 580 | w: 20, 581 | h: 90 582 | }, 583 | loc: { 584 | x: 150, 585 | y: 0 586 | } 587 | }, 588 | { 589 | type: "dial", 590 | label: "pan", 591 | action: function(data) { 592 | this.panvol1.pan.value = data.value 593 | }, 594 | size: { 595 | w: 25, 596 | h: 25 597 | }, 598 | loc: { 599 | x: 23, 600 | y: 0 601 | } 602 | }, 603 | { 604 | type: "dial", 605 | label: "pan", 606 | action: function(data) { 607 | this.panvol2.pan.value = data.value 608 | }, 609 | size: { 610 | w: 25, 611 | h: 25 612 | }, 613 | loc: { 614 | x: 73, 615 | y: 0 616 | } 617 | }, 618 | { 619 | type: "dial", 620 | label: "pan", 621 | action: function(data) { 622 | this.panvol3.pan.value = data.value 623 | }, 624 | size: { 625 | w: 25, 626 | h: 25 627 | }, 628 | loc: { 629 | x: 123, 630 | y: 0 631 | } 632 | }, 633 | { 634 | type: "dial", 635 | label: "pan", 636 | action: function(data) { 637 | this.panvol4.pan.value = data.value 638 | }, 639 | size: { 640 | w: 25, 641 | h: 25 642 | }, 643 | loc: { 644 | x: 173, 645 | y: 0 646 | } 647 | }, 648 | { 649 | type: "meter", 650 | label: "db", 651 | action: function(data) { 652 | 653 | }, 654 | size: { 655 | w: 20, 656 | h: 50 657 | }, 658 | loc: { 659 | x: 25, 660 | y: 40 661 | } 662 | }, 663 | { 664 | type: "meter", 665 | label: "db", 666 | action: function(data) { 667 | 668 | }, 669 | size: { 670 | w: 20, 671 | h: 50 672 | }, 673 | loc: { 674 | x: 75, 675 | y: 40 676 | } 677 | }, 678 | { 679 | type: "meter", 680 | label: "db", 681 | action: function(data) { 682 | 683 | }, 684 | size: { 685 | w: 20, 686 | h: 50 687 | }, 688 | loc: { 689 | x: 125, 690 | y: 40 691 | } 692 | }, 693 | { 694 | type: "meter", 695 | label: "db", 696 | action: function(data) { 697 | 698 | }, 699 | size: { 700 | w: 20, 701 | h: 50 702 | }, 703 | loc: { 704 | x: 175, 705 | y: 40 706 | } 707 | } 708 | ]}, 709 | "FMSeq": { 710 | size: { 711 | w: 240, 712 | h: 185 713 | }, 714 | audio: function() { 715 | this.unit = new Tone.PolySynth(4, Tone.FMSynth); 716 | this.unit.connect(this.output) 717 | }, 718 | interface: [ 719 | { 720 | label: "volume", 721 | type: "dial", 722 | action: function(data) { 723 | this.unit.volume.rampTo(-50+data.value*50,1); 724 | }, 725 | initial: { 726 | "value": 0.75 727 | }, 728 | size: { 729 | w: 40, 730 | h: 40 731 | }, 732 | loc: { 733 | x: 0, 734 | y: 0 735 | } 736 | },{ 737 | label: "harm", 738 | type: "dial", 739 | action: function(data) { 740 | this.unit.harmonicity.value = data.value*5; 741 | }, 742 | size: { 743 | w: 40, 744 | h: 40 745 | }, 746 | loc: { 747 | x: 40, 748 | y: 0 749 | } 750 | },{ 751 | label: "mod", 752 | type: "dial", 753 | action: function(data) { 754 | this.unit.modulationIndex.value = data.value*100; 755 | }, 756 | size: { 757 | w: 40, 758 | h: 40 759 | }, 760 | loc: { 761 | x: 80, 762 | y: 0 763 | } 764 | },{ 765 | label: "glide", 766 | type: "dial", 767 | action: function(data) { 768 | this.unit.portamento = data.value; 769 | }, 770 | size: { 771 | w: 40, 772 | h: 40 773 | }, 774 | loc: { 775 | x: 120, 776 | y: 0 777 | } 778 | },{ 779 | label: "pitch", 780 | type: "matrix", 781 | action: function(data) { 782 | var major = [0,2,4,5,7,9,11,12] 783 | if (data.list) { 784 | for (var i=0;i=this.col) { 810 | this.place=0; 811 | } 812 | }.bind(this), "24n"); 813 | }.bind(this),Tone.Transport.nextBeat('1n')) */ 814 | } 815 | } 816 | ]}, 817 | "microphone": { 818 | size: { w: 80 , h: 50 }, 819 | audio: function() { 820 | this.unit = new Tone.Microphone() 821 | this.unit.connect(this.output) 822 | }, 823 | interface: [ 824 | { 825 | label: "volume", 826 | type: "dial", 827 | action: function(data) { 828 | this.unit.volume.value = -50 + data.value*50; 829 | }, 830 | initial: { 831 | value: 0.5 832 | }, 833 | size: { w: 40 , h: 40 }, 834 | loc: { x: 0 , y: 0 } 835 | }, 836 | { 837 | label: "on", 838 | type: "toggle", 839 | action: function(data) { 840 | if (data.value) { 841 | this.unit.start(); 842 | } else { 843 | this.unit.stop(); 844 | } 845 | }, 846 | size: { w: 40 , h: 40 }, 847 | loc: { x: 40 , y: 0 } 848 | } 849 | ]}, 850 | } 851 | 852 | 853 | 854 | Modules.clix = { 855 | size: { w: 375 , h: 150 }, 856 | audio: function() { 857 | //this.noise = new Tone.Oscillator(0.01,"square").start() 858 | // this.noise = new Tone.Player("./audio/click.mp3") 859 | // this.noise.retrigger = true 860 | this.env = new Tone.AmplitudeEnvelope(0.02,0,1,0.02) 861 | this.noise = new Tone.Signal(1); 862 | this.filter = new Tone.Filter() 863 | this.reverb = new Tone.JCReverb(0.3) 864 | this.gain = new Tone.Volume(47); 865 | this.reverb.wet.value = 0.1 866 | this.filter.type = "bandpass" 867 | this.filter.Q.value = 50 868 | this.filter.gain.value = 1 869 | this.noise.connect(this.filter) 870 | this.filter.connect(this.env) 871 | this.env.connect(this.gain) 872 | this.gain.connect(this.reverb) 873 | this.reverb.connect(this.output) 874 | 875 | this.queue = [] 876 | this.beat = 0; 877 | this.gains = [1.0, 0.2, 0.3, 0.2, 0.4, 0.1, 0.2, 0.1, 878 | 0.5, 0.1, 0.3, 0.2, 0.4, 0.1, 0.2, 0.1, 879 | 0.8, 0.1, 0.3, 0.2, 0.5, 0.1, 0.2, 0.1, 880 | 0.4, 0.1, 0.3, 0.2, 0.3, 0.1, 0.2, 0.1 ] 881 | 882 | this.interval = setInterval(function() { 883 | this.beat++; 884 | this.beat %= 32 885 | if (this.queue[0]) { 886 | this.filter.frequency.setValueAtTime(nx.mtof(this.queue[0]),0); 887 | if (this.noise.value==1) { 888 | this.noise.setValueAtTime(-1); 889 | this.env.triggerAttackRelease(0.01,"+0.01",this.gains[this.beat]) 890 | } else { 891 | this.noise.setValueAtTime(1); 892 | this.env.triggerAttackRelease(0.01,"+0.01",this.gains[this.beat]) 893 | } 894 | this.queue = this.queue.slice(1) 895 | } 896 | }.bind(this), 100) 897 | }, 898 | interface: [ 899 | { 900 | label: "", 901 | type: "typewriter", 902 | action: function(data) { 903 | console.log(data) 904 | if (data.key=="shift") { 905 | if (data.on) { 906 | this.shift = true; 907 | } else { 908 | 909 | this.shift = false; 910 | } 911 | } 912 | if (data.on) { 913 | var octave = this.shift ? 12 : 24; 914 | this.queue.push(data.ascii + octave); 915 | } 916 | }, 917 | size: { w: 375 , h: 150 }, 918 | loc: { x: 0 , y: 0 } 919 | }] 920 | } 921 | 922 | Modules.metro = { 923 | size: { w: 200 , h: 50 }, 924 | audio: function() { 925 | this.event = mt.interval(100,function() { 926 | this.emit('bang',1) 927 | }.bind(this)) 928 | 929 | this.queue = [] 930 | this.beat = 0; 931 | this.gains = [1.0, 0.2, 0.3, 0.2, 0.4, 0.1, 0.2, 0.1, 932 | 0.5, 0.1, 0.3, 0.2, 0.4, 0.1, 0.2, 0.1, 933 | 0.8, 0.1, 0.3, 0.2, 0.5, 0.1, 0.2, 0.1, 934 | 0.4, 0.1, 0.3, 0.2, 0.3, 0.1, 0.2, 0.1 ] 935 | 936 | 937 | }, 938 | interface: [ 939 | { 940 | label: "", 941 | type: "typewriter", 942 | action: function(data) { 943 | console.log(data) 944 | if (data.key=="shift") { 945 | if (data.on) { 946 | this.shift = true; 947 | } else { 948 | 949 | this.shift = false; 950 | } 951 | } 952 | if (data.on) { 953 | var octave = this.shift ? 12 : 24; 954 | this.queue.push(data.ascii + octave); 955 | } 956 | }, 957 | size: { w: 375 , h: 150 }, 958 | loc: { x: 0 , y: 0 } 959 | }] 960 | } 961 | 962 | 963 | 964 | 965 | var WAM = new Wam(Modules); -------------------------------------------------------------------------------- /WAM2.js: -------------------------------------------------------------------------------- 1 | 2 | var Wam = function(Modules) { 3 | 4 | this.context; 5 | this.modules = [] 6 | 7 | this.colors = { 8 | body: "#090909", 9 | title: "#0bc", 10 | module: "#191919", 11 | label: "#0bc", 12 | text: "#000" 13 | } 14 | 15 | nx.onload = function() { 16 | nx.colorize("#0bc") 17 | nx.colorize("fill","#292929") 18 | nx.colorize("black","#fff") 19 | } 20 | 21 | for (var key in Modules) { 22 | this[key] = this.make.bind(this,key) 23 | } 24 | 25 | } 26 | 27 | Wam.prototype.init = function() { 28 | document.body.style.backgroundColor = this.colors.body 29 | } 30 | 31 | 32 | Wam.prototype.setContext = function(context) { 33 | this.context = context 34 | } 35 | 36 | Wam.prototype.out = function() { 37 | return { input: this.context.destination } 38 | } 39 | 40 | Wam.prototype.route = function(path) { 41 | for (var i=0;i0) { 257 | this.components[0].set({value: freq/1000}, true) 258 | } 259 | } 260 | this.vol = function(amp) { 261 | if (amp>=0) { 262 | this.components[1].set({value: amp}, true) 263 | } 264 | } 265 | this.start = function() { 266 | this.toneosc.start() 267 | } 268 | this.stop = function() { 269 | this.toneosc.stop() 270 | } 271 | }, 272 | interface: [ 273 | { 274 | type: "dial", 275 | label: "freq", 276 | action: function(data) { 277 | this.toneosc.frequency.setValueAtTime(data.value * 1000,0) 278 | }, 279 | initial: { 280 | value: 0 281 | }, 282 | size: { 283 | w: 40, 284 | h: 40 285 | }, 286 | loc: { 287 | x: 0, 288 | y: 0 289 | } 290 | }, 291 | { 292 | type: "dial", 293 | label: "vol", 294 | action: function(data) { 295 | this.toneosc.volume.value = -50 + data.value*60 296 | }, 297 | initial: { 298 | value: 0.75 299 | }, 300 | size: { 301 | w: 40, 302 | h: 40 303 | }, 304 | loc: { 305 | x: 40, 306 | y: 0 307 | } 308 | } 309 | ]}, 310 | "delay": { 311 | dependencies: [ "Tone" ], 312 | color: "#1bd", 313 | size: { 314 | w: 80, 315 | h: 50 316 | }, 317 | audio: function() { 318 | this.delayline = new Tone.FeedbackDelay(0.25, 0.8) 319 | this.delayline.connect(this.output) 320 | this.input.connect(this.delayline) 321 | this.input.connect(this.output) 322 | }, 323 | interface: [ 324 | { 325 | type: "dial", 326 | label: "fb", 327 | action: function(data) { 328 | this.delayline.feedback.value = data.value 329 | }, 330 | size: { 331 | w: 40, 332 | h: 40 333 | }, 334 | loc: { 335 | x: 0, 336 | y: 0 337 | } 338 | }, 339 | { 340 | type: "dial", 341 | label: "time", 342 | action: function(data) { 343 | this.delayline.delayTime.value = data.value*5 + 0.01 344 | }, 345 | size: { 346 | w: 40, 347 | h: 40 348 | }, 349 | init: function() { 350 | }, 351 | loc: { 352 | x: 40, 353 | y: 0 354 | } 355 | } 356 | ]}, 357 | "btLooper": { 358 | dependencies: [ "Tone" ], 359 | size: { 360 | w: 200, 361 | h: 110 362 | }, 363 | audio: function() { 364 | this.player = new Tone.Player() 365 | this.player.loop = true 366 | this.player.connect(this.output) 367 | }, 368 | custom: { 369 | "setFiles": function(list) { 370 | this.components[1].choices = []; 371 | this.components[1].init() 372 | this.components[1].choices = list; 373 | this.components[1].init() 374 | this.player.load("./audio/"+list[0],function() { 375 | this.components[2].setBuffer( this.player._buffer._buffer ) 376 | }.bind(this)) 377 | return this 378 | } 379 | }, 380 | interface: [ 381 | { 382 | type: "toggle", 383 | label: "", 384 | action: function(data) { 385 | if (data.value) { 386 | this.player.start() 387 | } else { 388 | this.player.stop() 389 | } 390 | }, 391 | size: { 392 | w: 20, 393 | h: 20 394 | }, 395 | loc: { 396 | x: 0, 397 | y: 1 398 | } 399 | }, 400 | { 401 | type: "select", 402 | label: "", 403 | action: function(data) { 404 | if (data.text) { 405 | this.player.load("./audio/"+data.text,function() { 406 | this.components[2].setBuffer( this.player._buffer._buffer ) 407 | }.bind(this)) 408 | } 409 | }, 410 | size: { 411 | w: 178, 412 | h: 20 413 | }, 414 | loc: { 415 | x: 23, 416 | y: 1 417 | }, 418 | init: function() { 419 | } 420 | }, 421 | { 422 | type: "waveform", 423 | label: "", 424 | action: function(data) { 425 | this.player.setLoopPoints(data.starttime/1000, data.stoptime/1000) 426 | }, 427 | size: { 428 | w: 200, 429 | h: 85 430 | }, 431 | init: function() { 432 | }, 433 | loc: { 434 | x: 0, 435 | y: 25 436 | } 437 | } 438 | ]}, 439 | "meter": { 440 | size: { 441 | w: 25, 442 | h: 50 443 | }, 444 | audio: function() { 445 | this.components[0].setup(WAM.context,this.input); 446 | this.input.connect(this.output) 447 | }, 448 | interface: [ 449 | { 450 | type: "meter", 451 | label: "db", 452 | action: function(data) { 453 | 454 | }, 455 | size: { 456 | w: 20, 457 | h: 40 458 | }, 459 | loc: { 460 | x: 3, 461 | y: 0 462 | } 463 | } 464 | ]}, 465 | "enveloop": { 466 | size: { 467 | w: 130, 468 | h: 50 469 | }, 470 | audio: function() { 471 | this.input.connect(this.output) 472 | this.components[0].looping = true 473 | this.components[0].start() 474 | }, 475 | interface: [ 476 | { 477 | type: "envelope", 478 | label: "volume", 479 | action: function(data) { 480 | this.input.gain.value = data.amp 481 | }, 482 | size: { 483 | w: 90, 484 | h: 40 485 | }, 486 | loc: { 487 | x: 0, 488 | y: 0 489 | } 490 | }, 491 | { 492 | type: "dial", 493 | label: "dur", 494 | action: function(data) { 495 | this.components[0].duration = data.value * 3000 + 10 496 | }, 497 | size: { 498 | w: 40, 499 | h: 40 500 | }, 501 | loc: { 502 | x: 90, 503 | y: 0 504 | } 505 | } 506 | ]}, 507 | "scratch": { 508 | // needs custom function for loadfile 509 | size: { 510 | w: 100, 511 | h: 100 512 | }, 513 | audio: function() { 514 | this.player = new Tone.Player() 515 | this.player.loop = true 516 | this.player.load("./audio/beat.mp3",function() { 517 | this.player.start() 518 | }.bind(this)) 519 | this.player.connect(this.output) 520 | }, 521 | interface: [ 522 | { 523 | type: "vinyl", 524 | label: "", 525 | action: function(data) { 526 | if (data.speed) { 527 | if (data.speed < 0 && !this.player.reverse) { 528 | this.player.reverse = true; 529 | } else if (data.speed >= 0 && this.player.reverse) { 530 | this.player.reverse = false; 531 | } 532 | data.speed = Math.abs(data.speed) 533 | this.player.playbackRate = data.speed 534 | } 535 | }, 536 | size: { 537 | w: 100, 538 | h: 100 539 | }, 540 | loc: { 541 | x: 0, 542 | y: 0 543 | } 544 | } 545 | ]}, 546 | "mixer4ch": { 547 | size: { 548 | w: 200, 549 | h: 100 550 | }, 551 | audio: function() { 552 | this.input = WAM.context.createChannelMerger(4); 553 | this.splitter = WAM.context.createChannelSplitter(4); 554 | 555 | this.input.connect(this.splitter) 556 | 557 | this.panvol1 = new Tone.PanVol(0.5, -2); 558 | this.panvol2 = new Tone.PanVol(0.5, -2); 559 | this.panvol3 = new Tone.PanVol(0.5, -2); 560 | this.panvol4 = new Tone.PanVol(0.5, -2); 561 | this.components[8].setup(WAM.context,this.panvol1.output.output); 562 | this.components[9].setup(WAM.context,this.panvol2.output.output); 563 | this.components[10].setup(WAM.context,this.panvol3.output.output); 564 | this.components[11].setup(WAM.context,this.panvol4.output.output); 565 | this.splitter.connect(this.panvol1,0) 566 | this.splitter.connect(this.panvol2,1) 567 | this.splitter.connect(this.panvol3,2) 568 | this.splitter.connect(this.panvol4,3) 569 | 570 | 571 | this.panvol1.output.connect(this.output) 572 | this.panvol2.output.connect(this.output) 573 | this.panvol3.output.connect(this.output) 574 | this.panvol4.output.connect(this.output) 575 | 576 | }, 577 | interface: [ 578 | { 579 | type: "slider", 580 | label: "1", 581 | action: function(data) { 582 | this.panvol1.volume.value = -50 + data.value*50 583 | }, 584 | size: { 585 | w: 20, 586 | h: 90 587 | }, 588 | loc: { 589 | x: 0, 590 | y: 0 591 | } 592 | }, 593 | { 594 | type: "slider", 595 | label: "2", 596 | action: function(data) { 597 | this.panvol2.volume.value = -50 + data.value*50 598 | }, 599 | size: { 600 | w: 20, 601 | h: 90 602 | }, 603 | loc: { 604 | x: 50, 605 | y: 0 606 | } 607 | }, 608 | { 609 | type: "slider", 610 | label: "3", 611 | action: function(data) { 612 | this.panvol3.volume.value = -50 + data.value*50 613 | }, 614 | size: { 615 | w: 20, 616 | h: 90 617 | }, 618 | loc: { 619 | x: 100, 620 | y: 0 621 | } 622 | }, 623 | { 624 | type: "slider", 625 | label: "4", 626 | action: function(data) { 627 | this.panvol4.volume.value = -50 + data.value*50 628 | }, 629 | size: { 630 | w: 20, 631 | h: 90 632 | }, 633 | loc: { 634 | x: 150, 635 | y: 0 636 | } 637 | }, 638 | { 639 | type: "dial", 640 | label: "pan", 641 | action: function(data) { 642 | this.panvol1.pan.value = data.value 643 | }, 644 | size: { 645 | w: 25, 646 | h: 25 647 | }, 648 | loc: { 649 | x: 23, 650 | y: 0 651 | } 652 | }, 653 | { 654 | type: "dial", 655 | label: "pan", 656 | action: function(data) { 657 | this.panvol2.pan.value = data.value 658 | }, 659 | size: { 660 | w: 25, 661 | h: 25 662 | }, 663 | loc: { 664 | x: 73, 665 | y: 0 666 | } 667 | }, 668 | { 669 | type: "dial", 670 | label: "pan", 671 | action: function(data) { 672 | this.panvol3.pan.value = data.value 673 | }, 674 | size: { 675 | w: 25, 676 | h: 25 677 | }, 678 | loc: { 679 | x: 123, 680 | y: 0 681 | } 682 | }, 683 | { 684 | type: "dial", 685 | label: "pan", 686 | action: function(data) { 687 | this.panvol4.pan.value = data.value 688 | }, 689 | size: { 690 | w: 25, 691 | h: 25 692 | }, 693 | loc: { 694 | x: 173, 695 | y: 0 696 | } 697 | }, 698 | { 699 | type: "meter", 700 | label: "db", 701 | action: function(data) { 702 | 703 | }, 704 | size: { 705 | w: 20, 706 | h: 50 707 | }, 708 | loc: { 709 | x: 25, 710 | y: 40 711 | } 712 | }, 713 | { 714 | type: "meter", 715 | label: "db", 716 | action: function(data) { 717 | 718 | }, 719 | size: { 720 | w: 20, 721 | h: 50 722 | }, 723 | loc: { 724 | x: 75, 725 | y: 40 726 | } 727 | }, 728 | { 729 | type: "meter", 730 | label: "db", 731 | action: function(data) { 732 | 733 | }, 734 | size: { 735 | w: 20, 736 | h: 50 737 | }, 738 | loc: { 739 | x: 125, 740 | y: 40 741 | } 742 | }, 743 | { 744 | type: "meter", 745 | label: "db", 746 | action: function(data) { 747 | 748 | }, 749 | size: { 750 | w: 20, 751 | h: 50 752 | }, 753 | loc: { 754 | x: 175, 755 | y: 40 756 | } 757 | } 758 | ]}, 759 | "FMSeq": { 760 | size: { 761 | w: 240, 762 | h: 185 763 | }, 764 | audio: function() { 765 | this.unit = new Tone.PolySynth(4, Tone.FMSynth); 766 | this.unit.connect(this.output) 767 | }, 768 | interface: [ 769 | { 770 | label: "volume", 771 | type: "dial", 772 | action: function(data) { 773 | this.unit.volume.rampTo(-50+data.value*50,1); 774 | }, 775 | initial: { 776 | "value": 0.75 777 | }, 778 | size: { 779 | w: 40, 780 | h: 40 781 | }, 782 | loc: { 783 | x: 0, 784 | y: 0 785 | } 786 | },{ 787 | label: "harm", 788 | type: "dial", 789 | action: function(data) { 790 | this.unit.harmonicity.value = data.value*5; 791 | }, 792 | size: { 793 | w: 40, 794 | h: 40 795 | }, 796 | loc: { 797 | x: 40, 798 | y: 0 799 | } 800 | },{ 801 | label: "mod", 802 | type: "dial", 803 | action: function(data) { 804 | this.unit.modulationIndex.value = data.value*100; 805 | }, 806 | size: { 807 | w: 40, 808 | h: 40 809 | }, 810 | loc: { 811 | x: 80, 812 | y: 0 813 | } 814 | },{ 815 | label: "glide", 816 | type: "dial", 817 | action: function(data) { 818 | this.unit.portamento = data.value; 819 | }, 820 | size: { 821 | w: 40, 822 | h: 40 823 | }, 824 | loc: { 825 | x: 120, 826 | y: 0 827 | } 828 | },{ 829 | label: "pitch", 830 | type: "matrix", 831 | action: function(data) { 832 | var major = [0,2,4,5,7,9,11,12] 833 | if (data.list) { 834 | for (var i=0;i=this.col) { 860 | this.place=0; 861 | } 862 | }.bind(this), "24n"); 863 | }.bind(this),Tone.Transport.nextBeat('1n')) */ 864 | } 865 | } 866 | ]}, 867 | "microphone": { 868 | size: { w: 80 , h: 50 }, 869 | audio: function() { 870 | this.unit = new Tone.Microphone() 871 | this.unit.connect(this.output) 872 | }, 873 | interface: [ 874 | { 875 | label: "volume", 876 | type: "dial", 877 | action: function(data) { 878 | this.unit.volume.value = -50 + data.value*50; 879 | }, 880 | initial: { 881 | value: 0.5 882 | }, 883 | size: { w: 40 , h: 40 }, 884 | loc: { x: 0 , y: 0 } 885 | }, 886 | { 887 | label: "on", 888 | type: "toggle", 889 | action: function(data) { 890 | if (data.value) { 891 | this.unit.start(); 892 | } else { 893 | this.unit.stop(); 894 | } 895 | }, 896 | size: { w: 40 , h: 40 }, 897 | loc: { x: 40 , y: 0 } 898 | } 899 | ]}, 900 | } 901 | 902 | 903 | 904 | Modules.clix = { 905 | size: { w: 375 , h: 150 }, 906 | audio: function() { 907 | //this.noise = new Tone.Oscillator(0.01,"square").start() 908 | // this.noise = new Tone.Player("./audio/click.mp3") 909 | // this.noise.retrigger = true 910 | this.env = new Tone.AmplitudeEnvelope(0.02,0,1,0.02) 911 | this.noise = new Tone.Signal(1); 912 | this.filter = new Tone.Filter() 913 | this.reverb = new Tone.JCReverb(0.3) 914 | this.gain = new Tone.Volume(47); 915 | this.reverb.wet.value = 0.1 916 | this.filter.type = "bandpass" 917 | this.filter.Q.value = 50 918 | this.filter.gain.value = 1 919 | this.noise.connect(this.filter) 920 | this.filter.connect(this.env) 921 | this.env.connect(this.gain) 922 | this.gain.connect(this.reverb) 923 | this.reverb.connect(this.output) 924 | 925 | this.queue = [] 926 | this.beat = 0; 927 | this.gains = [1.0, 0.2, 0.3, 0.2, 0.4, 0.1, 0.2, 0.1, 928 | 0.5, 0.1, 0.3, 0.2, 0.4, 0.1, 0.2, 0.1, 929 | 0.8, 0.1, 0.3, 0.2, 0.5, 0.1, 0.2, 0.1, 930 | 0.4, 0.1, 0.3, 0.2, 0.3, 0.1, 0.2, 0.1 ] 931 | 932 | this.interval = setInterval(function() { 933 | this.beat++; 934 | this.beat %= 32 935 | if (this.queue[0]) { 936 | this.filter.frequency.setValueAtTime(nx.mtof(this.queue[0]),0); 937 | if (this.noise.value==1) { 938 | this.noise.setValueAtTime(-1); 939 | this.env.triggerAttackRelease(0.01,"+0.01",this.gains[this.beat]) 940 | } else { 941 | this.noise.setValueAtTime(1); 942 | this.env.triggerAttackRelease(0.01,"+0.01",this.gains[this.beat]) 943 | } 944 | this.queue = this.queue.slice(1) 945 | } 946 | }.bind(this), 100) 947 | }, 948 | interface: [ 949 | { 950 | label: "", 951 | type: "typewriter", 952 | action: function(data) { 953 | console.log(data) 954 | if (data.key=="shift") { 955 | if (data.on) { 956 | this.shift = true; 957 | } else { 958 | 959 | this.shift = false; 960 | } 961 | } 962 | if (data.on) { 963 | var octave = this.shift ? 12 : 24; 964 | this.queue.push(data.ascii + octave); 965 | } 966 | }, 967 | size: { w: 375 , h: 150 }, 968 | loc: { x: 0 , y: 0 } 969 | }] 970 | } 971 | 972 | Modules.metro = { 973 | size: { w: 200 , h: 25 }, 974 | audio: function() { 975 | this.range = [ 100, 100 ] 976 | this.duration = 100 977 | this.pulse = mt.interval(this.duration) 978 | this.pulse.stop() 979 | this.pulse.event = function() { 980 | this.duration = mt.random(this.range[0],this.range[1]) 981 | this.pulse.ms( this.duration ) 982 | this.emit('bang',1) 983 | this.components[1].set({press: !this.components[1].val.press}) 984 | }.bind(this) 985 | }, 986 | interface: [ 987 | { 988 | type: "toggle", 989 | action: function(data) { 990 | if (data.value) { 991 | this.pulse.start() 992 | this.emit('on',1) 993 | } else { 994 | this.pulse.stop() 995 | this.components[1].set({press: 0}) 996 | this.emit('off',1) 997 | } 998 | }, 999 | size: { w: 25 , h: 25 }, 1000 | loc: { x: 0 , y: 0 } 1001 | }, 1002 | { 1003 | type: "button", 1004 | action: function(data) { 1005 | }, 1006 | size: { w: 25 , h: 25 }, 1007 | loc: { x: 30 , y: 0 } 1008 | }, 1009 | { 1010 | type: "range", 1011 | action: function(data) { 1012 | this.range = [data.start*10000,data.stop*10000] 1013 | }, 1014 | size: { w: 100 , h: 20 }, 1015 | loc: { x: 60 , y: 3 } 1016 | }, 1017 | 1018 | /*{ 1019 | type: "number", 1020 | action: function(data) { 1021 | }, 1022 | size: { w: 50 , h: 20 }, 1023 | loc: { x: 60 , y: 3 } 1024 | }, 1025 | { 1026 | type: "number", 1027 | action: function(data) { 1028 | }, 1029 | size: { w: 50 , h: 20 }, 1030 | loc: { x: 115 , y: 3 } 1031 | } */ 1032 | ] 1033 | } 1034 | 1035 | 1036 | Modules.pitchmatrix = { 1037 | size: { w: 200 , h: 120 }, 1038 | audio: function() { 1039 | // this.emit('bang',1) 1040 | // this.components[1].set({press: !this.components[1].val.press}) 1041 | this.activeNotes = [] 1042 | //this.major = [0,2,4,5,7,9,11,12] 1043 | this.major = [1/1,9/8,5/4,4/3,3/2,5/3,15/8] 1044 | this.dump = function() { 1045 | this.emit('dump',this.activeNotes) 1046 | } 1047 | }, 1048 | interface: [ 1049 | { 1050 | type: "matrix", 1051 | action: function(data) { 1052 | var degree = data.col 1053 | var octave = data.row 1054 | //var freq = mt.mtof(this.major[degree]+octave*12+24) 1055 | var fundamental = 100 1056 | //var freq = mt.octave(octave) * this.major[degree] * fundamental 1057 | var ratio = mt.octave(octave) * this.major[degree] * 0.25 1058 | if (data.level) { 1059 | this.activeNotes.push(ratio) 1060 | } else { 1061 | var noteIndex = this.activeNotes.indexOf(ratio) 1062 | this.activeNotes.splice(noteIndex,1) 1063 | } 1064 | }, 1065 | size: { w: 200, h: 120 }, 1066 | loc: { x: 0, y: 0 }, 1067 | init: function() { 1068 | this.col = 7; 1069 | this.row = 5; 1070 | this.init() 1071 | } 1072 | } 1073 | ], 1074 | api: { 1075 | dump: function() { 1076 | this.emit('dump',this.activeNotes) 1077 | }, 1078 | pick: function() { 1079 | 1080 | } 1081 | } 1082 | } 1083 | 1084 | Modules.envelope = { 1085 | size: { 1086 | w: 200, 1087 | h: 100 1088 | }, 1089 | audio: function() { 1090 | //an amplitude envelope 1091 | this.start = function() { 1092 | this.env.triggerAttack() 1093 | this.env.triggerRelease("+"+(this.env.attack+0.001)) 1094 | } 1095 | this.env = new Tone.Envelope({ 1096 | "attack" : 0.2, 1097 | "decay" : 0, 1098 | "sustain" : 1, 1099 | "release" : 12, 1100 | }); 1101 | this.env.connect(this.input.gain); 1102 | this.input.connect(this.output) 1103 | this.duration = 1000 1104 | this.points; 1105 | this.components[0].set({ 1106 | points: [ 1107 | { 1108 | x: 0, 1109 | y: 0 1110 | }, 1111 | { 1112 | x: 0.5, 1113 | y: 1 1114 | }, 1115 | { 1116 | x: 1, 1117 | y: 0 1118 | } 1119 | ] 1120 | }, true) 1121 | }, 1122 | interface: [ 1123 | { 1124 | type: "envelope", 1125 | label: "volume", 1126 | action: function(data) { 1127 | // this.input.gain.value = data.amp 1128 | // console.log(data) 1129 | this.points = data.points 1130 | this.env.attack = ( data.points[1].x - data.points[0].x ) * this.duration/1000 1131 | this.env.release = ( data.points[2].x - data.points[1].x ) * this.duration/100 1132 | // console.log("attack",this.env.attack) 1133 | // console.log("release",this.env.release) 1134 | }, 1135 | size: { 1136 | w: 150, 1137 | h: 85 1138 | }, 1139 | loc: { 1140 | x: 0, 1141 | y: 0 1142 | } 1143 | }, 1144 | { 1145 | type: "dial", 1146 | label: "dur", 1147 | action: function(data) { 1148 | this.duration = data.value * 3000 + 10 1149 | }, 1150 | size: { 1151 | w: 35, 1152 | h: 35 1153 | }, 1154 | loc: { 1155 | x: 160, 1156 | y: 0 1157 | } 1158 | }, 1159 | { 1160 | type: "button", 1161 | label: "start", 1162 | action: function(data) { 1163 | console.log(data) 1164 | if (data.press) { 1165 | // this.env.triggerAttackRelease(0.000001) 1166 | // this.env.triggerAttack(0.000001) 1167 | this.start() 1168 | } 1169 | // setInterval(console.log.bind(null,this.input.gain.value),100) 1170 | // this.components[0].duration = data.value * 3000 + 10 1171 | // if (data.value) 1172 | // this.components[0].start() 1173 | // for (var i=0;i