const Rhythm = require('./rhythm');
const Sequencer = require('./sequencer');
/**
* A part of a {@link Section}.
*
* Represents a musical idea, such as a melody, bassline, or drum groove.
* Produces musical notes whose {@link Pitch} (normally) depends on the
* {@link Scale} and {@link Harmony} of the containing {@link Section}
*
* See the overview on the [documentation homepage](./index.html).
* @extends Sequencer
*/
class Part extends Sequencer {
/**
* @arg {Object} options
* @arg {Number} options.channel The MIDI channel that this part's notes will output to.
*
* When using realtime MIDI output, the channel you use depends on what receives the MIDI.
* For example, you can use this to route to different tracks in your DAW.
*
* For MIDI file output, the part channel determines which MIDI file track will be used.
*
* Using the same channel for multiple parts can be used to, for example, create polyrhythms or counterpoint.
*
* Must be provided unless this instance is constructed by the containing {@link Section},
* in which case it will default to the `index+1` within that section's parts list.
* @arg {String} [options.mode] Determines how the containing {@link Section} interprets pitch numbers
* in this Part. Depending on the mode, pitch numbers will be relative to the section's {@link Scale}
* or the current {@link Chord} of the section's {@link Harmony}.
*
* Any integer pitch number is allowed as long as it produces a valid MIDI pitch value (TODO explain/link).
* When there are no more pitches in the scale/chord because the pitch number is too large,
* it wraps around to the beginning of the scale/chord an octave up.
* Negative numbers go downward and wrap around an octave down. TODO: is there a better place to explain this (in Scale/Chord)?
*
* Note: When the pitch is a {@link Pitch} object, that pitch will be produced and the mode has no effect.
*
* Supported mode values:
* - `"scale"` - Pitch numbers are relative to the section's scale and only produce notes from that scale.
*
* `0` is the scale's first pitch, `1` is the scale's second pitch, `2` is the third, and so on.
* When the index wraps around, it produces the scale's first pitch an octave up.
* Negative numbers go down the octaves (`-1` is the scale's last pitch an octave down).
*
* Use `"scale"` mode to explore scales while ignoring the current harmony/chords.
*
* - `"chromatic"` - Produces any pitch relative to the scale's first note.
*
* `0` is the scale's first pitch. `1` is the next pitch higher regardless of whether it's in the scale or not.
* `-1` is the next lower pitch below `0`.
*
* Use `"chromatic"` mode to remove pitch constraints when using relative pitch numbers.
*
* - `"lead"` - Similar to `"scale"` mode, except `0` starts from the first note of the current chord.
*
* Use `"lead"` mode for melodies.
*
* - `"bass"` - Similar to `"scale"` mode, except `0` starts from the root note of the current chord (ignoring any chord inversions).
* TODO: document Chord inversions and link
*
* Use `"bass"` mode for basslines.
*
* - `"chord"` - Produces the current chord.
*
* `0` is exactly the current chord, `1` is the next chord inversion up,
* `-1` is the next chord inversion down, and so on.
*
* Use `"chord"` mode to play the chord progression.
*
* - `"arpeggio"` - [arpeggiates](https://en.wikipedia.org/wiki/Arpeggio) the current chord.
*
* A pitch value `0` is the chord's first pitch, `1` is the chord's second pitch, `2` is the third, and so on.
*
* Use `"arpeggio"` mode to play the chord progression one note at a time.
*
* - `null` (no mode) - If no mode is set, pitch numbers are ambiguous and all pitches must be {@link Pitch} objects.
*
* Use no mode / pitch objects for drum parts.
*
* The supported modes are available via static {@link Part.MODES} constants of this class.
*
* @arg {Iterable} options.pitches
* @arg {Rhythm|String|Iterable|Object} [options.rhythm=pitches.map(p=>1)] Either a Rhythm object, or options for
* the {@link Rhythm|Rhythm constructor}. When a String or Iterable, it's used as the `pattern` option for the Rhythm
* constructor (for convenience). Otherwise, it's treated as the entire options object for the constructor.
* @arg {Number} [options.pulse] When the `rhythm` option is a String, this gets passed to the
* {@link Rhythm|Rhythm constructor}.
* @arg {Number} [options.octave=4]
* @arg {Number} [options.length] The length of the part in beats.
*
* Must be provided when the `looped` option is true.
*
* Also note the containing {@link Section} will default its length to the max length
* of its parts, so set your section and/or part lengths accordingly.
* @arg {Boolean} [options.looped=false] If true, this part will repeat infinitely, starting from the beginning each
* time the part length is reached.
* @arg {Number} [options.delay=0] Delays the start of the part (relative to the start of the containing {@link Section})
* by the given number of beats.
*/
constructor({ channel=1, mode, pitches=[], rhythm, pulse, octave=4, length, looped=false, delay=0 }={}) {
if (!rhythm) rhythm = pitches.map(() => 1);
rhythm = rhythm instanceof Rhythm ? rhythm : new Rhythm(
(typeof rhythm === 'string' || rhythm instanceof Array) ? { pattern: rhythm, pulse } : rhythm
);
length = length || rhythm.length;
super({ time: rhythm, pitch: pitches }, { length, looped, delay });
this.channel = channel;
this.mode = mode;
this.pitches = pitches;
this.rhythm = rhythm;
this.octave = octave;
}
}
// TODO: Should we move the verbose constructor documentation for the individual modes down here?
/**
* Constants for all supported Part.mode options. See [constructor documentation]{@link Part} for descriptions.
* @const
* @prop ARPEGGIO {String}
* @prop BASS {String}
* @prop CHORD {String}
* @prop CHROMATIC {String}
* @prop LEAD {String}
* @prop SCALE {String}
*/
Part.MODES = Object.freeze({
ARPEGGIO: 'arpeggio',
BASS: 'bass',
CHORD: 'chord',
CHROMATIC: 'chromatic',
LEAD: 'lead',
SCALE: 'scale',
});
module.exports = Object.freeze(Part);