Source: music21/tempo.js

/**
 * music21j -- Javascript reimplementation of Core music21 features.
 * music21/tempo -- tempo and (not in music21p) metronome objects
 *
 * Copyright (c) 2013-18, Michael Scott Cuthbert and cuthbertLab
 * Based on music21, Copyright (c) 2006–17, Michael Scott Cuthbert and cuthbertLab
 *
 * tempo module, see {@link music21.tempo}
 *
 * @exports music21/tempo
 * @namespace music21.tempo
 * @memberof music21
 * @requires music21/prebase
 * @requires music21/base
 * @requires MIDI
 * @property {number} [baseTempo=60] - basic tempo
 */
import * as $ from 'jquery';
import * as MIDI from 'midicube';

import * as prebase from './prebase.js';

/**
 * The tempo object itself is a full object in music21p -- TODO(msc): make it one here too.
 * (MetronomeMark)
 */

// noinspection JSNonASCIINames,NonAsciiCharacters
/**
 * Object mapping names to tempo values
 *
 * @name music21.tempo.defaultTempoValues
 * @memberof music21.tempo
 * @example
 * music21.tempo.defaultTempoValues.grave
 * // 40
 */
export const defaultTempoValues = {
    larghissimo: 16,
    largamente: 32,
    grave: 40,
    'molto adagio': 40,
    largo: 46,
    lento: 52,
    adagio: 56,
    slow: 56,
    langsam: 56,
    larghetto: 60,
    adagietto: 66,
    andante: 72,
    andantino: 80,
    'andante moderato': 83,
    maestoso: 88,
    moderato: 92,
    moderate: 92,
    allegretto: 108,
    animato: 120,
    'allegro moderato': 128,
    allegro: 132,
    fast: 132,
    schnell: 132,
    allegrissimo: 140,
    'molto allegro': 144,
    'très vite': 144,
    vivace: 160,
    vivacissimo: 168,
    presto: 184,
    prestissimo: 208,
};

export const baseTempo = 60;

/* --------- metronome ---------- */
/**
 *
 * @class Metronome
 * @memberof music21.tempo
 * @extends music21.prebase.ProtoM21Object
 * @param {number} [tempo=music21.tempo.baseTempo] - the tempo of the metronome to start
 * @property {number} tempo
 * @property {int} [numBeatsPerMeasure=4]
 * @property {number} [minTempo=10]
 * @property {number} [maxTempo=600]
 * @property {bool} [flash=false] - flash the tempo
 * @property {bool} [silent=false] - play silently
 * @property {int} beat - current beat number
 * @property {int} chirpTimeout - an index of a timeout object for chirping
 */
export class Metronome extends prebase.ProtoM21Object {
    constructor(tempoInt) {
        super();
        this._tempo = 60; // overridden by music21.tempo.baseTempo;
        if (tempoInt === undefined) {
            this.tempo = baseTempo;
        } else {
            this.tempo = tempoInt;
        }
        this.numBeatsPerMeasure = 4;
        this.minTempo = 10;
        this.maxTempo = 600;
        this.beat = this.numBeatsPerMeasure;
        this.chirpTimeout = undefined;
        this.silent = false;
        this.flash = false;
        this.tempoRanges = [0, 40, 60, 72, 120, 144, 240, 999];
        this.tempoIncreases = [0, 1, 2, 3, 4, 6, 8, 15, 100];
    }

    /**
     * @type {number}
     */
    get tempo() {
        return this._tempo;
    }

    set tempo(t) {
        this._tempo = t;
        if (this._tempo > this.maxTempo) {
            this._tempo = this.maxTempo;
        } else if (this._tempo < this.minTempo) {
            this._tempo = this.minTempo;
        }
    }

    /**
     *
     * @returns {number}
     */
    get beatLength() {
        return 60.0 / this.tempo;
    }

    _silentFlash(flashColor) {
        this.$metronomeDiv
            .find('.metroFlash')
            .css('background-color', flashColor)
            .fadeOut(this.beatLength * 1000 / 4, function silentFadeOut() {
                $(this)
                    .css('background-color', '#ffffff')
                    .fadeIn(1);
            });
    }

    /**
     * Play a note (a higher one on the downbeat) and start the metronome chirping.
     */
    chirp() {
        this.beat += 1;
        if (this.beat > this.numBeatsPerMeasure) {
            this.beat = 1;
            if (this.silent !== true) {
                MIDI.noteOn(0, 96, 100, 0);
                MIDI.noteOff(0, 96, 0.1);
            }
            if (this.flash === true) {
                this._silentFlash('#0000f0');
            }
        } else {
            if (this.silent !== true) {
                MIDI.noteOn(0, 84, 70, 0);
                MIDI.noteOff(0, 84, 0.1);
            }
            if (this.flash === true) {
                this._silentFlash('#ff0000');
            }
        }
        const that = this;
        this.chirpTimeout = setTimeout(() => {
            that.chirp();
        }, 1000 * 60 / this.tempo);
    }

