/**
 * VexTab Parser - A recursive descent parser for the VexTab language.
 * Copyright Mohit Cheppudira 2010 <mohit@muthanna.com>
 *
 * Requires the VexFlow rendering API - vexflow.js from:
 *
 *   http://vexflow.com
 *
 * Learn all about the VexTab language at:
 *
 *   http://vexflow.com/tabdiv/tutorial.html
 *
 * This file is licensed under the MIT license:
 *
 *   http://www.opensource.org/licenses/mit-license.php
 */

/**
 * @constructor
 * @requires Vex.Flow This parser depends on the VexFlow rendering API.
 *
 * Initialize and return a new VexTab parser. Example usage:
 *
 *   var JUSTIFY_WIDTH = 400;
 *   var CONTEXT = new Vex.Flow.Renderer(
 *      document.getElementById("canvas_id"),
 *      Vex.Flow.Renderer.Backends.CANVAS).getContext();
 *
 *   var parser = new Vex.Flow.VexTab();
 *
 *   try {
 *     parser.parse(vextab_code);
 *     if (parser.isValid()) {
 *       var elements = parser.getElements();
 *
 *       for (var i = 0; i < staves.length; ++i) {
 *         var stave = elements.staves[i];
 *         var voice_notes = elements.tabnotes[i];
 *         var voice_ties = elements.ties[i];
 *
 *         // Draw stave
 *         stave.setWidth(JUSTIFY_WIDTH);
 *         stave.setContext(CONTEXT).draw();
 *
 *         // Draw notes and modifiers.
 *         if (voice_notes) {
 *           Vex.Flow.Formatter.FormatAndDraw(CONTEXT, stave,
 *               voice_notes, JUSTIFY_WIDTH - 20);
 *         }
 *
 *         // Draw ties
 *         for (var j = 0; j < voice_ties.length; ++j) {
 *           voice_ties[j].setContext(CONTEXT).draw();
 *         }
 *       }
 *     }
 *   } catch (e) {
 *      console.log(e.message);
 *   }
 *
 */
Vex.Flow.VexTab = function() {
  this.init();
}

/**
 * Initialize VexTab.
 * @constructor
 */
Vex.Flow.VexTab.prototype.init = function() {
  // The VexFlow elements generated from the VexTab code.
  this.elements = {
    staves: [],
    tabnotes: [],
    notes: [],
    ties: [],
    beams: []
  };

  // Pre-parser state. This is used for error-reporting and
  // element generation.
  this.state = {
    key_manager: new Vex.Flow.KeyManager("c"),
    clef: "treble",
    current_line: 0,
    current_stave: -1,
    current_duration: "8",
    current_key: "C",
    has_notation: false,
    has_tab: true,
    beam_start: null
  };

  this.music = new Vex.Flow.Music();
  this.valid = false;         // Valid (parseable) VexTab?
  this.last_error = "";       // Last error message generated.
  this.last_error_line = 0;   // Line number of last error.
  this.height = 0;            // Total height of generated elements.
  this.tuning = new Vex.Flow.Tuning(); // Defaults to standard tuning.
}

/**
 * Returns true if the passed-in code parsed without errors.
 *
 * @return {Boolean} True if code is error-free.
 */
Vex.Flow.VexTab.prototype.isValid = function() { return this.valid; }

/**
 * Returns the generated VexFlow elements. Remember to call #isValid before
 * calling this method.
 *
 * @return {!Object} The generated VexFlow elements.
 */
Vex.Flow.VexTab.prototype.getElements = function() {
  return this.elements;
}

/**
 * @return {Number} The total height (in pixels) of the generated elements.
 */
Vex.Flow.VexTab.prototype.getHeight = function() { return this.height; }

/**
 * This method parses the VexTab code provided, and generates VexFlow
 * elements from it. The elements can be retrieved with the #getElements
 * method.
 *
 * If the parse fails, a Vex.RuntimeError of the type "ParseError" is
 * thrown with the line number and specific error message.
 *
 * Upon success, no exception is thrown and #isValid returns true.
 *
 * @param {String} code The VexTab code to parse.
 */
