Source: index.js

var circleOfFifths = require('./dict/circleOfFifths.json');
var accidental = require('./dict/accidentals.json');
var pitches = require('./dict/pitches.json');
var durations = require('./dict/durations.json');
var clefs = require('./dict/clefs.json');

var Parser = require('./lib/abc_parser');

/**
 * Returns the abc notation string from given input
 * @param {object} input - The parsed input from input file
 * @returns {string}
 */
function getAbcString(input) {
  var outputData = "";
  outputData += "X:"
    + input.id
    + "\n";
  outputData += "T:"
    + input.id
    + "\n";
  outputData += "M:"
    + input.attributes.time.beats
    + "/"
    + input.attributes.time["beat-type"]
    + "\n";
  outputData += "L:"
    + "1/"
    + (input.attributes.divisions * input.attributes.time["beat-type"])
    + "\n";
  outputData += "K:"
    + getAbcKey(input.attributes.key.fifths, input.attributes.key.mode)
    + "\n";
  outputData += "K:"
    + getAbcClef(input.attributes.clef.sign)
    + "\n";

  for (var i = 0; i < input.measures.length; i++) {
    if (i % 5 === 0 && i > 0) { // 5 measures per line
      outputData += "\n";
      outputData += "|";
    }
    var measure = input.measures[i];
    if (measure.attributes.repeat.left) {
      outputData += ":";
    }

    for (var j = 0; j < measure.notes.length; j++) {

      outputData += getAbcNote(measure.notes[j-1], measure.notes[j]);
    }

    if (measure.attributes.repeat.right) {
      outputData += ":";
    }
    outputData += "|";

  }

  return outputData;
}

/**
 * Returns the abc notation string from given input
 * @param {object} input - The parsed input from input file
 * @returns {string}
 */
function getMusicJSON(input) {
  var outputData = {};
  var parser = new Parser();
  parser.parse(input);
  var tune = parser.getTune();
  outputData = createJsonFromLines(tune);
  outputData.id = getJsonId(input);

  return JSON.stringify(outputData);
}

/**
 * Returns the key for abc notation from given fifths
 * @param {number} fifths - The position inside the circle of fifths
 * @param {string|undefined} mode - The mode (major / minor)
 * @returns {string}
 */
function getAbcKey(fifths, mode) {
  if (typeof mode === 'undefined') mode = 'major';
  return circleOfFifths[mode][fifths];
}

/**
 * Returns the key for abc notation from given fifths
 * @param {string} sign - The clef sign
 * @returns {string}
 */
function getAbcClef(sign) {
  return clefs[sign];
}

/**
 * Returns a note in abc notation from given note object (JSON)
 * @param {object} prevNote - The previous note
 * @param {object} curNote - The note that should be transformed to abc
 * @returns {string}
 */
function getAbcNote(prevNote, curNote) {
  var _accidental = accidental.json[curNote.pitch.accidental];
  var _pitch = pitches.json[curNote.pitch.octave][curNote.pitch.step];
  var _duration = curNote.duration;
  if (typeof prevNote !== 'undefined') {
    if (prevNote.dot) {
      _duration = _duration * 2;
    }
  }
  var _dotted = '';
  if (curNote.dot) {
    _dotted = '>';
  }

  // check if rest
  if (curNote.rest) {
    // return rest as abc
    return "z" + _duration + _dotted;
  } else {
    // return note as abc
    return _accidental + _pitch + _duration + _dotted;
  }
}

/**
 * Get id from abc string
 * @param data
 * @returns {string}
 */
var getJsonId = function getJSONId(data) {
  var lines = data.split('\n');
  for (var i = 0; i < lines.length; i++) {
    if (lines[i].indexOf('X:') > -1) {
      return lines[i].substr(lines[i].indexOf(':') + 1, lines[i].length);
    }
  }
  throw new Error('Could not determine "X:" field');
};

/**
 * Creates json object from abc tunes object
 * @param {object} tune - The parsed tune object
 * @returns {object}
 */
