text: /*!
   *  submono - a Web Audio subtractive, monophonic synthesizer
   *  (c) 2015 Benji Kay
   *  MIT License
   */
  
  var Monosynth = function Monosynth(audioCtx, config) {
    var synth;
    var Synth = function Synth() {
      synth = this;
      config = config || {};
      config.cutoff = config.cutoff || {};
  
      synth.audioCtx = audioCtx,
      synth.amp      = audioCtx.createGain(),
      synth.filter   = audioCtx.createBiquadFilter(),
      synth.osc      = audioCtx.createOscillator(),
      synth.pan      = audioCtx.createPanner(),
  
      synth.maxGain  = config.maxGain  || 0.9, // out of 1
      synth.attack   = config.attack   || 0.1, // in seconds
      synth.decay    = config.decay    || 0.0, // in seconds
      synth.sustain  = config.sustain  || 1.0, // out of 1
      synth.release  = config.release  || 0.8, // in seconds
  
      //low-pass filter
      synth.cutoff              = synth.filter.frequency;
      synth.cutoff.maxFrequency = config.cutoff.maxFrequency || 7500; // in hertz
      synth.cutoff.attack       = config.cutoff.attack       || 0.1; // in seconds
      synth.cutoff.decay        = config.cutoff.decay        || 2.5; // in seconds
      synth.cutoff.sustain      = config.cutoff.sustain      || 0.2; // out of 1
      
      synth.amp.gain.value = 0;
      synth.filter.type = 'lowpass';
      synth.filter.connect(synth.amp);
      synth.amp.connect(audioCtx.destination);
      synth.pan.panningModel = 'equalpower';
      synth.pan.setPosition(0, 0, 1); // start with stereo image centered
      synth.osc.connect(synth.pan);
      synth.pan.connect(synth.filter);
      synth.osc.start(0);
      
      synth.waveform(config.waveform || 'sine');
      synth.pitch(config.pitch || 440);
  
      return synth;
    };
  
    function getNow() {
      var now = synth.audioCtx.currentTime;
      synth.amp.gain.cancelScheduledValues(now);
      synth.amp.gain.setValueAtTime(synth.amp.gain.value, now);
      return now;
    };
    
    Synth.prototype.pitch = function pitch(newPitch) {
      if (newPitch) {
        var now = synth.audioCtx.currentTime;
        synth.osc.frequency.setValueAtTime(newPitch, now);
      }
      return synth.osc.frequency.value;
    };
  
    Synth.prototype.waveform = function waveform(newWaveform) {
      if (newWaveform) {
        synth.osc.type = newWaveform;
      }
      return synth.osc.type;
    };
  
    //apply attack, decay, sustain envelope
    Synth.prototype.start = function startSynth() {
      var atk  = parseFloat(synth.attack);
      var dec  = parseFloat(synth.decay);
      var cAtk = parseFloat(synth.cutoff.attack);
      var cDec = parseFloat(synth.cutoff.decay);
      var now  = getNow();
      synth.cutoff.cancelScheduledValues(now);
      synth.cutoff.linearRampToValueAtTime(synth.cutoff.value, now);
      synth.cutoff.linearRampToValueAtTime(synth.cutoff.maxFrequency, now + cAtk);
      synth.cutoff.linearRampToValueAtTime(synth.cutoff.sustain * synth.cutoff.maxFrequency, now + cAtk + cDec);
      synth.amp.gain.linearRampToValueAtTime(synth.maxGain, now + atk);
      synth.amp.gain.linearRampToValueAtTime(synth.sustain * synth.maxGain, now + atk + dec);
    };
  
    //apply release envelope
    Synth.prototype.stop = function stopSynth() {
      var rel = parseFloat(synth.release);
      var now = getNow();
      synth.amp.gain.linearRampToValueAtTime(0, now + rel);
    };
  
    return new Synth();
  };
  