Vex.Flow.VexTab.prototype.parse = function(code) {
  // Clear elements and initialize parse state
  this.init();

  // Separate code into lines
  var lines = code.split(/\r\n|\r|\n/);
  for (var i = 0; i < lines.length; ++i) {
    var line = lines[i];
    this.state.current_line++;
    // Strip leading and trailing spaces
    line = line.replace(/(^\s*)|(\s*$)/gi,"");
    // Skip blank lines
    if (line == "") continue;

    // This line has entropy. Parse it.
    this.parseLine(line);
  }

  this.valid = true;
  this.height += 30;

  return this;
}

/**
 * Throws a Vex.RuntimeError exception with the code set to "ParseError". The
 * error includes the line number and specific error message.
 *
 * @param {String} message The error string.
 * @private
 */
Vex.Flow.VexTab.prototype.parseError = function(message) {
  this.valid = false;
  this.last_error = message;
  this.last_error_line = this.state.current_line;

  // Create and throw the RuntimeError exception.
  var error = new Vex.RERR("ParseError",
      "Line " + this.state.current_line + ": " + message);
  error.line = this.state.current_line;
  throw error;
}

/**
 * A line of VexTab code is essentially structured as:
 *
 *    command [param] [param] ...
 *
 * This function parses out valid commands and executes the
 * relevant sub-parser to work on the parameters.
 *
 * @param {String} line One line of VexTab code.
 * @private
 */
Vex.Flow.VexTab.prototype.parseLine = function(line) {
  // Split line into tokens
  var tokens = line.split(/\s+/);

  // The first token is the command. Run it.
  var command = tokens[0];
  switch(command) {
    // Generate a TAB stave.
    case "tabstave": this.parseTabStave(tokens); break;

    // Parse tab notes.
    case "notes": this.parseNotes(tokens); break;

    // Unrecognized command. Throw an error.
    default: this.parseError("Invalid keyword: " + command);
  }
}

Vex.Flow.VexTab.prototype.parseKeyValue = function(token) {
  var pair = token.split(/\s*=\s*/);
  if (pair.length != 2) this.parseError("Invalid key value pair: " + token);
  return { key: pair[0], value: pair[1] };
}

Vex.Flow.VexTab.prototype.parseTabStave = function(tokens) {
  var has_standard_notation = false;
  var has_tablature = true;
  var stave_clef = "treble";
  var stave_key = "C";
  var stave_time = "";
  var stave_tuning = "";

  this.state.key_manager.reset();

  for (var i = 1; i < tokens.length; ++i) {
    var pair = this.parseKeyValue(tokens[i]);
    if (pair.key.toLowerCase() == "notation") {
      switch (pair.value.toLowerCase()) {
        case "true": has_standard_notation = true; break;
        case "false": has_standard_notation = false; break;
        default: this.parseError(
                     'notation must be "true" or "false": ' + pair.value);
      }
    } else if (pair.key.toLowerCase() == "tablature") {
      switch (pair.value.toLowerCase()) {
        case "true": has_tablature = true; break;
        case "false": has_tablature = false; break;
        default: this.parseError(
                     'tablature must be "true" or "false": ' + pair.value);
      }
    } else if (pair.key.toLowerCase() == "clef") {
      switch (pair.value.toLowerCase()) {
        case "treble": stave_clef = "treble"; break;
        case "alto": stave_clef = "alto"; break;
        case "tenor": stave_clef = "tenor"; break;
        case "bass": stave_clef = "bass"; break;
        default: this.parseError(
                   'clef must be treble, alto, tenor, or bass: ' + pair.value);
      }
    } else if (pair.key.toLowerCase() == "key") {
      if (Vex.Flow.keySignature.keySpecs[pair.value]) {
        stave_key = pair.value;
        this.state.current_key = pair.value;
        this.state.key_manager.setKey(pair.value);
      } else {
        this.parseError('Invalid key signature: ' + pair.value);
      }
    } else if (pair.key.toLowerCase() == "time") {
      var ts = new Vex.Flow.TimeSignature();
      try {
        ts.parseTimeSpec(pair.value);
        stave_time = pair.value;
      } catch (e) {
        this.parseError(
            'Invalid time signature: ' + pair.value)
      }
    } else if (pair.key.toLowerCase() == "tuning") {
      try {
        this.tuning.setTuning(pair.value);
      } catch (e) {
        this.parseError(
            'Invalid tuning: ' + pair.value)
      }
    } else {
      this.parseError("Invalid parameter for tabstave: " + pair.key)
    }
  }

  if (!has_tablature && !has_standard_notation) {
    this.parseError('notation & tablature cannot both be "false"');
  }

  this.genTabStave({
    notation: has_standard_notation,
    tablature: has_tablature,
    clef: stave_clef,
    key_signature: stave_key,
    time_signature: stave_time
    });
}

