Skip to content

Commit e33645b

Browse files
Implemented temperament-aware pitch stepping (Fixes #4033)
1 parent 4fc4c25 commit e33645b

File tree

5 files changed

+112
-6
lines changed

5 files changed

+112
-6
lines changed

js/blocks/PitchBlocks.js

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1810,27 +1810,25 @@ function setupPitchBlocks(activity) {
18101810

18111811
class StepPitchBlock extends FlowBlock {
18121812
constructor() {
1813-
//.TRANS: step some number of notes in current musical scale
1814-
super("steppitch", _("scalar step") + " (+/–)");
1813+
super("steppitch", _("scalar step") + " (+/-)");
18151814
this.setPalette("pitch", activity);
18161815
this.piemenuValuesC1 = [-7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7];
18171816
this.beginnerBlock(true);
18181817
this.setHelpString([
1819-
_("The Scalar Step block (in combination with a Number block) will play the next pitch in a scale,") +
1820-
" " +
1818+
_("The Scalar Step block (in combination with a Number block) will play the next pitch in a scale.") + " " +
18211819
_("eg if the last note played was sol, Scalar Step 1 will play la."),
18221820
"documentation",
18231821
""
18241822
]);
18251823
this.formBlock({
18261824
args: 1,
1827-
defaults: [1],
1825+
default: [1],
18281826
argTypes: ["anyin"]
18291827
});
18301828
}
18311829

18321830
flow(args, logo, turtle, blk) {
1833-
return Singer.PitchActions.stepPitch(args[0], turtle, blk);
1831+
return Singer.PitchActions.stepPitchTemperamentAware(args[0], turtle, blk);
18341832
}
18351833
}
18361834

js/turtleactions/PitchActions.js

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,111 @@ function setupPitchActions(activity) {
156156
return;
157157
}
158158

159+
/**
160+
* Gets the current temperament for the turtle.
161+
*
162+
* @param {Object} turtle - Turtle object
163+
* @returns {String} temperament name (e.g. "eqaul", "just", "pythagorean", "meantone")
164+
*/
165+
static getTemperament(turtle) {
166+
return turtle.singer.temperament || "equal";
167+
}
168+
169+
/**
170+
* Gets the current the key signature for the turtle.
171+
*
172+
* @param {Object} turtle - Turtle object
173+
* @returns {String} key signature (e.g. "C Major")
174+
*/
175+
static getKeySignature(turtle) {
176+
return turtle.singer.keySignature || "C Major";
177+
}
178+
179+
/**
180+
* Gets the last played pitch for the turtle.
181+
*
182+
* @param {Object} turtle - Turtle object
183+
* @returns {Array} [note, octave]
184+
*/
185+
static getCurrentPitch(turtle) {
186+
return turtle.singer.lastNotePlayed || ["C", 4];
187+
}
188+
189+
/**
190+
* Sets the last played pitch for the turtle.
191+
*
192+
* @param {String} note - Note name (e.g. "C", "G", "A")
193+
* @param {Number} octave - Octave number
194+
* @param {Object} turtle - Turtle object
195+
*/
196+
static setCurrentPitch(note, octave, turtle) {
197+
turtle.singer.lastNotePlayed = [note, octave];
198+
}
199+
200+
/**
201+
* Calculates the frequency for a note using temperament-specific logic.
202+
*
203+
* @param {String} note - Note name
204+
* @param {Number} octave - Octave number
205+
* @param {String} temperament - Temperament name
206+
* @param {String} keySignature - key signature
207+
* @returns {Array} [note, octave, frequency]
208+
*/
209+
static calculateTemperamentFrequency(note, octave, temperament, keySignature, step) {
210+
let frequency = 0;
211+
if(temperament === "equal") {
212+
frequency = pitchToFrequency(note, octave, keySignature, "equal");
213+
} else if(temperament == "just") {
214+
frequency = pitchToFrequency(note, octave, keySignature, "just");
215+
} else if(temperament === "pythagorean") {
216+
frequency = pitchToFrequency(note, octave, keySignature, "pythagorean");
217+
} else if(temperament === "meantone") {
218+
frequency = pitchToFrequency(note, octave, keySignature, "meantone");
219+
} else {
220+
frequency = pitchToFrequency(note, octave, keySignature, "equal");
221+
}
222+
return [note, octave, frequency];
223+
}
224+
225+
/**
226+
* Steps by Scalar degree using temperament-aware logic.
227+
*
228+
* @param {Number} step - number of scale degrees to step
229+
* @param {Number} turtle - Turtle index in turtles.turtleList
230+
* @param {Number|String} blk - corresponding Block object index in block.blockList or custom blockName
231+
* @returns {*} result of playPitch
232+
*/
233+
static stepPitchTemperamentAware(step, turtle, blk) {
234+
const temperament = this.getTemperament(activity.turtles.ithTurtle(turtle));
235+
const keySignature = this.getKeySignature(activity.turtles.ithTurtle(turtle));
236+
const [currentNote, currentOctave] = this.getCurrentPitch(activity.turtles.ithTurtle(turtle));
237+
238+
const scale = buildScale(keySignature)[0];
239+
let idx = scale.indexOf(currentNote);
240+
if (idx === -1) idx = 0;
241+
242+
let newIdx = idx + step;
243+
let octaveShift = 0;
244+
while(newIdx < 0) {
245+
newIdx += scale.length;
246+
octaveShift -= 1;
247+
}
248+
while(newIdx >= scale.length) {
249+
newIdx -= scale.length;
250+
octaveShift += 1;
251+
}
252+
const newNote = scale[newIdx];
253+
const newOctave = currentOctave + octaveShift;
254+
255+
const [note, octave, frequency] = this.calculateTemperamentFrequency(
256+
newNote, newOctave, temperament, keySignature, step
257+
);
258+
259+
this.setCurrentPitch(note, octave, activity.turtles.ithTurtle(turtle));
260+
261+
return this.playPitch(note, octave, 0, turtle, blk);
262+
}
263+
159264
/**
160265
* Plays a nth modal pitch block.
161266
*

venv/Scripts/python.exe

513 KB
Binary file not shown.

venv/Scripts/pythonw.exe

512 KB
Binary file not shown.

venv/pyvenv.cfg

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
home = C:\Users\Kusum Lata\AppData\Local\Programs\Python\Python38
2+
include-system-site-packages = false
3+
version = 3.8.0

0 commit comments

Comments
 (0)