|
|
import { getEvent, messageTypes, midiControllers } from "../../midi_parser/midi_message.js"; |
|
|
import { WorkletSequencerReturnMessageType } from "./sequencer_message.js"; |
|
|
import { resetArray } from "../../synthetizer/worklet_system/worklet_utilities/controller_tables.js"; |
|
|
import { |
|
|
nonResetableCCs |
|
|
} from "../../synthetizer/worklet_system/worklet_methods/controller_control/reset_controllers.js"; |
|
|
|
|
|
|
|
|
|
|
|
const defaultControllerArray = resetArray.slice(0, 128); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function _playTo(time, ticks = undefined) |
|
|
{ |
|
|
this.oneTickToSeconds = 60 / (120 * this.midiData.timeDivision); |
|
|
|
|
|
this.synth.resetAllControllers(); |
|
|
this.sendMIDIReset(); |
|
|
this._resetTimers(); |
|
|
|
|
|
const channelsToSave = this.synth.workletProcessorChannels.length; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const pitchBends = Array(channelsToSave).fill(8192); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const programs = []; |
|
|
for (let i = 0; i < channelsToSave; i++) |
|
|
{ |
|
|
programs.push({ |
|
|
program: -1, |
|
|
bank: 0, |
|
|
actualBank: 0 |
|
|
}); |
|
|
} |
|
|
|
|
|
const isCCNonSkippable = controllerNumber => ( |
|
|
controllerNumber === midiControllers.dataDecrement || |
|
|
controllerNumber === midiControllers.dataIncrement || |
|
|
controllerNumber === midiControllers.dataEntryMsb || |
|
|
controllerNumber === midiControllers.dataDecrement || |
|
|
controllerNumber === midiControllers.lsbForControl6DataEntry || |
|
|
controllerNumber === midiControllers.RPNLsb || |
|
|
controllerNumber === midiControllers.RPNMsb || |
|
|
controllerNumber === midiControllers.NRPNLsb || |
|
|
controllerNumber === midiControllers.NRPNMsb || |
|
|
controllerNumber === midiControllers.bankSelect || |
|
|
controllerNumber === midiControllers.lsbForControl0BankSelect || |
|
|
controllerNumber === midiControllers.resetAllControllers |
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const savedControllers = []; |
|
|
for (let i = 0; i < channelsToSave; i++) |
|
|
{ |
|
|
savedControllers.push(Array.from(defaultControllerArray)); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function resetAllControlllers(chan) |
|
|
{ |
|
|
|
|
|
pitchBends[chan] = 8192; |
|
|
if (savedControllers?.[chan] === undefined) |
|
|
{ |
|
|
return; |
|
|
} |
|
|
for (let i = 0; i < defaultControllerArray.length; i++) |
|
|
{ |
|
|
if (!nonResetableCCs.has(i)) |
|
|
{ |
|
|
savedControllers[chan][i] = defaultControllerArray[i]; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
while (true) |
|
|
{ |
|
|
|
|
|
let trackIndex = this._findFirstEventIndex(); |
|
|
let event = this.tracks[trackIndex][this.eventIndex[trackIndex]]; |
|
|
if (ticks !== undefined) |
|
|
{ |
|
|
if (event.ticks >= ticks) |
|
|
{ |
|
|
break; |
|
|
} |
|
|
} |
|
|
else |
|
|
{ |
|
|
if (this.playedTime >= time) |
|
|
{ |
|
|
break; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const info = getEvent(event.messageStatusByte); |
|
|
|
|
|
const channel = info.channel + (this.midiPortChannelOffsets[this.midiPorts[trackIndex]] || 0); |
|
|
switch (info.status) |
|
|
{ |
|
|
|
|
|
case messageTypes.noteOn: |
|
|
|
|
|
if (savedControllers[channel] === undefined) |
|
|
{ |
|
|
savedControllers[channel] = Array.from(defaultControllerArray); |
|
|
} |
|
|
savedControllers[channel][midiControllers.portamentoControl] = event.messageData[0]; |
|
|
break; |
|
|
|
|
|
case messageTypes.noteOff: |
|
|
break; |
|
|
|
|
|
|
|
|
case messageTypes.pitchBend: |
|
|
pitchBends[channel] = event.messageData[1] << 7 | event.messageData[0]; |
|
|
break; |
|
|
|
|
|
case messageTypes.programChange: |
|
|
|
|
|
if (this.midiData.isMultiPort && this.midiData.usedChannelsOnTrack[trackIndex].size === 0) |
|
|
{ |
|
|
break; |
|
|
} |
|
|
const p = programs[channel]; |
|
|
p.program = event.messageData[0]; |
|
|
p.actualBank = p.bank; |
|
|
break; |
|
|
|
|
|
case messageTypes.controllerChange: |
|
|
|
|
|
if (this.midiData.isMultiPort && this.midiData.usedChannelsOnTrack[trackIndex].size === 0) |
|
|
{ |
|
|
break; |
|
|
} |
|
|
|
|
|
const controllerNumber = event.messageData[0]; |
|
|
if (isCCNonSkippable(controllerNumber)) |
|
|
{ |
|
|
let ccV = event.messageData[1]; |
|
|
if (controllerNumber === midiControllers.bankSelect) |
|
|
{ |
|
|
|
|
|
programs[channel].bank = ccV; |
|
|
break; |
|
|
} |
|
|
else if (controllerNumber === midiControllers.resetAllControllers) |
|
|
{ |
|
|
resetAllControlllers(channel); |
|
|
} |
|
|
if (this.sendMIDIMessages) |
|
|
{ |
|
|
this.sendMIDICC(channel, controllerNumber, ccV); |
|
|
} |
|
|
else |
|
|
{ |
|
|
this.synth.controllerChange(channel, controllerNumber, ccV); |
|
|
} |
|
|
} |
|
|
else |
|
|
{ |
|
|
if (savedControllers[channel] === undefined) |
|
|
{ |
|
|
savedControllers[channel] = Array.from(defaultControllerArray); |
|
|
} |
|
|
savedControllers[channel][controllerNumber] = event.messageData[1]; |
|
|
} |
|
|
break; |
|
|
|
|
|
default: |
|
|
this._processEvent(event, trackIndex); |
|
|
break; |
|
|
} |
|
|
|
|
|
this.eventIndex[trackIndex]++; |
|
|
|
|
|
trackIndex = this._findFirstEventIndex(); |
|
|
let nextEvent = this.tracks[trackIndex][this.eventIndex[trackIndex]]; |
|
|
if (nextEvent === undefined) |
|
|
{ |
|
|
this.stop(); |
|
|
return false; |
|
|
} |
|
|
this.playedTime += this.oneTickToSeconds * (nextEvent.ticks - event.ticks); |
|
|
} |
|
|
|
|
|
|
|
|
if (this.sendMIDIMessages) |
|
|
{ |
|
|
for (let channelNumber = 0; channelNumber < channelsToSave; channelNumber++) |
|
|
{ |
|
|
|
|
|
if (pitchBends[channelNumber] !== undefined) |
|
|
{ |
|
|
this.sendMIDIPitchWheel( |
|
|
channelNumber, |
|
|
pitchBends[channelNumber] >> 7, |
|
|
pitchBends[channelNumber] & 0x7F |
|
|
); |
|
|
} |
|
|
if (savedControllers[channelNumber] !== undefined) |
|
|
{ |
|
|
|
|
|
savedControllers[channelNumber].forEach((value, index) => |
|
|
{ |
|
|
if (value !== defaultControllerArray[index] && !isCCNonSkippable( |
|
|
index)) |
|
|
{ |
|
|
this.sendMIDICC(channelNumber, index, value); |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
if (programs[channelNumber].program >= 0 && programs[channelNumber].actualBank >= 0) |
|
|
{ |
|
|
const bank = programs[channelNumber].actualBank; |
|
|
this.sendMIDICC(channelNumber, midiControllers.bankSelect, bank); |
|
|
this.sendMIDIProgramChange(channelNumber, programs[channelNumber].program); |
|
|
} |
|
|
} |
|
|
} |
|
|
else |
|
|
{ |
|
|
|
|
|
for (let channelNumber = 0; channelNumber < channelsToSave; channelNumber++) |
|
|
{ |
|
|
|
|
|
if (pitchBends[channelNumber] !== undefined) |
|
|
{ |
|
|
this.synth.pitchWheel(channelNumber, pitchBends[channelNumber] >> 7, pitchBends[channelNumber] & 0x7F); |
|
|
} |
|
|
if (savedControllers[channelNumber] !== undefined) |
|
|
{ |
|
|
|
|
|
savedControllers[channelNumber].forEach((value, index) => |
|
|
{ |
|
|
if (value !== defaultControllerArray[index] && !isCCNonSkippable( |
|
|
index)) |
|
|
{ |
|
|
this.synth.controllerChange( |
|
|
channelNumber, |
|
|
index, |
|
|
value |
|
|
); |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
if (programs[channelNumber].program >= 0 && programs[channelNumber].actualBank >= 0) |
|
|
{ |
|
|
const bank = programs[channelNumber].actualBank; |
|
|
this.synth.controllerChange(channelNumber, midiControllers.bankSelect, bank); |
|
|
this.synth.programChange(channelNumber, programs[channelNumber].program); |
|
|
} |
|
|
} |
|
|
} |
|
|
return true; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function play(resetTime = false) |
|
|
{ |
|
|
if (this.midiData === undefined) |
|
|
{ |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
if (resetTime) |
|
|
{ |
|
|
this.pausedTime = undefined; |
|
|
this.currentTime = 0; |
|
|
return; |
|
|
} |
|
|
|
|
|
if (this.currentTime >= this.duration) |
|
|
{ |
|
|
this.pausedTime = undefined; |
|
|
this.currentTime = 0; |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
if (this.paused) |
|
|
{ |
|
|
|
|
|
this._recalculateStartTime(this.pausedTime); |
|
|
this.pausedTime = undefined; |
|
|
} |
|
|
if (!this.sendMIDIMessages) |
|
|
{ |
|
|
this.playingNotes.forEach(n => |
|
|
{ |
|
|
this.synth.noteOn(n.channel, n.midiNote, n.velocity); |
|
|
}); |
|
|
} |
|
|
this.setProcessHandler(); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function setTimeTicks(ticks) |
|
|
{ |
|
|
this.stop(); |
|
|
this.playingNotes = []; |
|
|
this.pausedTime = undefined; |
|
|
this.post( |
|
|
WorkletSequencerReturnMessageType.timeChange, |
|
|
currentTime - this.midiData.MIDIticksToSeconds(ticks) |
|
|
); |
|
|
const isNotFinished = this._playTo(0, ticks); |
|
|
this._recalculateStartTime(this.playedTime); |
|
|
if (!isNotFinished) |
|
|
{ |
|
|
return; |
|
|
} |
|
|
this.play(); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function _recalculateStartTime(time) |
|
|
{ |
|
|
this.absoluteStartTime = currentTime - time / this._playbackRate; |
|
|
} |