import { ModificationBase, NoteDetails } from './ModificationBase.mjs';
import * as WebAudioApiErrors from '../modules/Errors.mjs';
/**
* Class representing a Turn modification.
*
* A Turn modification causes an implicit set of notes to be played after the primary
* printed note. In the case of an upper turn, the notes to be played are the principle
* note, the note above it, the principle note, the note below it, then finally the
* principle note again. In the case of a lower turn, the notes to be played are the
* principle note, the note below it, the principle note, the note above it, then finally
* the principle note again. The total cumulative duration of all notes in the turn is
* the same as the printed duration of the principle note.
*
* @extends ModificationBase
*/
export class Turn extends ModificationBase {
// Effect-specific private variables
/** @type {boolean} */
#isUpper;
// Major scale intervals
static upperOffsetsMajor = [2, 1, 2, 1, 1, 2, 1, 2, 1, 2, 1, 1];
static lowerOffsetsMajor = [1, 2, 1, 2, 1, 1, 2, 1, 2, 1, 2, 1];
/**
* Constructs a new {@link Turn} modification object.
*
* @param {boolean} isUpper - Whether this modification represents an upper turn
*/
constructor(isUpper, tempo, key, details) {
super(tempo, key, details);
this.#isUpper = isUpper;
}
/**
* 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: [],
sequence: []
},
optional: {
singleNote: ['upperOffset', 'lowerOffset'],
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 false;
}
static inferParametersFromSequence() {
throw new WebAudioApiErrors.WebAudioValueError('The "Turn" modification cannot infer any parameters from a sequence of notes');
}
/**
* Returns a list of all modified notes, durations, and velocities as generated by the
* corresponding modification class.
*
* The `details` variable may contain the following two keys:
*
* `upperOffset`: Upper offset value of the turn from the primary note
* `lowerOffset`: Lower offset value of the turn from the primary note
*
* @param {Object<string, number>} [details] - Details about the notes in the turn
* @returns {NoteDetails[]} List of {@link NoteDetails} to replace the original note
*/
getModifiedNoteDetails(details) {
let upperNote = this.unmodifiedDetails.note, lowerNote = this.unmodifiedDetails.note;
if (details && ('upperOffset' in details))
upperNote += Number(details.upperOffset);
else {
upperNote += Turn.upperOffsetsMajor[upperNote % 12];
upperNote += this.key.offsets[upperNote % 12];
}
if (details && ('lowerOffset' in details))
lowerNote -= Number(details.lowerOffset);
else {
lowerNote -= Turn.lowerOffsetsMajor[lowerNote % 12];
lowerNote += this.key.offsets[lowerNote % 12];
}
const turnNoteDuration = (this.unmodifiedDetails.duration >= 8) ?
(60.0 / ((5.0 * this.unmodifiedDetails.duration / this.tempo.beatBase) * this.tempo.beatsPerMinute)) :
(60.0 / ((32.0 / this.tempo.beatBase) * this.tempo.beatsPerMinute));
const primaryNoteDuration = ((this.unmodifiedDetails.duration < 0) ?
-this.unmodifiedDetails.duration : (60.0 / ((this.unmodifiedDetails.duration / this.tempo.beatBase) * this.tempo.beatsPerMinute))) -
(4 * turnNoteDuration);
return [new NoteDetails(
this.unmodifiedDetails.note,
this.unmodifiedDetails.velocity,
-turnNoteDuration,
0.0
),
new NoteDetails(
this.#isUpper ? upperNote : lowerNote,
this.unmodifiedDetails.velocity * 0.75,
-turnNoteDuration,
turnNoteDuration
),
new NoteDetails(
this.unmodifiedDetails.note,
this.unmodifiedDetails.velocity * 0.75,
-turnNoteDuration,
2 * turnNoteDuration
),
new NoteDetails(
this.#isUpper ? lowerNote : upperNote,
this.unmodifiedDetails.velocity * 0.75,
-turnNoteDuration,
3 * turnNoteDuration
),
new NoteDetails(
this.unmodifiedDetails.note,
this.unmodifiedDetails.velocity,
-primaryNoteDuration,
4 * turnNoteDuration
)];
}
}