spessasynth_lib 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.idea/modules.xml +8 -0
- package/.idea/spessasynth_lib.iml +12 -0
- package/.idea/vcs.xml +6 -0
- package/copy_version.sh +38 -0
- package/index.js +73 -0
- package/package/@types/externals/stbvorbis_sync/stbvorbis_sync.min.d.ts +1 -0
- package/package/@types/index.d.ts +34 -0
- package/package/@types/midi_handler/midi_handler.d.ts +39 -0
- package/package/@types/midi_handler/web_midi_link.d.ts +12 -0
- package/package/@types/midi_parser/midi_data.d.ts +95 -0
- package/package/@types/midi_parser/midi_editor.d.ts +45 -0
- package/package/@types/midi_parser/midi_loader.d.ts +100 -0
- package/package/@types/midi_parser/midi_message.d.ts +154 -0
- package/package/@types/midi_parser/midi_writer.d.ts +6 -0
- package/package/@types/midi_parser/rmidi_writer.d.ts +9 -0
- package/package/@types/midi_parser/used_keys_loaded.d.ts +7 -0
- package/package/@types/sequencer/sequencer.d.ts +180 -0
- package/package/@types/sequencer/worklet_sequencer/sequencer_message.d.ts +28 -0
- package/package/@types/soundfont/read/generators.d.ts +98 -0
- package/package/@types/soundfont/read/instruments.d.ts +50 -0
- package/package/@types/soundfont/read/modulators.d.ts +73 -0
- package/package/@types/soundfont/read/presets.d.ts +87 -0
- package/package/@types/soundfont/read/riff_chunk.d.ts +31 -0
- package/package/@types/soundfont/read/samples.d.ts +134 -0
- package/package/@types/soundfont/read/zones.d.ts +141 -0
- package/package/@types/soundfont/soundfont.d.ts +76 -0
- package/package/@types/soundfont/write/ibag.d.ts +6 -0
- package/package/@types/soundfont/write/igen.d.ts +6 -0
- package/package/@types/soundfont/write/imod.d.ts +6 -0
- package/package/@types/soundfont/write/inst.d.ts +6 -0
- package/package/@types/soundfont/write/pbag.d.ts +6 -0
- package/package/@types/soundfont/write/pgen.d.ts +6 -0
- package/package/@types/soundfont/write/phdr.d.ts +6 -0
- package/package/@types/soundfont/write/pmod.d.ts +6 -0
- package/package/@types/soundfont/write/sdta.d.ts +11 -0
- package/package/@types/soundfont/write/shdr.d.ts +8 -0
- package/package/@types/soundfont/write/soundfont_trimmer.d.ts +6 -0
- package/package/@types/soundfont/write/write.d.ts +21 -0
- package/package/@types/synthetizer/audio_effects/effects_config.d.ts +29 -0
- package/package/@types/synthetizer/audio_effects/fancy_chorus.d.ts +93 -0
- package/package/@types/synthetizer/audio_effects/reverb.d.ts +7 -0
- package/package/@types/synthetizer/synth_event_handler.d.ts +161 -0
- package/package/@types/synthetizer/synthetizer.d.ts +294 -0
- package/package/@types/synthetizer/worklet_system/message_protocol/worklet_message.d.ts +89 -0
- package/package/@types/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.d.ts +134 -0
- package/package/@types/synthetizer/worklet_url.d.ts +5 -0
- package/package/@types/utils/buffer_to_wav.d.ts +8 -0
- package/package/@types/utils/byte_functions/big_endian.d.ts +13 -0
- package/package/@types/utils/byte_functions/little_endian.d.ts +35 -0
- package/package/@types/utils/byte_functions/string.d.ts +22 -0
- package/package/@types/utils/byte_functions/variable_length_quantity.d.ts +12 -0
- package/package/@types/utils/indexed_array.d.ts +21 -0
- package/package/@types/utils/loggin.d.ts +26 -0
- package/package/@types/utils/other.d.ts +32 -0
- package/package/LICENSE +26 -0
- package/package/README.md +84 -0
- package/package/externals/NOTICE +9 -0
- package/package/externals/libvorbis/@types/OggVorbisEncoder.d.ts +34 -0
- package/package/externals/libvorbis/OggVorbisEncoder.min.js +1 -0
- package/package/externals/stbvorbis_sync/@types/stbvorbis_sync.d.ts +12 -0
- package/package/externals/stbvorbis_sync/LICENSE +202 -0
- package/package/externals/stbvorbis_sync/stbvorbis_sync.min.js +1 -0
- package/package/index.js +73 -0
- package/package/midi_handler/README.md +3 -0
- package/package/midi_handler/midi_handler.js +118 -0
- package/package/midi_handler/web_midi_link.js +41 -0
- package/package/midi_parser/README.md +3 -0
- package/package/midi_parser/midi_data.js +121 -0
- package/package/midi_parser/midi_editor.js +557 -0
- package/package/midi_parser/midi_loader.js +502 -0
- package/package/midi_parser/midi_message.js +234 -0
- package/package/midi_parser/midi_writer.js +95 -0
- package/package/midi_parser/rmidi_writer.js +271 -0
- package/package/midi_parser/used_keys_loaded.js +172 -0
- package/package/package.json +43 -0
- package/package/sequencer/README.md +23 -0
- package/package/sequencer/sequencer.js +439 -0
- package/package/sequencer/worklet_sequencer/events.js +92 -0
- package/package/sequencer/worklet_sequencer/play.js +309 -0
- package/package/sequencer/worklet_sequencer/process_event.js +167 -0
- package/package/sequencer/worklet_sequencer/process_tick.js +85 -0
- package/package/sequencer/worklet_sequencer/sequencer_message.js +39 -0
- package/package/sequencer/worklet_sequencer/song_control.js +193 -0
- package/package/sequencer/worklet_sequencer/worklet_sequencer.js +218 -0
- package/package/soundfont/README.md +8 -0
- package/package/soundfont/read/generators.js +212 -0
- package/package/soundfont/read/instruments.js +125 -0
- package/package/soundfont/read/modulators.js +249 -0
- package/package/soundfont/read/presets.js +300 -0
- package/package/soundfont/read/riff_chunk.js +81 -0
- package/package/soundfont/read/samples.js +398 -0
- package/package/soundfont/read/zones.js +310 -0
- package/package/soundfont/soundfont.js +357 -0
- package/package/soundfont/write/ibag.js +39 -0
- package/package/soundfont/write/igen.js +75 -0
- package/package/soundfont/write/imod.js +46 -0
- package/package/soundfont/write/inst.js +34 -0
- package/package/soundfont/write/pbag.js +39 -0
- package/package/soundfont/write/pgen.js +77 -0
- package/package/soundfont/write/phdr.js +42 -0
- package/package/soundfont/write/pmod.js +46 -0
- package/package/soundfont/write/sdta.js +72 -0
- package/package/soundfont/write/shdr.js +54 -0
- package/package/soundfont/write/soundfont_trimmer.js +169 -0
- package/package/soundfont/write/write.js +180 -0
- package/package/synthetizer/README.md +6 -0
- package/package/synthetizer/audio_effects/effects_config.js +21 -0
- package/package/synthetizer/audio_effects/fancy_chorus.js +120 -0
- package/package/synthetizer/audio_effects/impulse_response_2.flac +0 -0
- package/package/synthetizer/audio_effects/reverb.js +24 -0
- package/package/synthetizer/synth_event_handler.js +156 -0
- package/package/synthetizer/synthetizer.js +766 -0
- package/package/synthetizer/worklet_processor.min.js +13 -0
- package/package/synthetizer/worklet_system/README.md +6 -0
- package/package/synthetizer/worklet_system/main_processor.js +363 -0
- package/package/synthetizer/worklet_system/message_protocol/handle_message.js +197 -0
- package/package/synthetizer/worklet_system/message_protocol/message_sending.js +74 -0
- package/package/synthetizer/worklet_system/message_protocol/worklet_message.js +121 -0
- package/package/synthetizer/worklet_system/minify_processor.sh +4 -0
- package/package/synthetizer/worklet_system/worklet_methods/controller_control.js +230 -0
- package/package/synthetizer/worklet_system/worklet_methods/data_entry.js +277 -0
- package/package/synthetizer/worklet_system/worklet_methods/note_off.js +109 -0
- package/package/synthetizer/worklet_system/worklet_methods/note_on.js +91 -0
- package/package/synthetizer/worklet_system/worklet_methods/program_control.js +183 -0
- package/package/synthetizer/worklet_system/worklet_methods/reset_controllers.js +177 -0
- package/package/synthetizer/worklet_system/worklet_methods/snapshot.js +129 -0
- package/package/synthetizer/worklet_system/worklet_methods/system_exclusive.js +272 -0
- package/package/synthetizer/worklet_system/worklet_methods/tuning_control.js +195 -0
- package/package/synthetizer/worklet_system/worklet_methods/vibrato_control.js +29 -0
- package/package/synthetizer/worklet_system/worklet_methods/voice_control.js +233 -0
- package/package/synthetizer/worklet_system/worklet_processor.js +9 -0
- package/package/synthetizer/worklet_system/worklet_utilities/lfo.js +23 -0
- package/package/synthetizer/worklet_system/worklet_utilities/lowpass_filter.js +130 -0
- package/package/synthetizer/worklet_system/worklet_utilities/modulation_envelope.js +73 -0
- package/package/synthetizer/worklet_system/worklet_utilities/modulator_curves.js +86 -0
- package/package/synthetizer/worklet_system/worklet_utilities/stereo_panner.js +81 -0
- package/package/synthetizer/worklet_system/worklet_utilities/unit_converter.js +66 -0
- package/package/synthetizer/worklet_system/worklet_utilities/volume_envelope.js +265 -0
- package/package/synthetizer/worklet_system/worklet_utilities/wavetable_oscillator.js +83 -0
- package/package/synthetizer/worklet_system/worklet_utilities/worklet_modulator.js +234 -0
- package/package/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +116 -0
- package/package/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +272 -0
- package/package/synthetizer/worklet_url.js +5 -0
- package/package/utils/README.md +4 -0
- package/package/utils/buffer_to_wav.js +101 -0
- package/package/utils/byte_functions/big_endian.js +28 -0
- package/package/utils/byte_functions/little_endian.js +74 -0
- package/package/utils/byte_functions/string.js +97 -0
- package/package/utils/byte_functions/variable_length_quantity.js +37 -0
- package/package/utils/encode_vorbis.js +30 -0
- package/package/utils/indexed_array.js +41 -0
- package/package/utils/loggin.js +79 -0
- package/package/utils/other.js +54 -0
- package/package.json +43 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { returnMessageType } from '../../synthetizer/worklet_system/message_protocol/worklet_message.js'
|
|
2
|
+
import { WorkletSequencerMessageType, WorkletSequencerReturnMessageType } from './sequencer_message.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @param messageType {WorkletSequencerMessageType}
|
|
6
|
+
* @param messageData {any}
|
|
7
|
+
* @this {WorkletSequencer}
|
|
8
|
+
*/
|
|
9
|
+
export function processMessage(messageType, messageData)
|
|
10
|
+
{
|
|
11
|
+
switch (messageType)
|
|
12
|
+
{
|
|
13
|
+
default:
|
|
14
|
+
break;
|
|
15
|
+
|
|
16
|
+
case WorkletSequencerMessageType.loadNewSongList:
|
|
17
|
+
this.loadNewSongList(messageData);
|
|
18
|
+
break;
|
|
19
|
+
|
|
20
|
+
case WorkletSequencerMessageType.pause:
|
|
21
|
+
this.pause();
|
|
22
|
+
break;
|
|
23
|
+
|
|
24
|
+
case WorkletSequencerMessageType.play:
|
|
25
|
+
this.play(messageData);
|
|
26
|
+
break;
|
|
27
|
+
|
|
28
|
+
case WorkletSequencerMessageType.stop:
|
|
29
|
+
this.stop();
|
|
30
|
+
break;
|
|
31
|
+
|
|
32
|
+
case WorkletSequencerMessageType.setTime:
|
|
33
|
+
this.currentTime = messageData;
|
|
34
|
+
break;
|
|
35
|
+
|
|
36
|
+
case WorkletSequencerMessageType.changeMIDIMessageSending:
|
|
37
|
+
this.sendMIDIMessages = messageData;
|
|
38
|
+
break;
|
|
39
|
+
|
|
40
|
+
case WorkletSequencerMessageType.setPlaybackRate:
|
|
41
|
+
this.playbackRate = messageData;
|
|
42
|
+
break;
|
|
43
|
+
|
|
44
|
+
case WorkletSequencerMessageType.setLoop:
|
|
45
|
+
this.loop = messageData;
|
|
46
|
+
break;
|
|
47
|
+
|
|
48
|
+
case WorkletSequencerMessageType.changeSong:
|
|
49
|
+
if (messageData)
|
|
50
|
+
{
|
|
51
|
+
this.nextSong();
|
|
52
|
+
}
|
|
53
|
+
else
|
|
54
|
+
{
|
|
55
|
+
this.previousSong();
|
|
56
|
+
}
|
|
57
|
+
break;
|
|
58
|
+
|
|
59
|
+
case WorkletSequencerMessageType.getMIDI:
|
|
60
|
+
this.post(WorkletSequencerReturnMessageType.getMIDI, this.midiData);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
*
|
|
66
|
+
* @param messageType {WorkletSequencerReturnMessageType}
|
|
67
|
+
* @param messageData {any}
|
|
68
|
+
* @this {WorkletSequencer}
|
|
69
|
+
*/
|
|
70
|
+
export function post(messageType, messageData = undefined)
|
|
71
|
+
{
|
|
72
|
+
if(!this.synth.enableEventSystem)
|
|
73
|
+
{
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
this.synth.post({
|
|
77
|
+
messageType: returnMessageType.sequencerSpecific,
|
|
78
|
+
messageData: {
|
|
79
|
+
messageType: messageType,
|
|
80
|
+
messageData: messageData
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @param message {number[]}
|
|
87
|
+
* @this {WorkletSequencer}
|
|
88
|
+
*/
|
|
89
|
+
export function sendMIDIMessage(message)
|
|
90
|
+
{
|
|
91
|
+
this.post(WorkletSequencerReturnMessageType.midiEvent, message);
|
|
92
|
+
}
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { getEvent, messageTypes, midiControllers } from '../../midi_parser/midi_message.js'
|
|
2
|
+
import { WorkletSequencerReturnMessageType } from './sequencer_message.js'
|
|
3
|
+
import { MIDI_CHANNEL_COUNT } from '../../synthetizer/synthetizer.js'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
// an array with preset default values
|
|
7
|
+
const defaultControllerArray = new Int16Array(127);
|
|
8
|
+
// default values
|
|
9
|
+
defaultControllerArray[midiControllers.mainVolume] = 100;
|
|
10
|
+
defaultControllerArray[midiControllers.expressionController] = 127;
|
|
11
|
+
defaultControllerArray[midiControllers.pan] = 64;
|
|
12
|
+
defaultControllerArray[midiControllers.releaseTime] = 64;
|
|
13
|
+
defaultControllerArray[midiControllers.brightness] = 64;
|
|
14
|
+
defaultControllerArray[midiControllers.effects1Depth] = 40;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* plays from start to the target time, excluding note messages (to get the synth to the correct state)
|
|
18
|
+
* @private
|
|
19
|
+
* @param time {number} in seconds
|
|
20
|
+
* @param ticks {number} optional MIDI ticks, when given is used instead of time
|
|
21
|
+
* @returns {boolean} true if the midi file is not finished
|
|
22
|
+
* @this {WorkletSequencer}
|
|
23
|
+
*/
|
|
24
|
+
export function _playTo(time, ticks = undefined)
|
|
25
|
+
{
|
|
26
|
+
this.oneTickToSeconds = 60 / (120 * this.midiData.timeDivision);
|
|
27
|
+
// reset
|
|
28
|
+
this.synth.resetAllControllers();
|
|
29
|
+
if(this.sendMIDIMessages)
|
|
30
|
+
{
|
|
31
|
+
this.sendMIDIMessage([messageTypes.reset]);
|
|
32
|
+
for(let ch = 0; ch < MIDI_CHANNEL_COUNT; ch++)
|
|
33
|
+
{
|
|
34
|
+
this.sendMIDIMessage([messageTypes.controllerChange | ch, midiControllers.resetAllControllers, 0]);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
this._resetTimers()
|
|
38
|
+
|
|
39
|
+
const channelsToSave = this.synth.workletProcessorChannels.length;
|
|
40
|
+
/**
|
|
41
|
+
* save pitch bends here and send them only after
|
|
42
|
+
* @type {number[]}
|
|
43
|
+
*/
|
|
44
|
+
const pitchBends = Array(channelsToSave).fill(8192);
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Save programs here and send them only after
|
|
48
|
+
* @type {number[]}
|
|
49
|
+
*/
|
|
50
|
+
const programs = Array(channelsToSave).fill(-1 );
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Save controllers here and send them only after
|
|
54
|
+
* @type {number[][]}
|
|
55
|
+
*/
|
|
56
|
+
const savedControllers = [];
|
|
57
|
+
for (let i = 0; i < channelsToSave; i++)
|
|
58
|
+
{
|
|
59
|
+
savedControllers.push(Array.from(defaultControllerArray));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
while(true)
|
|
63
|
+
{
|
|
64
|
+
// find next event
|
|
65
|
+
let trackIndex = this._findFirstEventIndex();
|
|
66
|
+
let event = this.tracks[trackIndex][this.eventIndex[trackIndex]];
|
|
67
|
+
if(ticks !== undefined)
|
|
68
|
+
{
|
|
69
|
+
if(event.ticks >= ticks)
|
|
70
|
+
{
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
else
|
|
75
|
+
{
|
|
76
|
+
if(this.playedTime >= time)
|
|
77
|
+
{
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// skip note ons
|
|
83
|
+
const info = getEvent(event.messageStatusByte);
|
|
84
|
+
// Keep in mind midi ports to determine channel!!
|
|
85
|
+
const channel = info.channel + (this.midiPortChannelOffsets[this.midiPorts[trackIndex]] || 0);
|
|
86
|
+
switch(info.status)
|
|
87
|
+
{
|
|
88
|
+
// skip note messages
|
|
89
|
+
case messageTypes.noteOn:
|
|
90
|
+
case messageTypes.noteOff:
|
|
91
|
+
case messageTypes.keySignature:
|
|
92
|
+
break;
|
|
93
|
+
|
|
94
|
+
// skip pitch bend
|
|
95
|
+
case messageTypes.pitchBend:
|
|
96
|
+
pitchBends[channel] = event.messageData[1] << 7 | event.messageData[0];
|
|
97
|
+
break;
|
|
98
|
+
|
|
99
|
+
case messageTypes.programChange:
|
|
100
|
+
programs[channel] = event.messageData[0];
|
|
101
|
+
break;
|
|
102
|
+
|
|
103
|
+
case messageTypes.controllerChange:
|
|
104
|
+
// do not skip data entries
|
|
105
|
+
const controllerNumber = event.messageData[0];
|
|
106
|
+
if(
|
|
107
|
+
controllerNumber === midiControllers.dataDecrement ||
|
|
108
|
+
controllerNumber === midiControllers.dataIncrement ||
|
|
109
|
+
controllerNumber === midiControllers.dataEntryMsb ||
|
|
110
|
+
controllerNumber === midiControllers.dataDecrement ||
|
|
111
|
+
controllerNumber === midiControllers.lsbForControl6DataEntry ||
|
|
112
|
+
controllerNumber === midiControllers.RPNLsb ||
|
|
113
|
+
controllerNumber === midiControllers.RPNMsb ||
|
|
114
|
+
controllerNumber === midiControllers.NRPNLsb ||
|
|
115
|
+
controllerNumber === midiControllers.NRPNMsb ||
|
|
116
|
+
controllerNumber === midiControllers.bankSelect ||
|
|
117
|
+
controllerNumber === midiControllers.lsbForControl0BankSelect||
|
|
118
|
+
controllerNumber === midiControllers.resetAllControllers
|
|
119
|
+
)
|
|
120
|
+
{
|
|
121
|
+
if(this.sendMIDIMessages)
|
|
122
|
+
{
|
|
123
|
+
this.sendMIDIMessage([messageTypes.controllerChange | (channel % 16), controllerNumber, event.messageData[1]])
|
|
124
|
+
}
|
|
125
|
+
else
|
|
126
|
+
{
|
|
127
|
+
let ccV = event.messageData[1];
|
|
128
|
+
if(this.midiData.embeddedSoundFont !== undefined && controllerNumber === midiControllers.bankSelect)
|
|
129
|
+
{
|
|
130
|
+
// special case if the RMID is embedded: subtract 1 from bank. See wiki About-RMIDI
|
|
131
|
+
ccV--;
|
|
132
|
+
}
|
|
133
|
+
this.synth.controllerChange(channel, controllerNumber, ccV);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
else
|
|
137
|
+
{
|
|
138
|
+
if(savedControllers[channel] === undefined)
|
|
139
|
+
{
|
|
140
|
+
savedControllers[channel] = Array.from(defaultControllerArray);
|
|
141
|
+
}
|
|
142
|
+
savedControllers[channel][controllerNumber] = event.messageData[1];
|
|
143
|
+
}
|
|
144
|
+
break;
|
|
145
|
+
|
|
146
|
+
default:
|
|
147
|
+
this._processEvent(event, trackIndex);
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
this.eventIndex[trackIndex]++;
|
|
152
|
+
// find next event
|
|
153
|
+
trackIndex = this._findFirstEventIndex();
|
|
154
|
+
let nextEvent = this.tracks[trackIndex][this.eventIndex[trackIndex]];
|
|
155
|
+
if(nextEvent === undefined)
|
|
156
|
+
{
|
|
157
|
+
this.stop();
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
this.playedTime += this.oneTickToSeconds * (nextEvent.ticks - event.ticks);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// restoring saved controllers
|
|
164
|
+
if(this.sendMIDIMessages)
|
|
165
|
+
{
|
|
166
|
+
// for all 16 channels
|
|
167
|
+
for (let channelNumber = 0; channelNumber < channelsToSave; channelNumber++) {
|
|
168
|
+
// send saved pitch bend
|
|
169
|
+
this.sendMIDIMessage([messageTypes.pitchBend | (channelNumber % 16), pitchBends[channelNumber] & 0x7F, pitchBends[channelNumber] >> 7]);
|
|
170
|
+
|
|
171
|
+
// every controller that has changed
|
|
172
|
+
savedControllers[channelNumber].forEach((value, index) => {
|
|
173
|
+
if(value !== defaultControllerArray[index])
|
|
174
|
+
{
|
|
175
|
+
this.sendMIDIMessage([messageTypes.controllerChange | (channelNumber % 16), index, value])
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// restore programs
|
|
180
|
+
if(programs[channelNumber] !== 0)
|
|
181
|
+
{
|
|
182
|
+
this.sendMIDIMessage([messageTypes.programChange | (channelNumber % 16), programs[channelNumber]]);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
else
|
|
187
|
+
{
|
|
188
|
+
// for all synth channels
|
|
189
|
+
for (let channelNumber = 0; channelNumber < channelsToSave; channelNumber++)
|
|
190
|
+
{
|
|
191
|
+
// restore pitch bends
|
|
192
|
+
if(pitchBends[channelNumber] !== undefined)
|
|
193
|
+
{
|
|
194
|
+
this.synth.pitchWheel(channelNumber, pitchBends[channelNumber] >> 7, pitchBends[channelNumber] & 0x7F);
|
|
195
|
+
}
|
|
196
|
+
if(savedControllers[channelNumber] !== undefined)
|
|
197
|
+
{
|
|
198
|
+
// every controller that has changed
|
|
199
|
+
savedControllers[channelNumber].forEach((value, index) => {
|
|
200
|
+
if(value !== defaultControllerArray[index])
|
|
201
|
+
{
|
|
202
|
+
this.synth.controllerChange(channelNumber, index, value);
|
|
203
|
+
}
|
|
204
|
+
})
|
|
205
|
+
}
|
|
206
|
+
// restore programs
|
|
207
|
+
if(programs[channelNumber] !== -1)
|
|
208
|
+
{
|
|
209
|
+
this.synth.programChange(channelNumber, programs[channelNumber]);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Starts the playback
|
|
218
|
+
* @param resetTime {boolean} If true, time is set to 0s
|
|
219
|
+
* @this {WorkletSequencer}
|
|
220
|
+
*/
|
|
221
|
+
export function play(resetTime = false)
|
|
222
|
+
{
|
|
223
|
+
if(this.midiData === undefined)
|
|
224
|
+
{
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// reset the time if necesarry
|
|
229
|
+
if(resetTime)
|
|
230
|
+
{
|
|
231
|
+
this.currentTime = 0;
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if(this.currentTime >= this.duration)
|
|
236
|
+
{
|
|
237
|
+
this.currentTime = 0;
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// unpause if paused
|
|
242
|
+
if(this.paused)
|
|
243
|
+
{
|
|
244
|
+
// adjust the start time
|
|
245
|
+
this._recalculateStartTime(this.pausedTime)
|
|
246
|
+
this.pausedTime = undefined;
|
|
247
|
+
}
|
|
248
|
+
if(!this.sendMIDIMessages)
|
|
249
|
+
{
|
|
250
|
+
const time = this.currentTime;
|
|
251
|
+
this.playingNotes.forEach(n => {
|
|
252
|
+
const timeOffset = n.startTime - time;
|
|
253
|
+
this.synth.noteOn(n.channel, n.midiNote, n.velocity, false, true, currentTime + timeOffset);
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
this.setProcessHandler();
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Coverts ticks to time in seconds
|
|
261
|
+
* @param changes {{tempo: number, ticks: number}[]}
|
|
262
|
+
* @param ticks {number}
|
|
263
|
+
* @param division {number}
|
|
264
|
+
* @returns {number}
|
|
265
|
+
*/
|
|
266
|
+
export function ticksToSeconds(changes, ticks, division)
|
|
267
|
+
{
|
|
268
|
+
if (ticks <= 0) {
|
|
269
|
+
return 0;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// find the last tempo change that has occured
|
|
273
|
+
let tempo = changes.find(v => v.ticks < ticks);
|
|
274
|
+
|
|
275
|
+
let timeSinceLastTempo = ticks - tempo.ticks;
|
|
276
|
+
return ticksToSeconds(changes, ticks - timeSinceLastTempo, division) + (timeSinceLastTempo * 60) / (tempo.tempo * division);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* @this {WorkletSequencer}
|
|
281
|
+
* @param ticks {number}
|
|
282
|
+
*/
|
|
283
|
+
export function setTimeTicks(ticks)
|
|
284
|
+
{
|
|
285
|
+
this.stop();
|
|
286
|
+
this.playingNotes = [];
|
|
287
|
+
this.pausedTime = undefined;
|
|
288
|
+
this.post(
|
|
289
|
+
WorkletSequencerReturnMessageType.timeChange,
|
|
290
|
+
currentTime - ticksToSeconds(this.midiData.tempoChanges, ticks, this.midiData.timeDivision)
|
|
291
|
+
);
|
|
292
|
+
const isNotFinished = this._playTo(0, ticks);
|
|
293
|
+
this._recalculateStartTime(this.playedTime);
|
|
294
|
+
if(!isNotFinished)
|
|
295
|
+
{
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
this.play();
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* @param time
|
|
303
|
+
* @private
|
|
304
|
+
* @this {WorkletSequencer}
|
|
305
|
+
*/
|
|
306
|
+
export function _recalculateStartTime(time)
|
|
307
|
+
{
|
|
308
|
+
this.absoluteStartTime = currentTime - time / this._playbackRate;
|
|
309
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { getEvent, messageTypes, midiControllers } from '../../midi_parser/midi_message.js'
|
|
2
|
+
import { WorkletSequencerReturnMessageType } from './sequencer_message.js'
|
|
3
|
+
import { consoleColors } from '../../utils/other.js'
|
|
4
|
+
import { SpessaSynthWarn } from '../../utils/loggin.js'
|
|
5
|
+
import { readBytesAsUintBigEndian } from '../../utils/byte_functions/big_endian.js'
|
|
6
|
+
import { DEFAULT_PERCUSSION } from '../../synthetizer/synthetizer.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Processes a single event
|
|
10
|
+
* @param event {MidiMessage}
|
|
11
|
+
* @param trackIndex {number}
|
|
12
|
+
* @this {WorkletSequencer}
|
|
13
|
+
* @private
|
|
14
|
+
*/
|
|
15
|
+
export function _processEvent(event, trackIndex)
|
|
16
|
+
{
|
|
17
|
+
if(this.ignoreEvents) return;
|
|
18
|
+
if(this.sendMIDIMessages)
|
|
19
|
+
{
|
|
20
|
+
if(event.messageStatusByte >= 0x80)
|
|
21
|
+
{
|
|
22
|
+
this.sendMIDIMessage([event.messageStatusByte, ...event.messageData]);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const statusByteData = getEvent(event.messageStatusByte);
|
|
27
|
+
const offset = this.midiPortChannelOffsets[this.midiPorts[trackIndex]] || 0
|
|
28
|
+
statusByteData.channel += offset;
|
|
29
|
+
// process the event
|
|
30
|
+
switch (statusByteData.status) {
|
|
31
|
+
case messageTypes.noteOn:
|
|
32
|
+
const velocity = event.messageData[1];
|
|
33
|
+
if(velocity > 0)
|
|
34
|
+
{
|
|
35
|
+
this.synth.noteOn(statusByteData.channel, event.messageData[0], velocity);
|
|
36
|
+
this.playingNotes.push({
|
|
37
|
+
midiNote: event.messageData[0],
|
|
38
|
+
channel: statusByteData.channel,
|
|
39
|
+
velocity: velocity,
|
|
40
|
+
startTime: this.currentTime
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
else
|
|
44
|
+
{
|
|
45
|
+
this.synth.noteOff(statusByteData.channel, event.messageData[0]);
|
|
46
|
+
const toDelete = this.playingNotes.findIndex(n =>
|
|
47
|
+
n.midiNote === event.messageData[0] && n.channel === statusByteData.channel);
|
|
48
|
+
if(toDelete !== -1)
|
|
49
|
+
{
|
|
50
|
+
this.playingNotes.splice(toDelete, 1);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
break;
|
|
54
|
+
|
|
55
|
+
case messageTypes.noteOff:
|
|
56
|
+
this.synth.noteOff(statusByteData.channel, event.messageData[0]);
|
|
57
|
+
const toDelete = this.playingNotes.findIndex(n =>
|
|
58
|
+
n.midiNote === event.messageData[0] && n.channel === statusByteData.channel);
|
|
59
|
+
if(toDelete !== -1)
|
|
60
|
+
{
|
|
61
|
+
this.playingNotes.splice(toDelete, 1);
|
|
62
|
+
}
|
|
63
|
+
break;
|
|
64
|
+
|
|
65
|
+
case messageTypes.pitchBend:
|
|
66
|
+
this.synth.pitchWheel(statusByteData.channel, event.messageData[1], event.messageData[0]);
|
|
67
|
+
break;
|
|
68
|
+
|
|
69
|
+
case messageTypes.controllerChange:
|
|
70
|
+
// special case if the RMID is embedded: subtract 1 from bank. See wiki About-RMIDI
|
|
71
|
+
let v = event.messageData[1];
|
|
72
|
+
if(this.midiData.embeddedSoundFont && event.messageData[0] === midiControllers.bankSelect)
|
|
73
|
+
{
|
|
74
|
+
v--;
|
|
75
|
+
}
|
|
76
|
+
this.synth.controllerChange(statusByteData.channel, event.messageData[0], v);
|
|
77
|
+
break;
|
|
78
|
+
|
|
79
|
+
case messageTypes.programChange:
|
|
80
|
+
this.synth.programChange(statusByteData.channel, event.messageData[0]);
|
|
81
|
+
break;
|
|
82
|
+
|
|
83
|
+
case messageTypes.polyPressure:
|
|
84
|
+
this.synth.polyPressure(statusByteData.channel, event.messageData[0], event.messageData[1]);
|
|
85
|
+
break;
|
|
86
|
+
|
|
87
|
+
case messageTypes.channelPressure:
|
|
88
|
+
this.synth.channelPressure(statusByteData.channel, event.messageData[0]);
|
|
89
|
+
break;
|
|
90
|
+
|
|
91
|
+
case messageTypes.systemExclusive:
|
|
92
|
+
this.synth.systemExclusive(event.messageData, offset);
|
|
93
|
+
break;
|
|
94
|
+
|
|
95
|
+
case messageTypes.setTempo:
|
|
96
|
+
this.oneTickToSeconds = 60 / (getTempo(event) * this.midiData.timeDivision);
|
|
97
|
+
if(this.oneTickToSeconds === 0)
|
|
98
|
+
{
|
|
99
|
+
this.oneTickToSeconds = 60 / (120 * this.midiData.timeDivision);
|
|
100
|
+
SpessaSynthWarn("invalid tempo! falling back to 120 BPM");
|
|
101
|
+
}
|
|
102
|
+
break;
|
|
103
|
+
|
|
104
|
+
// recongized but ignored
|
|
105
|
+
case messageTypes.timeSignature:
|
|
106
|
+
case messageTypes.endOfTrack:
|
|
107
|
+
case messageTypes.midiChannelPrefix:
|
|
108
|
+
case messageTypes.songPosition:
|
|
109
|
+
case messageTypes.activeSensing:
|
|
110
|
+
case messageTypes.keySignature:
|
|
111
|
+
break;
|
|
112
|
+
|
|
113
|
+
case messageTypes.text:
|
|
114
|
+
case messageTypes.lyric:
|
|
115
|
+
case messageTypes.copyright:
|
|
116
|
+
case messageTypes.trackName:
|
|
117
|
+
case messageTypes.marker:
|
|
118
|
+
case messageTypes.cuePoint:
|
|
119
|
+
case messageTypes.instrumentName:
|
|
120
|
+
this.post(WorkletSequencerReturnMessageType.textEvent, [event.messageData, statusByteData.status])
|
|
121
|
+
break;
|
|
122
|
+
|
|
123
|
+
case messageTypes.midiPort:
|
|
124
|
+
this.assignMIDIPort(trackIndex, event.messageData[0]);
|
|
125
|
+
break;
|
|
126
|
+
|
|
127
|
+
case messageTypes.reset:
|
|
128
|
+
this.synth.stopAllChannels();
|
|
129
|
+
this.synth.resetAllControllers();
|
|
130
|
+
break;
|
|
131
|
+
|
|
132
|
+
default:
|
|
133
|
+
SpessaSynthWarn(`%cUnrecognized Event: %c${event.messageStatusByte}%c status byte: %c${Object.keys(messageTypes).find(k => messageTypes[k] === statusByteData.status)}`,
|
|
134
|
+
consoleColors.warn,
|
|
135
|
+
consoleColors.unrecognized,
|
|
136
|
+
consoleColors.warn,
|
|
137
|
+
consoleColors.value);
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Adds 16 channels to the synth
|
|
144
|
+
* @this {WorkletSequencer}
|
|
145
|
+
* @private
|
|
146
|
+
*/
|
|
147
|
+
export function _addNewMidiPort()
|
|
148
|
+
{
|
|
149
|
+
for (let i = 0; i < 16; i++) {
|
|
150
|
+
this.synth.createWorkletChannel(true);
|
|
151
|
+
if(i === DEFAULT_PERCUSSION)
|
|
152
|
+
{
|
|
153
|
+
this.synth.setDrums(this.synth.workletProcessorChannels.length - 1, true);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* gets tempo from the midi message
|
|
160
|
+
* @param event {MidiMessage}
|
|
161
|
+
* @return {number} the tempo in bpm
|
|
162
|
+
*/
|
|
163
|
+
function getTempo(event)
|
|
164
|
+
{
|
|
165
|
+
event.messageData.currentIndex = 0;
|
|
166
|
+
return 60000000 / readBytesAsUintBigEndian(event.messageData, 3);
|
|
167
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Processes a single tick
|
|
3
|
+
* @private
|
|
4
|
+
* @this {WorkletSequencer}
|
|
5
|
+
*/
|
|
6
|
+
export function _processTick()
|
|
7
|
+
{
|
|
8
|
+
let current = this.currentTime;
|
|
9
|
+
while(this.playedTime < current)
|
|
10
|
+
{
|
|
11
|
+
// find next event
|
|
12
|
+
let trackIndex = this._findFirstEventIndex();
|
|
13
|
+
let event = this.tracks[trackIndex][this.eventIndex[trackIndex]];
|
|
14
|
+
this._processEvent(event, trackIndex);
|
|
15
|
+
|
|
16
|
+
this.eventIndex[trackIndex]++;
|
|
17
|
+
|
|
18
|
+
// find next event
|
|
19
|
+
trackIndex = this._findFirstEventIndex();
|
|
20
|
+
if(this.tracks[trackIndex].length <= this.eventIndex[trackIndex])
|
|
21
|
+
{
|
|
22
|
+
// song has ended
|
|
23
|
+
if(this.loop)
|
|
24
|
+
{
|
|
25
|
+
this.setTimeTicks(this.midiData.loop.start);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
this.eventIndex[trackIndex]--;
|
|
29
|
+
this.pause(true);
|
|
30
|
+
if(this.songs.length > 1)
|
|
31
|
+
{
|
|
32
|
+
this.nextSong();
|
|
33
|
+
}
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
let eventNext = this.tracks[trackIndex][this.eventIndex[trackIndex]];
|
|
37
|
+
this.playedTime += this.oneTickToSeconds * (eventNext.ticks - event.ticks);
|
|
38
|
+
|
|
39
|
+
// loop
|
|
40
|
+
if((this.midiData.loop.end <= event.ticks) && this.loop)
|
|
41
|
+
{
|
|
42
|
+
this.setTimeTicks(this.midiData.loop.start);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
// if song has ended
|
|
46
|
+
else if(current >= this.duration)
|
|
47
|
+
{
|
|
48
|
+
if(this.loop)
|
|
49
|
+
{
|
|
50
|
+
this.setTimeTicks(this.midiData.loop.start);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
this.eventIndex[trackIndex]--;
|
|
54
|
+
this.pause(true);
|
|
55
|
+
if(this.songs.length > 1)
|
|
56
|
+
{
|
|
57
|
+
this.nextSong();
|
|
58
|
+
}
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @returns {number} the index of the first to the current played time
|
|
67
|
+
* @this {WorkletSequencer}
|
|
68
|
+
*/
|
|
69
|
+
export function _findFirstEventIndex()
|
|
70
|
+
{
|
|
71
|
+
let index = 0;
|
|
72
|
+
let ticks = Infinity;
|
|
73
|
+
this.tracks.forEach((track, i) => {
|
|
74
|
+
if(this.eventIndex[i] >= track.length)
|
|
75
|
+
{
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if(track[this.eventIndex[i]].ticks < ticks)
|
|
79
|
+
{
|
|
80
|
+
index = i;
|
|
81
|
+
ticks = track[this.eventIndex[i]].ticks;
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
return index;
|
|
85
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @enum {number}
|
|
3
|
+
* @property {number} loadNewSongList - 0 -> [...song<MIDI>]
|
|
4
|
+
* @property {number} pause - 1 -> isFinished<boolean>
|
|
5
|
+
* @property {number} stop - 2 -> (no data)
|
|
6
|
+
* @property {number} play - 3 -> resetTime<boolean>
|
|
7
|
+
* @property {number} setTime - 4 -> time<number>
|
|
8
|
+
* @property {number} changeMIDIMessageSending - 5 -> sendMIDIMessages<boolean>
|
|
9
|
+
* @property {number} setPlaybackRate - 6 -> playbackRate<number>
|
|
10
|
+
* @property {number} setLoop - 7 -> loop<boolean>
|
|
11
|
+
* @property {number} changeSong - 8 -> goForwards<boolean> if true, next song, if false, previous
|
|
12
|
+
* @property {number} getMIDI - 9 -> (no data)
|
|
13
|
+
*/
|
|
14
|
+
export const WorkletSequencerMessageType = {
|
|
15
|
+
loadNewSongList: 0,
|
|
16
|
+
pause: 1,
|
|
17
|
+
stop: 2,
|
|
18
|
+
play: 3,
|
|
19
|
+
setTime: 4,
|
|
20
|
+
changeMIDIMessageSending: 5,
|
|
21
|
+
setPlaybackRate: 6,
|
|
22
|
+
setLoop: 7,
|
|
23
|
+
changeSong: 8,
|
|
24
|
+
getMIDI: 9
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
*
|
|
29
|
+
* @enum {number}
|
|
30
|
+
*/
|
|
31
|
+
export const WorkletSequencerReturnMessageType = {
|
|
32
|
+
midiEvent: 0, // [...midiEventBytes<number>]
|
|
33
|
+
songChange: 1, // [midiData<MidiData>, songIndex<number>]
|
|
34
|
+
textEvent: 2, // [messageData<number[]>, statusByte<number]
|
|
35
|
+
timeChange: 3, // newAbsoluteTime<number>
|
|
36
|
+
pause: 4, // no data
|
|
37
|
+
getMIDI: 5, // midiData<MIDI>
|
|
38
|
+
midiError: 6, // errorMSG<string>
|
|
39
|
+
}
|