model / spessasynth_lib /midi_parser /midi_sequence.js
KEXEL's picture
1.1
b0bfea8 verified
/**
* This is the base type for MIDI files. It contains all the "metadata" and information.
* It extends to:
* - BasicMIDI, which contains the actual track data of the MIDI file. Essentially the MIDI file itself.
* - MIDIData, which contains all properties that MIDI does, except for tracks and the embedded soundfont.
* MIDIData is the "shell" of the file which is available on the main thread at all times, containing the metadata.
*/
class MIDISequenceData
{
/**
* The time division of the sequence, representing the number of ticks per beat.
* @type {number}
*/
timeDivision = 0;
/**
* The duration of the sequence, in seconds.
* @type {number}
*/
duration = 0;
/**
* The tempo changes in the sequence, ordered from the last change to the first.
* Each change is represented by an object with a tick position and a tempo value in beats per minute.
* @type {{ticks: number, tempo: number}[]}
*/
tempoChanges = [{ ticks: 0, tempo: 120 }];
/**
* A string containing the copyright information for the MIDI sequence if detected.
* @type {string}
*/
copyright = "";
/**
* The number of tracks in the MIDI sequence.
* @type {number}
*/
tracksAmount = 0;
/**
* The track names in the MIDI file, an empty string if not set.
* @type {string[]}
*/
trackNames = [];
/**
* An array containing the lyrics of the sequence, stored as binary chunks (Uint8Array).
* @type {Uint8Array[]}
*/
lyrics = [];
/**
* An array of tick positions where lyrics events occur in the sequence.
* @type {number[]}
*/
lyricsTicks = [];
/**
* The tick position of the first note-on event in the MIDI sequence.
* @type {number}
*/
firstNoteOn = 0;
/**
* The MIDI key range used in the sequence, represented by a minimum and maximum note value.
* @type {{min: number, max: number}}
*/
keyRange = { min: 0, max: 127 };
/**
* The tick position of the last voice event (such as note-on, note-off, or control change) in the sequence.
* @type {number}
*/
lastVoiceEventTick = 0;
/**
* An array of MIDI port numbers used by each track in the sequence.
* @type {number[]}
*/
midiPorts = [0];
/**
* An array of channel offsets for each MIDI port, using the SpessaSynth method.
* @type {number[]}
*/
midiPortChannelOffsets = [0];
/**
* A list of sets, where each set contains the MIDI channels used by each track in the sequence.
* @type {Set<number>[]}
*/
usedChannelsOnTrack = [];
/**
* The loop points (in ticks) of the sequence, including both start and end points.
* @type {{start: number, end: number}}
*/
loop = { start: 0, end: 0 };
/**
* The name of the MIDI sequence.
* @type {string}
*/
midiName = "";
/**
* A boolean indicating if the sequence's name is the same as the file name.
* @type {boolean}
*/
midiNameUsesFileName = false;
/**
* The file name of the MIDI sequence, if provided during parsing.
* @type {string}
*/
fileName = "";
/**
* The raw, encoded MIDI name, represented as a Uint8Array.
* Useful when the MIDI file uses a different code page.
* @type {Uint8Array}
*/
rawMidiName;
/**
* The format of the MIDI file, which can be 0, 1, or 2, indicating the type of the MIDI file.
* @type {number}
*/
format = 0;
/**
* The RMID (Resource-Interchangeable MIDI) info data, if the file is RMID formatted.
* Otherwise, this field is undefined.
* Chunk type (e.g. "INAM"): Chunk data as a binary array.
* @type {Object<string, IndexedByteArray>}
*/
RMIDInfo = {};
/**
* The bank offset used for RMID files.
* @type {number}
*/
bankOffset = 0;
/**
* If the MIDI file is a Soft Karaoke file (.kar), this flag is set to true.
* https://www.mixagesoftware.com/en/midikit/help/HTML/karaoke_formats.html
* @type {boolean}
*/
isKaraokeFile = false;
/**
* Indicates if this file is a Multi-Port MIDI file.
* @type {boolean}
*/
isMultiPort = false;
/**
* Converts ticks to time in seconds
* @param ticks {number} time in MIDI ticks
* @returns {number} time in seconds
*/
MIDIticksToSeconds(ticks)
{
let totalSeconds = 0;
while (ticks > 0)
{
// tempo changes are reversed, so the first element is the last tempo change
// and the last element is the first tempo change
// (always at tick 0 and tempo 120)
// find the last tempo change that has occurred
let tempo = this.tempoChanges.find(v => v.ticks < ticks);
// calculate the difference and tempo time
let timeSinceLastTempo = ticks - tempo.ticks;
totalSeconds += (timeSinceLastTempo * 60) / (tempo.tempo * this.timeDivision);
ticks -= timeSinceLastTempo;
}
return totalSeconds;
}
/**
* INTERNAL USE ONLY!
* DO NOT USE IN SPESSASYNTH_LIB
* @param sequence {MIDISequenceData}
* @protected
*/
_copyFromSequence(sequence)
{
// properties can be assigned
this.midiName = sequence.midiName;
this.midiNameUsesFileName = sequence.midiNameUsesFileName;
this.fileName = sequence.fileName;
this.timeDivision = sequence.timeDivision;
this.duration = sequence.duration;
this.copyright = sequence.copyright;
this.tracksAmount = sequence.tracksAmount;
this.firstNoteOn = sequence.firstNoteOn;
this.lastVoiceEventTick = sequence.lastVoiceEventTick;
this.format = sequence.format;
this.bankOffset = sequence.bankOffset;
this.isKaraokeFile = sequence.isKaraokeFile;
this.isMultiPort = sequence.isMultiPort;
// copying arrays
this.tempoChanges = [...sequence.tempoChanges];
this.lyrics = sequence.lyrics.map(arr => new Uint8Array(arr));
this.lyricsTicks = [...sequence.lyricsTicks];
this.midiPorts = [...sequence.midiPorts];
this.trackNames = [...sequence.trackNames];
this.midiPortChannelOffsets = [...sequence.midiPortChannelOffsets];
this.usedChannelsOnTrack = sequence.usedChannelsOnTrack.map(set => new Set(set));
this.rawMidiName = sequence.rawMidiName ? new Uint8Array(sequence.rawMidiName) : undefined;
// copying objects
this.loop = { ...sequence.loop };
this.keyRange = { ...sequence.keyRange };
this.RMIDInfo = { ...sequence.RMIDInfo };
}
}
export { MIDISequenceData };