spessasynth_lib 3.25.0 → 3.25.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/midi_parser/midi_editor.js +5 -16
- package/midi_parser/rmidi_writer.js +64 -33
- package/midi_parser/used_keys_loaded.js +47 -32
- package/package.json +1 -1
- package/sequencer/sequencer.js +0 -1
- package/synthetizer/synthetizer.js +48 -1
- package/synthetizer/worklet_processor.min.js +12 -12
- package/synthetizer/worklet_system/main_processor.js +17 -2
- package/synthetizer/worklet_system/message_protocol/handle_message.js +3 -0
- package/synthetizer/worklet_system/message_protocol/worklet_message.js +5 -6
- package/synthetizer/worklet_system/snapshot/channel_snapshot.js +1 -1
- package/synthetizer/worklet_system/snapshot/synthesizer_snapshot.js +1 -1
- package/synthetizer/worklet_system/worklet_methods/controller_control/controller_change.js +67 -131
- package/synthetizer/worklet_system/worklet_methods/controller_control/reset_controllers.js +2 -2
- package/synthetizer/worklet_system/worklet_methods/system_exclusive.js +6 -6
- package/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +35 -2
- package/utils/sysex_detector.js +46 -0
- package/utils/xg_hacks.js +128 -0
|
@@ -2,7 +2,11 @@ import { WorkletSequencer } from "../../sequencer/worklet_sequencer/worklet_sequ
|
|
|
2
2
|
import { SpessaSynthInfo } from "../../utils/loggin.js";
|
|
3
3
|
import { consoleColors } from "../../utils/other.js";
|
|
4
4
|
import { voiceKilling } from "./worklet_methods/stopping_notes/voice_killing.js";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
ALL_CHANNELS_OR_DIFFERENT_ACTION,
|
|
7
|
+
masterParameterType,
|
|
8
|
+
returnMessageType
|
|
9
|
+
} from "./message_protocol/worklet_message.js";
|
|
6
10
|
import { stbvorbis } from "../../externals/stbvorbis_sync/stbvorbis_sync.min.js";
|
|
7
11
|
import { VOLUME_ENVELOPE_SMOOTHING_FACTOR } from "./worklet_utilities/volume_envelope.js";
|
|
8
12
|
import { handleMessage } from "./message_protocol/handle_message.js";
|
|
@@ -181,7 +185,6 @@ class SpessaSynthProcessor extends AudioWorkletProcessor
|
|
|
181
185
|
* @type {SynthSystem}
|
|
182
186
|
*/
|
|
183
187
|
system = DEFAULT_SYNTH_MODE;
|
|
184
|
-
|
|
185
188
|
/**
|
|
186
189
|
* Current total voices amount
|
|
187
190
|
* @type {number}
|
|
@@ -300,6 +303,18 @@ class SpessaSynthProcessor extends AudioWorkletProcessor
|
|
|
300
303
|
return this.masterGain * this.midiVolume;
|
|
301
304
|
}
|
|
302
305
|
|
|
306
|
+
/**
|
|
307
|
+
* @param value {SynthSystem}
|
|
308
|
+
*/
|
|
309
|
+
setSystem(value)
|
|
310
|
+
{
|
|
311
|
+
this.system = value;
|
|
312
|
+
this.post({
|
|
313
|
+
messageType: returnMessageType.masterParameterChange,
|
|
314
|
+
messageData: [masterParameterType.midiSystem, this.system]
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
303
318
|
/**
|
|
304
319
|
* @param bank {number}
|
|
305
320
|
* @param program {number}
|
|
@@ -70,7 +70,8 @@ export const masterParameterType = {
|
|
|
70
70
|
mainVolume: 0,
|
|
71
71
|
masterPan: 1,
|
|
72
72
|
voicesCap: 2,
|
|
73
|
-
interpolationType: 3
|
|
73
|
+
interpolationType: 3,
|
|
74
|
+
midiSystem: 4
|
|
74
75
|
};
|
|
75
76
|
|
|
76
77
|
|
|
@@ -110,12 +111,11 @@ export const ALL_CHANNELS_OR_DIFFERENT_ACTION = -1;
|
|
|
110
111
|
*
|
|
111
112
|
* 0 - channel properties -> [...<ChannelProperty>] see message_sending.js line 29
|
|
112
113
|
* 1 - event call -> {eventName<string>, eventData:<the event's data>}
|
|
113
|
-
* 2 -
|
|
114
|
+
* 2 - master parameter change -> [parameter<masterParameterType>, value<string|number>]
|
|
114
115
|
* 3 - sequencer specific -> [messageType<WorkletSequencerReturnMessageType> messageData<any>] note: refer to sequencer_message.js
|
|
115
116
|
* 4 - synthesizer snapshot -> snapshot<SynthesizerSnapshot> note: refer to synthesizer_snapshot.js
|
|
116
117
|
* 5 - ready -> (no data)
|
|
117
118
|
* 6 - soundfontError -> errorMessage<string>
|
|
118
|
-
* 7 - idenfity -> version<string>
|
|
119
119
|
*/
|
|
120
120
|
|
|
121
121
|
/**
|
|
@@ -124,10 +124,9 @@ export const ALL_CHANNELS_OR_DIFFERENT_ACTION = -1;
|
|
|
124
124
|
export const returnMessageType = {
|
|
125
125
|
channelProperties: 0,
|
|
126
126
|
eventCall: 1,
|
|
127
|
-
|
|
127
|
+
masterParameterChange: 2,
|
|
128
128
|
sequencerSpecific: 3,
|
|
129
129
|
synthesizerSnapshot: 4,
|
|
130
130
|
ready: 5,
|
|
131
|
-
soundfontError: 6
|
|
132
|
-
identify: 7
|
|
131
|
+
soundfontError: 6
|
|
133
132
|
};
|
|
@@ -153,7 +153,7 @@ export class ChannelSnapshot
|
|
|
153
153
|
|
|
154
154
|
// restore preset and lock
|
|
155
155
|
channelObject.lockPreset = false;
|
|
156
|
-
channelObject.setBankSelect(channelSnapshot.bank);
|
|
156
|
+
channelObject.setBankSelect(channelSnapshot.bank, true);
|
|
157
157
|
channelObject.programChange(channelSnapshot.program);
|
|
158
158
|
channelObject.lockPreset = channelSnapshot.lockPreset;
|
|
159
159
|
}
|
|
@@ -94,7 +94,7 @@ export class SynthesizerSnapshot
|
|
|
94
94
|
static applySnapshot(workletProcessor, snapshot)
|
|
95
95
|
{
|
|
96
96
|
// restore system
|
|
97
|
-
workletProcessor.
|
|
97
|
+
workletProcessor.setSystem(snapshot.system);
|
|
98
98
|
|
|
99
99
|
// restore pan and volume
|
|
100
100
|
workletProcessor.setMasterGain(snapshot.mainVolume);
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import { SpessaSynthInfo } from "../../../../utils/loggin.js";
|
|
2
1
|
import { midiControllers } from "../../../../midi_parser/midi_message.js";
|
|
3
2
|
import { computeModulators } from "../../worklet_utilities/worklet_modulator.js";
|
|
4
|
-
import { consoleColors } from "../../../../utils/other.js";
|
|
5
3
|
import { channelConfiguration, dataEntryStates } from "../../worklet_utilities/controller_tables.js";
|
|
6
|
-
import { DEFAULT_PERCUSSION } from "../../../synth_constants.js";
|
|
7
4
|
|
|
8
5
|
/**
|
|
9
6
|
* @param controllerNumber {number}
|
|
@@ -56,144 +53,83 @@ export function controllerChange(controllerNumber, controllerValue, force = fals
|
|
|
56
53
|
this.midiControllers[controllerNumber] = controllerValue << 7;
|
|
57
54
|
|
|
58
55
|
// interpret special CCs
|
|
59
|
-
switch (controllerNumber)
|
|
60
56
|
{
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
switch (this.synth.system)
|
|
57
|
+
switch (controllerNumber)
|
|
58
|
+
{
|
|
59
|
+
case midiControllers.allNotesOff:
|
|
60
|
+
this.stopAllNotes();
|
|
61
|
+
break;
|
|
62
|
+
|
|
63
|
+
case midiControllers.allSoundOff:
|
|
64
|
+
this.stopAllNotes(true);
|
|
65
|
+
break;
|
|
66
|
+
|
|
67
|
+
// special case: bank select
|
|
68
|
+
case midiControllers.bankSelect:
|
|
69
|
+
if (!force)
|
|
75
70
|
{
|
|
76
|
-
|
|
77
|
-
// gm ignores bank select
|
|
78
|
-
SpessaSynthInfo(
|
|
79
|
-
`%cIgnoring the Bank Select (${controllerValue}), as the synth is in GM mode.`,
|
|
80
|
-
consoleColors.info
|
|
81
|
-
);
|
|
82
|
-
return;
|
|
83
|
-
|
|
84
|
-
case "xg":
|
|
85
|
-
// for xg, if msb is 120, 126 or 127, then it's drums
|
|
86
|
-
if (bankNr === 120 || bankNr === 126 || bankNr === 127)
|
|
87
|
-
{
|
|
88
|
-
this.setDrums(true);
|
|
89
|
-
}
|
|
90
|
-
else
|
|
91
|
-
{
|
|
92
|
-
// drums shall not be disabled on channel 9
|
|
93
|
-
if (this.channelNumber % 16 !== DEFAULT_PERCUSSION)
|
|
94
|
-
{
|
|
95
|
-
this.setDrums(false);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
break;
|
|
99
|
-
|
|
100
|
-
case "gm2":
|
|
101
|
-
if (bankNr === 120)
|
|
102
|
-
{
|
|
103
|
-
this.setDrums(true);
|
|
104
|
-
}
|
|
105
|
-
else
|
|
106
|
-
{
|
|
107
|
-
if (this.channelNumber % 16 !== DEFAULT_PERCUSSION)
|
|
108
|
-
{
|
|
109
|
-
this.setDrums(false);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
71
|
+
this.setBankSelect(controllerValue);
|
|
112
72
|
}
|
|
113
|
-
|
|
114
|
-
if (this.drumChannel)
|
|
73
|
+
else
|
|
115
74
|
{
|
|
116
|
-
|
|
117
|
-
bankNr = 128;
|
|
75
|
+
this.bank = controllerValue;
|
|
118
76
|
}
|
|
119
|
-
|
|
77
|
+
break;
|
|
78
|
+
|
|
79
|
+
case midiControllers.lsbForControl0BankSelect:
|
|
80
|
+
this.setBankSelect(controllerValue, false, true);
|
|
81
|
+
break;
|
|
82
|
+
|
|
83
|
+
// check for RPN and NPRN and data entry
|
|
84
|
+
case midiControllers.RPNLsb:
|
|
85
|
+
this.dataEntryState = dataEntryStates.RPFine;
|
|
86
|
+
break;
|
|
87
|
+
|
|
88
|
+
case midiControllers.RPNMsb:
|
|
89
|
+
this.dataEntryState = dataEntryStates.RPCoarse;
|
|
90
|
+
break;
|
|
91
|
+
|
|
92
|
+
case midiControllers.NRPNMsb:
|
|
93
|
+
this.dataEntryState = dataEntryStates.NRPCoarse;
|
|
94
|
+
break;
|
|
95
|
+
|
|
96
|
+
case midiControllers.NRPNLsb:
|
|
97
|
+
this.dataEntryState = dataEntryStates.NRPFine;
|
|
98
|
+
break;
|
|
99
|
+
|
|
100
|
+
case midiControllers.dataEntryMsb:
|
|
101
|
+
this.dataEntryCoarse(controllerValue);
|
|
102
|
+
break;
|
|
103
|
+
|
|
104
|
+
case midiControllers.lsbForControl6DataEntry:
|
|
105
|
+
this.dataEntryFine(controllerValue);
|
|
106
|
+
break;
|
|
107
|
+
|
|
108
|
+
case midiControllers.resetAllControllers:
|
|
109
|
+
this.resetControllersRP15Compliant();
|
|
110
|
+
break;
|
|
111
|
+
|
|
112
|
+
case midiControllers.sustainPedal:
|
|
113
|
+
if (controllerValue >= 64)
|
|
120
114
|
{
|
|
121
|
-
|
|
122
|
-
bankNr = this.getBankSelect();
|
|
115
|
+
this.holdPedal = true;
|
|
123
116
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
this.setBankSelect(bankNr);
|
|
127
|
-
break;
|
|
128
|
-
|
|
129
|
-
case midiControllers.lsbForControl0BankSelect:
|
|
130
|
-
if (this.synth.system === "xg")
|
|
131
|
-
{
|
|
132
|
-
if (!this.drumChannel)
|
|
117
|
+
else
|
|
133
118
|
{
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if (controllerValue !== 127)
|
|
119
|
+
this.holdPedal = false;
|
|
120
|
+
this.sustainedVoices.forEach(v =>
|
|
137
121
|
{
|
|
138
|
-
|
|
139
|
-
}
|
|
122
|
+
v.release();
|
|
123
|
+
});
|
|
124
|
+
this.sustainedVoices = [];
|
|
140
125
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
// check for RPN and NPRN and data entry
|
|
149
|
-
case midiControllers.RPNLsb:
|
|
150
|
-
this.dataEntryState = dataEntryStates.RPFine;
|
|
151
|
-
break;
|
|
152
|
-
|
|
153
|
-
case midiControllers.RPNMsb:
|
|
154
|
-
this.dataEntryState = dataEntryStates.RPCoarse;
|
|
155
|
-
break;
|
|
156
|
-
|
|
157
|
-
case midiControllers.NRPNMsb:
|
|
158
|
-
this.dataEntryState = dataEntryStates.NRPCoarse;
|
|
159
|
-
break;
|
|
160
|
-
|
|
161
|
-
case midiControllers.NRPNLsb:
|
|
162
|
-
this.dataEntryState = dataEntryStates.NRPFine;
|
|
163
|
-
break;
|
|
164
|
-
|
|
165
|
-
case midiControllers.dataEntryMsb:
|
|
166
|
-
this.dataEntryCoarse(controllerValue);
|
|
167
|
-
break;
|
|
168
|
-
|
|
169
|
-
case midiControllers.lsbForControl6DataEntry:
|
|
170
|
-
this.dataEntryFine(controllerValue);
|
|
171
|
-
break;
|
|
172
|
-
|
|
173
|
-
case midiControllers.resetAllControllers:
|
|
174
|
-
this.resetControllersRP15Compliant();
|
|
175
|
-
break;
|
|
176
|
-
|
|
177
|
-
case midiControllers.sustainPedal:
|
|
178
|
-
if (controllerValue >= 64)
|
|
179
|
-
{
|
|
180
|
-
this.holdPedal = true;
|
|
181
|
-
}
|
|
182
|
-
else
|
|
183
|
-
{
|
|
184
|
-
this.holdPedal = false;
|
|
185
|
-
this.sustainedVoices.forEach(v =>
|
|
186
|
-
{
|
|
187
|
-
v.release();
|
|
188
|
-
});
|
|
189
|
-
this.sustainedVoices = [];
|
|
190
|
-
}
|
|
191
|
-
break;
|
|
192
|
-
|
|
193
|
-
// default: just compute modulators
|
|
194
|
-
default:
|
|
195
|
-
this.voices.forEach(v => computeModulators(v, this.midiControllers, 1, controllerNumber));
|
|
196
|
-
break;
|
|
126
|
+
break;
|
|
127
|
+
|
|
128
|
+
// default: just compute modulators
|
|
129
|
+
default:
|
|
130
|
+
this.voices.forEach(v => computeModulators(v, this.midiControllers, 1, controllerNumber));
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
197
133
|
}
|
|
198
134
|
this.synth.callEvent("controllerchange", {
|
|
199
135
|
channel: this.channelNumber,
|
|
@@ -25,6 +25,7 @@ export function resetAllControllers(log = true)
|
|
|
25
25
|
SpessaSynthInfo("%cResetting all controllers!", consoleColors.info);
|
|
26
26
|
}
|
|
27
27
|
this.callEvent("allcontrollerreset", undefined);
|
|
28
|
+
this.setSystem(DEFAULT_SYNTH_MODE);
|
|
28
29
|
for (let channelNumber = 0; channelNumber < this.workletProcessorChannels.length; channelNumber++)
|
|
29
30
|
{
|
|
30
31
|
this.workletProcessorChannels[channelNumber].resetControllers();
|
|
@@ -38,7 +39,7 @@ export function resetAllControllers(log = true)
|
|
|
38
39
|
if (!ch.lockPreset)
|
|
39
40
|
{
|
|
40
41
|
ch.presetUsesOverride = true;
|
|
41
|
-
ch.setBankSelect(0);
|
|
42
|
+
ch.setBankSelect(0, true);
|
|
42
43
|
if (channelNumber % 16 === DEFAULT_PERCUSSION)
|
|
43
44
|
{
|
|
44
45
|
this.workletProcessorChannels[channelNumber].setPreset(this.drumPreset);
|
|
@@ -110,7 +111,6 @@ export function resetAllControllers(log = true)
|
|
|
110
111
|
}
|
|
111
112
|
|
|
112
113
|
this.setMIDIVolume(1);
|
|
113
|
-
this.system = DEFAULT_SYNTH_MODE;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
/**
|
|
@@ -148,17 +148,17 @@ export function systemExclusive(messageData, channelOffset = 0)
|
|
|
148
148
|
if (messageData[3] === 0x01)
|
|
149
149
|
{
|
|
150
150
|
SpessaSynthInfo("%cGM system on", consoleColors.info);
|
|
151
|
-
this.
|
|
151
|
+
this.setSystem("gm");
|
|
152
152
|
}
|
|
153
153
|
else if (messageData[3] === 0x03)
|
|
154
154
|
{
|
|
155
155
|
SpessaSynthInfo("%cGM2 system on", consoleColors.info);
|
|
156
|
-
this.
|
|
156
|
+
this.setSystem("gm2");
|
|
157
157
|
}
|
|
158
158
|
else
|
|
159
159
|
{
|
|
160
160
|
SpessaSynthInfo("%cGM system off, defaulting to GS", consoleColors.info);
|
|
161
|
-
this.
|
|
161
|
+
this.setSystem("gs");
|
|
162
162
|
}
|
|
163
163
|
break;
|
|
164
164
|
|
|
@@ -312,14 +312,14 @@ export function systemExclusive(messageData, channelOffset = 0)
|
|
|
312
312
|
// this is a GS reset
|
|
313
313
|
SpessaSynthInfo("%cGS Reset received!", consoleColors.info);
|
|
314
314
|
this.resetAllControllers(false);
|
|
315
|
-
this.
|
|
315
|
+
this.setSystem("gs");
|
|
316
316
|
}
|
|
317
317
|
else if (messageValue === 0x7F)
|
|
318
318
|
{
|
|
319
319
|
// GS mode off
|
|
320
320
|
SpessaSynthInfo("%cGS system off, switching to GM2", consoleColors.info);
|
|
321
321
|
this.resetAllControllers(false);
|
|
322
|
-
this.
|
|
322
|
+
this.setSystem("gm2");
|
|
323
323
|
}
|
|
324
324
|
return;
|
|
325
325
|
}
|
|
@@ -606,7 +606,7 @@ export function systemExclusive(messageData, channelOffset = 0)
|
|
|
606
606
|
case 0x7E:
|
|
607
607
|
SpessaSynthInfo("%cXG system on", consoleColors.info);
|
|
608
608
|
this.resetAllControllers(false);
|
|
609
|
-
this.
|
|
609
|
+
this.setSystem("xg");
|
|
610
610
|
break;
|
|
611
611
|
}
|
|
612
612
|
}
|
|
@@ -27,6 +27,7 @@ import { channelPressure } from "../worklet_methods/tuning_control/channel_press
|
|
|
27
27
|
import { pitchWheel } from "../worklet_methods/tuning_control/pitch_wheel.js";
|
|
28
28
|
import { setOctaveTuning } from "../worklet_methods/tuning_control/set_octave_tuning.js";
|
|
29
29
|
import { programChange } from "../worklet_methods/program_change.js";
|
|
30
|
+
import { parseBankSelect } from "../../../utils/xg_hacks.js";
|
|
30
31
|
|
|
31
32
|
/**
|
|
32
33
|
* This class represents a single MIDI Channel within the synthesizer.
|
|
@@ -232,13 +233,45 @@ class WorkletProcessorChannel
|
|
|
232
233
|
|
|
233
234
|
/**
|
|
234
235
|
* @param bank {number}
|
|
236
|
+
* @param force {boolean}
|
|
237
|
+
* @param isLSB {boolean}
|
|
235
238
|
*/
|
|
236
|
-
setBankSelect(bank)
|
|
239
|
+
setBankSelect(bank, force = false, isLSB = false)
|
|
237
240
|
{
|
|
238
|
-
if (
|
|
241
|
+
if (this.lockPreset)
|
|
242
|
+
{
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
if (force)
|
|
239
246
|
{
|
|
240
247
|
this.bank = bank;
|
|
241
248
|
}
|
|
249
|
+
else
|
|
250
|
+
{
|
|
251
|
+
const bankLogic = parseBankSelect(
|
|
252
|
+
this.getBankSelect(),
|
|
253
|
+
bank,
|
|
254
|
+
this.synth.system,
|
|
255
|
+
isLSB,
|
|
256
|
+
this.drumChannel,
|
|
257
|
+
this.channelNumber
|
|
258
|
+
);
|
|
259
|
+
this.bank = bankLogic.newBank;
|
|
260
|
+
switch (bankLogic.drumsStatus)
|
|
261
|
+
{
|
|
262
|
+
default:
|
|
263
|
+
case 0:
|
|
264
|
+
break;
|
|
265
|
+
|
|
266
|
+
case 1:
|
|
267
|
+
this.setDrums(false);
|
|
268
|
+
break;
|
|
269
|
+
|
|
270
|
+
case 2:
|
|
271
|
+
this.setDrums(true);
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
242
275
|
}
|
|
243
276
|
|
|
244
277
|
/**
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param e {MIDIMessage}
|
|
3
|
+
* @returns boolean
|
|
4
|
+
*/
|
|
5
|
+
export function isXGOn(e)
|
|
6
|
+
{
|
|
7
|
+
return e.messageData[0] === 0x43 && // Yamaha
|
|
8
|
+
e.messageData[2] === 0x4C && // XG ON
|
|
9
|
+
e.messageData[5] === 0x7E &&
|
|
10
|
+
e.messageData[6] === 0x00;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param e {MIDIMessage}
|
|
15
|
+
* @returns boolean
|
|
16
|
+
*/
|
|
17
|
+
export function isGSDrumsOn(e)
|
|
18
|
+
{
|
|
19
|
+
return e.messageData[0] === 0x41 && // roland
|
|
20
|
+
e.messageData[2] === 0x42 && // GS
|
|
21
|
+
e.messageData[3] === 0x12 && // GS
|
|
22
|
+
e.messageData[4] === 0x40 && // system parameter
|
|
23
|
+
(e.messageData[5] & 0x10) !== 0 && // part parameter
|
|
24
|
+
e.messageData[6] === 0x15; // drum pars
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param e {MIDIMessage}
|
|
29
|
+
* @returns boolean
|
|
30
|
+
*/
|
|
31
|
+
export function isGSOn(e)
|
|
32
|
+
{
|
|
33
|
+
return e.messageData[0] === 0x41 // roland
|
|
34
|
+
&& e.messageData[2] === 0x42 // GS
|
|
35
|
+
&& e.messageData[6] === 0x7F; // Mode set
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param e {MIDIMessage}
|
|
40
|
+
* @returns boolean
|
|
41
|
+
*/
|
|
42
|
+
export function isGMOn(e)
|
|
43
|
+
{
|
|
44
|
+
return e.messageData[0] === 0x7E // non realtime
|
|
45
|
+
&& e.messageData[2] === 0x09; // gm system
|
|
46
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { SpessaSynthInfo } from "./loggin.js";
|
|
2
|
+
import { consoleColors } from "./other.js";
|
|
3
|
+
import { DEFAULT_PERCUSSION } from "../synthetizer/synth_constants.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param bankNr {number}
|
|
7
|
+
* @returns {boolean}
|
|
8
|
+
*/
|
|
9
|
+
export function isXGDrums(bankNr)
|
|
10
|
+
{
|
|
11
|
+
return bankNr === 120 || bankNr === 126 || bankNr === 127;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Bank select hacks abstracted here
|
|
16
|
+
* @param bankBefore {number} the current bank number
|
|
17
|
+
* @param bank {number} the cc change bank number
|
|
18
|
+
* @param system {SynthSystem} MIDI system
|
|
19
|
+
* @param isLSB {boolean} is bank LSB?
|
|
20
|
+
* @param isDrums {boolean} is drum channel?
|
|
21
|
+
* @param channelNumber {number} channel number
|
|
22
|
+
* @returns {{
|
|
23
|
+
* newBank: number,
|
|
24
|
+
* drumsStatus: 0|1|2
|
|
25
|
+
* }} 0 - unchanged, 1 - OFF, 2 - ON
|
|
26
|
+
*/
|
|
27
|
+
export function parseBankSelect(bankBefore, bank, system, isLSB, isDrums, channelNumber)
|
|
28
|
+
{
|
|
29
|
+
// 64 means SFX in MSB, so it is allowed
|
|
30
|
+
let out = bankBefore;
|
|
31
|
+
let drumsStatus = 0;
|
|
32
|
+
const isValidMSB = b => isXGDrums(b) || b === 64;
|
|
33
|
+
if (isLSB)
|
|
34
|
+
{
|
|
35
|
+
if (system === "xg")
|
|
36
|
+
{
|
|
37
|
+
if (!isValidMSB(bank))
|
|
38
|
+
{
|
|
39
|
+
out = bank;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else if (system === "gm2")
|
|
43
|
+
{
|
|
44
|
+
out = bank;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else
|
|
48
|
+
{
|
|
49
|
+
let canSetBankSelect = true;
|
|
50
|
+
switch (system)
|
|
51
|
+
{
|
|
52
|
+
case "gm":
|
|
53
|
+
// gm ignores bank select
|
|
54
|
+
SpessaSynthInfo(
|
|
55
|
+
`%cIgnoring the Bank Select (${bank}), as the synth is in GM mode.`,
|
|
56
|
+
consoleColors.info
|
|
57
|
+
);
|
|
58
|
+
canSetBankSelect = false;
|
|
59
|
+
break;
|
|
60
|
+
|
|
61
|
+
case "xg":
|
|
62
|
+
canSetBankSelect = isValidMSB(bank);
|
|
63
|
+
// for xg, if msb is 120, 126 or 127, then it's drums
|
|
64
|
+
if (isXGDrums(bank))
|
|
65
|
+
{
|
|
66
|
+
drumsStatus = 2;
|
|
67
|
+
}
|
|
68
|
+
else
|
|
69
|
+
{
|
|
70
|
+
// drums shall not be disabled on channel 9
|
|
71
|
+
if (channelNumber % 16 !== DEFAULT_PERCUSSION)
|
|
72
|
+
{
|
|
73
|
+
drumsStatus = 1;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
break;
|
|
77
|
+
|
|
78
|
+
case "gm2":
|
|
79
|
+
if (bank === 120)
|
|
80
|
+
{
|
|
81
|
+
drumsStatus = 2;
|
|
82
|
+
}
|
|
83
|
+
else
|
|
84
|
+
{
|
|
85
|
+
if (channelNumber % 16 !== DEFAULT_PERCUSSION)
|
|
86
|
+
{
|
|
87
|
+
drumsStatus = 1;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (isDrums)
|
|
93
|
+
{
|
|
94
|
+
// 128 for percussion channel
|
|
95
|
+
bank = 128;
|
|
96
|
+
}
|
|
97
|
+
if (bank === 128 && !isDrums)
|
|
98
|
+
{
|
|
99
|
+
// if a channel is not for percussion, default to bank current
|
|
100
|
+
bank = bankBefore;
|
|
101
|
+
}
|
|
102
|
+
if (canSetBankSelect)
|
|
103
|
+
{
|
|
104
|
+
out = bank;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
newBank: out,
|
|
109
|
+
drumsStatus: drumsStatus
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @param msb {number}
|
|
116
|
+
* @param lsb {number}
|
|
117
|
+
*/
|
|
118
|
+
export function chooseBank(msb, lsb)
|
|
119
|
+
{
|
|
120
|
+
if (lsb > 0)
|
|
121
|
+
{
|
|
122
|
+
if (!isXGDrums(msb) && msb !== 64)
|
|
123
|
+
{
|
|
124
|
+
return lsb;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return msb;
|
|
128
|
+
}
|