Skip to content

Commit 1db2ce1

Browse files
committed
chore: implement pitch helper functions edge-cases and tests
1 parent 7ff1cd3 commit 1db2ce1

File tree

2 files changed

+246
-87
lines changed

2 files changed

+246
-87
lines changed

js/utils/__tests__/musicutils.test.js

Lines changed: 100 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ global._ = jest.fn(str => str);
2323
global.window = {
2424
btoa: jest.fn(str => Buffer.from(str, "utf8").toString("base64"))
2525
};
26+
global.INVALIDPITCH = "Not a valid pitch name";
2627

2728
const {
2829
scaleDegreeToPitchMapping,
@@ -97,14 +98,15 @@ const {
9798
numberToPitchSharp,
9899
getNumber,
99100
getNoteFromInterval,
100-
numberToPitch,
101-
GetNotesForInterval,
102-
base64Encode,
103-
NOTESFLAT,
104101
NOTESSHARP,
105102
MUSICALMODES,
106103
getStepSizeUp,
107-
getStepSizeDown
104+
getStepSizeDown,
105+
INVALIDPITCH,
106+
numberToPitch,
107+
GetNotesForInterval,
108+
base64Encode,
109+
NOTESFLAT
108110
} = require("../musicutils");
109111

110112
describe("musicutils", () => {
@@ -2241,39 +2243,6 @@ describe("getPitchInfo", () => {
22412243
});
22422244
});
22432245

2244-
describe("_calculate_pitch_number", () => {
2245-
let activity, tur;
2246-
2247-
beforeEach(() => {
2248-
activity = {
2249-
errorMsg: jest.fn()
2250-
};
2251-
2252-
tur = {
2253-
singer: {
2254-
lastNotePlayed: null,
2255-
inNoteBlock: {},
2256-
notePitches: {},
2257-
noteOctaves: {},
2258-
keySignature: "C major",
2259-
movable: false,
2260-
pitchNumberOffset: 0
2261-
}
2262-
};
2263-
});
2264-
2265-
it("calculates pitch number for a standard note string", () => {
2266-
const val = _calculate_pitch_number(activity, "C4", tur);
2267-
expect(typeof val).toBe("number");
2268-
});
2269-
2270-
it("calculates pitch number relative to another note", () => {
2271-
const valC4 = _calculate_pitch_number(activity, "C4", tur);
2272-
const valC5 = _calculate_pitch_number(activity, "C5", tur);
2273-
expect(valC5).toBeGreaterThan(valC4);
2274-
});
2275-
});
2276-
22772246
describe("NOTESFLAT", () => {
22782247
it("should contain 12 chromatic notes", () => {
22792248
expect(NOTESFLAT.length).toBe(12);
@@ -2404,3 +2373,96 @@ describe("getStepSizeUp", () => {
24042373
expect(result).toBe(0);
24052374
});
24062375
});
2376+
2377+
describe("_calculate_pitch_number", () => {
2378+
it("should return the correct pitch number for common notes", () => {
2379+
expect(_calculate_pitch_number("C", 4)).toBe(60);
2380+
expect(_calculate_pitch_number("A", 4)).toBe(69);
2381+
expect(_calculate_pitch_number("C", 5)).toBe(72);
2382+
});
2383+
2384+
it("should maintain enharmonic consistency", () => {
2385+
expect(_calculate_pitch_number("C#", 4)).toBe(61);
2386+
expect(_calculate_pitch_number("Db", 4)).toBe(61);
2387+
});
2388+
2389+
it("should return INVALIDPITCH for invalid input", () => {
2390+
expect(_calculate_pitch_number("Invalid", 4)).toBe("Not a valid pitch name");
2391+
expect(_calculate_pitch_number(null, 4)).toBe("Not a valid pitch name");
2392+
});
2393+
});
2394+
2395+
describe("getPitchInfo", () => {
2396+
it("should correctly parse string inputs", () => {
2397+
const info = getPitchInfo("C#5");
2398+
expect(info).toEqual({
2399+
name: "C#",
2400+
octave: 5,
2401+
pitchNumber: 73
2402+
});
2403+
2404+
const info10 = getPitchInfo("C10");
2405+
expect(info10).toEqual({
2406+
name: "C",
2407+
octave: 10,
2408+
pitchNumber: 132
2409+
});
2410+
2411+
// F𝄪5 (F double-sharp) → same pitch as G5 = (5+1)*12 + 7 = 79
2412+
const infoDoubleSharp = getPitchInfo("F\u{1D12A}5");
2413+
expect(infoDoubleSharp.pitchNumber).toBe(79);
2414+
expect(infoDoubleSharp.octave).toBe(5);
2415+
2416+
// Bb𝄫5 (B double-flat) → same pitch as A5 = (5+1)*12 + 9 = 81
2417+
const infoDoubleFlat = getPitchInfo("B\u{1D12B}5");
2418+
expect(infoDoubleFlat.pitchNumber).toBe(81);
2419+
expect(infoDoubleFlat.octave).toBe(5);
2420+
2421+
// Cx4 (C textual double-sharp) → same pitch as D4 = (4+1)*12 + 2 = 62
2422+
const infoX = getPitchInfo("Cx4");
2423+
expect(infoX.pitchNumber).toBe(62);
2424+
expect(infoX.octave).toBe(4);
2425+
});
2426+
2427+
it("should correctly parse numeric inputs", () => {
2428+
const info = getPitchInfo(60);
2429+
expect(info).toEqual({
2430+
name: "C",
2431+
octave: 4,
2432+
pitchNumber: 60
2433+
});
2434+
});
2435+
2436+
it("should handle invalid inputs", () => {
2437+
const info = getPitchInfo("InvalidNote");
2438+
expect(info).toEqual({
2439+
name: null,
2440+
octave: null,
2441+
pitchNumber: "Not a valid pitch name"
2442+
});
2443+
});
2444+
2445+
it("should handle accidental offset accumulation edge cases", () => {
2446+
// Gb-1: G(7) + flat(-1) = index 6 (G♭). (-1+1)*12 + 6 = 6
2447+
const infoGbNeg1 = getPitchInfo("Gb-1");
2448+
expect(infoGbNeg1.pitchNumber).toBe(6);
2449+
expect(infoGbNeg1.octave).toBe(-1);
2450+
2451+
// D𝄫-1 enharmonic of C at oct -1: (-1+1)*12 + 2 + (-2) = 0
2452+
// Note: D𝄫-2 (oct=-2) gives -12; octave must be -1 to reach pitch 0.
2453+
const infoDdblFlatNeg1 = getPitchInfo("D\u{1D12B}-1");
2454+
expect(infoDdblFlatNeg1.pitchNumber).toBe(0);
2455+
expect(infoDdblFlatNeg1.octave).toBe(-1);
2456+
2457+
// E##4: E(4) + ##(+2) → index 6 (F#). (4+1)*12 + 4 + 2 = 66
2458+
// Spelled with two ASCII '#' chars; each contributes +1 via ACCIDENTAL_SEMITONE_MAP.
2459+
const infoEDblSharp4 = getPitchInfo("E##4");
2460+
expect(infoEDblSharp4.pitchNumber).toBe(66);
2461+
expect(infoEDblSharp4.octave).toBe(4);
2462+
2463+
// Fx10: F(5) + x(+2) → index 7 (G). (10+1)*12 + 5 + 2 = 139
2464+
const infoFx10 = getPitchInfo("Fx10");
2465+
expect(infoFx10.pitchNumber).toBe(139);
2466+
expect(infoFx10.octave).toBe(10);
2467+
});
2468+
});

0 commit comments

Comments
 (0)