Source: music21/meter.js

/**
 * music21j -- Javascript reimplementation of Core music21p features.
 * music21/meter -- TimeSignature objects
 *
 *
 * Copyright (c) 2013-17, Michael Scott Cuthbert and cuthbertLab
 * Based on music21 (=music21p), Copyright (c) 2006–17, Michael Scott Cuthbert and cuthbertLab
 *
 * meter module. See {@link music21.meter} namespace for details.
 * Meter and TimeSignature Classes (esp. {@link music21.meter.TimeSignature} ) and methods.
 *
 * @exports music21/meter
 *
 * @namespace music21.meter
 * @memberof music21
 * @requires music21/base
 * @requires music21/duration
 */
import Vex from 'vexflow';

import * as base from './base';
import * as beam from './beam.js';
import * as common from './common.js';
import * as duration from './duration.js';

/**
 * A MUCH simpler version of the music21p TimeSignature object.
 *
 * @class TimeSignature
 * @memberof music21.meter
 * @extends music21.base.Music21Object
 * @param {string} meterString - a string ("4/4", "3/8" etc.) to initialize the TimeSignature.
 * @property {int} [numerator=4]
 * @property {int} [denominator=4]
 * @property {int[][]} beatGroups - groupings of beats; inner arrays are numerator, denominator
 * @property {string} ratioString - a string like "4/4"
 * @property {music21.duration.Duration} barDuration - a Duration object representing the expressed total length of the TimeSignature.
 */
export class TimeSignature extends base.Music21Object {
    constructor(meterString) {
        super();
        this.classSortOrder = 4;

        this._numerator = 4;
        this._denominator = 4;
        this._beatGroups = [];
        this._overwrittenBeatCount = undefined;
        this._overwrittenBeatDuration = undefined;
        if (typeof meterString === 'string') {
            this.ratioString = meterString;
        }
    }

    /**
     *
     * @returns {string}
     */
    stringInfo() {
        return this.ratioString;
    }

    /**
     *
     * @type {number}
     */
    get numerator() {
        return this._numerator;
    }

    set numerator(s) {
        this._numerator = s;
    }

    /**
     *
     * @type {number}
     */
    get denominator() {
        return this._denominator;
    }

    set denominator(s) {
        this._denominator = s;
    }

    /**
     *
     * @type {string}
     */
    get ratioString() {
        return this.numerator.toString() + '/' + this.denominator.toString();
    }

    set ratioString(meterString) {
        const meterList = meterString.split('/');
        this.numerator = parseInt(meterList[0]);
        this.denominator = parseInt(meterList[1]);
        this._beatGroups = [];
    }

    /**
     *
     * @type {music21.duration.Duration}
     */
    get barDuration() {
        const ql = 4.0 * this._numerator / this._denominator;
        return new duration.Duration(ql);
    }

    get beatGroups() {
        if (this._beatGroups.length === 0) {
            this._beatGroups = this.computeBeatGroups();
        }
        return this._beatGroups;
    }

    set beatGroups(newGroups) {
        this._beatGroups = newGroups;
    }

    /**
     *  Get the beatCount from the numerator, assuming fast 6/8, etc.
     *  unless .beatCount has been set manually.
     *  @type {number}
     */
    get beatCount() {
        if (this._overwrittenBeatCount !== undefined) {
            return this._overwrittenBeatCount;
        }
        if (this.numerator > 3 && this.numerator % 3 === 0) {
            return this.numerator / 3;
        } else {
            return this.numerator;
        }
    }

    /**
     *  Manually set the beatCount to an int.
     */
    set beatCount(overwrite) {
        this._overwrittenBeatCount = overwrite;
        return overwrite;
    }

    /**
     * Gets a single duration.Duration object representing
     * the length of a beat in this time signature (using beatCount)
     * or, if set manually, it can return a list of Durations For
     * asymmetrical meters.
     */
    get beatDuration() {
        const dur = this.barDuration;
        dur.quarterLength /= this.beatCount;
        return dur;
    }

    /**
     * Set beatDuration to a duration.Duration object or
     * if the client can handle it, a list of Duration objects...
     */
    set beatDuration(overwrite) {
        this._overwrittenBeatDuration = overwrite;
    }

    /**
     * Compute the Beat Group according to this time signature.
     *
     * @returns {Array<Array<int>>} a list of numerator and denominators, find a list of beat groups.
     */
    computeBeatGroups() {
        const tempBeatGroups = [];
        let numBeats = this.numerator;
        let beatValue = this.denominator;
        if (beatValue < 8 && numBeats >= 5) {
            const beatsToEighthNoteRatio = 8 / beatValue; // hopefully Int -- right Brian Ferneyhough?
            beatValue = 8;
            numBeats *= beatsToEighthNoteRatio;
        }

        if (beatValue >= 8) {
            while (numBeats >= 5) {
                tempBeatGroups.push([3, beatValue]);
                numBeats -= 3;
            }
            if (numBeats === 4) {
                tempBeatGroups.push([2, beatValue]);
                tempBeatGroups.push([2, beatValue]);
            } else if (numBeats > 0) {
                tempBeatGroups.push([numBeats, beatValue]);
            }
        } else if (beatValue === 2) {
            tempBeatGroups.push([1, 2]);
        } else if (beatValue <= 1) {
            tempBeatGroups.push([1, 1]);
        } else {
            // 4/4, 2/4, 3/4, standard stuff
            tempBeatGroups.push([2, 8]);
        }
        return tempBeatGroups;
    }

