Show:
+(function (global, factory) {
  if (typeof exports === 'undefined') {
    factory(global.webduino || {});
  } else {
    module.exports = factory;
  }
}(this, function (scope) {
  'use strict';

  var push = Array.prototype.push;

  var util = scope.util,
    Module = scope.Module,
    BoardEvent = scope.BoardEvent,
    proto;

  var BUZZER_MESSAGE = [0x04, 0x07],
    TONE_MIN_LENGTH = 100;

  var BUZZER_STATE = {
    PLAYING: 'playing',
    STOPPED: 'stopped',
    PAUSED: 'paused'
  };

  var FREQUENCY = {
    REST: 0,
    B0: 31,
    C1: 33,
    CS1: 35,
    D1: 37,
    DS1: 39,
    E1: 41,
    F1: 44,
    FS1: 46,
    G1: 49,
    GS1: 52,
    A1: 55,
    AS1: 58,
    B1: 62,
    C2: 65,
    CS2: 69,
    D2: 73,
    DS2: 78,
    E2: 82,
    F2: 87,
    FS2: 93,
    G2: 98,
    GS2: 104,
    A2: 110,
    AS2: 117,
    B2: 123,
    C3: 131,
    CS3: 139,
    D3: 147,
    DS3: 156,
    E3: 165,
    F3: 175,
    FS3: 185,
    G3: 196,
    GS3: 208,
    A3: 220,
    AS3: 233,
    B3: 247,
    C4: 262,
    CS4: 277,
    D4: 294,
    DS4: 311,
    E4: 330,
    F4: 349,
    FS4: 370,
    G4: 392,
    GS4: 415,
    A4: 440,
    AS4: 466,
    B4: 494,
    C5: 523,
    CS5: 554,
    D5: 587,
    DS5: 622,
    E5: 659,
    F5: 698,
    FS5: 740,
    G5: 784,
    GS5: 831,
    A5: 880,
    AS5: 932,
    B5: 988,
    C6: 1047,
    CS6: 1109,
    D6: 1175,
    DS6: 1245,
    E6: 1319,
    F6: 1397,
    FS6: 1480,
    G6: 1568,
    GS6: 1661,
    A6: 1760,
    AS6: 1865,
    B6: 1976,
    C7: 2093,
    CS7: 2217,
    D7: 2349,
    DS7: 2489,
    E7: 2637,
    F7: 2794,
    FS7: 2960,
    G7: 3136,
    GS7: 3322,
    A7: 3520,
    AS7: 3729,
    B7: 3951,
    C8: 4186,
    CS8: 4435,
    D8: 4699,
    DS8: 4978
  };

  /**
   * The Buzzer Class.
   *
   * @namespace webduino.module
   * @class Buzzer
   * @constructor
   * @param {webduino.Board} board The board that the buzzer is attached to.
   * @param {Integer} pin The pin that the buzzer is connected to.
   * @extends webduino.Module
   */
  function Buzzer(board, pin) {
    Module.call(this);

    this._board = board;
    this._pin = pin;
    this._timer = null;
    this._sequence = null;
    this._state = BUZZER_STATE.STOPPED;

    this._board.on(BoardEvent.BEFOREDISCONNECT, this.stop.bind(this));
    this._board.on(BoardEvent.ERROR, this.stop.bind(this));
  }

  function getDuration(duration) {
    duration = isNaN(duration = parseInt(duration)) ? TONE_MIN_LENGTH : duration;
    return Math.max(duration, TONE_MIN_LENGTH);
  }

  function padDurations(durations, len) {
    var durLen = durations.length,
      dur = durLen ? durations[durLen - 1] : TONE_MIN_LENGTH;

    if (durLen < len) {
      push.apply(durations, new Array(len - durLen));
      for (var i = durLen; i < durations.length; i++) {
        durations[i] = dur;
      }
    }

    return durations;
  }

  function playNext(self) {
    var seq = self._sequence,
      note;

    if (seq && seq.length > 0) {
      note = seq.pop();
      self.tone(note.frequency, note.duration);
      self._timer = setTimeout(function () {
        playNext(self);
      }, note.duration + Buzzer.TONE_DELAY);
    } else {
      self.stop();
    }
  }

  Buzzer.prototype = proto = Object.create(Module.prototype, {
    constructor: {
      value: Buzzer
    }
  });

  /**
   * Set Buzzer tone.
   *
   * @method tone 
   * @param {Integer} freq Frequency of tone.
   * @param {Integer} duration Duration of tone.
   */
  proto.tone = function (freq, duration) {
    var freqData = [];

    if (isNaN(freq = parseInt(freq)) || freq <= 0 || freq > 9999) {
      return;
    }

    freq = ('0000' + freq).substr(-4, 4);
    freqData[0] = parseInt('0x' + freq[0] + freq[1]);
    freqData[1] = parseInt('0x' + freq[2] + freq[3]);
    duration = Math.round(getDuration(duration) / TONE_MIN_LENGTH);
    this._board.sendSysex(BUZZER_MESSAGE[0], [BUZZER_MESSAGE[1], this._pin.number]
      .concat(freqData).concat(duration));
  };

  /**
   * Play specified note and tempo.
   *
   * @method play 
   * @param {Array} notes Array of notes.
   * @param {Array} tempos Array of tempos.
   * @example
   *     buzzer.play(["C6","D6","E6","F6","G6","A6","B6"], ["8","8","8","8","8","8","8"]);
   */
  proto.play = function (notes, tempos) {
    if (typeof notes !== 'undefined') {
      var len = notes.length,
        durations = padDurations(
          (util.isArray(tempos) ? tempos : []).map(function (t) {
            return getDuration(1000 / t);
          }), len
        );

      this.stop();
      this._sequence = [];
      for (var i = len - 1; i >= 0; i--) {
        this._sequence.push({
          frequency: FREQUENCY[notes[i].toUpperCase()],
          duration: durations[i]
        });
      }
    } else {
      if (this._state === BUZZER_STATE.PLAYING) {
        return;
      }
    }

    this._state = BUZZER_STATE.PLAYING;
    playNext(this);
  };

  /**
   * Pause the playback.
   *
   * @method pause 
   */
  proto.pause = function () {
    if (this._state !== BUZZER_STATE.PLAYING) {
      return;
    }

    if (this._timer) {
      clearTimeout(this._timer);
      delete this._timer;
    }

    this._state = BUZZER_STATE.PAUSED;
  };

  /**
   * Stop the plaback.
   *
   * @method stop 
   */
  proto.stop = function () {
    if (this._timer) {
      clearTimeout(this._timer);
      delete this._timer;
    }

    delete this._sequence;
    this._state = BUZZER_STATE.STOPPED;
  };

  /**
   * Indicates the frequency of tone.
   * 
   * @property FREQUENCY
   * @type {Number}
   * @static
   * @final
   */
  Buzzer.FREQUENCY = FREQUENCY;

  /**
   * Indicates the delay of tone.
   * 
   * @property TONE_DELAY
   * @type {Number}
   * @static
   * @final
   */
  Buzzer.TONE_DELAY = 60;

  scope.module.Buzzer = Buzzer;
}));