    /**
     * Stop the metronome from chirping.
     */
    stopChirp() {
        if (this.chirpTimeout !== undefined) {
            clearTimeout(this.chirpTimeout);
            this.chirpTimeout = undefined;
        }
    }

    /**
     * Increase the metronome tempo one "click".
     *
     * Value changes depending on the current tempo.  Uses standard metronome guidelines.
     *
     * To change the tempo, just set this.tempo = n
     *
     * @param {int} [n=1 - number of clicks to the right
     * @returns {number} new tempo
     */
    increaseSpeed(n=1) {
        // increase by one metronome 'click' for every n
        for (let i = 0; i < n; i++) {
            let t = this.tempo;
            for (let tr = 0; tr < this.tempoRanges.length; tr++) {
                const tempoExtreme = this.tempoRanges[tr];
                const tempoIncrease = this.tempoIncreases[tr];
                if (t < tempoExtreme) {
                    t += tempoIncrease;
                    t = tempoIncrease * Math.round(t / tempoIncrease);
                    break;
                }
            }
            // console.log(t);
            this.tempo = t;
        }
        return this.tempo;
    }

    /**
     * Decrease the metronome tempo one "click"
     *
     * To change the tempo, just set this.tempo = n
     *
     * @param {int} [n=1] - number of clicks to the left
     * @returns {number} new tempo
     */
    decreaseSpeed(n=1) {
        for (let i = 0; i < n; i++) {
            let t = this.tempo;
            const trL = this.tempoRanges.length;
            for (let tr = 1; tr <= trL; tr++) {
                const tempoExtreme = this.tempoRanges[trL - tr];
                const tempoIncrease = this.tempoIncreases[trL - tr + 1];
                if (t > tempoExtreme) {
                    t -= tempoIncrease;
                    t = tempoIncrease * Math.floor(t / tempoIncrease);
                    break;
                }
            }
            // console.log(t);
            this.tempo = t;
        }
        return this.tempo;
    }

    /**
     * add a Metronome interface onto the DOM at where
     *
     * @param {jQuery|HTMLElement} [where]
     * @returns {jQuery} - a div holding the metronome.
     */
    addDiv(where) {
        /**
         * @type {jQuery}
         */
        let $where;
        if (where !== undefined && where instanceof $) {
            $where = where;
        } else if (where !== undefined && !(where instanceof $)) {
            $where = $(where);
        } else {
            $where = $('body');
        }
        const metroThis = this;
        /**
         *
         * @type {jQuery}
         */
        const $tempoHolder = $(
            '<span class="tempoHolder">' + this.tempo.toString() + '</span>'
        );
        $tempoHolder.css({
            'font-size': '24px',
            'padding-left': '10px',
            'padding-right': '10px',
        });
        /**
         *
         * @type {jQuery}
         */
        const $newDiv = $('<div class="metronomeRendered"></div>');
        $newDiv.append($tempoHolder);

        /**
         *
         * @type {jQuery}
         */
        const b1 = $('<button>start</button>');
        b1.on('click', () => {
            metroThis.chirp();
        });

        /**
         *
         * @type {jQuery}
         */
        const b2 = $('<button>stop</button>');
        b2.on('click', () => {
            metroThis.stopChirp();
        });
        $newDiv.prepend(b2);
        $newDiv.prepend(b1);
        /**
         *
         * @type {jQuery}
         */
        const b3 = $('<button>up</button>');
        b3.on('click', function increaseSpeedButton() {
            metroThis.increaseSpeed();
            $(this)
                .prevAll('.tempoHolder')
                .html(metroThis.tempo.toString());
        });
        /**
         *
         * @type {jQuery}
         */
        const b4 = $('<button>down</button>');
        b4.on('click', function decreaseSpeedButton() {
            metroThis.decreaseSpeed();
            $(this)
                .prevAll('.tempoHolder')
                .html(metroThis.tempo.toString());
        });
        $newDiv.append(b3);
        $newDiv.append(b4);
        /**
         *
         * @type {jQuery}
         */
        const $flash = $(
            '<span class="metroFlash">'
                + '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>'
        );
        $flash.css('margin-left', '40px');
        $flash.css('height', '40px');

        $newDiv.append($flash);

        $where.append($newDiv);

        this.$metronomeDiv = $newDiv;
        return $newDiv;
    }
}
Music21j, Copyright © 2013-2021 Michael Scott Asato Cuthbert.
Documentation generated by JSDoc 3.6.3 on Wed Jul 31st 2019 using the DocStrap template.