text: function SineGenerator(freq) {
      var self = {'alive': true};
      var period = sampleRate / freq;
      var t = 0;
      
      self.generate = function(buf, offset, count) {
          for (; count; count--) {
              var phase = t / period;
              var result = Math.sin(phase * 2 * Math.PI);
              buf[offset++] += result;
              buf[offset++] += result;
              t++;
          }
      }
      
      return self;
  }
  
  function SquareGenerator(freq, phase) {
      var self = {'alive': true};
      var period = sampleRate / freq;
      var t = 0;
      
      self.generate = function(buf, offset, count) {
          for (; count; count--) {
              var result = ( (t / period) % 1 > phase ? 1 : -1);
              buf[offset++] += result;
              buf[offset++] += result;
              t++;
          }
      }
      
      return self;
  }
  
  function ADSRGenerator(child, attackAmplitude, sustainAmplitude, attackTimeS, decayTimeS, releaseTimeS) {
      var self = {'alive': true}
      var attackTime = sampleRate * attackTimeS;
      var decayTime = sampleRate * (attackTimeS + decayTimeS);
      var decayRate = (attackAmplitude - sustainAmplitude) / (decayTime - attackTime);
      var releaseTime = null; /* not known yet */
      var endTime = null; /* not known yet */
      var releaseRate = sustainAmplitude / (sampleRate * releaseTimeS);
      var t = 0;
      
      self.noteOff = function() {
          if (self.released) return;
          releaseTime = t;
          self.released = true;
          endTime = releaseTime + sampleRate * releaseTimeS;
      }
      
      self.generate = function(buf, offset, count) {
          if (!self.alive) return;
          var input = new Array(count * 2);
          for (var i = 0; i < count*2; i++) {
              input[i] = 0;
          }
          child.generate(input, 0, count);
          
          childOffset = 0;
          while(count) {
              if (releaseTime != null) {
                  if (t < endTime) {
                      /* release */
                      while(count && t < endTime) {
                          var ampl = sustainAmplitude - releaseRate * (t - releaseTime);
                          buf[offset++] += input[childOffset++] * ampl;
                          buf[offset++] += input[childOffset++] * ampl;
                          t++;
                          count--;
                      }
                  } else {
                      /* dead */
                      self.alive = false;
                      return;
                  }
              } else if (t < attackTime) {
                  /* attack */
                  while(count && t < attackTime) {
                      var ampl = attackAmplitude * t / attackTime;
                      buf[offset++] += input[childOffset++] * ampl;
                      buf[offset++] += input[childOffset++] * ampl;
                      t++;
                      count--;
                  }
              } else if (t < decayTime) {
                  /* decay */
                  while(count && t < decayTime) {
                      var ampl = attackAmplitude - decayRate * (t - attackTime);
                      buf[offset++] += input[childOffset++] * ampl;
                      buf[offset++] += input[childOffset++] * ampl;
                      t++;
                      count--;
                  }
              } else {
                  /* sustain */
                  while(count) {
                      buf[offset++] += input[childOffset++] * sustainAmplitude;
                      buf[offset++] += input[childOffset++] * sustainAmplitude;
                      t++;
                      count--;
                  }
              }
          }
      }
      
      return self;
  }
  
  function midiToFrequency(note) {
      return 440 * Math.pow(2, (note-69)/12);
  }
  
  PianoProgram = {
      'attackAmplitude': 0.2,
      'sustainAmplitude': 0.1,
      'attackTime': 0.02,
      'decayTime': 0.3,
      'releaseTime': 0.02,
      'createNote': function(note, velocity) {
          var frequency = midiToFrequency(note);
          return ADSRGenerator(
              SineGenerator(frequency),
              this.attackAmplitude * (velocity / 128), this.sustainAmplitude * (velocity / 128),
              this.attackTime, this.decayTime, this.releaseTime
          );
      }
  }
  
  StringProgram = {
      'createNote': function(note, velocity) {
          var frequency = midiToFrequency(note);
          return ADSRGenerator(
              SineGenerator(frequency),
              0.5 * (velocity / 128), 0.2 * (velocity / 128),
              0.4, 0.8, 0.4
          );
      }
  }
  
  PROGRAMS = {
      41: StringProgram,
      42: StringProgram,
      43: StringProgram,
      44: StringProgram,
      45: StringProgram,
      46: StringProgram,
      47: StringProgram,
      49: StringProgram,
      50: StringProgram
  };
  
  function Synth(sampleRate) {
      
      var generators = [];
      
      function addGenerator(generator) {
          generators.push(generator);
      }
      
      function generate(samples) {
          var data = new Array(samples*2);
          generateIntoBuffer(samples, data, 0);
          return data;
      }
      
      function generateIntoBuffer(samplesToGenerate, buffer, offset) {
          for (var i = offset; i < offset + samplesToGenerate * 2; i++) {
              buffer[i] = 0;
          }
          for (var i = generators.length - 1; i >= 0; i--) {
              generators[i].generate(buffer, offset, samplesToGenerate);
              if (!generators[i].alive) generators.splice(i, 1);
          }
      }
      
      return {
          'sampleRate': sampleRate,
          'addGenerator': addGenerator,
          'generate': generate,
          'generateIntoBuffer': generateIntoBuffer
      }
  }
  