import {WebMidi} from "./WebMidi.js";
/**
* The `Note` class represents a single note to be played. The `Note` can be played on a single
* channel by using [OutputChannel.playNote()]{@link OutputChannel#playNote} or on multiple
* channels at once by using [Output.playNote()]{@link Output#playNote}.
*
* If the note's `duration` property is set, the note will be stopped at the end of the duration. If
* no duration is set, it will play until it is explicitly stopped using
* [OutputChannel.stopNote()]{@link OutputChannel#stopNote} or
* [Output.stopNote()]{@link Output#stopNote}.
*
* @param value {string|number} The name or note number of the note to create. If a number is used,
* it must be an integer between 0 and 127. If a string is used, it must be the note name followed
* by the octave (`"C3"`, `"G#4"`, `"F-1"`, `"Db7"`, etc.). The octave range must be between -1 and
* 9. The lowest note is C-1 (MIDI note number 0) and the highest note is G9 (MIDI note number 127).
*
* @param {Object} [options={}]
*
* @param {number} [options.duration=Infinity] The number of milliseconds before the note should be
* explicitly stopped.
*
* @param {number} [options.attack=0.5] The note's attack velocity as a decimal number between 0 and
* 1.
*
* @param {number} [options.release=0.5] The note's release velocity as a decimal number between 0
* and 1.
*
* @param {number} [options.rawAttack=64] The note's attack velocity as an integer between 0 and
* 127.
*
* @param {number} [options.rawRelease=64] The note's release velocity as an integer between 0 and
* 127.
*
* @throws {Error} Invalid note name.
* @throws {Error} Invalid note number.
* @throws {RangeError} Invalid duration.
* @throws {RangeError} Invalid attack value.
* @throws {RangeError} Invalid rawAttack value.
* @throws {RangeError} Invalid release value.
* @throws {RangeError} Invalid rawRelease value.
*
* @since 3.0.0
*/
export class Note {
constructor(value, options = {}) {
if (Number.isInteger(value)) {
this.number = value;
} else {
this.name = value;
}
this.duration = (options.duration == undefined) ? Infinity : options.duration;
this.attack = (options.attack == undefined) ? 0.5 : options.attack;
this.release = (options.release == undefined) ? 0.5 : options.release;
if (options.rawAttack != undefined) this.rawAttack = options.rawAttack;
if (options.rawRelease != undefined) this.rawRelease = options.rawRelease;
}
/**
* The name of the note with the octave number (`"C3"`, `"G#4"`, `"F-1"`, `"Db7"`, etc.)
*
* @type {string}
*/
get name() {
return WebMidi.NOTES[this._number % 12] + WebMidi.getOctave(this.number);
}
set name(value) {
if (WebMidi.validation) {
if (WebMidi.guessNoteNumber(value) === false) throw new Error("Invalid note name.");
}
this._number = WebMidi.guessNoteNumber(value);
}
/**
* The MIDI note number as an integer between 0 and 127
* @type {number}
*/
get number() {
return this._number;
}
set number(value) {
if (WebMidi.validation) {
if (WebMidi.guessNoteNumber(value) === false) throw new Error("Invalid note number.");
}
this._number = WebMidi.guessNoteNumber(value);
}
/**
* The duration of the note as a positive decimal number representing the number of milliseconds
* that the note should play for.
*
* @type {number}
*/
get duration() {
return this._duration;
}
set duration(value) {
if (WebMidi.validation) {
value = parseFloat(value);
if (isNaN(value) || value === null || value < 0) throw new RangeError("Invalid duration.");
}
this._duration = value;
}
/**
* The attack velocity of the note as a decimal number between 0 and 1.
* @type {number}
*/
get attack() {
return this._rawAttack / 127;
}
set attack(value) {
if (WebMidi.validation) {
value = parseFloat(value);
if (isNaN(value) || value === null || !(value >= 0 && value <= 1)) {
throw new RangeError("Invalid attack value.");
}
}
this._rawAttack = Math.round(value * 127);
}
/**
* The raw attack velocity of the note as an integer between 0 and 127.
* @type {number}
*/
get rawAttack() {
return this._rawAttack;
}
set rawAttack(value) {
if (WebMidi.validation) {
value = parseFloat(value);
if (isNaN(value) || value === null || !(value >= 0 && value <= 127)) {
throw new RangeError("Invalid rawAttack value.");
}
}
this._rawAttack = value;
}
/**
* The release velocity of the note as a decimal number between 0 and 1.
* @type {number}
*/
get release() {
return this._rawRelease / 127;
}
set release(value) {
if (WebMidi.validation) {
value = parseFloat(value);
if (isNaN(value) || value === null || !(value >= 0 && value <= 1)) {
throw new RangeError("Invalid release value.");
}
}
this._rawRelease = Math.round(value * 127);
}
/**
* The raw release velocity of the note as an integer between 0 and 127.
* @type {number}
*/
get rawRelease() {
return this._rawRelease;
}
set rawRelease(value) {
if (WebMidi.validation) {
value = parseFloat(value);
if (isNaN(value) || value === null || !(value >= 0 && value <= 127)) {
throw new RangeError("Invalid rawRelease value.");
}
}
this._rawRelease = value;
}
/**
* The octave of the note as an integer between -1 and 9.
* @type {number}
*/
get octave() {
return Math.floor(Math.floor(this._number) / 12 - 1);
}
}