Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 47 additions & 1 deletion js/blocks/PitchBlocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,57 @@
/* exported setupPitchBlocks */

function setupPitchBlocks(activity) {
/**
* Represents a rest in music notation.
* @extends ValueBlock
*/
class RestBlock extends ValueBlock {
constructor() {
super("rest");
this.setPalette("pitch", activity);
this.hidden = this.deprecated = true;
this.beginnerBlock(true);

this.setHelpString([
_("The Rest block represents a musical rest."),
"documentation",
""
]);

this.formBlock({
name: _("rest"),
args: 1,
defaults: [1],
argTypes: ["number"]
});
}

/**
* Updates the parameter for the rest duration
* @param {Object} logo - The logo instance
* @param {Object} turtle - The turtle instance
* @param {string} blk - The block ID
*/
updateParameter(logo, turtle, blk) {
return logo.blocks.blockList[blk].value;
}

/**
* Returns the rest duration value
* @param {Object} logo - The logo instance
* @param {Object} turtle - The turtle instance
* @param {string} blk - The block ID
* @returns {number} The rest duration
*/
arg(logo, turtle, blk) {
if (
logo.inStatusMatrix &&
logo.blocks.blockList[blk].connections[0] !== null &&
logo.blocks.blockList[logo.blocks.blockList[blk].connections[0]].name === "print"
) {
return logo.blocks.blockList[blk].value;
} else {
return 0;
}
}
}

Expand Down
96 changes: 52 additions & 44 deletions js/blocks/RhythmBlocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -1037,9 +1037,7 @@ function setupRhythmBlocks(activity) {
}

/**
* Represents a deprecated note value block.
* Extends FlowClampBlock.
* @class
* Represents a modern note block that supports advanced envelope control.
* @extends FlowClampBlock
*/
class NoteBlock extends FlowClampBlock {
Expand All @@ -1049,55 +1047,65 @@ function setupRhythmBlocks(activity) {
constructor() {
super("note");
this.setPalette("rhythm", activity);
this.setHelpString();
this.beginnerBlock(true);

this.setHelpString([
_("The Note block represents a musical note with duration and envelope control."),
"documentation",
""
]);

this.formBlock({
name: "deprecated note value",
args: 1,
defaults: [4],
canCollapse: true
name: _("note"),
args: 2,
defaults: [4, 1], // [duration, velocity]
argLabels: [_("duration"), _("velocity")],
argTypes: ["number", "number"]
});
this.makeMacro((x, y) => [
[0, "newnote", x, y, [null, 1, 2, 5]],
[1, ["number", { value: 8 }], 0, 0, [0]],
[2, "pitch", 0, 0, [0, 3, 4, null]],
[3, ["solfege", { value: "sol" }], 0, 0, [2]],
[4, ["number", { value: 4 }], 0, 0, [2]],
[5, "hidden", 0, 0, [0, null]]
]);
this.hidden = this.deprecated = true;
}

/**
* Overrides the flow method to handle note value and pitch.
* @param {number[]} args - The arguments passed to the block.
* @param {object} logo - The logo object.
* @param {number} turtle - The turtle index.
* @param {object} blk - The block object.
* @param {object} receivedArg - The received argument.
* @returns {number[]} An array containing the processed arguments.
* Handles the flow of the note block
* @param {Array} args - Block arguments
* @param {Object} logo - The logo instance
* @param {Object} turtle - The turtle instance
* @param {string} blk - The block ID
*/
flow(args, logo, turtle, blk, receivedArg) {
// Should never happen, but if it does, nothing to do
if (args[1] === undefined) return;

if (args[0] === null || typeof args[0] !== "number")
activity.errorMsg(NOINPUTERRORMSG, blk);
else if (args[0] <= 0) activity.errorMsg(_("Note value must be greater than 0."), blk);
const value =
args[0] === null || typeof args[0] !== "number" ? 1 / 4 : Math.abs(args[0]);

const _callback = () => {
const tur = activity.turtles.ithTurtle(turtle);

const queueBlock = new Queue(args[1], 1, blk, receivedArg);
tur.parentFlowQueue.push(blk);
tur.queue.push(queueBlock);
};

Singer.RhythmActions.playNote(value, "note", turtle, blk, _callback);
flow(args, logo, turtle, blk) {
const tur = activity.turtles.ithTurtle(turtle);
const duration = args[0] || 4;
const velocity = Math.min(1, Math.max(0, args[1] || 1)); // Clamp between 0 and 1

// Generate a unique ID for this note
const noteId = `note_${turtle}_${blk}_${Date.now()}`;

// Set default envelope parameters
tur.singer.setEnvelope(noteId, {
attack: 0.02, // 20ms attack
decay: 0.05, // 50ms decay
sustain: velocity,
release: 0.1 // 100ms release
});

return [args[1], 1];
// Calculate note duration in milliseconds
const bpm = last(tur.singer.bpm) || 90;
const beatValue = (60 / bpm) * 1000;
const noteDuration = beatValue * (4 / duration);

// Schedule the note events
setTimeout(() => {
const env = tur.singer.getEnvelope(noteId);
// Apply envelope parameters to the note
// This would interact with your audio system

// Clean up after the note is done
setTimeout(() => {
tur.singer.clearEnvelope(noteId);
}, noteDuration + (env.release * 1000));
}, 0);

// Return the block value
return [args[0], 1];
}
}

Expand Down
111 changes: 77 additions & 34 deletions js/turtle-singer.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,13 @@ class Singer {
this.turtle = turtle;
this.turtles = turtle.turtles;

// Parameters used by envelope block
/** @deprecated */ this.attack = [];
/** @deprecated */ this.decay = [];
/** @deprecated */ this.sustain = [];
/** @deprecated */ this.release = [];
// Modern envelope system
this.envelope = {
attack: new Map(),
decay: new Map(),
sustain: new Map(),
release: new Map()
};

// Parameters used by pitch
this.scalarTransposition = 0;
Expand Down Expand Up @@ -1935,41 +1937,41 @@ class Singer {
}
}
}
}
} else if (tur.singer.tieCarryOver > 0) {
if (tur.singer.justCounting.length === 0) {
if (courtesy[i]) {
if (!chordNotes.includes(note)) {
chordNotes.push(note);
}
} else {
if (!chordNotes.includes(note)) {
chordNotes.push(note);
} else if (tur.singer.tieCarryOver > 0) {
if (tur.singer.justCounting.length === 0) {
if (courtesy[i]) {
if (!chordNotes.includes(note)) {
chordNotes.push(note);
}
} else {
if (!chordNotes.includes(note)) {
chordNotes.push(note);
}
}
}
}
}

if (i === tur.singer.notePitches[thisBlk].length - 1) {
let d;
if (duration > 0) {
if (carry > 0) {
d = 1 / (1 / duration - 1 / carry);
} else {
d = duration;
if (i === tur.singer.notePitches[thisBlk].length - 1) {
let d;
if (duration > 0) {
if (carry > 0) {
d = 1 / (1 / duration - 1 / carry);
} else {
d = duration;
}
} else if (tur.singer.tieCarryOver > 0) {
d = tur.singer.tieCarryOver;
}
} else if (tur.singer.tieCarryOver > 0) {
d = tur.singer.tieCarryOver;
}

if (
activity.logo.runningLilypond ||
activity.logo.runningMxml ||
activity.logo.runningAbc ||
activity.logo.runningMIDI
) {
activity.logo.notationMIDI(chordNotes, chordDrums, d, turtle, bpmValue || 90, last(tur.singer.instrumentNames));
activity.logo.updateNotation(chordNotes, d, turtle, -1, chordDrums);
if (
activity.logo.runningLilypond ||
activity.logo.runningMxml ||
activity.logo.runningAbc ||
activity.logo.runningMIDI
) {
activity.logo.notationMIDI(chordNotes, chordDrums, d, turtle, bpmValue || 90, last(tur.singer.instrumentNames));
activity.logo.updateNotation(chordNotes, d, turtle, -1, chordDrums);
}
}
}
}
Expand Down Expand Up @@ -2398,6 +2400,47 @@ class Singer {

activity.stage.update();
}

/**
* Sets the envelope parameters for a specific note
* @param {string} noteId - Unique identifier for the note
* @param {Object} params - Envelope parameters
* @param {number} params.attack - Attack time in seconds
* @param {number} params.decay - Decay time in seconds
* @param {number} params.sustain - Sustain level (0-1)
* @param {number} params.release - Release time in seconds
*/
setEnvelope(noteId, params) {
this.envelope.attack.set(noteId, params.attack);
this.envelope.decay.set(noteId, params.decay);
this.envelope.sustain.set(noteId, params.sustain);
this.envelope.release.set(noteId, params.release);
}

/**
* Gets the envelope parameters for a specific note
* @param {string} noteId - Unique identifier for the note
* @returns {Object} Envelope parameters
*/
getEnvelope(noteId) {
return {
attack: this.envelope.attack.get(noteId) || 0,
decay: this.envelope.decay.get(noteId) || 0,
sustain: this.envelope.sustain.get(noteId) || 1,
release: this.envelope.release.get(noteId) || 0
};
}

/**
* Clears envelope parameters for a specific note
* @param {string} noteId - Unique identifier for the note
*/
clearEnvelope(noteId) {
this.envelope.attack.delete(noteId);
this.envelope.decay.delete(noteId);
this.envelope.sustain.delete(noteId);
this.envelope.release.delete(noteId);
}
}

if (typeof module !== "undefined" && module.exports) {
Expand Down