|
|
import { BasicMIDI } from "./basic_midi.js"; |
|
|
import { messageTypes, MIDIMessage } from "./midi_message.js"; |
|
|
import { IndexedByteArray } from "../utils/indexed_array.js"; |
|
|
import { SpessaSynthWarn } from "../utils/loggin.js"; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export class MIDIBuilder extends BasicMIDI |
|
|
{ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
constructor(name, timeDivision = 480, initialTempo = 120) |
|
|
{ |
|
|
super(); |
|
|
this.timeDivision = timeDivision; |
|
|
this.midiName = name; |
|
|
this.encoder = new TextEncoder(); |
|
|
this.rawMidiName = this.encoder.encode(name); |
|
|
|
|
|
|
|
|
this.addNewTrack(name); |
|
|
this.addSetTempo(0, initialTempo); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
addSetTempo(ticks, tempo) |
|
|
{ |
|
|
const array = new IndexedByteArray(3); |
|
|
|
|
|
tempo = 60000000 / tempo; |
|
|
|
|
|
|
|
|
array[0] = (tempo >> 16) & 0xFF; |
|
|
array[1] = (tempo >> 8) & 0xFF; |
|
|
array[2] = tempo & 0xFF; |
|
|
|
|
|
this.addEvent(ticks, 0, messageTypes.setTempo, array); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
addNewTrack(name, port = 0) |
|
|
{ |
|
|
this.tracksAmount++; |
|
|
if (this.tracksAmount > 1) |
|
|
{ |
|
|
this.format = 1; |
|
|
} |
|
|
this.tracks.push([]); |
|
|
this.tracks[this.tracksAmount - 1].push( |
|
|
new MIDIMessage(0, messageTypes.endOfTrack, new IndexedByteArray(0)) |
|
|
); |
|
|
this.addEvent(0, this.tracksAmount - 1, messageTypes.trackName, this.encoder.encode(name)); |
|
|
this.addEvent(0, this.tracksAmount - 1, messageTypes.midiPort, [port]); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
addEvent(ticks, track, event, eventData) |
|
|
{ |
|
|
if (!this.tracks[track]) |
|
|
{ |
|
|
throw new Error(`Track ${track} does not exist. Add it via addTrack method.`); |
|
|
} |
|
|
if (event === messageTypes.endOfTrack) |
|
|
{ |
|
|
SpessaSynthWarn( |
|
|
"The EndOfTrack is added automatically and does not influence the duration. Consider adding a voice event instead."); |
|
|
return; |
|
|
} |
|
|
|
|
|
this.tracks[track].pop(); |
|
|
this.tracks[track].push(new MIDIMessage( |
|
|
ticks, |
|
|
event, |
|
|
new IndexedByteArray(eventData) |
|
|
)); |
|
|
|
|
|
this.tracks[track].push(new MIDIMessage( |
|
|
ticks, |
|
|
messageTypes.endOfTrack, |
|
|
new IndexedByteArray(0) |
|
|
)); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
addNoteOn(ticks, track, channel, midiNote, velocity) |
|
|
{ |
|
|
channel %= 16; |
|
|
midiNote %= 128; |
|
|
velocity %= 128; |
|
|
this.addEvent( |
|
|
ticks, |
|
|
track, |
|
|
messageTypes.noteOn | channel, |
|
|
[midiNote, velocity] |
|
|
); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
addNoteOff(ticks, track, channel, midiNote) |
|
|
{ |
|
|
channel %= 16; |
|
|
midiNote %= 128; |
|
|
this.addEvent( |
|
|
ticks, |
|
|
track, |
|
|
messageTypes.noteOff | channel, |
|
|
[midiNote, 64] |
|
|
); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
addProgramChange(ticks, track, channel, programNumber) |
|
|
{ |
|
|
channel %= 16; |
|
|
programNumber %= 128; |
|
|
this.addEvent( |
|
|
ticks, |
|
|
track, |
|
|
messageTypes.programChange | channel, |
|
|
[programNumber] |
|
|
); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
addControllerChange(ticks, track, channel, controllerNumber, controllerValue) |
|
|
{ |
|
|
channel %= 16; |
|
|
controllerNumber %= 128; |
|
|
controllerValue %= 128; |
|
|
this.addEvent( |
|
|
ticks, |
|
|
track, |
|
|
messageTypes.controllerChange | channel, |
|
|
[controllerNumber, controllerValue] |
|
|
); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
addPitchWheel(ticks, track, channel, MSB, LSB) |
|
|
{ |
|
|
channel %= 16; |
|
|
MSB %= 128; |
|
|
LSB %= 128; |
|
|
this.addEvent( |
|
|
ticks, |
|
|
track, |
|
|
messageTypes.pitchBend | channel, |
|
|
[LSB, MSB] |
|
|
); |
|
|
} |
|
|
} |