/**
 * VexTab notes consists of note-groups separated by spaces. For
 * example:
 *
 *   4-5-6/4 5 | (4/5.5/6.6.7)
 *
 * Each note group is parsed by a recursive-descent parser.
 *
 * @param {Array.<String>} tokens An array of note-groups.
 * @private
 */
Vex.Flow.VexTab.prototype.parseNotes = function(tokens) {
  for (var i = 1; i < tokens.length; ++i) {
    var token = tokens[i];
    switch (token) {
      // Bars are simple. No parsing necessary.
      case "|": this.genBar(); break;

      // Open beam
      case "[": this.parseOpenBeam(); break;

      // Close beam
      case "]": this.parseCloseBeam(); break;

      // Everything else goes through the recdescent toke parser.
      default:
        this.parseToken(tokens[i]);
        this.genElements();
    }
  }

  // Verify that all beams are closed.
  if (this.state.beam_start != null)
    this.parseError("Beam not closed");
}

/**
 * This method is a regular-expression based lexer. Its job is to:
 *
 *    1) Extract the next token.
 *    2) Strip out the remaining string.
 *    3) Keep track of errors (e.g., unexpected EOL, unrecognized token, etc.)
 *
 * The valid tokens are:
 *
 *    "t" - Taps (represented as Annotations in VexFlow)
 *    "s" - Slides (represented as Ties in VexFlow)
 *    "h" - Hammer-ons (represented as Ties in VexFlow)
 *    "p" - Pull-offs (represented as Ties in VexFlow)
 *    "b" - Bends / Open beams
 *    "v" - Soft vibrato
 *    "V" - Harsh vibrato
 *    "-" - Fret separators
 *    "/" - String separator
 *    "(" - Open chord
 *    "." - Note separator inside a chord
 *    ")" - Close chord
 *    ":(\S+)" - Duration
 *    \d+ - A fret or string number
 *
 * @private
 */
Vex.Flow.VexTab.prototype.getNextRegExp = function(re) {
  if (this.parse_state.done)
    this.parseError("Unexpected end of line");

  var match = this.parse_state.str.match(re);

  if (match) {
    this.parse_state.value = match[1];
    this.parse_state.str = match[2];
    if (this.parse_state.str == "") this.parse_state.done = true;
    return true;
  }

  this.parseError("Error parsing notes at: " + this.parse_state.str);
  return false;
}

Vex.Flow.VexTab.prototype.getNextToken = function() {
  return this.getNextRegExp(/^(\d+|[\)\(-tbhpsvV\.\/\|\:])(.*)/);
}

Vex.Flow.VexTab.prototype.getNextDurationToken = function() {
  return this.getNextRegExp(/^([0-9a-z]+|:)(.*)/);
}

/**
 * This is the start of the recdescent grammar for notes-lines. A notes-line
 * can begin with a "(", "t", or a fret number.
 *
 * @param {String} str A notes-line token.
 * @private
 */
Vex.Flow.VexTab.prototype.parseToken = function(str) {
  // Initialize the recursive descent parser state.
  this.parse_state = {
    str: str,                 // The leftover string to parse
    done: false,              // Finished parsing everything?
    expecting_string: false,  // Expecting a string number (as opposed to fret)

    // This keeps track of positions in time. A position can have multipe
    // string-fret combos (incase of chords), or just one.
    positions: [],
    durations: [],
    position_index: -1,
    annotations: [],          // Annotations associated with positions
    bends: [],                // Bends associated with positions
    vibratos: [],             // Vibrations associated with positions
    ties: [],                 // Ties associated with positions
    chord_ties: [],           // Chord ties associated with positions

    inside_bend: false,       // Are we inside a bend
    chord_index: -1          // The current chord index
  };

  var done = false;
  while (!done && this.getNextToken()) {
    switch (this.parse_state.value) {
      case "(": this.parseOpenChord(); break;
      case "t": this.parseTapAnnotation(); break;
      default: this.parseFret();
    }

    done = this.parse_state.done;
  }
}

Vex.Flow.VexTab.validDurations = {
  "w": "w",
  "h": "h",
  "q": "q",
  "8": "8",
  "8d": "8d",
  "16": "16",
  "16d": "16d",
  "32": "32",
  "32d": "32d"
}

