spessasynth_lib 3.13.1 → 3.14.0
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/@types/sequencer/sequencer.d.ts +5 -0
- package/@types/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.d.ts +5 -0
- package/README.md +23 -13
- package/midi_parser/rmidi_writer.js +1 -1
- package/package.json +1 -1
- package/sequencer/sequencer.js +8 -0
- package/synthetizer/worklet_processor.min.js +6 -6
- package/synthetizer/worklet_system/combine_class.js +104 -0
- package/synthetizer/worklet_system/main_processor.js +13 -109
- package/synthetizer/worklet_system/worklet_methods/program_control.js +6 -0
- package/synthetizer/worklet_system/worklet_methods/reset_controllers.js +2 -0
- package/synthetizer/worklet_system/worklet_methods/snapshot.js +3 -0
- package/synthetizer/worklet_system/worklet_methods/system_exclusive.js +219 -82
- package/synthetizer/worklet_system/worklet_methods/tuning_control.js +15 -0
- package/synthetizer/worklet_system/worklet_methods/voice_control.js +2 -1
- package/synthetizer/worklet_system/worklet_processor.js +1 -1
- package/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +2 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { SpessaSynthProcessor } from './main_processor.js'
|
|
2
|
+
import { releaseVoice, renderVoice, voiceKilling } from './worklet_methods/voice_control.js'
|
|
3
|
+
import { handleMessage } from './message_protocol/handle_message.js'
|
|
4
|
+
import { callEvent, post, sendChannelProperties } from './message_protocol/message_sending.js'
|
|
5
|
+
import { systemExclusive } from './worklet_methods/system_exclusive.js'
|
|
6
|
+
import { noteOn } from './worklet_methods/note_on.js'
|
|
7
|
+
import { killNote, noteOff, stopAll, stopAllChannels } from './worklet_methods/note_off.js'
|
|
8
|
+
import {
|
|
9
|
+
channelPressure, pitchWheel,
|
|
10
|
+
polyPressure, setChannelTuning, setChannelTuningSemitones, setMasterTuning, setModulationDepth, setOctaveTuning,
|
|
11
|
+
transposeAllChannels,
|
|
12
|
+
transposeChannel,
|
|
13
|
+
} from './worklet_methods/tuning_control.js'
|
|
14
|
+
import {
|
|
15
|
+
controllerChange,
|
|
16
|
+
muteChannel,
|
|
17
|
+
setMasterGain,
|
|
18
|
+
setMasterPan,
|
|
19
|
+
setMIDIVolume,
|
|
20
|
+
} from './worklet_methods/controller_control.js'
|
|
21
|
+
import { disableAndLockVibrato, setVibrato } from './worklet_methods/vibrato_control.js'
|
|
22
|
+
import { dataEntryCoarse, dataEntryFine } from './worklet_methods/data_entry.js'
|
|
23
|
+
import { createWorkletChannel } from './worklet_utilities/worklet_processor_channel.js'
|
|
24
|
+
import { resetAllControllers, resetControllers, resetParameters } from './worklet_methods/reset_controllers.js'
|
|
25
|
+
import {
|
|
26
|
+
clearSoundFont,
|
|
27
|
+
getPreset,
|
|
28
|
+
programChange,
|
|
29
|
+
reloadSoundFont, sampleDump, sendPresetList,
|
|
30
|
+
setDrums,
|
|
31
|
+
setPreset,
|
|
32
|
+
} from './worklet_methods/program_control.js'
|
|
33
|
+
import { applySynthesizerSnapshot, sendSynthesizerSnapshot } from './worklet_methods/snapshot.js'
|
|
34
|
+
|
|
35
|
+
// include other methods
|
|
36
|
+
// voice related
|
|
37
|
+
SpessaSynthProcessor.prototype.renderVoice = renderVoice;
|
|
38
|
+
SpessaSynthProcessor.prototype.releaseVoice = releaseVoice;
|
|
39
|
+
SpessaSynthProcessor.prototype.voiceKilling = voiceKilling;
|
|
40
|
+
|
|
41
|
+
// message port related
|
|
42
|
+
SpessaSynthProcessor.prototype.handleMessage = handleMessage;
|
|
43
|
+
SpessaSynthProcessor.prototype.post = post;
|
|
44
|
+
SpessaSynthProcessor.prototype.sendChannelProperties = sendChannelProperties;
|
|
45
|
+
SpessaSynthProcessor.prototype.callEvent = callEvent;
|
|
46
|
+
|
|
47
|
+
// system exlcusive related
|
|
48
|
+
SpessaSynthProcessor.prototype.systemExclusive = systemExclusive;
|
|
49
|
+
|
|
50
|
+
// note messages related
|
|
51
|
+
SpessaSynthProcessor.prototype.noteOn = noteOn;
|
|
52
|
+
SpessaSynthProcessor.prototype.noteOff = noteOff;
|
|
53
|
+
SpessaSynthProcessor.prototype.polyPressure = polyPressure;
|
|
54
|
+
SpessaSynthProcessor.prototype.killNote = killNote;
|
|
55
|
+
SpessaSynthProcessor.prototype.stopAll = stopAll;
|
|
56
|
+
SpessaSynthProcessor.prototype.stopAllChannels = stopAllChannels;
|
|
57
|
+
SpessaSynthProcessor.prototype.muteChannel = muteChannel;
|
|
58
|
+
|
|
59
|
+
// custom vibrato related
|
|
60
|
+
SpessaSynthProcessor.prototype.setVibrato = setVibrato;
|
|
61
|
+
SpessaSynthProcessor.prototype.disableAndLockVibrato = disableAndLockVibrato;
|
|
62
|
+
|
|
63
|
+
// data entry related
|
|
64
|
+
SpessaSynthProcessor.prototype.dataEntryCoarse = dataEntryCoarse;
|
|
65
|
+
SpessaSynthProcessor.prototype.dataEntryFine = dataEntryFine;
|
|
66
|
+
|
|
67
|
+
// channel related
|
|
68
|
+
SpessaSynthProcessor.prototype.createWorkletChannel = createWorkletChannel;
|
|
69
|
+
SpessaSynthProcessor.prototype.controllerChange = controllerChange;
|
|
70
|
+
SpessaSynthProcessor.prototype.channelPressure = channelPressure;
|
|
71
|
+
SpessaSynthProcessor.prototype.resetAllControllers = resetAllControllers;
|
|
72
|
+
SpessaSynthProcessor.prototype.resetControllers = resetControllers;
|
|
73
|
+
SpessaSynthProcessor.prototype.resetParameters = resetParameters;
|
|
74
|
+
|
|
75
|
+
// master parameter related
|
|
76
|
+
SpessaSynthProcessor.prototype.setMasterGain = setMasterGain;
|
|
77
|
+
SpessaSynthProcessor.prototype.setMasterPan = setMasterPan;
|
|
78
|
+
SpessaSynthProcessor.prototype.setMIDIVolume = setMIDIVolume;
|
|
79
|
+
|
|
80
|
+
// tuning related
|
|
81
|
+
SpessaSynthProcessor.prototype.transposeAllChannels = transposeAllChannels;
|
|
82
|
+
SpessaSynthProcessor.prototype.transposeChannel = transposeChannel;
|
|
83
|
+
SpessaSynthProcessor.prototype.setChannelTuning = setChannelTuning;
|
|
84
|
+
SpessaSynthProcessor.prototype.setChannelTuningSemitones = setChannelTuningSemitones;
|
|
85
|
+
SpessaSynthProcessor.prototype.setMasterTuning = setMasterTuning;
|
|
86
|
+
SpessaSynthProcessor.prototype.setModulationDepth = setModulationDepth;
|
|
87
|
+
SpessaSynthProcessor.prototype.pitchWheel = pitchWheel;
|
|
88
|
+
SpessaSynthProcessor.prototype.setOctaveTuning = setOctaveTuning;
|
|
89
|
+
|
|
90
|
+
// program related
|
|
91
|
+
SpessaSynthProcessor.prototype.programChange = programChange;
|
|
92
|
+
SpessaSynthProcessor.prototype.getPreset = getPreset;
|
|
93
|
+
SpessaSynthProcessor.prototype.setPreset = setPreset;
|
|
94
|
+
SpessaSynthProcessor.prototype.setDrums = setDrums;
|
|
95
|
+
SpessaSynthProcessor.prototype.reloadSoundFont = reloadSoundFont;
|
|
96
|
+
SpessaSynthProcessor.prototype.clearSoundFont = clearSoundFont;
|
|
97
|
+
SpessaSynthProcessor.prototype.sampleDump = sampleDump;
|
|
98
|
+
SpessaSynthProcessor.prototype.sendPresetList = sendPresetList;
|
|
99
|
+
|
|
100
|
+
// snapshot related
|
|
101
|
+
SpessaSynthProcessor.prototype.sendSynthesizerSnapshot = sendSynthesizerSnapshot;
|
|
102
|
+
SpessaSynthProcessor.prototype.applySynthesizerSnapshot = applySynthesizerSnapshot;
|
|
103
|
+
|
|
104
|
+
export {SpessaSynthProcessor}
|
|
@@ -1,45 +1,12 @@
|
|
|
1
1
|
import { DEFAULT_PERCUSSION, DEFAULT_SYNTH_MODE, VOICE_CAP } from '../synthetizer.js'
|
|
2
|
-
import {
|
|
3
|
-
createWorkletChannel,
|
|
4
|
-
} from './worklet_utilities/worklet_processor_channel.js'
|
|
5
|
-
|
|
6
2
|
import { SoundFont2 } from '../../soundfont/soundfont.js'
|
|
7
|
-
import { handleMessage } from './message_protocol/handle_message.js'
|
|
8
|
-
import { systemExclusive } from './worklet_methods/system_exclusive.js'
|
|
9
|
-
import { noteOn } from './worklet_methods/note_on.js'
|
|
10
|
-
import { dataEntryCoarse, dataEntryFine } from './worklet_methods/data_entry.js'
|
|
11
|
-
import { killNote, noteOff, stopAll, stopAllChannels } from './worklet_methods/note_off.js'
|
|
12
|
-
import {
|
|
13
|
-
controllerChange, muteChannel, setMasterGain, setMasterPan, setMIDIVolume,
|
|
14
|
-
} from './worklet_methods/controller_control.js'
|
|
15
|
-
import { callEvent, post, sendChannelProperties } from './message_protocol/message_sending.js'
|
|
16
|
-
import {
|
|
17
|
-
channelPressure,
|
|
18
|
-
pitchWheel, polyPressure,
|
|
19
|
-
setChannelTuning, setChannelTuningSemitones,
|
|
20
|
-
setMasterTuning, setModulationDepth,
|
|
21
|
-
transposeAllChannels,
|
|
22
|
-
transposeChannel,
|
|
23
|
-
} from './worklet_methods/tuning_control.js'
|
|
24
|
-
import {
|
|
25
|
-
clearSoundFont, getPreset,
|
|
26
|
-
programChange,
|
|
27
|
-
reloadSoundFont,
|
|
28
|
-
sampleDump,
|
|
29
|
-
sendPresetList,
|
|
30
|
-
setDrums,
|
|
31
|
-
setPreset,
|
|
32
|
-
} from './worklet_methods/program_control.js'
|
|
33
|
-
import { disableAndLockVibrato, setVibrato } from './worklet_methods/vibrato_control.js'
|
|
34
3
|
import { WorkletSequencer } from '../../sequencer/worklet_sequencer/worklet_sequencer.js'
|
|
35
4
|
import { SpessaSynthInfo } from '../../utils/loggin.js'
|
|
36
|
-
import { applySynthesizerSnapshot, sendSynthesizerSnapshot } from './worklet_methods/snapshot.js'
|
|
37
5
|
import { consoleColors } from '../../utils/other.js'
|
|
38
|
-
import { PAN_SMOOTHING_FACTOR
|
|
39
|
-
import { returnMessageType } from './message_protocol/worklet_message.js'
|
|
6
|
+
import { PAN_SMOOTHING_FACTOR } from './worklet_methods/voice_control.js'
|
|
7
|
+
import { ALL_CHANNELS_OR_DIFFERENT_ACTION, returnMessageType } from './message_protocol/worklet_message.js'
|
|
40
8
|
import { stbvorbis } from '../../externals/stbvorbis_sync/stbvorbis_sync.min.js'
|
|
41
9
|
import { VOLUME_ENVELOPE_SMOOTHING_FACTOR } from './worklet_utilities/volume_envelope.js'
|
|
42
|
-
import { resetAllControllers, resetControllers, resetParameters } from './worklet_methods/reset_controllers.js'
|
|
43
10
|
|
|
44
11
|
|
|
45
12
|
/**
|
|
@@ -51,7 +18,8 @@ export const MIN_NOTE_LENGTH = 0.07; // if the note is released faster than that
|
|
|
51
18
|
|
|
52
19
|
export const SYNTHESIZER_GAIN = 1.0;
|
|
53
20
|
|
|
54
|
-
class SpessaSynthProcessor extends AudioWorkletProcessor
|
|
21
|
+
export class SpessaSynthProcessor extends AudioWorkletProcessor
|
|
22
|
+
{
|
|
55
23
|
/**
|
|
56
24
|
* Creates a new worklet synthesis system. contains all channels
|
|
57
25
|
* @param options {{
|
|
@@ -66,13 +34,20 @@ class SpessaSynthProcessor extends AudioWorkletProcessor {
|
|
|
66
34
|
* }
|
|
67
35
|
* }}}
|
|
68
36
|
*/
|
|
69
|
-
constructor(options)
|
|
37
|
+
constructor(options)
|
|
38
|
+
{
|
|
70
39
|
super();
|
|
71
40
|
this.oneOutputMode = options.processorOptions?.startRenderingData?.oneOutput === true;
|
|
72
41
|
this._outputsAmount = this.oneOutputMode ? 1 : options.processorOptions.midiChannels;
|
|
73
42
|
|
|
74
43
|
this.enableEventSystem = options.processorOptions.enableEventSystem;
|
|
75
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Synth's device id: -1 means all
|
|
47
|
+
* @type {number}
|
|
48
|
+
*/
|
|
49
|
+
this.deviceID = ALL_CHANNELS_OR_DIFFERENT_ACTION;
|
|
50
|
+
|
|
76
51
|
/**
|
|
77
52
|
* @type {function}
|
|
78
53
|
*/
|
|
@@ -304,75 +279,4 @@ class SpessaSynthProcessor extends AudioWorkletProcessor {
|
|
|
304
279
|
}
|
|
305
280
|
return true;
|
|
306
281
|
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// include other methods
|
|
310
|
-
// voice related
|
|
311
|
-
SpessaSynthProcessor.prototype.renderVoice = renderVoice;
|
|
312
|
-
SpessaSynthProcessor.prototype.releaseVoice = releaseVoice;
|
|
313
|
-
SpessaSynthProcessor.prototype.voiceKilling = voiceKilling;
|
|
314
|
-
|
|
315
|
-
// message port related
|
|
316
|
-
SpessaSynthProcessor.prototype.handleMessage = handleMessage;
|
|
317
|
-
SpessaSynthProcessor.prototype.post = post;
|
|
318
|
-
SpessaSynthProcessor.prototype.sendChannelProperties = sendChannelProperties;
|
|
319
|
-
SpessaSynthProcessor.prototype.callEvent = callEvent;
|
|
320
|
-
|
|
321
|
-
// system exlcusive related
|
|
322
|
-
SpessaSynthProcessor.prototype.systemExclusive = systemExclusive;
|
|
323
|
-
|
|
324
|
-
// note messages related
|
|
325
|
-
SpessaSynthProcessor.prototype.noteOn = noteOn;
|
|
326
|
-
SpessaSynthProcessor.prototype.noteOff = noteOff;
|
|
327
|
-
SpessaSynthProcessor.prototype.polyPressure = polyPressure;
|
|
328
|
-
SpessaSynthProcessor.prototype.killNote = killNote;
|
|
329
|
-
SpessaSynthProcessor.prototype.stopAll = stopAll;
|
|
330
|
-
SpessaSynthProcessor.prototype.stopAllChannels = stopAllChannels;
|
|
331
|
-
SpessaSynthProcessor.prototype.muteChannel = muteChannel;
|
|
332
|
-
|
|
333
|
-
// custom vibrato related
|
|
334
|
-
SpessaSynthProcessor.prototype.setVibrato = setVibrato;
|
|
335
|
-
SpessaSynthProcessor.prototype.disableAndLockVibrato = disableAndLockVibrato;
|
|
336
|
-
|
|
337
|
-
// data entry related
|
|
338
|
-
SpessaSynthProcessor.prototype.dataEntryCoarse = dataEntryCoarse;
|
|
339
|
-
SpessaSynthProcessor.prototype.dataEntryFine = dataEntryFine;
|
|
340
|
-
|
|
341
|
-
// channel related
|
|
342
|
-
SpessaSynthProcessor.prototype.createWorkletChannel = createWorkletChannel;
|
|
343
|
-
SpessaSynthProcessor.prototype.controllerChange = controllerChange;
|
|
344
|
-
SpessaSynthProcessor.prototype.channelPressure = channelPressure;
|
|
345
|
-
SpessaSynthProcessor.prototype.resetAllControllers = resetAllControllers;
|
|
346
|
-
SpessaSynthProcessor.prototype.resetControllers = resetControllers;
|
|
347
|
-
SpessaSynthProcessor.prototype.resetParameters = resetParameters;
|
|
348
|
-
|
|
349
|
-
// master parameter related
|
|
350
|
-
SpessaSynthProcessor.prototype.setMasterGain = setMasterGain;
|
|
351
|
-
SpessaSynthProcessor.prototype.setMasterPan = setMasterPan;
|
|
352
|
-
SpessaSynthProcessor.prototype.setMIDIVolume = setMIDIVolume;
|
|
353
|
-
|
|
354
|
-
// tuning related
|
|
355
|
-
SpessaSynthProcessor.prototype.transposeAllChannels = transposeAllChannels;
|
|
356
|
-
SpessaSynthProcessor.prototype.transposeChannel = transposeChannel;
|
|
357
|
-
SpessaSynthProcessor.prototype.setChannelTuning = setChannelTuning;
|
|
358
|
-
SpessaSynthProcessor.prototype.setChannelTuningSemitones = setChannelTuningSemitones;
|
|
359
|
-
SpessaSynthProcessor.prototype.setMasterTuning = setMasterTuning;
|
|
360
|
-
SpessaSynthProcessor.prototype.setModulationDepth = setModulationDepth;
|
|
361
|
-
SpessaSynthProcessor.prototype.pitchWheel = pitchWheel;
|
|
362
|
-
|
|
363
|
-
// program related
|
|
364
|
-
SpessaSynthProcessor.prototype.programChange = programChange;
|
|
365
|
-
SpessaSynthProcessor.prototype.getPreset = getPreset;
|
|
366
|
-
SpessaSynthProcessor.prototype.setPreset = setPreset;
|
|
367
|
-
SpessaSynthProcessor.prototype.setDrums = setDrums;
|
|
368
|
-
SpessaSynthProcessor.prototype.reloadSoundFont = reloadSoundFont;
|
|
369
|
-
SpessaSynthProcessor.prototype.clearSoundFont = clearSoundFont;
|
|
370
|
-
SpessaSynthProcessor.prototype.sampleDump = sampleDump;
|
|
371
|
-
SpessaSynthProcessor.prototype.sendPresetList = sendPresetList;
|
|
372
|
-
|
|
373
|
-
// snapshot related
|
|
374
|
-
SpessaSynthProcessor.prototype.sendSynthesizerSnapshot = sendSynthesizerSnapshot;
|
|
375
|
-
SpessaSynthProcessor.prototype.applySynthesizerSnapshot = applySynthesizerSnapshot;
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
export { SpessaSynthProcessor }
|
|
282
|
+
}
|
|
@@ -96,6 +96,7 @@ export function setPreset(channel, preset)
|
|
|
96
96
|
{
|
|
97
97
|
return;
|
|
98
98
|
}
|
|
99
|
+
delete this.workletProcessorChannels[channel].preset;
|
|
99
100
|
this.workletProcessorChannels[channel].preset = preset;
|
|
100
101
|
|
|
101
102
|
// reset cached voices
|
|
@@ -235,6 +236,11 @@ export function reloadSoundFont(buffer, isOverride = false)
|
|
|
235
236
|
});
|
|
236
237
|
return;
|
|
237
238
|
}
|
|
239
|
+
this.defaultPreset = this.getPreset(0, 0);
|
|
240
|
+
this.drumPreset = this.getPreset(128, 0);
|
|
241
|
+
this.workletProcessorChannels.forEach((c, cNum) => {
|
|
242
|
+
this.programChange(cNum, c.preset.program);
|
|
243
|
+
});
|
|
238
244
|
this.post({messageType: returnMessageType.ready, messageData: undefined});
|
|
239
245
|
this.sendPresetList();
|
|
240
246
|
SpessaSynthInfo("%cSpessaSynth is ready!", consoleColors.recognized);
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* @property {number} channelVibrato.rate - vibrato rate in Hz
|
|
20
20
|
*
|
|
21
21
|
* @property {number} channelTransposeKeyShift - key shift for the channel
|
|
22
|
+
* @property {Int8Array} channelOctaveTuning - the channel's octave tuning in cents
|
|
22
23
|
* @property {boolean} isMuted - indicates whether the channel is muted
|
|
23
24
|
* @property {boolean} drumChannel - indicates whether the channel is a drum channel
|
|
24
25
|
*/
|
|
@@ -60,6 +61,7 @@ export function sendSynthesizerSnapshot()
|
|
|
60
61
|
lockVibrato: channel.lockVibrato,
|
|
61
62
|
|
|
62
63
|
channelTransposeKeyShift: channel.channelTransposeKeyShift,
|
|
64
|
+
channelOctaveTuning: channel.channelOctaveTuning,
|
|
63
65
|
isMuted: channel.isMuted,
|
|
64
66
|
drumChannel: channel.drumChannel
|
|
65
67
|
}
|
|
@@ -118,6 +120,7 @@ export function applySynthesizerSnapshot(snapshot)
|
|
|
118
120
|
channelObject.channelVibrato = channelSnapshot.channelVibrato;
|
|
119
121
|
channelObject.lockVibrato = channelSnapshot.lockVibrato;
|
|
120
122
|
channelObject.channelTransposeKeyShift = channelSnapshot.channelTransposeKeyShift;
|
|
123
|
+
channelObject.channelOctaveTuning = channelSnapshot.channelOctaveTuning;
|
|
121
124
|
|
|
122
125
|
// restore preset and lock
|
|
123
126
|
channelObject.lockPreset = false;
|
|
@@ -1,16 +1,56 @@
|
|
|
1
1
|
import { arrayToHexString, consoleColors } from '../../../utils/other.js'
|
|
2
2
|
import { SpessaSynthInfo, SpessaSynthWarn } from '../../../utils/loggin.js'
|
|
3
3
|
import { midiControllers } from '../../../midi_parser/midi_message.js'
|
|
4
|
+
import { ALL_CHANNELS_OR_DIFFERENT_ACTION } from '../message_protocol/worklet_message.js'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Calculates freqency for MIDI Tuning Standard
|
|
9
|
+
* @param byte1 {number}
|
|
10
|
+
* @param byte2 {number}
|
|
11
|
+
* @param byte3 {number}
|
|
12
|
+
* @return {number|null}
|
|
13
|
+
*/
|
|
14
|
+
function calculateFrequency(byte1, byte2, byte3)
|
|
15
|
+
{
|
|
16
|
+
// handle special case of "no change"
|
|
17
|
+
if (byte1 === 0x7F && byte2 === 0x7F && byte3 === 0x7F)
|
|
18
|
+
{
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Frequency base for MIDI note number 0
|
|
23
|
+
const baseFrequency = 8.1758;
|
|
24
|
+
|
|
25
|
+
const semitone = byte1;
|
|
26
|
+
|
|
27
|
+
// combine byte2 and byte3
|
|
28
|
+
const fraction = (byte2 << 7) | byte3;
|
|
29
|
+
|
|
30
|
+
// get total cents
|
|
31
|
+
const totalCents = semitone * 100 + (fraction / 16384) * 100;
|
|
32
|
+
|
|
33
|
+
return baseFrequency * Math.pow(2, totalCents / 1200);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
4
37
|
/**
|
|
5
38
|
* Executes a system exclusive
|
|
6
39
|
* @param messageData {number[]|IndexedByteArray} - the message data without f0
|
|
7
40
|
* @param channelOffset {number}
|
|
8
41
|
* @this {SpessaSynthProcessor}
|
|
9
42
|
*/
|
|
10
|
-
|
|
11
43
|
export function systemExclusive(messageData, channelOffset = 0)
|
|
12
44
|
{
|
|
13
45
|
const type = messageData[0];
|
|
46
|
+
if(this.deviceID !== ALL_CHANNELS_OR_DIFFERENT_ACTION && messageData[1] !== 0x7F)
|
|
47
|
+
{
|
|
48
|
+
if(this.deviceID !== messageData[1])
|
|
49
|
+
{
|
|
50
|
+
// not our device ID
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
14
54
|
switch (type)
|
|
15
55
|
{
|
|
16
56
|
default:
|
|
@@ -21,91 +61,188 @@ export function systemExclusive(messageData, channelOffset = 0)
|
|
|
21
61
|
|
|
22
62
|
// non realtime
|
|
23
63
|
case 0x7E:
|
|
24
|
-
// gm system
|
|
25
|
-
if(messageData[2] === 0x09)
|
|
26
|
-
{
|
|
27
|
-
if(messageData[3] === 0x01)
|
|
28
|
-
{
|
|
29
|
-
SpessaSynthInfo("%cGM system on", consoleColors.info);
|
|
30
|
-
this.system = "gm";
|
|
31
|
-
}
|
|
32
|
-
else if(messageData[3] === 0x03)
|
|
33
|
-
{
|
|
34
|
-
SpessaSynthInfo("%cGM2 system on", consoleColors.info);
|
|
35
|
-
this.system = "gm2";
|
|
36
|
-
}
|
|
37
|
-
else
|
|
38
|
-
{
|
|
39
|
-
SpessaSynthInfo("%cGM system off, defaulting to GS", consoleColors.info);
|
|
40
|
-
this.system = "gs";
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
break;
|
|
44
|
-
|
|
45
|
-
// realtime
|
|
46
|
-
// https://midi.org/midi-1-0-universal-system-exclusive-messages
|
|
47
64
|
case 0x7F:
|
|
48
|
-
|
|
65
|
+
switch(messageData[2])
|
|
49
66
|
{
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
case 0x04:
|
|
68
|
+
let cents
|
|
69
|
+
// device control
|
|
70
|
+
switch(messageData[3])
|
|
71
|
+
{
|
|
72
|
+
case 0x01:
|
|
73
|
+
// main volume
|
|
74
|
+
const vol = messageData[5] << 7 | messageData[4];
|
|
75
|
+
this.setMIDIVolume(vol / 16384);
|
|
76
|
+
SpessaSynthInfo(`%cMaster Volume. Volume: %c${vol}`,
|
|
77
|
+
consoleColors.info,
|
|
78
|
+
consoleColors.value);
|
|
79
|
+
break;
|
|
80
|
+
|
|
81
|
+
case 0x02:
|
|
82
|
+
// main balance
|
|
83
|
+
// midi spec page 62
|
|
84
|
+
const balance = messageData[5] << 7 | messageData[4];
|
|
85
|
+
const pan = (balance - 8192) / 8192;
|
|
86
|
+
this.setMasterPan(pan);
|
|
87
|
+
SpessaSynthInfo(`%cMaster Pan. Pan: %c${pan}`,
|
|
88
|
+
consoleColors.info,
|
|
89
|
+
consoleColors.value);
|
|
90
|
+
break;
|
|
73
91
|
|
|
74
92
|
|
|
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
|
-
|
|
93
|
+
case 0x03:
|
|
94
|
+
// fine tuning
|
|
95
|
+
const tuningValue = ((messageData[5] << 7) | messageData[6]) - 8192;
|
|
96
|
+
cents = Math.floor(tuningValue / 81.92); // [-100;+99] cents range
|
|
97
|
+
this.setMasterTuning(cents);
|
|
98
|
+
SpessaSynthInfo(`%cMaster Fine Tuning. Cents: %c${cents}`,
|
|
99
|
+
consoleColors.info,
|
|
100
|
+
consoleColors.value);
|
|
101
|
+
break;
|
|
102
|
+
|
|
103
|
+
case 0x04:
|
|
104
|
+
// coarse tuning
|
|
105
|
+
// lsb is ignored
|
|
106
|
+
const semitones = messageData[5] - 64;
|
|
107
|
+
cents = semitones * 100;
|
|
108
|
+
this.setMasterTuning(cents);
|
|
109
|
+
SpessaSynthInfo(`%cMaster Coarse Tuning. Cents: %c${cents}`,
|
|
110
|
+
consoleColors.info,
|
|
111
|
+
consoleColors.value)
|
|
112
|
+
break;
|
|
113
|
+
|
|
114
|
+
default:
|
|
115
|
+
SpessaSynthWarn(
|
|
116
|
+
`%cUnrecognized MIDI Device Control Real-time message: %c${arrayToHexString(messageData)}`,
|
|
117
|
+
consoleColors.warn,
|
|
118
|
+
consoleColors.unrecognized);
|
|
119
|
+
}
|
|
120
|
+
break;
|
|
121
|
+
|
|
122
|
+
case 0x09:
|
|
123
|
+
// gm system related
|
|
124
|
+
if(messageData[3] === 0x01)
|
|
125
|
+
{
|
|
126
|
+
SpessaSynthInfo("%cGM system on", consoleColors.info);
|
|
127
|
+
this.system = "gm";
|
|
128
|
+
}
|
|
129
|
+
else if(messageData[3] === 0x03)
|
|
130
|
+
{
|
|
131
|
+
SpessaSynthInfo("%cGM2 system on", consoleColors.info);
|
|
132
|
+
this.system = "gm2";
|
|
133
|
+
}
|
|
134
|
+
else
|
|
135
|
+
{
|
|
136
|
+
SpessaSynthInfo("%cGM system off, defaulting to GS", consoleColors.info);
|
|
137
|
+
this.system = "gs";
|
|
138
|
+
}
|
|
139
|
+
break;
|
|
140
|
+
|
|
141
|
+
// MIDI Tuning standard
|
|
142
|
+
// https://midi.org/midi-tuning-updated-specification
|
|
143
|
+
case 0x08:
|
|
144
|
+
switch(messageData[3])
|
|
145
|
+
{
|
|
146
|
+
// single note change
|
|
147
|
+
case 0x02:
|
|
148
|
+
const tuningProgram = messageData[4];
|
|
149
|
+
const numberOfChanges = messageData[5];
|
|
150
|
+
const keys = [];
|
|
151
|
+
let currentMessageIndex = 6
|
|
152
|
+
for (let i = 0; i < numberOfChanges; i++)
|
|
153
|
+
{
|
|
154
|
+
keys.push(messageData[currentMessageIndex]);
|
|
155
|
+
currentMessageIndex++;
|
|
156
|
+
}
|
|
157
|
+
const frequencies = [];
|
|
158
|
+
for (let i = 0; i < numberOfChanges; i++)
|
|
159
|
+
{
|
|
160
|
+
frequencies.push(calculateFrequency(
|
|
161
|
+
messageData[currentMessageIndex++],
|
|
162
|
+
messageData[currentMessageIndex++],
|
|
163
|
+
messageData[currentMessageIndex++],
|
|
164
|
+
));
|
|
165
|
+
}
|
|
166
|
+
console.log(tuningProgram, numberOfChanges, keys, frequencies)
|
|
167
|
+
break;
|
|
168
|
+
|
|
169
|
+
// octave tuning (1 byte)
|
|
170
|
+
// and octave tuning (2 bytes)
|
|
171
|
+
case 0x09:
|
|
172
|
+
case 0x08:
|
|
173
|
+
// get tuning:
|
|
174
|
+
const newOctaveTuning = new Int8Array(12);
|
|
175
|
+
// start from bit 7
|
|
176
|
+
if(messageData[3] === 0x08)
|
|
177
|
+
{
|
|
178
|
+
// 1 byte tuning: 0 is -64 cents, 64 is 0, 127 is +63
|
|
179
|
+
for (let i = 0; i < 12; i++)
|
|
180
|
+
{
|
|
181
|
+
newOctaveTuning[i] = messageData[7 + i] - 64;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
else
|
|
185
|
+
{
|
|
186
|
+
// 2 byte tuning. Like fine tune: 0 is -100 cents, 8192 is 0 cents, 16383 is +100 cents
|
|
187
|
+
for (let i = 0; i < 24; i += 2)
|
|
188
|
+
{
|
|
189
|
+
const tuning = ((messageData[7 + i] << 7) | messageData[8 + i]) - 8192;
|
|
190
|
+
newOctaveTuning[i / 2] = Math.floor(tuning / 81.92); // map to [-100;+99] cents
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// apply to channels (ordered from 0)
|
|
194
|
+
// bit 1: 14 and 15
|
|
195
|
+
if((messageData[4] & 1) === 1)
|
|
196
|
+
{
|
|
197
|
+
this.setOctaveTuning(14 + channelOffset, newOctaveTuning);
|
|
198
|
+
}
|
|
199
|
+
if(((messageData[4] >> 1) & 1) === 1)
|
|
200
|
+
{
|
|
201
|
+
this.setOctaveTuning(15 + channelOffset, newOctaveTuning);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// bit 2: channels 7 to 13
|
|
205
|
+
for (let i = 0; i < 7; i++)
|
|
206
|
+
{
|
|
207
|
+
const bit = (messageData[5] >> i) & 1;
|
|
208
|
+
if(bit === 1)
|
|
209
|
+
{
|
|
210
|
+
this.setOctaveTuning(7 + i + channelOffset, newOctaveTuning);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// bit 3: channels 0 to 16
|
|
215
|
+
for (let i = 0; i < 7; i++)
|
|
216
|
+
{
|
|
217
|
+
const bit = (messageData[6] >> i) & 1;
|
|
218
|
+
if(bit === 1)
|
|
219
|
+
{
|
|
220
|
+
this.setOctaveTuning(i + channelOffset, newOctaveTuning);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
SpessaSynthInfo(`%cMIDI Octave Scale ${
|
|
225
|
+
messageData[3] === 0x08 ? "(1 byte)" : "(2 bytes)"
|
|
226
|
+
} tuning via Tuning: %c${newOctaveTuning.join(" ")}`,
|
|
227
|
+
consoleColors.info,
|
|
228
|
+
consoleColors.value);
|
|
229
|
+
break;
|
|
230
|
+
|
|
231
|
+
default:
|
|
232
|
+
SpessaSynthWarn(
|
|
233
|
+
`%cUnrecognized MIDI Tuning standard message: %c${arrayToHexString(messageData)}`,
|
|
234
|
+
consoleColors.warn,
|
|
235
|
+
consoleColors.unrecognized)
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
break;
|
|
239
|
+
|
|
240
|
+
default:
|
|
241
|
+
SpessaSynthWarn(
|
|
242
|
+
`%cUnrecognized MIDI Realtime/non realtime message: %c${arrayToHexString(messageData)}`,
|
|
243
|
+
consoleColors.warn,
|
|
244
|
+
consoleColors.unrecognized)
|
|
245
|
+
|
|
109
246
|
}
|
|
110
247
|
break;
|
|
111
248
|
|
|
@@ -192,4 +192,19 @@ export function polyPressure(channel, midiNote, pressure)
|
|
|
192
192
|
midiNote: midiNote,
|
|
193
193
|
pressure: pressure
|
|
194
194
|
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Sets the octave tuning for a given channel
|
|
199
|
+
* @this {SpessaSynthProcessor}
|
|
200
|
+
* @param channel {number} usually 0-15: the channel to use
|
|
201
|
+
* @param tuning {Int8Array} LENGTH of 12!!! relative cent tuning. min -128 max 127.
|
|
202
|
+
*/
|
|
203
|
+
export function setOctaveTuning(channel, tuning)
|
|
204
|
+
{
|
|
205
|
+
if(tuning.length !== 12)
|
|
206
|
+
{
|
|
207
|
+
throw new Error("Tuning is not the length of 12.");
|
|
208
|
+
}
|
|
209
|
+
this.workletProcessorChannels[channel].channelOctaveTuning = tuning;
|
|
195
210
|
}
|
|
@@ -60,7 +60,8 @@ export function renderVoice(
|
|
|
60
60
|
let cents = voice.modulatedGenerators[generatorTypes.fineTune]
|
|
61
61
|
+ channel.customControllers[customControllers.channelTuning]
|
|
62
62
|
+ channel.customControllers[customControllers.channelTransposeFine]
|
|
63
|
-
+ channel.customControllers[customControllers.masterTuning]
|
|
63
|
+
+ channel.customControllers[customControllers.masterTuning]
|
|
64
|
+
+ channel.channelOctaveTuning[voice.midiNote % 12];
|
|
64
65
|
let semitones = voice.modulatedGenerators[generatorTypes.coarseTune]
|
|
65
66
|
+ channel.customControllers[customControllers.channelTuningSemitones];
|
|
66
67
|
|