import { ModificationBase, NoteDetails } from './ModificationBase.mjs';
import * as WebAudioApiErrors from '../modules/Errors.mjs';
/**
* Class representing a Slur modification.
*
* A Slur modification causes a sequence of notes to be played as if belonging to a
* single phrase. This translates to a slight crescendo/descrescendo over the course
* of the sequence, as well as a somewhat early release of the final note.
*
* @extends ModificationBase
*/
export class Slur extends ModificationBase {
/**
* Constructs a new {@link Slur} modification object.
*/
constructor(tempo, key, details) {
super(tempo, key, details);
}
/**
* Returns a list of all parameters available for use in this modification, including whether
* the parameter is required or optional when playing back either a "sequence" or just a
* single "note".
*
* @returns {Object<string,Object<string,string[]>>} List of modification-specific parameter keys and when they are required
*/
static getParameters() {
return {
required: {
singleNote: ['noteIndex', 'totalNumNotes'],
sequence: []
},
optional: {
singleNote: [],
sequence: []
}
};
}
/**
* Returns whether this modification can be used to modify a sequence of notes.
*
* @returns {boolean} Whether this modification can be used to modify a sequence of notes
*/
static canModifySequence() {
return true;
}
static inferParametersFromSequence(sequence, index) {
return {
'noteIndex': index,
'totalNumNotes': sequence.length
};
}
/**
* Returns a list of all modified notes, durations, and velocities as generated by the
* corresponding modification class.
*
* The `details` variable must contain the following two keys:
*
* `noteIndex`: Which note this modification applies to within the total modified phrase (starting at 1)
* `totalNumNotes`: Total number of notes present in the modified phrase
*
* @param {Object<string, number>} details - Information about the total modified phrase and this note's place in it
* @returns {NoteDetails[]} List of {@link NoteDetails} to replace the original note
*/
getModifiedNoteDetails(details) {
if (!('noteIndex' in details) || !('totalNumNotes' in details))
throw new WebAudioApiErrors.WebAudioValueError('The "details" variable must contain the following keys: noteIndex, totalNumNotes');
else if (!Number.isInteger(details.noteIndex) || (Number(details.noteIndex) < 1))
throw new WebAudioApiErrors.WebAudioValueError(`The "noteIndex" value (${details.noteIndex}) must be a positive integer > 0`);
else if (!Number.isInteger(details.totalNumNotes) || (Number(details.totalNumNotes) < 1) ||
(Number(details.totalNumNotes) < Number(details.noteIndex)))
throw new WebAudioApiErrors.WebAudioValueError(`The "totalNumNotes" value (${details.totalNumNotes}) must be a positive integer >= noteIndex`);
const velocityModification = 1.0 + Math.sin(Math.PI * Number(details.noteIndex) / Number(details.totalNumNotes));
const duration = (this.unmodifiedDetails.duration < 0) ? -this.unmodifiedDetails.duration :
(60.0 / ((this.unmodifiedDetails.duration / this.tempo.beatBase) * this.tempo.beatsPerMinute));
return [new NoteDetails(
this.unmodifiedDetails.note,
this.unmodifiedDetails.velocity * velocityModification,
(Number(details.noteIndex) < Number(details.totalNumNotes)) ? this.unmodifiedDetails.duration : -duration + (60.0 / ((32.0 / this.tempo.beatBase) * this.tempo.beatsPerMinute)),
0.0,
this.unmodifiedDetails.duration
)];
}
}