diff --git a/js/base64Utils.js b/js/base64Utils.js index 833d109215..24043007ad 100644 --- a/js/base64Utils.js +++ b/js/base64Utils.js @@ -28,3 +28,4 @@ function base64Decode(str) { } export default { base64Encode, base64Decode }; +//module.exports = { base64Encode, base64Decode }; \ No newline at end of file diff --git a/js/utils/__tests__/munsell.test.js b/js/utils/__tests__/munsell.test.js new file mode 100644 index 0000000000..412e46abdb --- /dev/null +++ b/js/utils/__tests__/munsell.test.js @@ -0,0 +1,95 @@ +const { interpColor, getMunsellColor, getcolor, searchColors } = require('../munsell'); + +global.createjs = { + Graphics: { + getRGB: (r, g, b, a) => { + return `rgba(${r}, ${g}, ${b}, ${a})`; + }, + }, +}; + +describe('munsell', () => { + + describe('interpColor', () => { + it('should interpolate between two colors', () => { + expect( + interpColor('#ff0000', '#0000ff', 0.5) // red and blue + ).toBe('rgba(127, 0, 127, 1)'); // purple + expect( + interpColor('#00ff00', '#000000', 0.75) + ).toBe('rgba(0, 191, 0, 1)'); + }); + + it('should return the first color if p = 1', () => { + expect( + interpColor('#123456', '#abcdef', 1) + ).toBe('#123456'); + }); + + it('should return the second color if p = 0', () => { + expect( + interpColor('#123456', '#abcdef', 0) + ).toBe('#abcdef'); + }); + + it('should handle undefined colors gracefully', () => { + expect( + interpColor(undefined, '#123456', 0.5) + ).toBe('rgba(18, 52, 86, 1)'); + expect( + interpColor('#123456', undefined, 0.5) + ).toBe('rgba(18, 52, 86, 1)'); + }); + }); + + describe('getMunsellColor', () => { + it('should return the correct Munsell color', () => { + const color = getMunsellColor(50, 50, 50); + expect(color).toMatch(/^#[0-9a-fA-F]{6}$/); // Ensure valid hex color format + }); + + it('should handle edge cases for hue, value, and chroma', () => { + expect(getMunsellColor(0, 0, 0)).toBeDefined(); + expect(getMunsellColor(100, 100, 100)).toBeDefined(); + expect(getMunsellColor(-10, -10, -10)).toBeDefined(); + expect(getMunsellColor(110, 110, 110)).toBeDefined(); + }); + }); + + describe('getcolor', () => { + it('should return a valid array for a given color value', () => { + const color = getcolor(50); + expect(Array.isArray(color)).toBe(true); + expect(color.length).toBe(3); + expect(typeof color[0]).toBe('number'); + expect(typeof color[1]).toBe('number'); + expect(color[2]).toMatch(/^#[0-9a-fA-F]{6}$/); // Ensure RGB component is a valid hex color + }); + + it('should handle edge cases for color value', () => { + expect(getcolor(0)).toBeDefined(); + expect(getcolor(-10)).toBeDefined(); + expect(getcolor(110)).toBeDefined(); + }); + }); + + describe('searchColors', () => { + it('should return a color value between 0 and 100', () => { + const color = searchColors(128, 128, 128); + expect(color).toBeGreaterThanOrEqual(0); + expect(color).toBeLessThanOrEqual(100); + }); + + it('should find the nearest color for black', () => { + const color = searchColors(0, 0, 0); + const nearestColor = getcolor(color); + expect(nearestColor[2]).toMatch(/^#[0-9a-fA-F]{6}$/); + }); + + it('should identify a close match for a RGB value', () => { + const color = searchColors(100, 150, 200); + const nearestColor = getcolor(color); + expect(nearestColor[2]).toMatch(/^#[0-9a-fA-F]{6}$/); + }); + }); +}); \ No newline at end of file diff --git a/js/utils/__tests__/musicutils.test.js b/js/utils/__tests__/musicutils.test.js new file mode 100644 index 0000000000..41bc22c671 --- /dev/null +++ b/js/utils/__tests__/musicutils.test.js @@ -0,0 +1,383 @@ +/* eslint-disable no-trailing-spaces */ +/* eslint-disable indent */ +/* eslint-disable quotes */ +/* eslint-disable no-undef */ +// Mock global window for btoa function +global._ = jest.fn((str) => str); +global.window = { + btoa: jest.fn((str) => Buffer.from(str, "utf8").toString("base64")) +}; + +const { + setOctaveRatio, + getOctaveRatio, + TEMPERAMENT, + TEMPERAMENTS, + INITIALTEMPERAMENTS, + PreDefinedTemperaments, + getTemperamentsList, + getTemperament, + getTemperamentKeys, + addTemperamentToList, + addTemperamentToDictionary, + updateTemperaments, + deleteTemperamentFromList, + DEFAULTINVERT, + DEFAULTMODE, + customMode, + getInvertMode, + getIntervalNumber, + getIntervalDirection, + getIntervalRatio, + getModeNumbers, + getDrumIndex, + getDrumName, + getDrumSymbol, + getFilterTypes +} = require("../musicutils"); + + +describe("musicutils", () => { + it("should set and get Octave Ratio", () => { + setOctaveRatio(4); + const octaveR = getOctaveRatio(); + expect(octaveR).toBe(4); + }); +}); + +describe("Temperament Functions", () => { + test("getTemperamentsList should return the list of temperaments", () => { + expect(getTemperamentsList()).toEqual([ + [_("Equal (12EDO)"), "equal", "equal"], + [_("Equal (5EDO)"), "equal5", "equal5"], + [_("Equal (7EDO)"), "equal7", "equal7"], + [_("Equal (19EDO)"), "equal19", "equal19"], + [_("Equal (31EDO)"), "equal31", "equal31"], + [_("5-limit Just Intonation"), "just intonation", "just intonation"], + [_("Pythagorean (3-limit JI)"), "Pythagorean", "Pythagorean"], + [_("Meantone") + " (1/3)", "1/3 comma meantone", "meantone (1/3)"], + [_("Meantone") + " (1/4)", "1/4 comma meantone", "meantone (1/4)"], + [_("Custom"), "custom", "custom"] + ]); + }); + + describe("getTemperament", () => { + it("should return the correct temperament for a valid key", () => { + const equalTemperament = getTemperament("equal"); + expect(equalTemperament).toHaveProperty("perfect 1"); + expect(equalTemperament).toHaveProperty("minor 2"); + expect(equalTemperament).toHaveProperty("pitchNumber", 12); + }); + + it('should return the correct temperament for equal5 key', () => { + const equal5Temperament = getTemperament("equal5"); + expect(equal5Temperament).toHaveProperty("perfect 1"); + expect(equal5Temperament).toHaveProperty("minor 2"); + expect(equal5Temperament).toHaveProperty("pitchNumber", 5); + }); + + it("should return undefined for an invalid key", () => { + const invalidTemperament = getTemperament("invalid"); + expect(invalidTemperament).toBeUndefined(); + }); + }); + + describe("getTemperamentKeys", () => { + it("should return an array with the correct length", () => { + const keys = getTemperamentKeys(); + expect(keys.length).toBe(10); + }); + + it('should return an array containing all keys', () => { + const keys = getTemperamentKeys(); + expect(keys).toEqual( + expect.arrayContaining([ + "equal", + "equal5", + "equal7", + "equal19", + "equal31", + "just intonation", + "Pythagorean", + "1/3 comma meantone", + "1/4 comma meantone", + "custom" + ]) + ); + }); + }); + + describe("Temperament Management", () => { + beforeEach(() => { + // Reset global variables before each test + global.TEMPERAMENTS = [...INITIALTEMPERAMENTS]; + global.TEMPERAMENT = { + equal: { + pitchNumber: 12, + interval: ["perfect 1", "minor 2", "major 2"] + } + }; + }); + + test("addTemperamentToList should add a new temperament if not predefined", () => { + const newEntry = "customTemperament"; + addTemperamentToList(newEntry); + expect(TEMPERAMENTS).toContain(newEntry); + }); + + test("addTemperamentToList should not add a predefined temperament", () => { + const predefinedEntry = "equal"; + addTemperamentToList(predefinedEntry); + expect(TEMPERAMENTS).not.toContain(predefinedEntry); + }); + + test("deleteTemperamentFromList should remove a temperament from the dictionary", () => { + const oldEntry = "equal"; + deleteTemperamentFromList(oldEntry); + expect(TEMPERAMENT[oldEntry]).toBeUndefined(); + }); + + test("addTemperamentToDictionary should add a new temperament to the dictionary", () => { + const entryName = "newTemperament"; + const entryValue = { + pitchNumber: 7, + interval: ["perfect 1", "minor 3", "major 3"] + }; + addTemperamentToDictionary(entryName, entryValue); + expect(TEMPERAMENT[entryName]).toEqual(entryValue); + }); + + test("updateTemperaments should update TEMPERAMENTS with new entries", () => { + const newEntry = "customTemperament"; + TEMPERAMENT[newEntry] = { + pitchNumber: 8, + interval: ["perfect 1", "minor 3"] + }; + updateTemperaments(); + expect(TEMPERAMENTS.some(([_, name]) => name === newEntry)).toBe(true); + }); + + test("updateTemperaments should not duplicate predefined temperaments", () => { + updateTemperaments(); + const predefinedEntries = TEMPERAMENTS.filter(([_, name]) => name in PreDefinedTemperaments); + expect(predefinedEntries.length).toBe(Object.keys(PreDefinedTemperaments).length); + }); + }); +}); + +describe("Constants", () => { + test("should have correct default values", () => { + expect(DEFAULTINVERT).toBe("even"); + expect(DEFAULTMODE).toBe("major"); + }); +}); + +describe("customMode", () => { + test("should return custom mode from MUSICALMODES", () => { + expect(customMode).toEqual([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]); + }); +}); + +describe("getInvertMode", () => { + test("should return the correct invert mode name", () => { + expect(getInvertMode("even")).toBe("even"); + expect(getInvertMode("scalar")).toBe("scalar"); + expect(getInvertMode("nonexistent")).toBe("nonexistent"); + }); +}); + +describe("getIntervalNumber", () => { + test("should return the number of semi-tones for a given interval", () => { + expect(getIntervalNumber("perfect 5")).toBe(7); + expect(getIntervalNumber("major 3")).toBe(4); + }); +}); + +describe("getIntervalDirection", () => { + test("should return the direction of the interval", () => { + expect(getIntervalDirection("diminished 6")).toBe(-1); + expect(getIntervalDirection("minor 3")).toBe(-1); + }); +}); + +describe("getIntervalRatio", () => { + test("should return the ratio for a given interval", () => { + expect(getIntervalRatio("perfect 5")).toBe(1.5); + expect(getIntervalRatio("major 3")).toBe(1.25); + }); +}); + +// + +describe("getModeNumbers", () => { + test("should return the correct mode numbers for a valid mode", () => { + expect(getModeNumbers("chromatic")).toBe("0 1 2 3 4 5 6 7 8 9 10 11"); + expect(getModeNumbers("major")).toBe("0 2 4 5 7 9 11"); + expect(getModeNumbers("minor")).toBe("0 2 3 5 7 8 10"); + }); + + test("should return an empty string for an invalid mode", () => { + expect(getModeNumbers("invalidMode")).toBe(""); + }); + + test("should handle custom mode correctly", () => { + expect(getModeNumbers("custom")).toBe("0 1 2 3 4 5 6 7 8 9 10 11"); + }); +}); + +// + +jest.mock('../musicutils', () => { + const actualModule = jest.requireActual('../musicutils'); + return { + ...actualModule, + getDrumIndex: jest.fn(), + getDrumName: jest.fn(), + getDrumSymbol: jest.fn() + }; +}); + +describe('getDrum', () => { + let DRUMNAMES, DEFAULTDRUM; + + beforeEach(() => { + DRUMNAMES = [ + ["snare drum", "snare drum", "images/snaredrum.svg", "sn", "drum"], + ["kick drum", "kick drum", "images/kick.svg", "hh", "drum"], + ["tom tom", "tom tom", "images/tom.svg", "tomml", "drum"], + ["floor tom", "floor tom", "images/floortom.svg", "tomfl", "drum"], + ["bass drum", "bass drum", "images/kick.svg", "tomfl", "drum"], + ["hi hat", "hi hat", "images/hihat.svg", "hh", "bell"] + ]; + DEFAULTDRUM = "kick drum"; + + // Mock for getDrumIndex + require('../musicutils').getDrumIndex.mockImplementation((name) => { + if (name.slice(0, 4) === "http") return null; + if (name === "") return DRUMNAMES.findIndex(drum => drum[0] === DEFAULTDRUM); + + const index = DRUMNAMES.findIndex( + (drum) => drum[0].toLowerCase() === name.toLowerCase() + ); + return index >= 0 ? index : -1; + }); + + // Mock for getDrumName + require('../musicutils').getDrumName.mockImplementation((name) => { + if (name === "") name = DEFAULTDRUM; + if (name.slice(0, 4) === "http") return null; + + for (let drum = 0; drum < DRUMNAMES.length; drum++) { + if (DRUMNAMES[drum][0].toLowerCase() === name.toLowerCase()) { + return DRUMNAMES[drum][0]; + } else if (DRUMNAMES[drum][1].toLowerCase() === name.toLowerCase()) { + return DRUMNAMES[drum][1]; + } + } + + return null; + }); + + require('../musicutils').getDrumSymbol.mockImplementation((name) => { + if (name === "") return "hh"; + + for (let drum = 0; drum < DRUMNAMES.length; drum++) { + if (DRUMNAMES[drum][0].toLowerCase() === name.toLowerCase()) { + return DRUMNAMES[drum][3]; + } else if (DRUMNAMES[drum][1].toLowerCase() === name.toLowerCase()) { + return "hh"; + } + } + + return "hh"; + }); + }); + + describe('getDrumIndex', () => { + it('should return the index of a valid drum name', () => { + expect(getDrumIndex('snare drum')).toBe(0); + expect(getDrumIndex('kick drum')).toBe(1); + expect(getDrumIndex('floor tom')).toBe(3); + }); + + it('should return -1 for an invalid drum name', () => { + expect(getDrumIndex('invalid drum')).toBe(-1); + }); + + it('should return the index of the DEFAULTDRUM for empty input', () => { + expect(getDrumIndex('')).toBe(1); + }); + + it('should ignore case sensitivity when matching drum names', () => { + expect(getDrumIndex('SNARE DRUM')).toBe(0); + }); + + it('should return null for names starting with "http"', () => { + expect(getDrumIndex('http')).toBe(null); + }); + }); + + describe('getDrumName', () => { + it('should return the name of a valid drum', () => { + expect(getDrumName('snare drum')).toBe('snare drum'); + expect(getDrumName('kick drum')).toBe('kick drum'); + }); + + it('should return the DEFAULTDRUM name for empty input', () => { + expect(getDrumName('')).toBe('kick drum'); + }); + + it('should return null for names starting with "http"', () => { + expect(getDrumName('http')).toBe(null); + }); + + it('should ignore case sensitivity when matching drum names', () => { + expect(getDrumName('SNARE DRUM')).toBe('snare drum'); + expect(getDrumName('KICK DRUM')).toBe('kick drum'); + }); + + it('should return null for an invalid drum name', () => { + expect(getDrumName('invalid drum')).toBe(null); + }); + + it('should match the second element of DRUMNAMES if provided', () => { + expect(getDrumName('kick drum')).toBe('kick drum'); + }); + }); + + describe('getDrumSymbol', () => { + it('should return the correct symbol for a valid drum name', () => { + expect(getDrumSymbol('snare drum')).toBe('sn'); + expect(getDrumSymbol('kick drum')).toBe('hh'); + expect(getDrumSymbol('floor tom')).toBe('tomfl'); + }); + + it('should return "hh" for an empty name', () => { + expect(getDrumSymbol('')).toBe('hh'); + }); + + it('should return "hh" for an invalid drum name', () => { + expect(getDrumSymbol('invalid drum')).toBe('hh'); + }); + + it('should return "hh" for a name matching the second element of DRUMNAMES', () => { + expect(getDrumSymbol('snare drum')).toBe('sn'); + expect(getDrumSymbol('kick drum')).toBe('hh'); // As per logic + }); + + it('should ignore case sensitivity when matching drum names', () => { + expect(getDrumSymbol('SNARE DRUM')).toBe('sn'); + expect(getDrumSymbol('KICK DRUM')).toBe('hh'); + }); + }); +}); + +describe('getFilterTypes',() => { + it('should return default filter type', () => { + expect(getFilterTypes('')).toBe('highpass'); //DEFAULTFILTERTYPES + }); + it('should return correct filter types', () => { + expect(getFilterTypes('highpass')).toBe('highpass'); + expect(getFilterTypes('notch')).toBe('notch'); + }); +}); \ No newline at end of file diff --git a/js/utils/munsell.js b/js/utils/munsell.js index 93e8a18354..056245929c 100644 --- a/js/utils/munsell.js +++ b/js/utils/munsell.js @@ -6858,7 +6858,7 @@ let searchColors = (r, g, b) => { return nearestColor; }; - +module.exports = {interpColor, getMunsellColor, getcolor, searchColors}; // /** // * @deprecated // * @param {number} r - intensity of red diff --git a/js/utils/musicutils.js b/js/utils/musicutils.js index c51a0b44f3..b2beea5105 100644 --- a/js/utils/musicutils.js +++ b/js/utils/musicutils.js @@ -140,7 +140,7 @@ const NSYMBOLS = { 1: "𝅝", 2: "𝅗𝅥", 4: "♩", 8: "♪", 16: "𝅘𝅥𝅯" }; * @constant {Object.} * @default */ -const RSYMBOLS = { 1: "𝄻", 2: "𝄼", 4: "𝄽",8: "𝄾", 16: "𝄿" }; +const RSYMBOLS = { 1: "𝄻", 2: "𝄼", 4: "𝄽", 8: "𝄾", 16: "𝄿" }; /** * Maps from notes with flats to their corresponding notes with '♭' (flat) symbol. @@ -203,7 +203,6 @@ const NOTESSHARP = [ "B" ]; - /** * Array of notes with flats. * @constant {string[]} @@ -324,7 +323,7 @@ const CONVERT_UP = { "G♯": "A" + FLAT, "A♯": "B" + FLAT, "B♯": "C", - "B": "C" + FLAT, + "B": "C" + FLAT }; /** @@ -679,36 +678,35 @@ const ALLNOTESTEP = { "B#": 0 }; - /** * semitone/intervalnumber --> lettergap/notenamesgap -->intervalnames * @constant {Object.} */ const SEMITONETOINTERVALMAP = { - 0: { 0: _("Perfect unison"), 1: _("Diminished second") }, - 1: { 1: _("Minor second"), 0: _("Augmented unison") }, - 2: { 1: _("Major second"), 2: _("Diminished third") }, - 3: { 2: _("Minor third"), 1: _("Augmented second") }, - 4: { 2: _("Major third"), 3: _("Diminished fourth") }, - 5: { 3: _("Perfect fourth"), 2: _("Augmented third") }, - 6: { 4: _("Diminished fifth"), 3: _("Augmented fourth") }, - 7: { 4: _("Perfect fifth"), 5: _("Diminished sixth") }, - 8: { 5: _("Minor sixth"), 4: _("Augmented fifth") }, - 9: { 5: _("Major sixth"), 6: _("Diminished seventh") }, - 10: { 6: _("Minor seventh"), 5: _("Augmented sixth") }, - 11: { 6: _("Major seventh"), 0: _("Diminished octave") }, - 12: { 0: _("Perfect octave"), 6: _("Augmented seventh") }, - 13: { 1: _("Minor ninth"), 0: _("Augmented octave") }, - 14: { 1: _("Major ninth"), 2: _("Diminished tenth") }, - 15: { 2: _("Minor tenth"), 1: _("Augmented ninth") }, - 16: { 2: _("Major tenth"), 3: _("Diminished eleventh") }, - 17: { 3: _("Perfect eleventh"), 2: _("Augmented tenth") }, - 18: { 4: _("Diminished twelfth"), 3: _("Augmented eleventh") }, - 19: { 4: _("Perfect twelfth"), 5: _("Diminished thirteenth") }, - 20: { 5: _("Minor thirteenth"), 4: _("Augmented fifth, plus an octave") }, - 21: { 5: _("Major thirteenth"), 6: _("Diminished seventh, plus an octave") }, - }; + 0: { 0: _("Perfect unison"), 1: _("Diminished second") }, + 1: { 1: _("Minor second"), 0: _("Augmented unison") }, + 2: { 1: _("Major second"), 2: _("Diminished third") }, + 3: { 2: _("Minor third"), 1: _("Augmented second") }, + 4: { 2: _("Major third"), 3: _("Diminished fourth") }, + 5: { 3: _("Perfect fourth"), 2: _("Augmented third") }, + 6: { 4: _("Diminished fifth"), 3: _("Augmented fourth") }, + 7: { 4: _("Perfect fifth"), 5: _("Diminished sixth") }, + 8: { 5: _("Minor sixth"), 4: _("Augmented fifth") }, + 9: { 5: _("Major sixth"), 6: _("Diminished seventh") }, + 10: { 6: _("Minor seventh"), 5: _("Augmented sixth") }, + 11: { 6: _("Major seventh"), 0: _("Diminished octave") }, + 12: { 0: _("Perfect octave"), 6: _("Augmented seventh") }, + 13: { 1: _("Minor ninth"), 0: _("Augmented octave") }, + 14: { 1: _("Major ninth"), 2: _("Diminished tenth") }, + 15: { 2: _("Minor tenth"), 1: _("Augmented ninth") }, + 16: { 2: _("Major tenth"), 3: _("Diminished eleventh") }, + 17: { 3: _("Perfect eleventh"), 2: _("Augmented tenth") }, + 18: { 4: _("Diminished twelfth"), 3: _("Augmented eleventh") }, + 19: { 4: _("Perfect twelfth"), 5: _("Diminished thirteenth") }, + 20: { 5: _("Minor thirteenth"), 4: _("Augmented fifth, plus an octave") }, + 21: { 5: _("Major thirteenth"), 6: _("Diminished seventh, plus an octave") } +}; /** * Array containing preferences for keys with sharps. @@ -968,16 +966,11 @@ const MATRIXSOLFEHEIGHT = 30; * Image URL for a whole note. * @constant {string} */ -const wholeNoteImg = - "data:image/svg+xml;base64," + window.btoa(base64Encode(WHOLENOTE)); -const halfNoteImg = - "data:image/svg+xml;base64," + window.btoa(base64Encode(HALFNOTE)); -const quarterNoteImg = - "data:image/svg+xml;base64," + window.btoa(base64Encode(QUARTERNOTE)); -const eighthNoteImg = - "data:image/svg+xml;base64," + window.btoa(base64Encode(EIGHTHNOTE)); -const sixteenthNoteImg = - "data:image/svg+xml;base64," + window.btoa(base64Encode(SIXTEENTHNOTE)); +const wholeNoteImg = "data:image/svg+xml;base64," + window.btoa(base64Encode(WHOLENOTE)); +const halfNoteImg = "data:image/svg+xml;base64," + window.btoa(base64Encode(HALFNOTE)); +const quarterNoteImg = "data:image/svg+xml;base64," + window.btoa(base64Encode(QUARTERNOTE)); +const eighthNoteImg = "data:image/svg+xml;base64," + window.btoa(base64Encode(EIGHTHNOTE)); +const sixteenthNoteImg = "data:image/svg+xml;base64," + window.btoa(base64Encode(SIXTEENTHNOTE)); const thirtysecondNoteImg = "data:image/svg+xml;base64," + window.btoa(base64Encode(THIRTYSECONDNOTE)); const sixtyfourthNoteImg = @@ -986,7 +979,8 @@ const sixtyfourthNoteImg = /** * Map from note duration to corresponding note symbols. * @constant {Object.} - */ + */ + const NOTESYMBOLS = { 1: wholeNoteImg, 2: halfNoteImg, @@ -1271,15 +1265,59 @@ const DEFAULTCHORD = CHORDNAMES[9]; */ const CHORDVALUES = [ //scalar - [[0, 0], [2, 0], [4, 0]], - [[2, 0], [4, 0], [7, 0]], - [[-3, 0], [0, 0], [2, 0]], - [[0, 0], [2, 0], [4, 0], [6, 0]], - [[2, 0], [4, 0], [6, 0], [7, 0]], - [[-3, 0], [-1, 0], [0, 0], [2, 0]], - [[-1, 0], [0, 0], [2, 0], [4, 0]], - [[0, 0], [2, 0], [4, 0], [6, 0], [8, 0]], - [[0, 0], [2, 0], [4, 0], [6, 0], [12, 0]], + [ + [0, 0], + [2, 0], + [4, 0] + ], + [ + [2, 0], + [4, 0], + [7, 0] + ], + [ + [-3, 0], + [0, 0], + [2, 0] + ], + [ + [0, 0], + [2, 0], + [4, 0], + [6, 0] + ], + [ + [2, 0], + [4, 0], + [6, 0], + [7, 0] + ], + [ + [-3, 0], + [-1, 0], + [0, 0], + [2, 0] + ], + [ + [-1, 0], + [0, 0], + [2, 0], + [4, 0] + ], + [ + [0, 0], + [2, 0], + [4, 0], + [6, 0], + [8, 0] + ], + [ + [0, 0], + [2, 0], + [4, 0], + [6, 0], + [12, 0] + ], //semitone [[0, 0], [0, 4], [0, 7]], [[0, 0], [0, 3], [0, 7]], @@ -1292,7 +1330,11 @@ const CHORDVALUES = [ [[0, 0], [0, 3], [0, 6], [0, 9]], [[0, 0], [0, 3], [0, 6], [0, 10]], // custom is always at the end of the list - [[0, 0], [0, 4], [0, 7]], + [ + [0, 0], + [0, 4], + [0, 7] + ] ]; /** @@ -1643,14 +1685,14 @@ const TEMPERAMENT = { "perfect 8" ] }, - "equal5":{ + "equal5": { // Equal 5EDO temperament: 5 Equal Divisions of the Octave - "perfect 1": Math.pow(2, 0 / 5),//Unison + "perfect 1": Math.pow(2, 0 / 5), //Unison "minor 2": Math.pow(2, 1 / 5), "augmented 1": Math.pow(2, 1 / 5), "major 2": Math.pow(2, 2 / 5), "augmented 2": Math.pow(2, 2 / 5), - "minor 3": Math.pow(2, 2/ 5), + "minor 3": Math.pow(2, 2 / 5), "major 3": Math.pow(2, 3 / 5), "augmented 3": Math.pow(2, 3 / 5), "diminished 4": Math.pow(2, 3 / 5), @@ -1668,19 +1710,11 @@ const TEMPERAMENT = { "diminished 8": Math.pow(2, 5 / 5), "perfect 8": Math.pow(2, 5 / 5), "pitchNumber": 5, - "interval": [ - "perfect 1", - "minor 2", - "major 2", - "major 3", - "augmented 4", - "perfect 5" - - ] + "interval": ["perfect 1", "minor 2", "major 2", "major 3", "augmented 4", "perfect 5"] }, - "equal7":{ + "equal7": { // Equal 7EDO Temperament: 7 Equal Divisions of the Octave - "perfect 1": Math.pow(2, 0 / 7),//Unison + "perfect 1": Math.pow(2, 0 / 7), //Unison "minor 2": Math.pow(2, 1 / 7), "augmented 1": Math.pow(2, 1 / 7), "major 2": Math.pow(2, 2 / 7), @@ -1699,7 +1733,7 @@ const TEMPERAMENT = { "augmented 6": Math.pow(2, 7 / 7), "minor 7": Math.pow(2, 6 / 7), "major 7": Math.pow(2, 6 / 7), - "augmented 7": Math.pow(2, 7 / 7),// wraps around + "augmented 7": Math.pow(2, 7 / 7), // wraps around "diminished 8": Math.pow(2, 7 / 7), "perfect 8": Math.pow(2, 7 / 7), "pitchNumber": 7, @@ -1714,55 +1748,55 @@ const TEMPERAMENT = { "perfect 8" ] }, - "equal19":{ + "equal19": { // Equal 19EDO Temperament: 19 Equal Divisions of the Octave - "perfect 1": Math.pow(2, 0 / 19), - "minor 2": Math.pow(2, 2 / 19), - "augmented 1": Math.pow(2, 1 / 19), - "major 2": Math.pow(2, 3 / 19), - "augmented 2": Math.pow(2, 4 / 19), - "minor 3": Math.pow(2, 5 / 19), - "major 3": Math.pow(2, 6 / 19), - "augmented 3": Math.pow(2, 7 / 19), - "diminished 4": Math.pow(2, 7 / 19), - "perfect 4": Math.pow(2, 8 / 19), - "augmented 4": Math.pow(2, 9 / 19), - "diminished 5": Math.pow(2, 9 / 19), - "perfect 5": Math.pow(2, 10 / 19), - "augmented 5": Math.pow(2, 11 / 19), - "minor 6": Math.pow(2, 12 / 19), - "major 6": Math.pow(2, 13 / 19), - "augmented 6": Math.pow(2, 14 / 19), - "minor 7": Math.pow(2, 15 / 19), - "major 7": Math.pow(2, 16 / 19), - "augmented 7": Math.pow(2, 17 / 19), - "diminished 8": Math.pow(2, 18 / 19), - "perfect 8": Math.pow(2, 19 / 19), + "perfect 1": Math.pow(2, 0 / 19), + "minor 2": Math.pow(2, 2 / 19), + "augmented 1": Math.pow(2, 1 / 19), + "major 2": Math.pow(2, 3 / 19), + "augmented 2": Math.pow(2, 4 / 19), + "minor 3": Math.pow(2, 5 / 19), + "major 3": Math.pow(2, 6 / 19), + "augmented 3": Math.pow(2, 7 / 19), + "diminished 4": Math.pow(2, 7 / 19), + "perfect 4": Math.pow(2, 8 / 19), + "augmented 4": Math.pow(2, 9 / 19), + "diminished 5": Math.pow(2, 9 / 19), + "perfect 5": Math.pow(2, 10 / 19), + "augmented 5": Math.pow(2, 11 / 19), + "minor 6": Math.pow(2, 12 / 19), + "major 6": Math.pow(2, 13 / 19), + "augmented 6": Math.pow(2, 14 / 19), + "minor 7": Math.pow(2, 15 / 19), + "major 7": Math.pow(2, 16 / 19), + "augmented 7": Math.pow(2, 17 / 19), + "diminished 8": Math.pow(2, 18 / 19), + "perfect 8": Math.pow(2, 19 / 19), "pitchNumber": 19, "interval": [ "perfect 1", - "augmented 1", - "minor 2", - "major 2", - "augmented 2", - "minor 3", - "major 3", - "augmented 3", - "perfect 4", - "augmented 4", - "perfect 5", + "augmented 1", + "minor 2", + "major 2", + "augmented 2", + "minor 3", + "major 3", + "augmented 3", + "perfect 4", + "augmented 4", + "perfect 5", "augmented 5", - "minor 6", - "major 6", - "augmented 6", - "minor 7", - "major 7", - "augmented 7", - "diminished 8", - "perfect 8", - ] + "minor 6", + "major 6", + "augmented 6", + "minor 7", + "major 7", + "augmented 7", + "diminished 8", + "perfect 8" + ] }, - "equal31":{ + "equal31": { // Equal 31EDO Temperament: 31 Equal Divisions of the Octave "perfect 1": Math.pow(2, 0 / 31), "minor 2": Math.pow(2, 3 / 31), @@ -1777,7 +1811,7 @@ const TEMPERAMENT = { "augmented 4": Math.pow(2, 15 / 31), "diminished 5": Math.pow(2, 16 / 31), "perfect 5": Math.pow(2, 18 / 31), - "augmented 5":Math.pow(2, 19 / 31), + "augmented 5": Math.pow(2, 19 / 31), "minor 6": Math.pow(2, 21 / 31), "major 6": Math.pow(2, 23 / 31), "augmented 6": Math.pow(2, 24 / 31), @@ -1788,28 +1822,28 @@ const TEMPERAMENT = { "perfect 8": Math.pow(2, 31 / 31), "pitchNumber": 21, "interval": [ - "perfect 1", + "perfect 1", "augmented 1", - "minor 2", - "major 2", - "augmented 2", - "minor 3", - "major 3", - "augmented 3", - "diminished 4", - "perfect 4", - "augmented 4", - "diminished 5", - "perfect 5", + "minor 2", + "major 2", + "augmented 2", + "minor 3", + "major 3", + "augmented 3", + "diminished 4", + "perfect 4", + "augmented 4", + "diminished 5", + "perfect 5", "augmented 5", - "minor 6", - "major 6", - "augmented 6", - "minor 7", - "major 7", + "minor 6", + "major 6", + "augmented 6", + "minor 7", + "major 7", "augmented 7", - "diminished 8", - "perfect 8" + "diminished 8", + "perfect 8" ] }, "just intonation": { @@ -2124,7 +2158,6 @@ const updateTemperaments = () => { } }; - /** * Default invert mode. * @constant {string} @@ -2217,7 +2250,9 @@ const getInvertMode = (name) => { * @param {string} name - The name of the interval. * @returns {number} The number of semi-tones for the interval. */ -const getIntervalNumber = name => { return INTERVALVALUES[name][0]; }; +const getIntervalNumber = (name) => { + return INTERVALVALUES[name][0]; +}; /** * Get the direction of the interval (-1 down, 0 neutral, 1 up). @@ -2225,7 +2260,9 @@ const getIntervalNumber = name => { return INTERVALVALUES[name][0]; }; * @param {string} name - The name of the interval. * @returns {number} The direction of the interval. */ -const getIntervalDirection = name => { return INTERVALVALUES[name][1];}; +const getIntervalDirection = (name) => { + return INTERVALVALUES[name][1]; +}; /** * Get the ratio for a specific interval. @@ -2233,8 +2270,9 @@ const getIntervalDirection = name => { return INTERVALVALUES[name][1];}; * @param {string} name - The name of the interval. * @returns {number} The ratio for the interval. */ -const getIntervalRatio = name => { return INTERVALVALUES[name][2];}; - +const getIntervalRatio = (name) => { + return INTERVALVALUES[name][2]; +}; /** * Get the mode numbers for a specific mode name. @@ -2243,7 +2281,7 @@ const getIntervalRatio = name => { return INTERVALVALUES[name][2];}; * @returns {string} The mode numbers. */ const getModeNumbers = (name) => { - const __convert = obj => { + const __convert = (obj) => { let n = 0; let m = ""; for (let i = 0; i < obj.length; i++) { @@ -2461,7 +2499,6 @@ const getNoiseName = (name) => { return DEFAULTNOISE; }; - /** * Get the noise icon file path based on its name. * @function @@ -2661,7 +2698,7 @@ const frequencyToPitch = (hz) => { const f = A0 * Math.pow(TWELVEHUNDRETHROOT2, i); if (hz < f * 1.0003 && hz > f * 0.9997) { cents = i % 100; - let j = Math.floor((i / 100)); + let j = Math.floor(i / 100); if (cents > 50) { cents -= 100; j += 1; @@ -2700,7 +2737,7 @@ const getArticulation = (note) => { .replace("E", "") .replace("F", "") .replace("G", "") - .replace("^^", "") // up/down from custom notes + .replace("^^", "") // up/down from custom notes .replace("vv", "") .replace("^", "") .replace("v", ""); @@ -3441,7 +3478,7 @@ const getNoteFromInterval = (pitch, interval) => { const pitches = ["C", "D", "E", "F", "G", "A", "B"]; const priorAttrs = [DOUBLEFLAT, FLAT, "", SHARP, DOUBLESHARP]; // let majorintervalNote; - + /** * Find the note that corresponds to a major interval. * @function @@ -3486,7 +3523,7 @@ const getNoteFromInterval = (pitch, interval) => { } } }; - + /** * Find notes for intervals other than major intervals. * @function @@ -3679,11 +3716,11 @@ const GetNotesForInterval = (tur) => { secondNote = "C", octave = 0; if (noteStatus && noteStatus[0]) { - firstNote = noteStatus[0][0].replace(/\d/g,"");//removing all numbers like '1' + firstNote = noteStatus[0][0].replace(/\d/g, ""); //removing all numbers like '1' if (!noteStatus[0][1]) { return { firstNote, secondNote: firstNote, octave }; } - secondNote = noteStatus[0][1].replace(/\d/g,""); + secondNote = noteStatus[0][1].replace(/\d/g, ""); const octavea = parseInt(noteStatus[0][0].replace(/[^0-9]/g, "")); const octaveb = parseInt(noteStatus[0][1].replace(/[^0-9]/g, "")); octave = octaveb - octavea; @@ -3694,9 +3731,8 @@ const GetNotesForInterval = (tur) => { } if (intervals && intervals.length) { - octave=Math.floor(intervals[0]/7); - - } else if (noteOctave&¬eOctave[last(tur.singer.inNoteBlock)]) { + octave = Math.floor(intervals[0] / 7); + } else if (noteOctave && noteOctave[last(tur.singer.inNoteBlock)]) { const octaveblk = noteOctave[last(tur.singer.inNoteBlock)]; octave = octaveblk[octaveblk.length - 1] - octaveblk[0]; } @@ -3709,6 +3745,17 @@ const GetNotesForInterval = (tur) => { return { firstNote, secondNote, octave }; }; +/** + * Encodes a string to Base64 format. + * @param {string} str - The string to encode. + * @returns {string} - The Base64 encoded string. + */ +function base64Encode(str) { + let encoder = new TextEncoder(); + let uint8Array = encoder.encode(str); + let binaryString = String.fromCharCode(...uint8Array); + return binaryString; +} /** * Get the note based on various parameters. @@ -3807,11 +3854,7 @@ function getNote( if (kOffset === -1) { kOffset = 0; // eslint-disable-next-line no-console - console.log( - "Cannot find " + - keySignature.split(" ")[0] + - ". Reverting to C" - ); + console.log("Cannot find " + keySignature.split(" ")[0] + ". Reverting to C"); } } if (getSharpFlatPreference(keySignature) === "sharp") { @@ -4419,10 +4462,28 @@ const _calculate_pitch_number = (activity, np, tur) => { const buildScale = (keySignature) => { // FIX ME: temporary hard-coded fix to avoid errors in pitch preview if (keySignature == "C♭ major") { - const scale = ["C" + FLAT, "D" + FLAT, "E" + FLAT, "F" + FLAT, "G" + FLAT, "A" + FLAT, "B" + FLAT, "C" + FLAT]; + const scale = [ + "C" + FLAT, + "D" + FLAT, + "E" + FLAT, + "F" + FLAT, + "G" + FLAT, + "A" + FLAT, + "B" + FLAT, + "C" + FLAT + ]; return [scale, [2, 2, 1, 2, 2, 2, 1]]; } else if (keySignature == "F♭ major") { - const scale = ["F" + FLAT, "G" + FLAT, "A" + FLAT, "B" + DOUBLEFLAT, "C" + FLAT, "D" + FLAT, "E" + FLAT, "F" + FLAT]; + const scale = [ + "F" + FLAT, + "G" + FLAT, + "A" + FLAT, + "B" + DOUBLEFLAT, + "C" + FLAT, + "D" + FLAT, + "E" + FLAT, + "F" + FLAT + ]; return [scale, [2, 2, 1, 2, 2, 2, 1]]; } @@ -4551,7 +4612,7 @@ const _getStepSize = (keySignature, pitch, direction, transposition, temperament } else if (thisPitch in STOSHARP) { thisPitch = STOSHARP[thisPitch]; } - + /** * Check if two pitches are logically equivalent. * @function @@ -4716,14 +4777,15 @@ const getStepSizeDown = (keySignature, pitch, transposition, temperament) => { return _getStepSize(keySignature, pitch, "down", transposition, temperament); }; - /** * Get the length of the mode (number of notes) for the given key signature. * @function * @param {string} keySignature - The key signature. * @returns {number} The length of the mode. */ -const getModeLength = keySignature => { return buildScale(keySignature)[1].length; }; +const getModeLength = (keySignature) => { + return buildScale(keySignature)[1].length; +}; /** * Map scale degree to pitch or vice versa for a chosen mode. @@ -5175,7 +5237,7 @@ const getInterval = (interval, keySignature, pitch) => { * @returns {string} The reduced fraction as a string. */ const reducedFraction = (a, b) => { - const greatestCommonMultiple = (a, b) => { + const greatestCommonMultiple = (a, b) => { return b === 0 ? a : greatestCommonMultiple(b, a % b); }; @@ -5594,7 +5656,7 @@ const calcOctave = (currentOctave, arg, lastNotePlayed, currentNote) => { * @param {(number|string)} arg - The argument for interval octave calculation. * @returns {number} The calculated octave value. */ -const calcOctaveInterval = arg => { +const calcOctaveInterval = (arg) => { // Used by intervals to determine octave to use in an interval. let value = 0; switch (arg) { @@ -5662,7 +5724,7 @@ const convertFromSolfege = (note) => { * @param {number} factor - The duration factor to convert. * @returns {string|null} The string representation of the duration factor. */ -const convertFactor = factor => { +const convertFactor = (factor) => { switch (factor) { case 0.0625: // 1/16 return "16"; @@ -5774,7 +5836,7 @@ const getPitchInfo = (activity, type, currentNote, tur) => { } return SOLFEGENAMES[buildScale(tur.singer.keySignature)[0].indexOf(pitch)]; case "pitch class": - return ((pitchToNumber(pitch, octave, tur.singer.keySignature) - 3) % 12); + return (pitchToNumber(pitch, octave, tur.singer.keySignature) - 3) % 12; case "scalar class": return scaleDegreeToPitchMapping( tur.singer.keySignature, @@ -5789,7 +5851,7 @@ const getPitchInfo = (activity, type, currentNote, tur) => { tur.singer.movable, pitch ); - return (obj[0] + obj[1]); + return obj[0] + obj[1]; case "nth degree": return buildScale(tur.singer.keySignature)[0].indexOf(pitch); case "staff y": @@ -5825,3 +5887,49 @@ const getPitchInfo = (activity, type, currentNote, tur) => { console.debug("Waiting for note to play"); } }; +module.exports = { + setOctaveRatio, + getOctaveRatio, + TEMPERAMENT, + TEMPERAMENTS, + INITIALTEMPERAMENTS, + PreDefinedTemperaments, + getTemperamentsList, + getTemperament, + getTemperamentKeys, + addTemperamentToList, + deleteTemperamentFromList, + addTemperamentToDictionary, + updateTemperaments, + + DEFAULTINVERT, + DEFAULTMODE, + customMode, + getInvertMode, + getIntervalNumber, + getIntervalDirection, + getIntervalRatio, + + getModeNumbers, + getDrumIndex, + getDrumName, + getDrumSymbol, + getFilterTypes, + getOscillatorTypes, + getDrumIcon, + getDrumSynthName, + getNoiseName, + getNoiseIcon, + getNoiseSynthName, + getVoiceName, + getVoiceIcon, + getVoiceSynthName, + isCustomTemperament, + getTemperamentName, + noteToObj, + frequencyToPitch, + getArticulation, + keySignatureToMode, + getScaleAndHalfSteps, + modeMapper +};