Source: structure/sequencer.js

/**
 * Generic sequencing logic *intended for internal use*.
 *
 * The superclass of {@link Rhythm}, {@link Harmony}, and {@link Part}.
 */
class Sequencer {

  /**
   *
   * @param {Object} iterablesByName the property names and iterables used by @@iterator()
   * @param {Object} options see subclass documentation
   */
  constructor(iterablesByName={}, { length, looped=false, delay=0 }={}) {
    this.iterablesByName = iterablesByName;
    this.length = length;
    this.looped = looped;
    this.delay = delay;
  }

  /**
   * @function @@iterator
   * @memberOf Sequencer
   * @instance
   * @description The `[Symbol.iterator]()` generator function* that implements the
   *              [iterable protocol]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#The_iterable_protocol}
   * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols|MDN: Iteration Protocols}
   * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/iterator|MDN: Symbol.iterator}
   * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*|MDN: function*}
   */
  *[Symbol.iterator]() {
    const iterablesByName = this.iterablesByName;
    const names =  Object.keys(iterablesByName);
    const iterables = names.map(name => iterablesByName[name]);
    const iterators = iterables.map(iterable => iterable[Symbol.iterator]());
    const isDones = iterators.map(() => false);
    let timeOffset = this.delay;
    let result;
    do {
      if (result) yield result;
      const nexts = iterators.map(iterator => iterator.next());
      result = {};
      for (let i = 0; i < nexts.length; i++) {
        const name = names[i];
        if (nexts[i].done) {
          isDones[i] = true;
          iterators[i] = iterables[i][Symbol.iterator]();
          nexts[i] = iterators[i].next();
          if (nexts[i].done) return; // empty iterator, give up
          if (name === 'time') timeOffset += this.length;
        }
        let value = nexts[i].value;
        if (value && value.constructor === Object) {
          for (const subname of Object.keys(value)) {
            result[subname] = value[subname];
            if (subname === 'time') result[subname] += timeOffset;
          }
        } else {
          if (value && value.next instanceof Function) { // nested Iterator (such as a Random generator function)
            value = value.next().value;
          }
          result[name] = value;
          if (name === 'time') result[name] += timeOffset;
        }
      }
    } while (this.looped || isDones.includes(false));
  }
}

module.exports = Sequencer;