music21j-port
Version:
A toolkit for computer-aided musicology, Javascript version
1,569 lines (1,391 loc) • 116 kB
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Music21j Source: music21/stream.js</title>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/sunlight.default.css">
<link type="text/css" rel="stylesheet" href="styles/site.spruce.css">
</head>
<body>
<div class="container-fluid">
<div class="navbar navbar-fixed-top navbar-inverse">
<div class="navbar-inner">
<a class="brand" href="index.html">Music21j</a>
<ul class="nav">
<li class="dropdown">
<a href="namespaces.list.html" class="dropdown-toggle" data-toggle="dropdown">Namespaces<b
class="caret"></b></a>
<ul class="dropdown-menu ">
<li>
<a href="music21.html">music21</a>
</li>
<li>
<a href="music21.articulations.html">music21.articulations</a>
</li>
<li>
<a href="music21.audioSearch.html">music21.audioSearch</a>
</li>
<li>
<a href="music21.base.html">music21.base</a>
</li>
<li>
<a href="music21.beam.html">music21.beam</a>
</li>
<li>
<a href="music21.chord.html">music21.chord</a>
</li>
<li>
<a href="music21.clef.html">music21.clef</a>
</li>
<li>
<a href="music21.common.html">music21.common</a>
</li>
<li>
<a href="music21.duration.html">music21.duration</a>
</li>
<li>
<a href="music21.dynamics.html">music21.dynamics</a>
</li>
<li>
<a href="music21.expressions.html">music21.expressions</a>
</li>
<li>
<a href="music21.fromPython.html">music21.fromPython</a>
</li>
<li>
<a href="music21.instrument.html">music21.instrument</a>
</li>
<li>
<a href="music21.interval.html">music21.interval</a>
</li>
<li>
<a href="music21.key.html">music21.key</a>
</li>
<li>
<a href="music21.keyboard.html">music21.keyboard</a>
</li>
<li>
<a href="music21.meter.html">music21.meter</a>
</li>
<li>
<a href="music21.miditools.html">music21.miditools</a>
</li>
<li>
<a href="music21.note.html">music21.note</a>
</li>
<li>
<a href="music21.pitch.html">music21.pitch</a>
</li>
<li>
<a href="music21.prebase.html">music21.prebase</a>
</li>
<li>
<a href="music21.renderOptions.html">music21.renderOptions</a>
</li>
<li>
<a href="music21.roman.html">music21.roman</a>
</li>
<li>
<a href="music21.scale.html">music21.scale</a>
</li>
<li>
<a href="music21.sites.html">music21.sites</a>
</li>
<li>
<a href="music21.stream.html">music21.stream</a>
</li>
<li>
<a href="music21.tempo.html">music21.tempo</a>
</li>
<li>
<a href="music21.tie.html">music21.tie</a>
</li>
<li>
<a href="music21.tinyNotation.html">music21.tinyNotation</a>
</li>
<li>
<a href="music21.vfShow.html">music21.vfShow</a>
</li>
<li>
<a href="music21.webmidi.html">music21.webmidi</a>
</li>
</ul>
</li>
<li class="dropdown">
<a href="classes.list.html" class="dropdown-toggle" data-toggle="dropdown">Classes<b
class="caret"></b></a>
<ul class="dropdown-menu ">
<li>
<a href="music21.articulations.Accent.html">music21.articulations.Accent</a>
</li>
<li>
<a href="music21.articulations.Articulation.html">music21.articulations.Articulation</a>
</li>
<li>
<a href="music21.articulations.DynamicArticulation.html">music21.articulations.DynamicArticulation</a>
</li>
<li>
<a href="music21.articulations.LengthArticulation.html">music21.articulations.LengthArticulation</a>
</li>
<li>
<a href="music21.articulations.Marcato.html">music21.articulations.Marcato</a>
</li>
<li>
<a href="music21.articulations.PitchArticulation.html">music21.articulations.PitchArticulation</a>
</li>
<li>
<a href="music21.articulations.Spiccato.html">music21.articulations.Spiccato</a>
</li>
<li>
<a href="music21.articulations.Staccatissimo.html">music21.articulations.Staccatissimo</a>
</li>
<li>
<a href="music21.articulations.Staccato.html">music21.articulations.Staccato</a>
</li>
<li>
<a href="music21.articulations.StrongAccent.html">music21.articulations.StrongAccent</a>
</li>
<li>
<a href="music21.articulations.Tenuto.html">music21.articulations.Tenuto</a>
</li>
<li>
<a href="music21.articulations.TimbreArticulation.html">music21.articulations.TimbreArticulation</a>
</li>
<li>
<a href="music21.base.Music21Object.html">music21.base.Music21Object</a>
</li>
<li>
<a href="music21.beam.Beam.html">music21.beam.Beam</a>
</li>
<li>
<a href="music21.beam.Beams.html">music21.beam.Beams</a>
</li>
<li>
<a href="music21.chord.Chord.html">music21.chord.Chord</a>
</li>
<li>
<a href="music21.clef.AltoClef.html">music21.clef.AltoClef</a>
</li>
<li>
<a href="music21.clef.BassClef.html">music21.clef.BassClef</a>
</li>
<li>
<a href="music21.clef.Clef.html">music21.clef.Clef</a>
</li>
<li>
<a href="music21.clef.MezzoSopranoClef.html">music21.clef.MezzoSopranoClef</a>
</li>
<li>
<a href="music21.clef.PercussionClef.html">music21.clef.PercussionClef</a>
</li>
<li>
<a href="music21.clef.SopranoClef.html">music21.clef.SopranoClef</a>
</li>
<li>
<a href="music21.clef.TenorClef.html">music21.clef.TenorClef</a>
</li>
<li>
<a href="music21.clef.Treble8vaClef.html">music21.clef.Treble8vaClef</a>
</li>
<li>
<a href="music21.clef.Treble8vbClef.html">music21.clef.Treble8vbClef</a>
</li>
<li>
<a href="music21.clef.TrebleClef.html">music21.clef.TrebleClef</a>
</li>
<li>
<a href="music21.duration.Duration.html">music21.duration.Duration</a>
</li>
<li>
<a href="music21.duration.Tuplet.html">music21.duration.Tuplet</a>
</li>
<li>
<a href="music21.dynamics.Dynamic.html">music21.dynamics.Dynamic</a>
</li>
<li>
<a href="music21.expressions.Expression.html">music21.expressions.Expression</a>
</li>
<li>
<a href="music21.expressions.Fermata.html">music21.expressions.Fermata</a>
</li>
<li>
<a href="music21.fromPython.Converter.html">music21.fromPython.Converter</a>
</li>
<li>
<a href="music21.instrument.Instrument.html">music21.instrument.Instrument</a>
</li>
<li>
<a href="music21.interval.ChromaticInterval.html">music21.interval.ChromaticInterval</a>
</li>
<li>
<a href="music21.interval.DiatonicInterval.html">music21.interval.DiatonicInterval</a>
</li>
<li>
<a href="music21.interval.GenericInterval.html">music21.interval.GenericInterval</a>
</li>
<li>
<a href="music21.interval.Interval.html">music21.interval.Interval</a>
</li>
<li>
<a href="music21.key.Key.html">music21.key.Key</a>
</li>
<li>
<a href="music21.key.KeySignature.html">music21.key.KeySignature</a>
</li>
<li>
<a href="music21.keyboard.BlackKey.html">music21.keyboard.BlackKey</a>
</li>
<li>
<a href="music21.keyboard.Key.html">music21.keyboard.Key</a>
</li>
<li>
<a href="music21.keyboard.Keyboard.html">music21.keyboard.Keyboard</a>
</li>
<li>
<a href="music21.keyboard.WhiteKey.html">music21.keyboard.WhiteKey</a>
</li>
<li>
<a href="music21.meter.TimeSignature.html">music21.meter.TimeSignature</a>
</li>
<li>
<a href="music21.miditools.Event.html">music21.miditools.Event</a>
</li>
<li>
<a href="music21.miditools.MidiPlayer.html">music21.miditools.MidiPlayer</a>
</li>
<li>
<a href="music21.note.GeneralNote.html">music21.note.GeneralNote</a>
</li>
<li>
<a href="music21.note.Lyric.html">music21.note.Lyric</a>
</li>
<li>
<a href="music21.note.Note.html">music21.note.Note</a>
</li>
<li>
<a href="music21.note.NotRest.html">music21.note.NotRest</a>
</li>
<li>
<a href="music21.note.Rest.html">music21.note.Rest</a>
</li>
<li>
<a href="music21.pitch.Accidental.html">music21.pitch.Accidental</a>
</li>
<li>
<a href="music21.pitch.Pitch.html">music21.pitch.Pitch</a>
</li>
<li>
<a href="music21.prebase.ProtoM21Object.html">music21.prebase.ProtoM21Object</a>
</li>
<li>
<a href="music21.renderOptions.RenderOptions.html">music21.renderOptions.RenderOptions</a>
</li>
<li>
<a href="music21.roman.RomanNumeral.html">music21.roman.RomanNumeral</a>
</li>
<li>
<a href="music21.stream.Measure.html">music21.stream.Measure</a>
</li>
<li>
<a href="music21.stream.Part.html">music21.stream.Part</a>
</li>
<li>
<a href="music21.stream.Score.html">music21.stream.Score</a>
</li>
<li>
<a href="music21.stream.Stream.html">music21.stream.Stream</a>
</li>
<li>
<a href="music21.stream.Voice.html">music21.stream.Voice</a>
</li>
<li>
<a href="music21.tempo.Metronome.html">music21.tempo.Metronome</a>
</li>
<li>
<a href="music21.tie.Tie.html">music21.tie.Tie</a>
</li>
<li>
<a href="music21.vfShow.Renderer.html">music21.vfShow.Renderer</a>
</li>
<li>
<a href="music21.vfShow.RenderStack.html">music21.vfShow.RenderStack</a>
</li>
<li>
<a href="Notation.html">Notation</a>
</li>
<li>
<a href="Page.html">Page</a>
</li>
<li>
<a href="Recorder.html">Recorder</a>
</li>
</ul>
</li>
<li class="dropdown">
<a href="global.html" class="dropdown-toggle" data-toggle="dropdown">Global<b
class="caret"></b></a>
<ul class="dropdown-menu ">
<li>
<a href="global.html#getM21attribute">getM21attribute</a>
</li>
<li>
<a href="global.html#layout">layout</a>
</li>
<li>
<a href="global.html#midiNumDiffFromFrequency">midiNumDiffFromFrequency</a>
</li>
<li>
<a href="global.html#recorderWorkerJs">recorderWorkerJs</a>
</li>
<li>
<a href="global.html#unpickler">unpickler</a>
</li>
<li>
<a href="global.html#warnBanner">warnBanner</a>
</li>
</ul>
</li>
</ul>
</div>
</div>
<div class="row-fluid">
<div class="span12">
<div id="main">
<h1 class="page-title">Source: music21/stream.js</h1>
<section>
<article>
<pre
class="sunlight-highlight-javascript linenums">/**
* music21j -- Javascript reimplementation of Core music21p features.
* music21/stream -- Streams -- objects that hold other Music21Objects
*
* Does not implement the full features of music21p Streams by a long shot...
*
* Copyright (c) 2013-17, Michael Scott Cuthbert and cuthbertLab
* Based on music21 (=music21p), Copyright (c) 2006-17, Michael Scott Cuthbert and cuthbertLab
*
*/
import * as $ from 'jquery';
import * as MIDI from 'MIDI';
import { Music21Exception } from './exceptions21.js';
import { base } from './base.js';
import { beam } from './beam.js';
import { clef } from './clef.js';
import { common } from './common.js';
import { debug } from './debug.js';
import { duration } from './duration.js';
import { instrument } from './instrument.js';
import { meter } from './meter.js';
import { pitch } from './pitch.js';
import { renderOptions } from './renderOptions.js';
import { vfShow } from './vfShow.js';
/**
* powerful stream module, See {@link music21.stream} namespace
* @exports music21/stream
*/
/**
* Streams are powerful music21 objects that hold Music21Object collections,
* such as {@link music21.note.Note} or {@link music21.chord.Chord} objects.
*
* Understanding the {@link music21.stream.Stream} object is of fundamental
* importance for using music21. Definitely read the music21(python) tutorial
* on using Streams before proceeding
*
* @namespace music21.stream
* @memberof music21
* @requires music21/base
* @requires music21/renderOptions
* @requires music21/clef
* @requires music21/vfShow
* @requires music21/duration
* @requires music21/common
* @requires music21/meter
* @requires music21/pitch
* @requires jquery
*/
export const stream = {};
class StreamException extends Music21Exception {}
stream.StreamException = StreamException;
/**
* A generic Stream class -- a holder for other music21 objects
* Will be subclassed into {@link music21.stream.Score},
* {@link music21.stream.Part},
* {@link music21.stream.Measure},
* {@link music21.stream.Voice}, but most functions will be found here.
*
* @class Stream
* @memberof music21.stream
* @extends music21.base.Music21Object
*
* @property {Array<music21.base.Music21Object>} elements - the elements in the stream. DO NOT MODIFY individual components (consider it like a Python tuple)
* @property {int} length - (readonly) the number of elements in the stream.
* @property {music21.duration.Duration} duration - the total duration of the stream's elements
* @property {number} highestTime -- the highest time point in the stream's elements
* @property {music21.clef.Clef} clef - the clef for the Stream (if there is one; if there are multiple, then the first clef)
* @property {music21.meter.TimeSignature} timeSignature - the first TimeSignature of the Stream
* @property {music21.key.KeySignature} keySignature - the first KeySignature for the Stream
* @property {music21.renderOptions.RenderOptions} renderOptions - an object specifying how to render the stream
* @property {music21.stream.Stream} flat - (readonly) a flattened representation of the Stream
* @property {music21.stream.Stream} notes - (readonly) the stream with only {@link music21.note.Note} and {@link music21.chord.Chord} objects included
* @property {music21.stream.Stream} notesAndRests - (readonly) like notes but also with {@link music21.note.Rest} objects included
* @property {music21.stream.Stream} parts - (readonly) a filter on the Stream to just get the parts (NON-recursive)
* @property {music21.stream.Stream} measures - (readonly) a filter on the Stream to just get the measures (NON-recursive)
* @property {number} tempo - tempo in beats per minute (will become more sophisticated later, but for now the whole stream has one tempo
* @property {music21.instrument.Instrument|undefined} instrument - an instrument object associated with the stream (can be set with a string also, but will return an `Instrument` object)
* @property {Boolean} autoBeam - whether the notes should be beamed automatically or not (will be moved to `renderOptions` soon)
* @property {int} [staffLines=5] - number of staff lines
* @property {function|undefined} changedCallbackFunction - function to call when the Stream changes through a standard interface
* @property {number} maxSystemWidth - confusing... should be in renderOptions
*/
export class Stream extends base.Music21Object {
constructor() {
super();
// class variables;
this.isStream = true;
this.isMeasure = false;
this.classSortOrder = -20;
this.recursionType = 'elementsFirst';
this._duration = undefined;
this._elements = [];
this._elementOffsets = [];
this._clef = undefined;
this.displayClef = undefined;
this._keySignature = undefined; // a music21.key.KeySignature object
this._timeSignature = undefined; // a music21.meter.TimeSignature object
this._instrument = undefined;
this._autoBeam = undefined;
this.activeVFStave = undefined;
this.activeVFRenderer = undefined;
this.renderOptions = new renderOptions.RenderOptions();
this._tempo = undefined;
this.staffLines = 5;
this._stopPlaying = false;
this._allowMultipleSimultaneousPlays = true; // not implemented yet.
this.changedCallbackFunction = undefined; // for editable svges
/**
* A function bound to the current stream that
* will changes the stream. Used in editableAccidentalDOM, among other places.
*
* var can = s.appendNewDOM();
* $(can).on('click', s.DOMChangerFunction);
*
* @memberof music21.stream.Stream
* @param {Event} e
* @returns {music21.base.Music21Object|undefined} - returns whatever changedCallbackFunction does.
*/
this.DOMChangerFunction = e => {
const canvasOrSVGElement = e.currentTarget;
const [clickedDiatonicNoteNum, foundNote] = this.findNoteForClick(
canvasOrSVGElement,
e
);
if (foundNote === undefined) {
if (debug) {
console.log('No note found');
}
return undefined;
}
return this.noteChanged(
clickedDiatonicNoteNum,
foundNote,
canvasOrSVGElement
);
};
}
* [Symbol.iterator]() {
for (let i = 0; i < this.length; i++) {
yield this.get(i);
}
}
get duration() {
if (this._duration !== undefined) {
return this._duration;
}
return new duration.Duration(this.highestTime);
}
set duration(newDuration) {
this._duration = newDuration;
}
get highestTime() {
let highestTime = 0.0;
for (const el of this) {
let endTime = el.offset;
if (el.duration !== undefined) {
endTime += el.duration.quarterLength;
}
if (endTime > highestTime) {
highestTime = endTime;
}
}
return highestTime;
}
get semiFlat() {
return this._getFlatOrSemiFlat(true);
}
get flat() {
return this._getFlatOrSemiFlat(false);
}
_getFlatOrSemiFlat(retainContainers) {
if (!this.hasSubStreams()) {
return this;
}
const tempEls = [];
for (const el of this) {
if (el.isStream) {
if (retainContainers) {
tempEls.push(el);
}
const offsetShift = el.offset;
// console.log('offsetShift', offsetShift, el.classes[el.classes.length -1]);
const elFlat = el._getFlatOrSemiFlat(retainContainers);
for (let j = 0; j < elFlat.length; j++) {
// offset should NOT be null because already in Stream
elFlat.get(j).offset += offsetShift;
}
tempEls.push(...elFlat._elements);
} else {
tempEls.push(el);
}
}
const newSt = this.clone(false);
newSt.elements = tempEls;
return newSt;
}
get notes() {
return this.getElementsByClass(['Note', 'Chord']);
}
get notesAndRests() {
return this.getElementsByClass('GeneralNote');
}
get tempo() {
if (this._tempo === undefined && this.activeSite !== undefined) {
return this.activeSite.tempo;
} else if (this._tempo === undefined) {
return 150;
} else {
return this._tempo;
}
}
set tempo(newTempo) {
this._tempo = newTempo;
}
get instrument() {
if (this._instrument === undefined && this.activeSite !== undefined) {
return this.activeSite.instrument;
} else {
return this._instrument;
}
}
set instrument(newInstrument) {
if (typeof newInstrument === 'string') {
newInstrument = new instrument.Instrument(newInstrument);
}
this._instrument = newInstrument;
}
get clef() {
if (this._clef === undefined && this.activeSite === undefined) {
return new clef.Clef('treble');
} else if (this._clef === undefined) {
return this.activeSite.clef;
} else {
return this._clef;
}
}
set clef(newClef) {
this._clef = newClef;
}
get keySignature() {
if (this._keySignature === undefined && this.activeSite !== undefined) {
return this.activeSite.keySignature;
} else {
return this._keySignature;
}
}
set keySignature(newKeySignature) {
this._keySignature = newKeySignature;
}
get timeSignature() {
if (
this._timeSignature === undefined
&& this.activeSite !== undefined
) {
return this.activeSite.timeSignature;
} else {
return this._timeSignature;
}
}
set timeSignature(newTimeSignature) {
if (typeof newTimeSignature === 'string') {
newTimeSignature = new meter.TimeSignature(newTimeSignature);
}
this._timeSignature = newTimeSignature;
}
get autoBeam() {
if (this._autoBeam === undefined && this.activeSite !== undefined) {
return this.activeSite.autoBeam;
} else if (this._autoBeam !== undefined) {
return this._autoBeam;
} else {
return true; // default...
}
}
set autoBeam(ab) {
this._autoBeam = ab;
}
get maxSystemWidth() {
let baseMaxSystemWidth = 750;
if (
this.renderOptions.maxSystemWidth === undefined
&& this.activeSite !== undefined
) {
baseMaxSystemWidth = this.activeSite.maxSystemWidth;
} else if (this.renderOptions.maxSystemWidth !== undefined) {
baseMaxSystemWidth = this.renderOptions.maxSystemWidth;
}
return baseMaxSystemWidth / this.renderOptions.scaleFactor.x;
}
set maxSystemWidth(newSW) {
this.renderOptions.maxSystemWidth
= newSW * this.renderOptions.scaleFactor.x;
}
get parts() {
return this.getElementsByClass('Part');
}
get measures() {
return this.getElementsByClass('Measure');
}
get voices() {
return this.getElementsByClass('Voice');
}
get length() {
return this._elements.length;
}
get elements() {
return this._elements;
}
set elements(newElements) {
let highestOffsetSoFar = 0.0;
this._elements = [];
this._elementOffsets = [];
const tempInsert = [];
let i;
let thisEl;
for (i = 0; i < newElements.length; i++) {
thisEl = newElements[i];
const thisElOffset = thisEl.offset;
if (thisElOffset === null || thisElOffset === highestOffsetSoFar) {
// append
this._elements.push(thisEl);
thisEl.offset = highestOffsetSoFar;
this._elementOffsets.push(highestOffsetSoFar);
if (thisEl.duration === undefined) {
console.error('No duration for ', thisEl, ' in ', this);
}
highestOffsetSoFar += thisEl.duration.quarterLength;
} else {
// append -- slow
tempInsert.push(thisEl);
}
}
// console.warn('end', highestOffsetSoFar, tempInsert);
for (i = 0; i < tempInsert.length; i++) {
thisEl = tempInsert[i];
this.insert(thisEl.offset, thisEl);
}
}
/* override protoM21Object.clone() */
clone(deep=true) {
const ret = Object.create(this.constructor.prototype);
for (const key in this) {
if ({}.hasOwnProperty.call(this, key) === false) {
continue;
}
if (key === 'activeSite') {
ret[key] = this[key];
} else if (key === 'renderOptions') {
ret[key] = common.merge({}, this[key]);
} else if (
deep !== true
&& (key === '_elements' || key === '_elementOffsets')
) {
ret[key] = this[key].slice(); // shallow copy...
} else if (
deep
&& (key === '_elements' || key === '_elementOffsets')
) {
if (key === '_elements') {
// console.log('got elements for deepcopy');
ret._elements = [];
ret._elementOffsets = [];
for (let j = 0; j < this._elements.length; j++) {
ret._elementOffsets[j] = this._elementOffsets[j];
const el = this._elements[j];
// console.log('cloning el: ', el.name);
const elCopy = el.clone(deep);
elCopy.activeSite = ret;
ret._elements[j] = elCopy;
}
}
} else if (
key === 'activeVexflowNote'
|| key === 'storedVexflowstave'
) {
// do nothing -- do not copy vexflowNotes -- permanent recursion
} else if (key in this._cloneCallbacks) {
if (this._cloneCallbacks[key] === true) {
ret[key] = this[key];
} else if (this._cloneCallbacks[key] === false) {
ret[key] = undefined;
} else {
// call the cloneCallbacks function
this._cloneCallbacks[key](key, ret, this);
}
} else if (
Object.getOwnPropertyDescriptor(this, key).get !== undefined
|| Object.getOwnPropertyDescriptor(this, key).set !== undefined
) {
// do nothing
} else if (typeof this[key] === 'function') {
// do nothing -- events might not be copied.
} else if (
this[key] !== null
&& this[key] !== undefined
&& this[key].isMusic21Object === true
) {
// console.log('cloning...', key);
ret[key] = this[key].clone(deep);
} else {
ret[key] = this[key];
}
}
return ret;
}
/**
* Add an element to the end of the stream, setting its `.offset` accordingly
*
* @memberof music21.stream.Stream
* @param {music21.base.Music21Object|Array} el - element or list of elements to append
* @returns {this}
*/
append(elOrElList) {
if (Array.isArray(elOrElList)) {
for (const el of elOrElList) {
this.append(el);
}
return this;
}
const el = elOrElList;
try {
if (
el.isClassOrSubclass !== undefined
&& el.isClassOrSubclass('NotRest')
) {
// set stem direction on output...;
}
let elOffset = 0.0;
if (this._elements.length > 0) {
const lastQL = this._elements[this._elements.length - 1]
.duration.quarterLength;
elOffset
= this._elementOffsets[this._elementOffsets.length - 1]
+ lastQL;
}
this._elementOffsets.push(elOffset);
this._elements.push(el);
el.offset = elOffset;
el.sites.add(this);
el.activeSite = this; // would prefer weakref, but does not exist in JS.
} catch (err) {
console.error(
'Cannot append element ',
el,
' to stream ',
this,
' : ',
err
);
}
return this;
}
/**
* Add an element to the specified place in the stream, setting its `.offset` accordingly
*
* @memberof music21.stream.Stream
* @param {number} offset - offset to place.
* @param {music21.base.Music21Object} el - element to append
* @returns {this}
*/
insert(offset, el) {
try {
if (
el.isClassOrSubclass !== undefined
&& el.isClassOrSubclass('NotRest')
) {
// set stem direction on output
// this.clef.setStemDirection(el);
}
for (let i = 0; i < this._elements.length; i++) {
const testOffset = this._elementOffsets[i];
if (testOffset <= offset) {
continue;
} else {
this._elementOffsets.splice(i, 0, offset);
this._elements.splice(i, 0, el);
el.offset = offset;
el.activeSite = this;
return this;
}
}
// no element found. insert at end;
this._elementOffsets.push(offset);
this._elements.push(el);
el.offset = offset;
el.sites.add(this);
el.activeSite = this; // would prefer weakref, but does not exist in JS.
} catch (err) {
console.error(
'Cannot insert element ',
el,
' to stream ',
this,
' : ',
err
);
}
return this;
}
/**
* Inserts a single element at offset, shifting elements at or after it begins
* later in the stream.
*
* In single argument form, assumes it is an element and takes the offset from the element.
*
* Unlike music21p, does not take a list of elements. TODO(msc): add this.
*/
insertAndShift(offset, elementOrNone) {
let element;
if (elementOrNone === undefined) {
element = offset;
offset = element.offset;
} else {
element = elementOrNone;
}
const amountToShift = element.duration.quarterLength;
let shiftingOffsets = false;
for (let i = 0; i < this.length; i++) {
if (!shiftingOffsets && this._elementOffsets[i] >= offset) {
shiftingOffsets = true;
}
if (shiftingOffsets) {
this._elementOffsets[i] += amountToShift;
this._elements[i].offset = this._elementOffsets[i];
}
}
this.insert(offset, element);
}
/**
* Return the first matched index
*/
index(el) {
let count = 0;
for (const e of this._elements) {
if (el === e) {
return count;
}
count += 1;
}
// endElements
throw new StreamException(
`cannot find object (${el}) in Stream`
);
}
/**
* Remove and return the last element in the stream,
* or return undefined if the stream is empty
*
* @memberof music21.stream.Stream
* @returns {music21.base.Music21Object|undefined} last element in the stream
*/
pop() {
// remove last element;
if (this.length > 0) {
const el = this.get(-1);
this._elementOffsets.pop();
this._elements.pop();
el.sites.remove(this);
return el;
} else {
return undefined;
}
}
/**
* Remove an object from this Stream
*/
remove(targetOrList,
{
firstMatchOnly=true,
shiftOffsets=false,
recurse=false,
} = {}) {
if (shiftOffsets === true) {
throw new StreamException('sorry cannot shiftOffsets yet');
}
if (recurse === true) {
throw new StreamException('sorry cannot recurse yet');
}
let targetList;
if (!Array.isArray(targetOrList)) {
targetList = [targetOrList];
} else {
targetList = targetOrList;
}
// if (targetList.length > 1) {
// sort targetList
// }
// let shiftDur = 0.0; // for shiftOffsets
let i = -1;
for (const target of targetList) {
i += 1;
let indexInStream;
try {
indexInStream = this.index(target);
} catch (err) {
if (err instanceof StreamException) {
if (recurse) {
// do something
}
continue;
}
throw err;
}
// const matchOffset = this._elementOffsets[indexInStream];
// let match;
// handle _endElements
// let matchedEndElement = false;
// let baseElementCount = this._elements.length;
this._elements.splice(indexInStream, 1);
this._elementOffsets.splice(indexInStream, 1);
// remove from sites if needed.
// if (shiftOffsets) {
// const matchDuration = target.duration.quarterLength;
// const shiftedRegionStart = matchOffset + matchDuration;
// shiftDur += matchDuration;
// let shiftedRegionEnd;
// if ((i + 1) < targetList.length) {
// const nextElIndex = this.index(targetList[i + 1]);
// const nextElOffset = this._elementOffsets[nextElIndex];
// shiftedRegionEnd = nextElOffset;
// } else {
// shiftedRegionEnd = this.duration.quarterLength;
// }
// if (shiftDur !== 0.0) {
// for (const e of this.getElementsByOffset(
// shiftedRegionStart,
// shiftedRegionEnd,
// {
// includeEndBoundary: false,
// mustFinishInSpan: false,
// mustBeginInSpan: false,
// }
// )) {
// const elementOffset = this.elementOffset(e);
// this.setElementOffset(e, elementOffset - shiftDur);
// }
// }
// }
}
}
/**
* Get the `index`th element from the Stream. Equivalent to the
* music21p format of s[index]. Can use negative indexing to get from the end.
*
* @memberof music21.stream.Stream
* @param {int} index - can be -1, -2, to index from the end, like python
* @returns {music21.base.Music21Object|undefined}
*/
get(index) {
// substitute for Python stream __getitem__; supports -1 indexing, etc.
let el;
if (index === undefined) {
return undefined;
} else if (Math.abs(index) > this._elements.length) {
return undefined;
} else if (index === this._elements.length) {
return undefined;
} else if (index < 0) {
el = this._elements[this._elements.length + index];
el.offset = this._elementOffsets[this._elements.length + index];
return el;
} else {
el = this._elements[index];
el.offset = this._elementOffsets[index];
return el;
}
}
setElementOffset(el, value, addElement = false) {
for (let i = 0; i < this.length; i++) {
if (this._elements[i] === el) {
this._elementOffsets[i] = value;
return;
}
}
if (!addElement) {
throw new StreamException(
'Cannot set the offset for elemenet '
+ el.toString()
+ ', not in Stream'
);
} else {
this.insert(value, el);
}
}
elementOffset(element, stringReturns = false) {
for (let i = 0; i < this.length; i++) {
if (this._elements[i] === element) {
return this._elementOffsets[i];
}
}
throw new StreamException(
'An entry for this object is not stored in this Stream.'
);
}
/* --- ############# END ELEMENT FUNCTIONS ########## --- */
/**
* Returns Boolean about whether this stream contains any other streams.
*
* @memberof music21.stream.Stream
* @returns {Boolean}
*/
hasSubStreams() {
let hasSubStreams = false;
for (const el of this) {
if (el.isClassOrSubclass('Stream')) {
hasSubStreams = true;
break;
}
}
return hasSubStreams;
}
/**
* Takes a stream and places all of its elements into
* measures (:class:`~music21.stream.Measure` objects)
* based on the :class:`~music21.meter.TimeSignature` objects
* placed within
* the stream. If no TimeSignatures are found in the
* stream, a default of 4/4 is used.
* If `options.inPlace` is true, the original Stream is modified and lost
* if `options.inPlace` is False, this returns a modified deep copy.
* @memberof music21.stream.Stream
* @param {Object} options
* @returns {music21.stream.Stream}
*/
makeMeasures(options) {
const params = {
meterStream: undefined,
refStreamOrTimeRange: undefined,
searchContext: false,
innerBarline: undefined,
finalBarline: 'final',
bestClef: false,
inPlace: false,
};
common.merge(params, options);
let voiceCount;
if (this.hasVoices()) {
voiceCount = this.getElementsByClass('Voice').length;
} else {
voiceCount = 0;
}
// meterStream
const meterStream = this.getElementsByClass('TimeSignature');
if (meterStream.length === 0) {
meterStream.append(this.timeSignature);
}
// getContextByClass('Clef')
const clefObj = this.clef;
const offsetMap = this.offsetMap();
let oMax = 0;
for (let i = 0; i < offsetMap.length; i++) {
if (offsetMap[i].endTime > oMax) {
oMax = offsetMap[i].endTime;
}
}
// console.log('oMax: ', oMax);
const post = new this.constructor();
// derivation
let o = 0.0;
let measureCount = 0;
let lastTimeSignature;
let m;
let mStart;
while (measureCount === 0 || o < oMax) {
m = new stream.Measure();
m.number = measureCount + 1;
// var thisTimeSignature = meterStream.getElementAtOrBefore(o);
const thisTimeSignature = this.timeSignature;
if (thisTimeSignature === undefined) {
break;
}
const oneMeasureLength
= thisTimeSignature.barDuration.quarterLength;
if (oneMeasureLength === 0) {
// if for some reason we are advancing not at all, then get out!
break;
}
if (measureCount === 0) {
// simplified...
}
m.clef = clefObj;
m.timeSignature = thisTimeSignature.clone();
for (let voiceIndex = 0; voiceIndex < voiceCount; voiceIndex++) {
const v = new stream.Voice();
v.id = voiceIndex;
m.insert(0, v);
}
post.insert(o, m);
o += oneMeasureLength;
measureCount += 1;
}
for (let i = 0; i < offsetMap.length; i++) {
const ob = offsetMap[i];
const e = ob.element;
const start = ob.offset;
const voiceIndex = ob.voiceIndex;
// if 'Spanner' in e.classes;
lastTimeSignature = undefined;
for (let j = 0; j < post.length; j++) {
m = post.get(j); // nothing but measures...
if (m.timeSignature !== undefined) {
lastTimeSignature = m.timeSignature;
}
mStart = m.getOffsetBySite(post);
const mEnd
= mStart + lastTimeSignature.barDuration.quarterLength;
if (start >= mStart && start < mEnd) {
break;
}
}
// if not match, raise Exception;
const oNew = start - mStart;
if (m.clef === e) {
continue;
}
if (oNew === 0 && e.isClassOrSubclass('TimeSignature')) {
continue;
}
let insertStream = m;
if (voiceIndex !== undefined) {
insertStream = m.getElementsByClass('Voice').get(voiceIndex);
}
insertStream.insert(oNew, e);
}
// set barlines, etc.
if (params.inPlace !== true) {
return post;
} else {
this.elements = [];
// endElements
// elementsChanged;
for (const e of post) {
this.insert(e.offset, e);
}
return this; // javascript style;
}
}
/**
* Return a new Stream or modify this stream
* to have beams.
*
* NOT yet being called March 2018
*/
makeBeams(options) {
const params = { inPlace: false };
common.merge(params, options);
let returnObj = this;
if (!params.inPlace) {
returnObj = this.clone(true);
}
let mColl;
if (this.classes.includes('Measure')) {
mColl = [returnObj];
} else {
mColl = [];
for (const m of returnObj.getElementsByClass('Measure').elements) {
mColl.push(m);
}
}
let lastTimeSignature;
for (const m of mColl) {
if (m.timeSignature !== undefined) {
lastTimeSignature = m.timeSignature;
}
if (lastTimeSignature === undefined) {
throw new StreamException('Need a Time Signature to process beams');
}
// todo voices!
if (m.length <= 1) {
continue; // nothing to beam.
}
const noteStream = m.notesAndRests;
const durList = [];
for (const n of noteStream) {
durList.push(n.duration);
}
const durSum = durList.map(a => a.quarterLength).reduce((total, val) => total + val);
const barQL = lastTimeSignature.barDuration.quarterLength;
if (durSum > barQL) {
continue;
}
let offset = 0.0;
if (m.paddingLeft !== 0.0 && m.paddingLeft !== undefined) {
offset = m.paddingLeft;
} else if (noteStream.highestTime < barQL) {
offset = barQL - noteStream.highestTime;
}
const beamsList = lastTimeSignature.getBeams(noteStream, { measureStartOffset: offset });
for (let i = 0; i < noteStream.length; i++) {
const n = noteStream.get(i);
const thisBeams = beamsList[i];
if (thisBeams !== undefined) {
n.beams = thisBeams;
} else {
n.beams = new beam.Beams();
}
}
}
// returnObj.streamStatus.beams = true;
return returnObj;
}
/**
* Returns true if any note in the stream has lyrics, otherwise false
*
* @memberof music21.stream.Stream
* @returns {Boolean}
*/
hasLyrics() {
for (const el of this) {
if (el.lyric !== undefined) {
return true;
}
}
return false;
}
/**
* Returns a list of OffsetMap objects
*
* @memberof music21.stream.Stream
* @returns [music21.stream.OffsetMap]
*/
offsetMap() {
const offsetMap = [];
let groups = [];
if (this.hasVoices()) {
$.each(this.getElementsByClass('Voice').elements, (i, v) => {
groups.push([v.flat, i]);
});
} else {
groups = [[this, undefined]];
}
for (let i = 0; i < groups.length; i++) {
const group = groups[i][0];
const voiceIndex = groups[i][1];
for (let j = 0; j < group.length; j++) {
const e = group.get(j);
const dur = e.duration.quarterLength;
const offset = e.offset; // TODO: getOffsetBySite(group)
const endTime = offset + dur;
const thisOffsetMap = new stream.OffsetMap(
e,
offset,
endTime,
voiceIndex
);
offsetMap.push(thisOffsetMap);
}
}
return offsetMap;
}
/**
* Find all elements with a certain class; if an Array is given, then any
* matching class will work.
*
* @memberof music21.stream.Stream
* @param {Array<string>|string} classList - a list of classes to find
* @returns {music21.stream.Stream}
*/
getElementsByClass(classList) {
const tempEls = [];
for (const thisEl of this) {
// console.warn(thisEl);
if (thisEl.isClassOrSubclass === undefined) {
console.error(
'what the hell is a ',
thisEl,
'doing in a Stream?'
);
} else if (thisEl.isClassOrSubclass(classList)) {
tempEls.push(thisEl);
}
}
const newSt = this.clone(false);
newSt.elements = tempEls;
return newSt;
}
/**
* Sets Pitch.accidental.displayStatus for every element with a
* pitch or pitches in the stream. If a natural needs to be displayed
* and the Pitch does not have an accidental object yet, adds one.
*
* Called automatically before appendDOM routines are called.
*
* @memberof music21.stream.Stream
* @returns {music21.stream.Stream} this
*/
makeAccidentals() {
// cheap version of music21p method
const extendableStepList = {};
const stepNames = ['C', 'D', 'E', 'F', 'G', 'A', 'B'];
for (const stepName of stepNames) {
let stepAlter = 0;
if (this.keySignature !== undefined) {
const tempAccidental = this.keySignature.accidentalByStep(
stepName
);
if (tempAccidental !== undefined) {
stepAlter = tempAccidental.alter;
// console.log(stepAlter + " " + stepName);
}
}
extendableStepList[stepName] = stepAlter;
}
const lastOctaveStepList = [];
for (let i = 0; i < 10; i++) {
const tempOctaveStepDict = $.extend({}, extendableStepList);
lastOctaveStepList.push(tempOctaveStepDict);
}
const lastOctavelessStepDict = $.extend({}, extendableStepList); // probably unnecessary, but safe...
for (const el of this) {
if (el.pitch !== undefined) {
// note
const p = el.pitch;
const lastStepDict = lastOctaveStepList[p.octave];
this._makeAccidentalForOnePitch(
p,
lastStepDict,
lastOctavelessStepDict
);
} else if (el._notes !== undefined) {
// chord
for (const chordNote of el._notes) {
const p = chordNote.pitch;
const lastStepDict = lastOctaveStepList[p.octave];
this._makeAccidentalForOnePitch(
p,
lastStepDict,
lastOctavelessStepDict
);
}
}