/**
 * Parse ":" - Note duration
 *
 * @private
 */
Vex.Flow.VexTab.prototype.parseDuration = function() {
  this.getNextDurationToken();

  var duration = Vex.Flow.VexTab.validDurations[this.parse_state.value];
  if (duration) {
    this.state.current_duration = duration;
  } else {
    this.parseError("Invalid duration: " + this.parse_state.value);
  }

  if (!this.parse_state.done) {
    this.getNextDurationToken();
    if (this.parse_state.value != ":")
      this.parseError("Unexpected token: " + this.parse_state.str);

    if (!this.parse_state.done)
      this.parseError("Unexpected token: " + this.parse_state.str);
  }
}


/**
 * Parse "(" - Start a chord.
 *
 * @private
 */
Vex.Flow.VexTab.prototype.parseOpenChord = function() {
  // Add a position for this chord.
  this.parse_state.positions.push([]);
  this.parse_state.durations.push(this.state.current_duration);
  this.parse_state.position_index++;

  // Reset the chord-index.
  this.parse_state.chord_index = -1;

  // The next token must be a fret.
  this.getNextToken();
  this.parseChordFret();
}

/**
 * Parse "t" - Tap annotations.
 *
 * @private
 */
Vex.Flow.VexTab.prototype.parseTapAnnotation = function() {
  // Create an annotation and assosiate it with the note in the
  // next position.
  this.parse_state.annotations.push({
      position: this.parse_state.position_index + 1,
      text: "T" });

  // The next token must be a fret.
  this.getNextToken();
  switch (this.parse_state.value) {
    case "(": this.parseOpenChord(); break;
    default: this.parseFret();
  }
}


/**
 * Parse one note in a chord. The note must have a fret and string, and
 * may contain a bend.
 *
 * @private
 */
Vex.Flow.VexTab.prototype.parseChordFret = function() {
  // Extract duration (if available)
  if (this.parse_state.value == ":") {
    this.parseFretDuration();
    this.parse_state.durations[this.parse_state.durations.length - 1] =
      this.state.current_duration;
    if (this.parse_state.done) return;
    this.getNextToken();
  }

  // Do we have a valid fret?
  var fret = this.parse_state.value;
  if (isNaN(parseInt(fret)))
    this.parseError("Invalid fret number: " + fret);

  // The next token can either be a bend or a slash
  this.getNextToken();
  if (this.parse_state.value == "b") {
    // This is a bend, parse it out.
    this.parseChordBend();
  } else if (this.parse_state.value != "/") {
    this.parseError("Expecting / for string number: " + this.parse_state.value);
  }

  // We found a slash, parse out the string number and make sure
  // it's valid.
  this.getNextToken();
  var str = parseInt(this.parse_state.value);
  if (isNaN(parseInt(str)))
    this.parseError("Invalid string number: " + this.parse_state.value);

  // Add current fret-string to current position. Don't create a new
  // position because this is a chord.
  this.parse_state.positions[this.parse_state.position_index].push(
      { fret: fret, str: str });
  this.parse_state.chord_index++;

  // Next token can either be a chord separator ".", or a close chord ")"
  this.getNextToken();
  switch(this.parse_state.value) {
    case ".": this.getNextToken(); this.parseChordFret(); break;
    case ")": this.parseCloseChord(); break;
    default: this.parseError("Unexpected token: " + this.parse_state.value);
  }
}

/**
 * Parse a close chord token ")".
 *
 * @private
 */
Vex.Flow.VexTab.prototype.parseCloseChord = function() {
  // Reset chord index.
  this.chord_index = -1;

  // This is a valid place for parsing to end.
  if (this.parse_state.done) return;

  // There are more tokens. The only legitimate next token can be a
  // vibrato.
  this.getNextToken();
  switch (this.parse_state.value) {
    case "h": this.parseChordTie(); break;
    case "p": this.parseChordTie(); break;
    case "s": this.parseChordTie(); break;
    case "t": this.parseChordTie(); break;
    case "v": this.parseVibrato(); break;
    case "V": this.parseVibrato(); break;
    default: this.parseError("Unexpected token: " + this.parse_state.value);
  }
}

/**
 * Parse bends inside chords.
 * @private
 */