    /**
     * Return a span of [start, end] for the current beat/beam grouping
     */
    offsetToSpan(offset) {
        const beatDuration = this.beatDuration.quarterLength;
        const beatsFromStart = Math.floor(offset / beatDuration);
        const start = beatsFromStart * beatDuration;
        const end = start + beatDuration;
        return [start, end];
    }

    /**
     * @param {music21.stream.Stream} srcStream - a stream of elements.
     * @param {Object} options - an object with measureStartOffset
     */
    getBeams(srcStream, options) {
        const params = { measureStartOffset: 0.0 };
        common.merge(params, options);
        const measureStartOffset = params.measureStartOffset;
        let beamsList = beam.Beams.naiveBeams(srcStream);
        beamsList = beam.Beams.removeSandwichedUnbeamables(beamsList);
        const fixBeamsOneElementDepth = (i, el, depth) => {
            const beams = beamsList[i];
            if (!beams) {
                return;
            }
            const beamNumber = depth + 1;
            if (!(beams.getNumbers().includes(beamNumber))) {
                return;
            }
            const dur = el.duration;
            const pos = el.offset + measureStartOffset;

            const start = pos; // opFrac
            const end = pos + dur.quarterLength; // opFrac;
            const startNext = end;
            const isLast = (i === srcStream.length - 1);
            const isFirst = (i === 0);
            let beamNext;
            let beamPrevious;
            if (!isFirst) {
                beamPrevious = beamsList[i - 1];
            }
            if (!isLast) {
                beamNext = beamsList[i + 1];
            }
            const [archetypeSpanStart, archetypeSpanEnd] = this.offsetToSpan(start);
            let archetypeSpanNextStart = 0.0;
            if (beamNext !== undefined) {
                archetypeSpanNextStart = this.offsetToSpan(startNext)[0];
            }
            if (start === archetypeSpanStart && end === archetypeSpanEnd) {
                beamsList[i] = undefined;
                return;
            }

            let beamType;
            if (isFirst) {
                beamType = 'start';
                if (beamNext === undefined || !(beamNext.getNumbers().includes(beamNumber))) {
                    beamType = 'partial-right';
                }
            } else if (isLast) {
                beamType = 'start';
                if (beamPrevious === undefined || !beamPrevious.getNumbers().includes(beamNumber)) {
                    beamType = 'partial-left';
                }
            } else if (beamPrevious === undefined || !beamPrevious.getNumbers().includes(beamNumber)) {
                if (beamNumber === 1 && beamNext === undefined) {
                    beamsList[i] = undefined;
                    return;
                } else if (beamNext === undefined && beamNumber > 1) {
                    beamType = 'partial-left';
                } else if (startNext >= archetypeSpanEnd) {
                    beamType = 'partial-left';
                } else if (beamNext === undefined || !(beamNext.getNumbers().includes(beamNumber))) {
                    beamType = 'partial-right';
                } else {
                    beamType = 'start';
                }
            } else if (beamPrevious
                        && beamPrevious.getNumbers().includes(beamNumber)
                        && ['stop', 'partial-left'].includes(beamPrevious.getTypeByNumber(beamNumber))
            ) {
                if (beamNext !== undefined) {
                    beamType = 'start';
                } else {
                    beamType = 'partial-left';
                }
            } else if (beamNext === undefined || !beamNext.getNumbers().includes(beamNumber)) {
                beamType = 'stop';
            } else if (startNext < archetypeSpanEnd) {
                beamType = 'continue';
            } else if (startNext >= archetypeSpanNextStart) {
                beamType = 'stop';
            } else {
                console.warn('Cannot match beamType');
                return;
            }
            beams.setByNumber(beamNumber, beamType);
        };

        for (let depth = 0; depth < beam.beamableDurationTypes.length; depth++) {
            let i = 0;
            for (const el of srcStream) {
                fixBeamsOneElementDepth(i, el, depth);
                i += 1;
            }
        }

        beamsList = beam.Beams.sanitizePartialBeams(beamsList);
        beamsList = beam.Beams.mergeConnectingPartialBeams(beamsList);
        return beamsList;
    }

    /**
     * Compute the Beat Group according to this time signature for VexFlow. For beaming.
     *
     * @returns {Array<Vex.Flow.Fraction>} a list of numerator and denominator groups, for VexFlow
     */
    vexflowBeatGroups() {
        const tempBeatGroups = this.beatGroups;
        // console.log(tempBeatGroups);
        const vfBeatGroups = [];
        for (let i = 0; i < tempBeatGroups.length; i++) {
            const bg = tempBeatGroups[i];
            vfBeatGroups.push(new Vex.Flow.Fraction(bg[0], bg[1]));
        }
        return vfBeatGroups;

        //  if (numBeats % 3 == 0 && beatValue < 4) {
        //  // 6/8, 3/8, 9/8, etc.
        //  numBeats = numBeats / 3;
        //  beatValue = beatValue / 3;
        //  }
    }
}
Music21j, Copyright © 2013-2021 Michael Scott Asato Cuthbert.
Documentation generated by JSDoc 3.6.3 on Wed Jul 31st 2019 using the DocStrap template.