KEXEL's picture
1.1
b0bfea8 verified
import { WorkletSequencerReturnMessageType } from "./sequencer_message.js";
import { consoleColors, formatTime } from "../../utils/other.js";
import {
SpessaSynthGroupCollapsed,
SpessaSynthGroupEnd,
SpessaSynthInfo,
SpessaSynthWarn
} from "../../utils/loggin.js";
import { MIDIData } from "../../midi_parser/midi_data.js";
import { MIDI } from "../../midi_parser/midi_loader.js";
import { BasicMIDI } from "../../midi_parser/basic_midi.js";
/**
* @param trackNum {number}
* @param port {number}
* @this {WorkletSequencer}
*/
export function assignMIDIPort(trackNum, port)
{
// do not assign ports to empty tracks
if (this.midiData.usedChannelsOnTrack[trackNum].size === 0)
{
return;
}
// assign new 16 channels if the port is not occupied yet
if (this.midiPortChannelOffset === 0)
{
this.midiPortChannelOffset += 16;
this.midiPortChannelOffsets[port] = 0;
}
if (this.midiPortChannelOffsets[port] === undefined)
{
if (this.synth.workletProcessorChannels.length < this.midiPortChannelOffset + 15)
{
this._addNewMidiPort();
}
this.midiPortChannelOffsets[port] = this.midiPortChannelOffset;
this.midiPortChannelOffset += 16;
}
this.midiPorts[trackNum] = port;
}
/**
* Loads a new sequence
* @param parsedMidi {BasicMIDI}
* @param autoPlay {boolean}
* @this {WorkletSequencer}
*/
export function loadNewSequence(parsedMidi, autoPlay = true)
{
this.stop();
if (!parsedMidi.tracks)
{
throw new Error("This MIDI has no tracks!");
}
this.oneTickToSeconds = 60 / (120 * parsedMidi.timeDivision);
/**
* @type {BasicMIDI}
*/
this.midiData = parsedMidi;
// check for embedded soundfont
if (this.midiData.embeddedSoundFont !== undefined)
{
SpessaSynthInfo("%cEmbedded soundfont detected! Using it.", consoleColors.recognized);
this.synth.setEmbeddedSoundFont(this.midiData.embeddedSoundFont, this.midiData.bankOffset);
}
else
{
if (this.synth.overrideSoundfont)
{
// clean up the embedded soundfont
this.synth.clearSoundFont(true, true);
}
SpessaSynthGroupCollapsed("%cPreloading samples...", consoleColors.info);
// smart preloading: load only samples used in the midi!
const used = this.midiData.getUsedProgramsAndKeys(this.synth.soundfontManager);
for (const [programBank, combos] of Object.entries(used))
{
const bank = parseInt(programBank.split(":")[0]);
const program = parseInt(programBank.split(":")[1]);
const preset = this.synth.getPreset(bank, program);
SpessaSynthInfo(
`%cPreloading used samples on %c${preset.presetName}%c...`,
consoleColors.info,
consoleColors.recognized,
consoleColors.info
);
for (const combo of combos)
{
const split = combo.split("-");
preset.preloadSpecific(parseInt(split[0]), parseInt(split[1]));
}
}
SpessaSynthGroupEnd();
}
/**
* the midi track data
* @type {MIDIMessage[][]}
*/
this.tracks = this.midiData.tracks;
// copy over the port data
this.midiPorts = this.midiData.midiPorts.slice();
// clear last port data
this.midiPortChannelOffset = 0;
this.midiPortChannelOffsets = {};
// assign port offsets
this.midiData.midiPorts.forEach((port, trackIndex) =>
{
this.assignMIDIPort(trackIndex, port);
});
/**
* Same as "audio.duration" property (seconds)
* @type {number}
*/
this.duration = this.midiData.duration;
this.firstNoteTime = this.midiData.MIDIticksToSeconds(this.midiData.firstNoteOn);
SpessaSynthInfo(`%cTotal song time: ${formatTime(Math.ceil(this.duration)).time}`, consoleColors.recognized);
this.post(WorkletSequencerReturnMessageType.songChange, [this.songIndex, autoPlay]);
if (this.duration <= 1)
{
SpessaSynthWarn(
`%cVery short song: (${formatTime(Math.round(this.duration)).time}). Disabling loop!`,
consoleColors.warn
);
this.loop = false;
}
if (autoPlay)
{
this.play(true);
}
else
{
// this shall not play: play to the first note and then wait
const targetTime = this.skipToFirstNoteOn ? this.midiData.firstNoteOn - 1 : 0;
this.setTimeTicks(targetTime);
this.pause();
}
}
/**
* @param midiBuffers {MIDIFile[]}
* @param autoPlay {boolean}
* @this {WorkletSequencer}
*/
export function loadNewSongList(midiBuffers, autoPlay = true)
{
/**
* parse the MIDIs (only the array buffers, MIDI is unchanged)
* @type {BasicMIDI[]}
*/
this.songs = midiBuffers.reduce((mids, b) =>
{
if (b.duration)
{
mids.push(BasicMIDI.copyFrom(b));
return mids;
}
try
{
mids.push(new MIDI(b.binary, b.altName || ""));
}
catch (e)
{
console.error(e);
this.post(WorkletSequencerReturnMessageType.midiError, e);
return mids;
}
return mids;
}, []);
if (this.songs.length < 1)
{
return;
}
this.songIndex = 0;
if (this.songs.length > 1)
{
this.loop = false;
}
this.shuffleSongIndexes();
const midiDatas = this.songs.map(s => new MIDIData(s));
this.post(WorkletSequencerReturnMessageType.songListChange, midiDatas);
this.loadCurrentSong(autoPlay);
}
/**
* @this {WorkletSequencer}
*/
export function nextSong()
{
if (this.songs.length === 1)
{
this.currentTime = 0;
return;
}
this.songIndex++;
this.songIndex %= this.songs.length;
this.loadCurrentSong();
}
/**
* @this {WorkletSequencer}
*/
export function previousSong()
{
if (this.songs.length === 1)
{
this.currentTime = 0;
return;
}
this.songIndex--;
if (this.songIndex < 0)
{
this.songIndex = this.songs.length - 1;
}
this.loadCurrentSong();
}