Vex.Flow.VexTab.prototype.parseChordBend = function() {
  // Next token has to be a fret number.
  this.getNextToken();
  var fret = parseInt(this.parse_state.value);
  if (isNaN(fret)) this.parseError("Expecting fret: " + this.parse_state.value);

  // If we're already inside a bend, then mark this as a release, otherwise
  // create a new bend
  if (this.parse_state.inside_bend) {
    var this_bend = this.parse_state.bends.length - 1;
    // We're actually incrementing a bend count here because we want to
    // be able to support multiple sequential bends on one string.
    this.parse_state.bends[this_bend].count++;
  } else {
    this.parse_state.inside_bend = true;
    this.parse_state.bends.push(
        { position: this.parse_state.position_index, count: 1,
          index: this.parse_state.chord_index + 1, to_fret: fret });
  }

  // Next token can either be another bend, or a slash. (Remember, we're inside
  // a chord, so we can't really do slides or hammer/pulloff unambiguously.)
  this.getNextToken();
  switch (this.parse_state.value) {
    case "b": this.parseChordBend(); break;
    case "/": break;
    default:
      this.parseError("Unexpected token: " + this.parse_state.value);
  }

  this.parse_state.inside_bend = false;
}

/**
 * Parse ":" (note duration) within notes stanza.
 *
 * @private
 */
Vex.Flow.VexTab.prototype.parseFretDuration = function() {
  this.getNextDurationToken();

  var duration = Vex.Flow.VexTab.validDurations[this.parse_state.value];
  if (duration) {
    this.state.current_duration = duration;
  } else {
    this.parseError("Invalid duration: " + this.parse_state.value);
  }

  if (this.parse_state.done) return;

  this.getNextDurationToken();
  if (this.parse_state.value != ":")
    this.parseError("Unexpected token: " + this.parse_state.str);
}

/**
 * Parse fret number (outside a chord context).
 * @private
 */
Vex.Flow.VexTab.prototype.parseFret = function() {
  // Extract duration (if available)
  if (this.parse_state.value == ":") {
    this.parseFretDuration();
    if (this.parse_state.done) return;
    this.getNextToken();
  }

  // Fret number must be valid.
  var str = this.parse_state.value;
  if (isNaN(parseInt(str)))
    this.parseError("Invalid fret number: " + str);

  // Create a new position for this fret/string pair.
  this.parse_state.positions.push([{ fret: str }]);
  this.parse_state.durations.push(this.state.current_duration);
  this.parse_state.position_index++;

  // Extract and parse next token.
  this.getNextToken();
  switch(this.parse_state.value) {
    case "-": this.parseDash(); break;
    case "/": this.parseSlash(); break;
    case "b": this.parseBend(); break;
    case "s": this.parseTie(); break;
    case "t": this.parseTie(); break;
    case "h": this.parseTie(); break;
    case "p": this.parseTie(); break;
    case "v": this.parseFretVibrato(); break;
    case "V": this.parseFretVibrato(); break;
    default: this.parseError("Unexpected token: " + this.parse_state.value);
  }
}

/**
 * Parse dashes.
 * @private
 */
Vex.Flow.VexTab.prototype.parseDash = function() {
  // Dashes break us out of bend contexts
  this.parse_state.inside_bend = false;

  // Dashes are not alloed on strings
  if (this.parse_state.expecting_string)
    this.parseError("No dashes on strings: " + this.parse_state.str);
}

/**
 * Parse vibratos.
 * @private
 */
Vex.Flow.VexTab.prototype.parseVibrato = function() {
  var harsh = false;

  // Capital V means harsh vibrato.
  if (this.parse_state.value == "V") harsh = true;

  var position = this.parse_state.position_index;
  if (this.parse_state.inside_bend) {
    // If we're inside a bend we associate the vibrato with the first
    // fret of the bend
    var count = this.parse_state.bends[this.parse_state.bends.length - 1].count;
    position -= count;
  }

  this.parse_state.vibratos.push({position: position, harsh: harsh});
}

/**
 * Parse vibratos inside a fret context.
 * @private
 */
Vex.Flow.VexTab.prototype.parseFretVibrato = function() {
  this.parseVibrato();
  this.getNextToken();
  switch(this.parse_state.value) {
    case "-": this.parseDash(); break;
    case "s": this.parseTie(); break;
    case "h": this.parseTie(); break;
    case "p": this.parseTie(); break;
    case "t": this.parseTie(); break;
    case "/": this.parseSlash(); break;
    default: this.parseError("Unexpected token: " + this.parse_state.value);
  }
}

