UNPKG

music21j-port

Version:

A toolkit for computer-aided musicology, Javascript version

1,569 lines (1,391 loc) 116 kB
<!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&lt;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 &lt; 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 &lt; 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 &amp;&amp; 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 &amp;&amp; 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 &amp;&amp; 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 &amp;&amp; this.activeSite !== undefined) { return this.activeSite.keySignature; } else { return this._keySignature; } } set keySignature(newKeySignature) { this._keySignature = newKeySignature; } get timeSignature() { if ( this._timeSignature === undefined &amp;&amp; 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 &amp;&amp; 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 &amp;&amp; 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 &lt; 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 &lt; 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 &amp;&amp; (key === '_elements' || key === '_elementOffsets') ) { ret[key] = this[key].slice(); // shallow copy... } else if ( deep &amp;&amp; (key === '_elements' || key === '_elementOffsets') ) { if (key === '_elements') { // console.log('got elements for deepcopy'); ret._elements = []; ret._elementOffsets = []; for (let j = 0; j &lt; 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 &amp;&amp; this[key] !== undefined &amp;&amp; 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 &amp;&amp; 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 &amp;&amp; el.isClassOrSubclass('NotRest') ) { // set stem direction on output // this.clef.setStemDirection(el); } for (let i = 0; i &lt; this._elements.length; i++) { const testOffset = this._elementOffsets[i]; if (testOffset &lt;= 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 &lt; this.length; i++) { if (!shiftingOffsets &amp;&amp; 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) &lt; 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 &lt; 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 &lt; 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 &lt; 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 &lt; 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 &lt; 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 &lt; 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 &lt; 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 &lt; 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 &amp;&amp; start &lt; mEnd) { break; } } // if not match, raise Exception; const oNew = start - mStart; if (m.clef === e) { continue; } if (oNew === 0 &amp;&amp; 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 &lt;= 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 &amp;&amp; m.paddingLeft !== undefined) { offset = m.paddingLeft; } else if (noteStream.highestTime &lt; barQL) { offset = barQL - noteStream.highestTime; } const beamsList = lastTimeSignature.getBeams(noteStream, { measureStartOffset: offset }); for (let i = 0; i &lt; 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 &lt; groups.length; i++) { const group = groups[i][0]; const voiceIndex = groups[i][1]; for (let j = 0; j &lt; 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&lt;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 &lt; 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 ); } }