docs: function (line) {
      members = line.split(',');
  
      var soundType  = members[4];
      var objectType = members[3];
  
      var hitObject = {
        startTime:  parseInt(members[2]),
        newCombo:   ((objectType & 4) == 4),
        soundTypes: [],
        position: [
          parseInt(members[0]),
          parseInt(members[1])
        ]
      };
  
      /**
       * sound type is a bitwise flag enum
       * 0 : normal
       * 2 : whistle
       * 4 : finish
       * 8 : clap
       */
      if ((soundType & 2) == 2)              { hitObject.soundTypes.push('whistle'); }
      if ((soundType & 4) == 4)              { hitObject.soundTypes.push('finish');  }
      if ((soundType & 8) == 8)              { hitObject.soundTypes.push('clap');    }
      if (hitObject.soundTypes.length === 0) { hitObject.soundTypes.push('normal'); }
  
      /**
       * object type is a bitwise flag enum
       * 1: circle
       * 2: slider
       * 8: spinner
       */
      if ((objectType & 1) == 1) {
        // Circle
        beatmap.nbCircles++;
        hitObject.objectName = 'circle';
        hitObject.additions  = parseAdditions(members[5]);
      } else if ((objectType & 8) == 8) {
        // Spinner
        beatmap.nbSpinners++;
        hitObject.objectName = 'spinner';
        hitObject.endTime    = parseInt(members[5]);
        hitObject.additions  = parseAdditions(members[6]);
      } else if ((objectType & 2) == 2) {
        // Slider
        beatmap.nbSliders++;
        hitObject.objectName  = 'slider';
        hitObject.repeatCount = parseInt(members[6]);
        hitObject.pixelLength = parseInt(members[7]);
        hitObject.additions   = parseAdditions(members[10]);
        hitObject.edges       = [];
        hitObject.points      = [
          [hitObject.position[0], hitObject.position[1]]
        ];
  
        /**
         * Calculate slider duration
         */
        var timing = getTimingPoint(hitObject.startTime);
  
        if (timing) {
          var pxPerBeat      = beatmap.SliderMultiplier * 100 * timing.velocity;
          var beatsNumber    = (hitObject.pixelLength * hitObject.repeatCount) / pxPerBeat;
          hitObject.duration = Math.ceil(beatsNumber * timing.beatLength);
          hitObject.endTime  = hitObject.startTime + hitObject.duration;
        }
        /**
         * Parse slider points
         */
        var points = (members[5] || '').split('|');
        if (points.length) {
          hitObject.curveType = curveTypes[points[0]] || 'unknown';
  
          for (var i = 1, l = points.length; i < l; i++) {
            var coordinates = points[i].split(':');
            hitObject.points.push([
              parseInt(coordinates[0]),
              parseInt(coordinates[1])
            ]);
          }
        }
  
        var edgeSounds    = [];
        var edgeAdditions = [];
        if (members[8]) { edgeSounds    = members[8].split('|'); }
        if (members[9]) { edgeAdditions = members[9].split('|'); }
  
        /**
         * Get soundTypes and additions for each slider edge
         */
        for (var j = 0, lgt = hitObject.repeatCount + 1; j < lgt; j++) {
          var edge = {
            soundTypes: [],
            additions: parseAdditions(edgeAdditions[j])
          };
  
          if (edgeSounds[j]) {
            var sound = edgeSounds[j];
            if ((sound & 2) == 2)             { edge.soundTypes.push('whistle'); }
            if ((sound & 4) == 4)             { edge.soundTypes.push('finish');  }
            if ((sound & 8) == 8)             { edge.soundTypes.push('clap');    }
            if (edge.soundTypes.length === 0) { edge.soundTypes.push('normal');  }
          } else {
            edge.soundTypes.push('normal');
          }
  
          hitObject.edges.push(edge);
        }
  
        // get coordinates of the slider endpoint
        var endPoint = slidercalc.getEndPoint(hitObject.curveType, hitObject.pixelLength, hitObject.points);
        if (endPoint && endPoint[0] && endPoint[1]) {
          hitObject.endPosition = [
            Math.round(endPoint[0]),
            Math.round(endPoint[1])
          ];
        } else {
          // If endPosition could not be calculated, approximate it by setting it to the last point
          hitObject.endPosition = hitObject.points[hitObject.points.length - 1];
        }
      } else {
        // Unknown
        hitObject.objectName = 'unknown';
      }
  
      beatmap.hitObjects.push(hitObject);
    }