/**
 * Parse string separator "/".
 * @private
 */
Vex.Flow.VexTab.prototype.parseSlash = function(str) {
  this.parse_state.inside_bend = false;
  this.parse_state.expecting_string = true;

  // Next token must be a string number
  this.getNextToken();
  this.parseString();
}

/**
 * Parse string number.
 * @private
 */
Vex.Flow.VexTab.prototype.parseString = function() {
  var str = this.parse_state.value;
  if (this.parse_state.positions.length == 0)
    this.parseError("String without frets: " + str);

  // Associate string with all positions in this note-group.
  for (var i = 0; i < this.parse_state.positions.length; ++i) {
    this.parse_state.positions[i][0].str = str;
  }
}

/**
 * Parse ties, hammerons, slides, etc.
 * @private
 */
Vex.Flow.VexTab.prototype.parseTie = function() {
  this.parse_state.inside_bend = false;
  if (this.parse_state.expecting_string)
    this.parseError("Unexpected token on string: " + this.parse_state.str);

  this.parse_state.ties.push({
    position: this.parse_state.position_index,
    index: this.parse_state.chord_index + 1,
    effect: this.parse_state.value.toUpperCase()
  });

  // Next token has to be a fret number.
  this.getNextToken();
  this.parseFret();
}

/**
 * Parse ties, hammerons, slides, etc. in chords.
 * @private
 */
Vex.Flow.VexTab.prototype.parseChordTie = function() {
  this.parse_state.chord_ties.push({
    position: this.parse_state.position_index,
    effect: this.parse_state.value.toUpperCase()
  });

  // Next token has to be a fret number.
  this.getNextToken();
  if (this.parse_state.value != "(")
    this.parseError("Expecting ( after chord ties/slides");
  this.parseOpenChord();
}


/**
 * Parse bends outside a chord context.
 * @private
 */
Vex.Flow.VexTab.prototype.parseBend = function() {
  if (this.parse_state.expecting_string)
    this.parseError("Unexpected token on string: " + this.parse_state.str);

  if (this.parse_state.inside_bend) {
    var this_bend = this.parse_state.bends.length - 1;
    this.parse_state.bends[this_bend].count++;
  } else {
    this.parse_state.inside_bend = true;
    this.parse_state.bends.push(
        { position: this.parse_state.position_index, count: 1, index: 0 });
  }

  // Next token must be a fret.
  this.getNextToken();
  this.parseFret();
}

/**
 * Generate VexFlow elements from current parser state. The elements can
 * be retrieved with #getElements.
 *
 * @private
 */
