commit b0cb3950faaf8cf4af6ea60af53de1255c8e8249
Author: Eric Rosenbaum <eric.rosenbaum@gmail.com>
Date:   Thu Oct 13 14:54:07 2016 -0400

    initial commit

diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..e84613d
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,11 @@
+root = true
+
+[*]
+end_of_line = lf
+insert_final_newline = true
+charset = utf-8
+indent_size = 4
+trim_trailing_whitespace = true
+
+[*.{js,html}]
+indent_style = space
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000..8809c41
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,2 @@
+node_modules/*
+dist.js
diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 0000000..5be40e9
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,22 @@
+{
+    "parser": "babel-eslint",
+    "rules": {
+        "curly": [2, "multi-line"],
+        "eol-last": [2],
+        "indent": [2, 4],
+        "linebreak-style": [2, "unix"],
+        "max-len": [2, 120, 4],
+        "no-trailing-spaces": [2, { "skipBlankLines": true }],
+        "no-unused-vars": [2, {"args": "after-used", "varsIgnorePattern": "^_"}],
+        "quotes": [2, "single"],
+        "semi": [2, "always"],
+        "space-before-function-paren": [2, "always"],
+        "strict": [2, "never"]
+    },
+    "env": {
+        "browser": true,
+        "es6": true,
+        "node": true
+    },
+    "extends": ["eslint:recommended"]
+}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..132627c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,12 @@
+# Mac OS
+.DS_Store
+
+# NPM
+/node_modules
+npm-*
+
+# Testing
+/.nyc_output
+/coverage
+
+/dist.js
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..d305b94
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,7 @@
+language: node_js
+node_js:
+- '4.2'
+- 'stable'
+cache:
+  directories:
+  - node_modules
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..e8a689a
--- /dev/null
+++ b/package.json
@@ -0,0 +1,32 @@
+{
+  "name": "scratch-audioengine",
+  "version": "0.1.0",
+  "description": "audio engine for scratch 3.0",
+  "main": "dist.js",
+  "scripts": {
+    "test": "npm run lint && npm run build",
+    "build": "webpack --bail",
+    "watch": "webpack --watch",
+    "lint": "eslint ."
+  },
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/LLK/scratch-audioengine.git"
+  },
+  "author": "Massachusetts Institute of Technology",
+  "license": "BSD-3-Clause",
+  "bugs": {
+    "url": "https://github.com/LLK/scratch-audioengine/issues"
+  },
+  "homepage": "https://github.com/LLK/scratch-audioengine#readme",
+  "devDependencies": {
+    "babel-core": "^6.17.0",
+    "babel-eslint": "^7.0.0",
+    "babel-loader": "^6.2.5",
+    "babel-preset-es2015": "^6.16.0",
+    "eslint": "^3.7.1",
+    "soundfont-player": "^0.10.5",
+    "tone": "^0.8.0",
+    "webpack": "^1.13.2"
+  }
+}
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 0000000..13b3106
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,159 @@
+var Tone = require('tone');
+var Soundfont = require('soundfont-player');
+
+function AudioEngine () {
+
+    // tone setup
+
+    this.tone = new Tone();
+
+	// effects setup
+
+    this.delay = new Tone.FeedbackDelay(0.25, 0.5);
+    this.panner = new Tone.Panner();
+    this.reverb = new Tone.Freeverb();
+
+    this.clearEffects();
+
+    Tone.Master.chain(this.delay, this.panner, this.reverb);
+
+    // drum sounds
+
+    // var drumFileNames = ['high_conga', 'small_cowbell',
+    // 'snare_drum', 'splash cymbal'];
+    // this.drumSamplers = this._loadSoundFiles(drumFileNames);
+
+    // sound urls - map each url to its tone.sampler
+    this.soundSamplers = [];
+
+       // soundfont setup
+
+    // instrument names used by Musyng Kite soundfont, in order to
+    // match scratch instruments
+    this.instrumentNames = ['acoustic_grand_piano', 'electric_piano_1',
+        'drawbar_organ', 'acoustic_guitar_nylon', 'electric_guitar_clean',
+         'acoustic_bass', 'pizzicato_strings', 'cello', 'trombone', 'clarinet'];
+
+    Soundfont.instrument(Tone.context, this.instrumentNames[0]).then(
+        function (inst) {
+            this.instrument = inst;
+            this.instrument.connect(Tone.Master);
+        }.bind(this)
+    );
+}
+
+AudioEngine.prototype.playSound = function (soundNum) {
+    this.soundSamplers[soundNum].triggerAttack();
+};
+
+AudioEngine.prototype.playSoundFromUrl = function (url) {
+    if (url) {
+        // if we've loaded it already, play it
+        if (this.soundSamplers[url]) {
+            // this.soundSamplers[url].triggerAttack();
+            this.soundSamplers[url].player.start();
+        } else {
+        // else load, play, and store it
+        // this results in a delay the first time you play the sound
+            var sampler = new Tone.Sampler(url, function () {
+                sampler.triggerAttack();
+                this.soundSamplers[url] = sampler;
+            }.bind(this)).toMaster();
+        }
+    }
+};
+
+AudioEngine.prototype.getSoundDuration = function (url) {
+    return this.soundSamplers[url].player.buffer.duration;
+};
+
+AudioEngine.prototype.playNoteForBeats = function (note, beats) {
+    this.instrument.play(
+        note, Tone.context.currentTime, {duration : Number(beats)}
+    );
+};
+
+AudioEngine.prototype.playDrumForBeats = function (drumNum) {
+    this.drumSamplers[drumNum].triggerAttack();
+};
+
+AudioEngine.prototype.stopAllSounds = function () {
+    // stop drum notes
+    // for (var i = 0; i<this.drumSamplers.length; i++) {
+    //     this.drumSamplers[i].triggerRelease();
+    // }
+    // stop sounds triggered with playSound (indexed by their urls)
+    for (var key in this.soundSamplers) {
+        this.soundSamplers[key].triggerRelease();
+    }
+    // stop soundfont notes
+    this.instrument.stop();
+};
+
+AudioEngine.prototype.setEffect = function (effect, value) {
+    switch (effect) {
+    case 'ECHO':
+        this.delay.wet.value = (value / 100) / 2; // max 50% wet
+        break;
+    case 'PAN':
+        this.panner.pan.value = value / 100;
+        break;
+    case 'REVERB':
+        this.reverb.wet.value = value / 100;
+        break;
+    case 'PITCH':
+        this._setPitchShift(value / 20);
+        break;
+    }
+};
+
+AudioEngine.prototype.changeEffect = function (effect, value) {
+    switch (effect) {
+    case 'ECHO':
+        this.delay.wet.value += (value / 100) / 2; // max 50% wet
+        this.delay.wet.value = this._clamp(this.delay.wet.value, 0, 0.5);
+        break;
+    case 'PAN':
+        this.panner.pan.value += value / 100;
+        this.panner.pan.value = this._clamp(this.panner.pan.value, -1, 1);
+        break;
+    case 'REVERB':
+        this.reverb.wet.value += value / 100;
+        this.reverb.wet.value = this._clamp(this.reverb.wet.value, 0, 1);
+        break;
+    case 'PITCH':
+        // this.pitchShift.pitch += value / 20;
+        break;
+    }
+};
+
+AudioEngine.prototype._setPitchShift = function (value) {
+    for (var i in this.soundSamplers) {
+        this.soundSamplers[i].player.playbackRate = 1 + value;
+    }
+};
+
+AudioEngine.prototype.clearEffects = function () {
+    this.delay.wet.value = 0;
+    this._setPitchShift(0);
+    this.panner.pan.value = 0;
+    this.reverb.wet.value = 0;
+};
+
+// AudioEngine.prototype.loadSoundFromUrl = function(url) {
+
+// };
+
+AudioEngine.prototype.loadSounds = function (sounds) {
+    for (var sound in sounds) {
+        var sampler = new Tone.Sampler(sound.fileUrl).toMaster();
+        this.soundSamplers[sound.fileUrl] = sampler;
+    }
+};
+
+AudioEngine.prototype._clamp = function (input, min, max) {
+    return Math.min(Math.max(input, min), max);
+};
+
+module.exports = AudioEngine;
+
diff --git a/webpack.config.js b/webpack.config.js
new file mode 100644
index 0000000..4ad8cfe
--- /dev/null
+++ b/webpack.config.js
@@ -0,0 +1,23 @@
+var path = require('path');
+
+module.exports = {
+    entry: {
+        'dist': './src/index.js'
+    },
+    output: {
+        path: __dirname,
+        library: 'AudioEngine',
+        libraryTarget: 'commonjs2',
+        filename: '[name].js'
+    },
+    module: {
+        loaders: [{
+            test: /\.js$/,
+            loader: 'babel-loader',
+            include: path.resolve(__dirname, 'src'),
+            query: {
+                presets: ['es2015']
+            }
+        }]
+    }
+};