File size: 6,289 Bytes
b0bfea8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
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();
}