Vex.Flow.VexTab.prototype.genElements = function() {
  // If there's no Tab Stave, generate one.
  if (this.state.current_stave == -1) this.genTabStave();

  // Start by building notes.
  var positions = this.parse_state.positions;
  var durations = this.parse_state.durations;
  var clef = this.state.clef;
  var tabnotes = [];
  var notes = [];

  // Associate notes with relevant positions.
  for (var i = 0; i < positions.length; ++i) {
    var position = positions[i];
    var duration = durations[i];

    var tabnote = new Vex.Flow.TabNote(
        {positions: position, duration: duration});
    tabnotes.push({note: tabnote, persist: true});

    if (this.state.has_notation) {
      var keys = [];
      var accidentals = [];

      for (var j = 0; j < position.length; ++j) {
        var notefret = position[j];
        var spec = this.tuning.getNoteForFret(notefret.fret, notefret.str);
        var spec_props = Vex.Flow.keyProperties(spec);

        var selected_note = this.state.key_manager.selectNote(spec_props.key);

        if (selected_note.change) {
          if (selected_note.accidental == null)
            accidentals.push("n")
          else accidentals.push(selected_note.accidental);
        } else {
          accidentals.push(null);
        }

        var new_note = selected_note.note;
        var new_octave = spec_props.octave;

        if (this.music.getNoteParts(selected_note.note).root == "b" &&
            this.music.getNoteParts(spec_props.key).root == "c") {
            new_octave--;
        }
        else if (this.music.getNoteParts(selected_note.note).root == "c" &&
            this.music.getNoteParts(spec_props.key).root == "b") {
            new_octave++;
        }

        var new_spec = new_note + "/" + new_octave;
        keys.push(new_spec);
      }

      var note = new Vex.Flow.StaveNote({
          keys: keys, duration: duration, clef: clef });

      for (var j = 0; j < accidentals.length; ++j) {
        var acc = accidentals[j];

        if (acc) {
          note.addAccidental(j, new Vex.Flow.Accidental(accidentals[j]));
        }
      }

      notes.push(note);
    }
  }

  // Add bends.
  var bends = this.parse_state.bends;
  for (var i = 0; i < bends.length; ++i) {
    var bend = bends[i];
    var from_fret = parseInt(positions[bend.position][bend.index].fret);
    var to_fret;

    // Bent notes must not persist in position list.
    if (bends[i].to_fret) {
      to_fret = bends[i].to_fret;
    } else {
      to_fret = parseInt(positions[bend.position + 1][bend.index].fret);

      for (var count = 1; count <= bend.count; ++count) {
          tabnotes[bend.position + count].persist = false;
      }
    }

    var release = false;
    if (bend.count > 1) release = true;

    var bent_note = tabnotes[bend.position].note;

    // Calculate bend amount and annotate appropriately.
    switch (to_fret - from_fret) {
      case 1: bent_note.addModifier(
                  new Vex.Flow.Bend("1/2", release), bend.index); break;
      case 2: bent_note.addModifier(
                  new Vex.Flow.Bend("Full", release), bend.index); break;
      case 3: bent_note.addModifier(
                  new Vex.Flow.Bend("1 1/2", release), bend.index); break;
      case 4: bent_note.addModifier(
                  new Vex.Flow.Bend("2 Steps", release), bend.index); break;
      default: bent_note.addModifier(
                  new Vex.Flow.Bend("Bend to " + to_fret, release), bend.index);
    }
  }

  function persistentPosition(pos) {
    for (var i = pos; i >= 0; --i) {
      if (tabnotes[i].persist == true) return i;
    }
    throw new Vex.RERR("GenError", "Invalid position: " + pos);
  }

  // Add vibratos
  var vibratos = this.parse_state.vibratos;
  for (var i = 0; i < vibratos.length; ++i) {
    var vibrato = vibratos[i];
    tabnotes[vibrato.position].note.addModifier(new Vex.Flow.Vibrato().
      setHarsh(vibrato.harsh));
  }

  // Add annotations
  var annotations = this.parse_state.annotations;
  for (var i = 0; i < annotations.length; ++i) {
    var annotation = annotations[i];
    tabnotes[annotation.position].note.addModifier(
        new Vex.Flow.Annotation(annotation.text));
  }

  // Add ties
  var ties = this.parse_state.ties;
  for (var i = 0; i < ties.length; ++i) {
    var tie = ties[i];
    var effect = null;
    var pos = persistentPosition(tie.position);

    if (this.state.has_tablature) {
      if (tie.effect == "S") {
        // Slides are a special case.
        effect = new Vex.Flow.TabSlide({
          first_note: tabnotes[pos].note,
          last_note: tabnotes[tie.position + 1].note
        });
      } else {
        effect = new Vex.Flow.TabTie({
          first_note: tabnotes[pos].note,
          last_note: tabnotes[tie.position + 1].note
        }, tie.effect);
      }
    }

    if (this.state.has_notation) {
      this.elements.ties[this.state.current_stave].push(
          new Vex.Flow.StaveTie({
            first_note: notes[pos],
            last_note: notes[tie.position + 1]
          }));
    }
    if (effect) this.elements.ties[this.state.current_stave].push(effect);

  }

  // Add chord ties
  var chord_ties = this.parse_state.chord_ties;
  for (var i = 0; i < chord_ties.length; ++i) {
    var tie = chord_ties[i];
    var effect;
    var pos = persistentPosition(tie.position);

    // Organize indices by string number.
    var indices = [];
    for (var p = 0; p < positions[pos].length; ++p) {
      var position = positions[pos][p];
      indices[position.str] = {from: p};
    }
    for (var p = 0; p < positions[tie.position + 1].length; ++p) {
      var position = positions[tie.position + 1][p];
      if (!indices[position.str]) indices[position.str] = {};
      indices[position.str].to = p;
    }

    // Build indices for the ties.
    var first_indices = [];
    var last_indices = [];

    for (var j = 0; j < indices.length; ++j) {
      var index = indices[j];
      if (!index) continue;

      // Ensure that we only have tie notes that have both
      // and from and to fret.
      if (typeof(index.from) == "undefined" ||
          typeof(index.to) == "undefined") continue;

      first_indices.push(index.from);
      last_indices.push(index.to);
    }

    if (this.state.has_tablature) {
      if (tie.effect == "S") {
        // Slides are a special case.
        effect = new Vex.Flow.TabSlide({
          first_note: tabnotes[pos].note,
          last_note: tabnotes[tie.position + 1].note,
          first_indices: first_indices,
          last_indices: last_indices
        });
      } else {
        effect = new Vex.Flow.TabTie({
          first_note: tabnotes[pos].note,
          last_note: tabnotes[tie.position + 1].note,
          first_indices: first_indices,
          last_indices: last_indices
        }, tie.effect);
      }
    }

    if (this.state.has_notation) {
      this.elements.ties[this.state.current_stave].push(
          new Vex.Flow.StaveTie({
            first_note: notes[pos],
            last_note: notes[tie.position + 1],
            first_indices: first_indices,
            last_indices: last_indices
          }));
    }

    this.elements.ties[this.state.current_stave].push(effect);
  }

  // Push tabnotes, skipping non-persistent notes.
  for (var i = 0; i < tabnotes.length; ++i) {
    var note = tabnotes[i];
    if (note.persist) {
      this.elements.tabnotes[this.state.current_stave].push(note.note);
    } else {
      this.elements.tabnotes[this.state.current_stave].push(
          new Vex.Flow.GhostNote(this.state.current_duration));
    }
  }

  // Push notes, skipping non-persistent notes.
  for (var i = 0; i < notes.length; ++i) {
    var note = notes[i];
    this.elements.notes[this.state.current_stave].push(note);
  }
}

