Skip to content

Commit

Permalink
Merge pull request #92 from cuthbertLab/fix_new_beaming_incomplete_me…
Browse files Browse the repository at this point in the history
…asures

0.10.4 fix beams, opFrac on notes
  • Loading branch information
mscuthbert authored Mar 20, 2021
2 parents c3e5fcc + 9792bad commit 0d7aa31
Show file tree
Hide file tree
Showing 13 changed files with 302 additions and 149 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "music21j",
"version": "0.10.3",
"version": "0.10.4",
"description": "A toolkit for computer-aided musicology, Javascript version",
"main": "src/music21_modules.js",
"files": [
Expand Down
216 changes: 150 additions & 66 deletions releases/music21.debug.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion releases/music21.debug.js.map

Large diffs are not rendered by default.

25 changes: 8 additions & 17 deletions src/music21/audioSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ export const animateLoop = () => {
}
};

export function smoothPitchExtraction(frequency) {
export function smoothPitchExtraction(frequency: number): [number, number] {
if (frequency === -1) {
config.lastPitchClassesDetected.shift();
config.lastPitchesDetected.shift();
Expand Down Expand Up @@ -191,7 +191,7 @@ export function smoothPitchExtraction(frequency) {
return [mostCommonPitch, centsOff];
}

export function sampleCallback(frequency) {
export function sampleCallback(frequency: number): number {
// noinspection JSUnusedLocalSymbols
const [unused_midiNum, unused_centsOff] = smoothPitchExtraction(
frequency
Expand All @@ -202,15 +202,12 @@ export function sampleCallback(frequency) {
// from Chris Wilson. Replace with Jordi's
export function autoCorrelate(
buf,
sampleRate,
minFrequency,
maxFrequency
sampleRate: number,
minFrequency: number = 0,
maxFrequency?: number
) {
const SIZE = buf.length;
const MAX_SAMPLES = Math.floor(SIZE / 2);
if (minFrequency === undefined) {
minFrequency = 0;
}
if (maxFrequency === undefined) {
maxFrequency = sampleRate;
}
Expand Down Expand Up @@ -278,16 +275,10 @@ export function autoCorrelate(
// var best_frequency = sampleRate/best_offset;
}

/**
*
* @function midiNumDiffFromFrequency
* @param {Number} frequency
* @returns {Array<int>} [miniNumber, centsOff]
*/
export function midiNumDiffFromFrequency(
frequency
) {
const midiNumFloat = 12 * (Math.log(frequency / 440) / Math.log(2)) + 69;
frequency: number
): [number, number] {
const midiNumFloat = 12 * (Math.log2(frequency / 440)) + 69;
const midiNum = Math.round(midiNumFloat);
const centsOff = Math.round(100 * (midiNumFloat - midiNum));
return [midiNum, centsOff];
Expand Down
1 change: 1 addition & 0 deletions src/music21/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ export class Music21Object extends prebase.ProtoM21Object {
}

set offset(newOffset: number) {
newOffset = common.opFrac(newOffset);
if (this.activeSite === undefined) {
this._naiveOffset = newOffset;
} else {
Expand Down
66 changes: 45 additions & 21 deletions src/music21/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -452,24 +452,48 @@ export function isFloat(num) {
return Number(num) === num && num % 1 !== 0;
}

// TODO: implement and test
// export function opFrac(num) {
//
// //const DENOM_LIMIT = 65535; // move to another location
// if (isFloat(num)) { // no builtin isFloat function exists
// // no fraction support yet
// return num;
// }
// else if (Number.isInteger(num)) {
// return num; // number, no int's
// }
// else if (num === 'Fraction') { // Replace with fraction object
// return num; // no fraction support yet
// }
// else if (num === null) {
// return null;
// }
// else {
// return null;
// }
// }
const shared_buffer = new ArrayBuffer(4); // just enough bytes for 32-bit Array
const int_view = new Int32Array(shared_buffer);
const float_view = new Float32Array(shared_buffer);

function byte_2_relevant_bits(num) {
// extract bits 24 to 28 of the floating point number.
// if all 1s or all 0s then it's close enough to a
// float expressible as fraction with power of 2 denominator
let out = '';
for (let i = 10; i >= 4; i -= 1) {
// noinspection JSBitwiseOperatorUsage
out += (num & (1 << i)) ? '1' : '0'; // eslint-disable-line no-bitwise
}
return out;
}

function is_power_of_2_denominator(num) {
float_view[0] = num;
const float_as_int = int_view[0]; // magic conversion
const out_bits = byte_2_relevant_bits(float_as_int);
if (out_bits === '1111111' || out_bits === '0000000') {
return true;
}
return false;
}

/**
* Returns either the original number (never a fraction, since js does not have them)
* or the slightly rounded, correct representation.
*
* Uses a shared memory buffer to give the conversion.
*/
export function opFrac(num) {
if (num === Math.floor(num)) {
return num;
}
if (num * 1024 === Math.floor(num * 1024)) {
return num;
}
if (is_power_of_2_denominator(num)) {
return parseFloat(num.toPrecision(6));
} else {
return num;
}
}
5 changes: 3 additions & 2 deletions src/music21/duration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ export class Duration extends prebase.ProtoM21Object {
if (ql === undefined) {
ql = 1.0;
}
ql = common.opFrac(ql);
this._quarterLength = ql;
this.updateFeaturesFromQl();
}
Expand Down Expand Up @@ -285,7 +286,7 @@ export class Duration extends prebase.ProtoM21Object {
this._tuplets.forEach(tuplet => {
tupletCorrectedQl *= tuplet.tupletMultiplier();
});
this._quarterLength = tupletCorrectedQl;
this._quarterLength = common.opFrac(tupletCorrectedQl);
}

updateFeaturesFromQl() {
Expand All @@ -296,7 +297,7 @@ export class Duration extends prebase.ProtoM21Object {
this._dots = 0;
return;
}
const powerOfTwo = Math.floor(Math.log(ql + 0.00001) / Math.log(2));
const powerOfTwo = Math.floor(Math.log2(ql + 0.00001));
let typeNumber = quarterTypeIndex - powerOfTwo;
this._type = ordinalTypeFromNum[typeNumber];
// console.log(this._findDots);
Expand Down
32 changes: 14 additions & 18 deletions src/music21/stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,8 +319,6 @@ export class Stream extends base.Music21Object {
}
}

// TODO(MSC) 2020-07-20 -- Add .map() => stream.iterator.StreamIterator.map()

get duration(): Duration {
if (this._overriddenDuration instanceof Duration) {
// return new duration.Duration(32.0);
Expand Down Expand Up @@ -417,11 +415,9 @@ export class Stream extends base.Music21Object {
/**
* Return an array of the outer bounds of each MetronomeMark in the stream.
* [offsetStart, offsetEnd, tempo.MetronomeMark]
*
* @returns {Array<number|music21.tempo.MetronomeMark>}
*/
_metronomeMarkBoundaries() {
const mmBoundaries = [];
_metronomeMarkBoundaries(): [number, number, tempo.MetronomeMark][] {
const mmBoundaries: [number, number, tempo.MetronomeMark][] = [];
const thisFlat = this.flat;
const metronomeMarks = thisFlat.getElementsByClass('MetronomeMark');

Expand All @@ -433,8 +429,8 @@ export class Stream extends base.Music21Object {
if (!metronomeMarks.length) {
mmBoundaries.push([lowestOffset, highestTime, mmDefault]);
} else if (metronomeMarks.length === 1) {
const metronomeMark = metronomeMarks.get(0);
const offset = metronomeMark.getOffsetBySite(thisFlat);
const metronomeMark = metronomeMarks.get(0) as tempo.MetronomeMark;
const offset = metronomeMark.getOffsetBySite(thisFlat) as number;
if (offset > lowestOffset) {
mmBoundaries.push([lowestOffset, offset, mmDefault]);
mmBoundaries.push([offset, highestTime, metronomeMark]);
Expand Down Expand Up @@ -572,11 +568,11 @@ export class Stream extends base.Music21Object {
this._clef = newClef;
}

get keySignature() {
get keySignature(): key.KeySignature {
return this.getSpecialContext('keySignature', false);
}

set keySignature(newKeySignature) {
set keySignature(newKeySignature: key.KeySignature) {
const oldKS = this._firstElementContext('keySignature');
if (oldKS !== undefined) {
this.replace(oldKS, newKeySignature);
Expand All @@ -586,11 +582,11 @@ export class Stream extends base.Music21Object {
this._keySignature = newKeySignature;
}

get timeSignature() {
get timeSignature(): meter.TimeSignature {
return this.getSpecialContext('timeSignature', false);
}

set timeSignature(newTimeSignature) {
set timeSignature(newTimeSignature: meter.TimeSignature) {
if (typeof newTimeSignature === 'string') {
newTimeSignature = new meter.TimeSignature(newTimeSignature);
}
Expand Down Expand Up @@ -2172,7 +2168,7 @@ export class Stream extends base.Music21Object {
*
* elementType can be `svg` (default) or `canvas`
*
* returns a $div encompasing either the SVG or Canvas element.
* returns a $div encompassing either the SVG or Canvas element.
*
* if width is undefined, will use `this.estimateStaffLength()`
* + `this.renderOptions.staffPadding`
Expand Down Expand Up @@ -2237,10 +2233,6 @@ export class Stream extends base.Music21Object {
*
* Called from appendNewDOM() etc.
*
* @param {number|string|undefined} [width]
* @param {number|string|undefined} [height]
* @param {string} [elementType='svg'] - what type of element, default = svg
* @returns {JQuery} canvas or svg
*/
createPlayableDOM(
width: number|string|undefined = undefined,
Expand Down Expand Up @@ -2320,7 +2312,11 @@ export class Stream extends base.Music21Object {
* @param {string} elementType - what type of element, default = svg
* @returns {JQuery} the svg
*/
replaceDOM(where, preserveSvgSize: boolean=false, elementType: string='svg') {
replaceDOM(
where,
preserveSvgSize: boolean=false,
elementType: string='svg'
): JQuery {
// if called with no where, replaces all the svg elements on the page...
if (where === undefined) {
where = document.body;
Expand Down
36 changes: 24 additions & 12 deletions src/music21/stream/makeNotation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,36 @@ import * as pitch from '../pitch'; // for typing only
import * as stream from '../stream';
import {StreamException} from '../stream'; // for typing only -- circular import otherwise

export declare interface MakeBeamsOptions {
export interface MakeBeamsOptions {
inPlace?: boolean,
setStemDirections?: boolean,
}

export function makeBeams(s, {
export interface StemDirectionBeatGroupOptions {
setNewStems?: boolean,
overrideConsistentStemDirections?: boolean,
}

export interface IterateBeamGroupsOptions {
skipNoBeams?: boolean,
recurse?: boolean,
}

export function makeBeams(s: stream.Stream, {
inPlace=false,
setStemDirections=true,
}: MakeBeamsOptions = {}): stream.Stream {
let returnObj = s;
let returnObj: stream.Stream = s;
if (!inPlace) {
returnObj = s.clone(true);
}
let mColl;
let mColl: stream.Measure[];
if (s.classes.includes('Measure')) {
mColl = [returnObj];
mColl = [returnObj as stream.Measure];
} else {
mColl = [];
for (const m of returnObj.getElementsByClass('Measure')) {
mColl.push(m);
mColl.push(m as stream.Measure);
}
}
let lastTimeSignature;
Expand All @@ -44,11 +54,13 @@ export function makeBeams(s, {
if (m.length <= 1) {
continue; // nothing to beam.
}
const noteStream = m.notesAndRests;
const noteStreamIterator = m.notesAndRests;
const durList = [];
for (const n of noteStream) {
for (const n of noteStreamIterator) {
durList.push(n.duration);
}
const noteStream = noteStreamIterator.stream();

const durSumErr = durList.map(a => a.quarterLength).reduce((total, val) => total + val);
const durSum = parseFloat(durSumErr.toFixed(8)); // remove fraction errors
const barQL = lastTimeSignature.barDuration.quarterLength;
Expand All @@ -63,7 +75,7 @@ export function makeBeams(s, {
}
const beamsList = lastTimeSignature.getBeams(noteStream, { measureStartOffset: offset });
for (let i = 0; i < noteStream.length; i++) {
const n = noteStream.get(i);
const n = noteStream.get(i) as note.NotRest;
const thisBeams = beamsList[i];
if (thisBeams !== undefined) {
n.beams = thisBeams;
Expand All @@ -86,7 +98,7 @@ export function * iterateBeamGroups(
{
skipNoBeams=true,
recurse=true,
}: {skipNoBeams?: boolean, recurse?: boolean} = {}
}: IterateBeamGroupsOptions = {}
): Generator<note.NotRest[], void, void> {
let iterator: stream.iterator.StreamIterator;
if (recurse) {
Expand Down Expand Up @@ -129,7 +141,7 @@ export function setStemDirectionForBeamGroups(
{
setNewStems=true,
overrideConsistentStemDirections=false,
}: {setNewStems?: boolean, overrideConsistentStemDirections?: boolean} = {}
}: StemDirectionBeatGroupOptions = {}
): void {
for (const beamGroup of iterateBeamGroups(s, {skipNoBeams: true, recurse: true})) {
setStemDirectionOneGroup(
Expand All @@ -147,7 +159,7 @@ export function setStemDirectionOneGroup(
{
setNewStems=true,
overrideConsistentStemDirections=false,
}: {setNewStems?: boolean, overrideConsistentStemDirections?: boolean} = {}
}: StemDirectionBeatGroupOptions = {}
): void {
if (!group.length) {
return; // should not happen
Expand Down
4 changes: 2 additions & 2 deletions src/music21/tempo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ export class Metronome extends prebase.ProtoM21Object {
* @param {int} [n=1 - number of clicks to the right
* @returns {number} new tempo
*/
increaseSpeed(n=1) {
increaseSpeed(n: number = 1) {
// increase by one metronome 'click' for every n
for (let i = 0; i < n; i++) {
let t = this.tempo;
Expand All @@ -216,7 +216,7 @@ export class Metronome extends prebase.ProtoM21Object {
* @param {int} [n=1] - number of clicks to the left
* @returns {number} new tempo
*/
decreaseSpeed(n=1) {
decreaseSpeed(n: number = 1) {
for (let i = 0; i < n; i++) {
let t = this.tempo;
const trL = this.tempoRanges.length;
Expand Down
Loading

0 comments on commit 0d7aa31

Please sign in to comment.