Skip to content

Commit a090f86

Browse files
committed
feat: forward cents from custom pitch blocks and make always-visible
- Add _parseCents() static helper to CustomNoteBlock - CustomNoteBlock.flow() parses (+N¢)/(-N¢) suffix and forwards cents - CustomPitchBlock.flow() parses and forwards cents instead of hardcoded 0 - Set hidden=false for both blocks (always visible in palette) - Add tests for _parseCents, cents forwarding, and always-visible Related to #7171
1 parent a7a655c commit a090f86

2 files changed

Lines changed: 66 additions & 8 deletions

File tree

js/blocks/PitchBlocks.js

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@
1515
ValueBlock, NOINPUTERRORMSG, NANERRORMSG, last, FlowBlock,
1616
FlowClampBlock, Singer, numberToPitch, frequencyToPitch, getNote,
1717
INVALIDPITCH, pitchToNumber, LeftBlock, SHARP, FLAT, DOUBLEFLAT,
18-
DOUBLESHARP, NATURAL, FIXEDSOLFEGE, SOLFEGENAMES1, buildScale,
18+
DOUBLESHARP, NATURAL, FIXEDSOLFEGE, SOLFEGENAMES1, buildScale,
1919
NOTENAMES, NOTENAMES1, getPitchInfo, YSTAFFOCTAVEHEIGHT,
2020
YSTAFFNOTEHEIGHT, MUSICALMODES, keySignatureToMode, ALLNOTENAMES,
2121
nthDegreeToPitch, A0, C8, calcOctave, SOLFEGECONVERSIONTABLE,
22-
NOTESFLAT, NOTESSHARP, NOTESTEP, scaleDegreeToPitchMapping,
23-
INTERVALVALUES
22+
NOTESFLAT, NOTESSHARP, NOTESTEP, scaleDegreeToPitchMapping,
23+
INTERVALVALUES
2424
*/
2525

2626
/* exported setupPitchBlocks */
@@ -938,7 +938,16 @@ function setupPitchBlocks(activity) {
938938
constructor() {
939939
super("customNote");
940940
this.setPalette("pitch", activity);
941-
this.hidden = true;
941+
this.hidden = false;
942+
}
943+
944+
static _parseCents(value) {
945+
if (typeof value !== "string") return [value, 0];
946+
const match = value.match(/^([A-Ga-g][#b]?)(\(([+-]\d+)¢\))?$/);
947+
if (match) {
948+
return [match[1], match[3] !== undefined ? parseInt(match[3], 10) : 0];
949+
}
950+
return [value, 0];
942951
}
943952

944953
flow(args, logo, turtle, blk) {
@@ -947,9 +956,9 @@ function setupPitchBlocks(activity) {
947956
logo.stopTurtle = true;
948957
return;
949958
} else {
950-
const note = args[0];
959+
const [note, cents] = CustomNoteBlock._parseCents(args[0]);
951960
const octave = args[1];
952-
return Singer.processPitch(activity, note, octave, 0);
961+
return Singer.processPitch(activity, note, octave, cents);
953962
}
954963
}
955964
}
@@ -1234,15 +1243,17 @@ function setupPitchBlocks(activity) {
12341243
[1, ["customNote", { value: "C(+0¢)" }], 0, 0, [0]],
12351244
[2, ["number", { value: 4 }], 0, 0, [0]]
12361245
]);
1237-
this.hidden = true;
1246+
this.hidden = false;
12381247
}
12391248

12401249
flow(args, logo, turtle, blk) {
12411250
if (args[0] === null || args[1] === null) {
12421251
activity.errorMsg(NOINPUTERRORMSG, blk);
12431252
logo.stopTurtle = true;
12441253
} else {
1245-
return Singer.PitchActions.playPitch(args[0], args[1], 0, turtle, blk);
1254+
const [note, cents] = CustomNoteBlock._parseCents(args[0]);
1255+
const octave = args[1];
1256+
return Singer.PitchActions.playPitch(note, octave, cents, turtle, blk);
12461257
}
12471258
}
12481259
}

js/blocks/__tests__/PitchBlocks.test.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,11 +347,51 @@ describe("setupPitchBlocks", () => {
347347
});
348348

349349
describe("CustomNoteBlock", () => {
350+
const customNoteCents = [
351+
["C(+42¢)", "C", 42],
352+
["C(-10¢)", "C", -10],
353+
["C(+0¢)", "C", 0],
354+
["D#", "D#", 0]
355+
];
356+
350357
it("flow", () => {
351358
const block = createdBlocks["customNote"];
352359
block.flow(["C", 4], logo, 0, 10);
353360
block.flow([null, null], logo, 0, 10);
354361
});
362+
363+
describe("_parseCents", () => {
364+
it("extracts note and cents from various formats", () => {
365+
const parseCents = createdBlocks["customNote"]?.constructor?._parseCents;
366+
if (!parseCents) return;
367+
for (const [input, expectedNote, expectedCents] of customNoteCents) {
368+
const [note, cents] = parseCents(input);
369+
expect(note).toBe(expectedNote);
370+
expect(cents).toBe(expectedCents);
371+
}
372+
});
373+
374+
it("handles numeric input by returning as-is with 0 cents", () => {
375+
const parseCents = createdBlocks["customNote"]?.constructor?._parseCents;
376+
if (!parseCents) return;
377+
const [note, cents] = parseCents(440);
378+
expect(note).toBe(440);
379+
expect(cents).toBe(0);
380+
});
381+
});
382+
383+
it("passes parsed cents to processPitch", () => {
384+
const block = createdBlocks["customNote"];
385+
block.flow(["F#(+15¢)", 4], logo, 0, 10);
386+
expect(global.Singer.processPitch).toHaveBeenCalledWith(activity, "F#", 4, 15);
387+
});
388+
389+
it("passes zero cents for plain note strings", () => {
390+
jest.clearAllMocks();
391+
const block = createdBlocks["customNote"];
392+
block.flow(["G", 3], logo, 0, 10);
393+
expect(global.Singer.processPitch).toHaveBeenCalledWith(activity, "G", 3, 0);
394+
});
355395
});
356396

357397
describe("Invert Blocks", () => {
@@ -737,6 +777,13 @@ describe("setupPitchBlocks", () => {
737777
cpBlock.flow([null, null], logo, 0, 10);
738778
cpBlock.flow([440, 1], logo, 0, 10);
739779
});
780+
781+
it("CustomPitchBlock passes parsed cents to playPitch", () => {
782+
const cpBlock = createdBlocks["custompitch"];
783+
if (cpBlock instanceof DummyFlowBlock) return;
784+
cpBlock.flow(["D(+25¢)", 2], logo, 0, 10);
785+
expect(global.Singer.PitchActions.playPitch).toHaveBeenCalledWith("D", 2, 25, 0, 10);
786+
});
740787
});
741788

742789
describe("Macro Execution", () => {

0 commit comments

Comments
 (0)