/**
 * Generate the tab stave and add it to the element list.
 * @private
 */
Vex.Flow.VexTab.prototype.genTabStave = function(params) {
  var notation = false;
  var tablature = true;
  var clef = "treble";
  var key_signature = "C";
  var time_signature = "";

  if (params) {
    notation = params.notation;
    tablature = params.tablature;
    clef = params.clef;
    key_signature = params.key_signature;
    time_signature = params.time_signature;
  }

  var notestave = null;
  var tabstave_start_x = 40; // line up tab and note staves

  if (notation) {
    notestave = new Vex.Flow.Stave(20, this.height, 380).
      addClef(clef).addKeySignature(key_signature);
    if (time_signature != "") notestave.addTimeSignature(time_signature);
    tabstave_start_x = notestave.getNoteStartX();
  }

  var tabstave = tablature ?
    new Vex.Flow.TabStave(20,
      notation ? notestave.getHeight() + this.height : this.height, 380).
    addTabGlyph().setNoteStartX(tabstave_start_x) : null;

  this.elements.staves.push({tab: tabstave, note: notestave});
  this.height += (tablature ? tabstave.getHeight() : null) +
    (notation ? notestave.getHeight() : null);

  this.state.current_stave++;
  this.state.has_notation = notation;
  this.state.has_tablature = tablature;
  this.state.clef = clef;
  this.elements.tabnotes[this.state.current_stave] = [];
  this.elements.notes[this.state.current_stave] = [];
  this.elements.ties[this.state.current_stave] = [];
}

Vex.Flow.VexTab.prototype.parseOpenBeam = function() {
  if (this.state.beam_start != null)
    this.parseError("Beam already open: [");

  this.state.beam_start = this.elements.notes[this.state.current_stave].length;
}

Vex.Flow.VexTab.prototype.parseCloseBeam = function() {
  if (this.state.beam_start == null)
    this.parseError("Can't close beam without openeing: ]");

  var beam_end = this.elements.notes[this.state.current_stave].length;

  if ((beam_end - this.state.beam_start) < 2)
    this.parseError("Must have at least two notes in a beam.");

  var beam_notes = [];

  for (var i = this.state.beam_start; i < beam_end; ++i) {
    beam_notes.push(this.elements.notes[this.state.current_stave][i]);
  }

  this.elements.beams.push(new Vex.Flow.Beam(beam_notes));
  this.state.beam_start = null;
}

/**
 * Generate bar line and add it to the element list.
 * @private
 */
Vex.Flow.VexTab.prototype.genBar = function() {
  // If there's no Tab Stave, generate one.
  if (this.state.current_stave == -1) this.genTabStave();
  this.elements.tabnotes[this.state.current_stave].push(new Vex.Flow.BarNote());
  this.elements.notes[this.state.current_stave].push(new Vex.Flow.BarNote());
}