var createJsonFromLines = function(tune) {
  var ret = {
    attributes: {
      divisions: 1 /tune.getBeatLength(),
      clef: {
        line: 2
      },
      key: {},
      time: {}
    }
  };
  var measures = [];
  var measureCounter = 0;
  var barlineCounter = 0;

  // parse lines
  for (var l = 0; l < tune.lines.length; l++) {
    for (var s = 0; s < tune.lines[l].staff.length; s++) {
      var staff = tune.lines[l].staff[s];

      // parse default clef, key, meter
      if (l === 0 && s === 0) {
        ret.attributes.clef.sign = getKeyByValue(clefs, staff.clef.type);
        ret.attributes.clef.line = staff.clef.clefPos / 2;
        ret.attributes.key.fifths = parseInt(getKeyByValue(circleOfFifths, staff.key.root));
        ret.attributes.time.beats = staff.meter.value[0].num;
        ret.attributes.time['beat-type'] = staff.meter.value[0].den;
      }

      for (var v = 0; v < staff.voices.length; v++) {
        for (var t = 0; t < staff.voices[v].length; t++) {
          var token = staff.voices[v][t];

          // init measure if none exists
          if (measures[measureCounter] === undefined) {
            measures[measureCounter] = new Measure();
          }

          switch (token.el_type) {
            case "note":
              measures[measureCounter].addNote(token, tune, ret.attributes.divisions, ret.attributes.time['beat-type']);
              break;
            case "bar":
              if (token.type === 'bar_right_repeat') {
                measures[measureCounter].setRepeatRight();
              }
              measureCounter++;
              if (measures[measureCounter] === undefined) {
                measures[measureCounter] = new Measure();
              }
              if (token.type === 'bar_left_repeat') {
                measures[measureCounter].setRepeatLeft();
              }
              break;
          }
        }
      }
    }
  }

  // put measures together
  ret.measures = [];
  for (var i = 0; i < measures.length; i++) {
    var measure = measures[i].get();
    if (measure.notes.length > 0) {
      ret.measures.push(measure);
    }
  }
  return ret;
};

/**
 * Constructor for measure objects
 */
var Measure = function() {
  var attributes = {
    repeat: {
      left: false,
      right: false
    }
  };
  var notes = [];

  this.setRepeatLeft = function () {
    attributes.repeat.left = true;
  };
  this.setRepeatRight = function () {
    attributes.repeat.right = true;
  };

  this.addNote = function(note, tune, divisions, beatType) {
    var _note = {pitch:{}};
    var _octave = 5, _step, _alter = 0;
    if (note.hasOwnProperty('pitches')) {
      _octave--;
      _step = note.pitches[0].pitch;
      while (_step > 6) {
        _octave++;
        _step -= 7;
      }
      while (_step < 0) {
        _octave--;
        _step +=7
      }
      _note.pitch.step = pitches.abc[_step];
      _note.rest = false;
      if (note.pitches[0].hasOwnProperty('accidental')) {
        _alter = accidental.abc[note.pitches[0].accidental];
        _note.pitch.accidental = note.pitches[0].accidental;
      }
    } else {
      _note.pitch.step = "C";
      _note.pitch.octave = 5;
      _note.rest = true;
      _note.pitch.alter = 0;
    }
    _note.pitch.octave = _octave;
    _note.pitch.alter = _alter;

    for (var i = 0; i < durations.length; i++) {
      if (typeof durations[i+1] !== 'undefined') {
        if (durations[i].duration > note.duration && durations[i+1].duration <= note.duration) {
          var diff = note.duration - durations[i+1].duration;
          _note.duration = durations[i+1].duration * divisions * beatType;
          _note.type = durations[i+1].type;
          if (diff > 0) {
            if ((diff / durations[i+1].duration) === 0.5) {
              _note.dot = true;
            } else {
              throw new Error('Unknown duration: ' + note.duration);
            }
          }
          break;
        }
      } else {
        throw new Error('Unknown duration: ' + note.duration);
      }
    }

    notes.push(_note);
  };

  this.get = function() {
    return {
      attributes: attributes,
      notes: notes
    };
  };
};

/**
 * Get object key by value
 * @param {object} object - The object to search in
 * @param {string} value - The value to search for
 * @returns {string}
 */
var getKeyByValue = function(object, value) {
  for (var key in object) {
    if (!object.hasOwnProperty(key)) continue;
    if (typeof object[key] === 'object') {
      return getKeyByValue(object[key], value);
    } else {
      if (object[key] == value) return key;
    }
  }
};

/**
 * Returns a string in abc notation from given data
 * @param {object} data - The JSON string data that should be transformed to abc
 * @returns {string}
 */
exports.convert2Abc = function(data) {
  return getAbcString(JSON.parse(data));
};

/**
 * Returns a string in json notation from given abc data
 * @param {object} data - The abc string that should be transformed to json
 * @returns {string}
 */
exports.convert2JSON = function(data) {
  return getMusicJSON(data);
};

// Run jsdoc with: jsdoc index.js -d doc -R README.md