spessasynth_core 4.3.8 → 4.3.10

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/dist/index.d.ts CHANGED
@@ -1982,17 +1982,17 @@ interface ChannelMIDIParameter {
1982
1982
  */
1983
1983
  randomPan: boolean;
1984
1984
  /**
1985
- * Assign mode for the channel.
1986
- * `ASSIGN MODE` is the parameter that determines how voice assignment will be handled when sounds overlap on identical note numbers in the same channel (i.e., repeatedly struck notes).
1987
- * This is initialized to a mode suitable for each Part, so for general purposes there is no need to change this.
1985
+ * Assign mode for the channel:
1986
+ * - `0` - A new note will kill the previous one if it is still playing.
1987
+ * - Any other value - A new note will not kill the previous notes (default).
1988
1988
  *
1989
- * - 0 - Single: If the same note is played multiple times in succession, the previously-sounding note will be completely silenced, and then the new note will be sounded.
1990
- * - 1 - LimitedMulti: If the same note is played multiple times in succession, the previously-sounding note will be continued to a certain extent even after the new note is sounded. (Default setting)
1991
- * - 2 - FullMulti: If the same note is played multiple times in succession, the previously-sounding note(s) will continue sounding for their natural length even after the new note is sounded.
1989
+ * While GS and XG differentiate 1 (Limited Multi for GS/Multi for XG) and 2 (Full Multi for GS/Inst (for Drum)),
1990
+ * SpessaSynth treats them both as full Multi. (no note killing is performed)
1992
1991
  *
1993
- * SpessaSynth treats LimitedMulti like FullMulti.
1994
- * Essentially Limited and Full are normal
1995
- * and Single is like `monophonicRetrigger` system parameter.
1992
+ * This may be useful for emulating SC-55 hi-hat cutoff or MSGS note cutoff.
1993
+ *
1994
+ * Refer to [SC-8850 Owner's Manual](https://cdn.roland.com/assets/media/pdf/SC-8850_OM.pdf), page 238 for more description.
1995
+ * Note that `SAME NOTE NUMBER KEY ON ASSIGN` in XG is also recognized as assign mode.
1996
1996
  */
1997
1997
  assignMode: number;
1998
1998
  /**
@@ -2217,6 +2217,8 @@ interface GlobalSystemParameter {
2217
2217
  /**
2218
2218
  * The master pan.
2219
2219
  * From -1 (left) to 1 (right). 0 is center.
2220
+ *
2221
+ * This uses the cosine panning law, so the perceived loudness remains constant as the pan changes.
2220
2222
  */
2221
2223
  pan: number;
2222
2224
  /**
@@ -2827,13 +2829,17 @@ interface GlobalMIDIParameter {
2827
2829
  */
2828
2830
  fineTune: number;
2829
2831
  /**
2830
- * The master gain.
2831
- * From 0 to any number. 1 is 100% volume.
2832
+ * The master volume.
2833
+ * From 0 (silent) to 1 (full volume).
2834
+ *
2835
+ * This differs from the `gain` system parameter in that it is squared internally.
2832
2836
  */
2833
- gain: number;
2837
+ volume: number;
2834
2838
  /**
2835
2839
  * The master pan.
2836
2840
  * From -1 (left) to 1 (right). 0 is center.
2841
+ *
2842
+ * This uses the cosine panning law, so the perceived loudness remains constant as the pan changes.
2837
2843
  */
2838
2844
  pan: number;
2839
2845
  }
@@ -4218,7 +4224,7 @@ declare class BasicMIDI {
4218
4224
  * Do not call in audioWorkletGlobalScope as it uses TextDecoder.
4219
4225
  * RMIDI encoding overrides the provided encoding.
4220
4226
  */
4221
- getExtraMetadata(encoding?: string): any[];
4227
+ getExtraMetadata(encoding?: string): string[];
4222
4228
  /**
4223
4229
  * Sets a given RMIDI info value.
4224
4230
  * @param infoType The type to set.
package/dist/index.js CHANGED
@@ -1698,9 +1698,9 @@ var MIDIUtils = class MIDIUtils {
1698
1698
  ])];
1699
1699
  }
1700
1700
  }
1701
- case "gain": switch (system) {
1701
+ case "volume": switch (system) {
1702
1702
  default: {
1703
- const gainValue = Math.floor(Math.sqrt(value) * 16383);
1703
+ const gainValue = Math.floor(value * 16383);
1704
1704
  return [MIDIUtils.deviceControlMessage(ticks, 1, [gainValue & 127, gainValue >> 7 & 127])];
1705
1705
  }
1706
1706
  case "xg": {
@@ -1747,7 +1747,11 @@ var MIDIUtils = class MIDIUtils {
1747
1747
  case "keyShift": return MIDIMessage.registeredParameter(ticks, channel, RegisteredParameterTypes.coarseTuning, value + 64 << 7);
1748
1748
  case "fineTune": return MIDIMessage.registeredParameter(ticks, channel, RegisteredParameterTypes.fineTuning, Math.floor(value * 81.92 + 8192));
1749
1749
  case "randomPan": return system === "xg" ? [MIDIUtils.xgMessage(ticks, 8, channel, 14, [0])] : [MIDIUtils.gsMessage(ticks, 64, 16 | gsChannel, 28, [0])];
1750
- case "assignMode": return [MIDIUtils.gsMessage(ticks, 64, 16 | gsChannel, 20, [value])];
1750
+ case "assignMode": switch (system) {
1751
+ default:
1752
+ case "gs": return [MIDIUtils.gsMessage(ticks, 64, 16 | gsChannel, 20, [value])];
1753
+ case "xg": return [MIDIUtils.xgMessage(ticks, 8, channel, 6, [value])];
1754
+ }
1751
1755
  case "efxAssign": return [MIDIUtils.gsMessage(ticks, 64, 16 | gsChannel, 34, [value])];
1752
1756
  case "cc1": return [MIDIUtils.gsMessage(ticks, 64, 16 | gsChannel, 31, [value])];
1753
1757
  case "cc2": return [MIDIUtils.gsMessage(ticks, 64, 16 | gsChannel, 32, [value])];
@@ -1924,7 +1928,7 @@ var MIDIUtils = class MIDIUtils {
1924
1928
  const value = (syx[5] << 7 | syx[4]) / 16383;
1925
1929
  return {
1926
1930
  type: "Global MIDI Param",
1927
- parameter: "gain",
1931
+ parameter: "volume",
1928
1932
  value: Math.pow(value, 2)
1929
1933
  };
1930
1934
  }
@@ -1949,11 +1953,12 @@ var MIDIUtils = class MIDIUtils {
1949
1953
  default: return OTHER;
1950
1954
  case 1: switch (syx[9]) {
1951
1955
  default: return OTHER;
1952
- case 1:
1953
- case 2: return { type: "Reverb Param" };
1956
+ case 0:
1957
+ case 1: return { type: "Reverb Param" };
1954
1958
  }
1955
1959
  case 2: switch (syx[9]) {
1956
1960
  default: return OTHER;
1961
+ case 0:
1957
1962
  case 1:
1958
1963
  case 2:
1959
1964
  case 3:
@@ -2015,7 +2020,6 @@ var MIDIUtils = class MIDIUtils {
2015
2020
  if (a1 === 3 && a2 === 0) return { type: "Variation Param" };
2016
2021
  if (a1 === 8) {
2017
2022
  const channel = a2;
2018
- if (channel >= 16) return OTHER;
2019
2023
  switch (a3) {
2020
2024
  default: return OTHER;
2021
2025
  case 1: return {
@@ -2041,6 +2045,12 @@ var MIDIUtils = class MIDIUtils {
2041
2045
  controller: data === 1 ? MIDIControllers.polyModeOn : MIDIControllers.monoModeOn,
2042
2046
  value: 0
2043
2047
  };
2048
+ case 6: return {
2049
+ type: "Channel MIDI Param",
2050
+ channel,
2051
+ parameter: "assignMode",
2052
+ value: data
2053
+ };
2044
2054
  case 7: return {
2045
2055
  type: "Drums On",
2046
2056
  channel,
@@ -2145,7 +2155,7 @@ var MIDIUtils = class MIDIUtils {
2145
2155
  };
2146
2156
  case 4: return {
2147
2157
  type: "Global MIDI Param",
2148
- parameter: "gain",
2158
+ parameter: "volume",
2149
2159
  value: data / 127
2150
2160
  };
2151
2161
  case 5: return {
@@ -2160,7 +2170,8 @@ var MIDIUtils = class MIDIUtils {
2160
2170
  };
2161
2171
  case 127:
2162
2172
  switch (data) {
2163
- case 0: return {
2173
+ case 0:
2174
+ case 1: return {
2164
2175
  type: "Global MIDI Param",
2165
2176
  parameter: "system",
2166
2177
  value: "gs"
@@ -2174,12 +2185,8 @@ var MIDIUtils = class MIDIUtils {
2174
2185
  return OTHER;
2175
2186
  }
2176
2187
  if (a1 === 65) return { type: "Drum Setup" };
2177
- if (a1 !== 64) return OTHER;
2178
- if (a2 === 0 && a3 === 5) return {
2179
- type: "Global MIDI Param",
2180
- parameter: "keyShift",
2181
- value: data - 64
2182
- };
2188
+ if (a1 !== 64 && a1 !== 80) return OTHER;
2189
+ const channelOffset = a1 === 80 ? 16 : 0;
2183
2190
  if (a2 === 1) {
2184
2191
  if (a3 >= 48 && a3 <= 55) return { type: "Reverb Param" };
2185
2192
  if (a3 >= 56 && a3 <= 64) return { type: "Chorus Param" };
@@ -2187,7 +2194,7 @@ var MIDIUtils = class MIDIUtils {
2187
2194
  }
2188
2195
  if (a2 === 3 && a3 >= 0 && a3 <= 127) return { type: "Insertion Param" };
2189
2196
  if (a2 >> 4 === 1) {
2190
- const channel = MIDIUtils.syxToChannel(a2 & 15);
2197
+ const channel = MIDIUtils.syxToChannel(a2 & 15) + channelOffset;
2191
2198
  switch (a3) {
2192
2199
  default: return OTHER;
2193
2200
  case 0: return {
@@ -2329,7 +2336,7 @@ var MIDIUtils = class MIDIUtils {
2329
2336
  }
2330
2337
  }
2331
2338
  if (a2 >> 4 === 4) {
2332
- const channel = MIDIUtils.syxToChannel(a2 & 15);
2339
+ const channel = MIDIUtils.syxToChannel(a2 & 15) + channelOffset;
2333
2340
  switch (a3) {
2334
2341
  default: return OTHER;
2335
2342
  case 0:
@@ -2389,6 +2396,7 @@ function correctBankOffsetInternal(mid, bankOffset, soundBank) {
2389
2396
  default: return;
2390
2397
  case "Drums On": {
2391
2398
  const sysexChannel = syx.channel + portOffset;
2399
+ if (!channels[sysexChannel]) return;
2392
2400
  channels[sysexChannel].isDrum = syx.isDrum;
2393
2401
  return;
2394
2402
  }
@@ -2403,6 +2411,7 @@ function correctBankOffsetInternal(mid, bankOffset, soundBank) {
2403
2411
  break;
2404
2412
  case "Controller Change": {
2405
2413
  const t = mid.tracks[trackNum];
2414
+ if (syx.channel >= 16) return;
2406
2415
  const newEvent = MIDIMessage.controllerChange(e.ticks, syx.channel, syx.controller, syx.value);
2407
2416
  t.events[eventIndexes[trackNum]] = newEvent;
2408
2417
  e = newEvent;
@@ -2410,6 +2419,7 @@ function correctBankOffsetInternal(mid, bankOffset, soundBank) {
2410
2419
  break;
2411
2420
  }
2412
2421
  case "Program Change": {
2422
+ if (syx.channel >= 16) return;
2413
2423
  const t = mid.tracks[trackNum];
2414
2424
  const newEvent = MIDIMessage.programChange(e.ticks, syx.channel, syx.value);
2415
2425
  t.events[eventIndexes[trackNum]] = newEvent;
@@ -2838,16 +2848,19 @@ function getUsedProgramsAndKeys(mid, soundBank) {
2838
2848
  case "Channel MIDI Param":
2839
2849
  if (syx.parameter === "keyShift") {
2840
2850
  const ch = channels[syx.channel];
2851
+ if (!ch) break;
2841
2852
  ch.keyShift = ch.isDrum ? 0 : syx.value;
2842
2853
  }
2843
2854
  break;
2844
2855
  case "Drums On": {
2845
2856
  const sysexChannel = syx.channel + channelOffset;
2857
+ if (!channels[sysexChannel]) break;
2846
2858
  channels[sysexChannel].isDrum = syx.isDrum;
2847
2859
  break;
2848
2860
  }
2849
2861
  case "Program Change": {
2850
2862
  const ch = channels[syx.channel + channelOffset];
2863
+ if (!ch) break;
2851
2864
  ch.preset = soundBank.getPreset({
2852
2865
  bankMSB: ch.bankMSB,
2853
2866
  bankLSB: ch.bankLSB,
@@ -2857,9 +2870,10 @@ function getUsedProgramsAndKeys(mid, soundBank) {
2857
2870
  break;
2858
2871
  }
2859
2872
  case "Controller Change": {
2860
- const sysexChannel = syx.channel + channelOffset;
2861
- if (syx.controller === MIDIControllers.bankSelectLSB) channels[sysexChannel].bankLSB = syx.value;
2862
- else if (syx.controller === MIDIControllers.bankSelect) channels[sysexChannel].bankMSB = syx.value;
2873
+ const ch = channels[syx.channel + channelOffset];
2874
+ if (!ch) break;
2875
+ if (syx.controller === MIDIControllers.bankSelectLSB) ch.bankLSB = syx.value;
2876
+ else if (syx.controller === MIDIControllers.bankSelect) ch.bankMSB = syx.value;
2863
2877
  }
2864
2878
  }
2865
2879
  }
@@ -3307,7 +3321,7 @@ function modifyMIDIInternal(midi, opts) {
3307
3321
  }
3308
3322
  if (syx.parameter === "fineTune") {
3309
3323
  const syxStatus = channelStatuses[syx.channel + portOffset];
3310
- if (syxStatus.isFirstNoteOn && syxChannel) {
3324
+ if (syxStatus && syxStatus.isFirstNoteOn && syxChannel) {
3311
3325
  const newTune = syxStatus.fineTune + syx.value;
3312
3326
  syxStatus.currentKeyShift = Math.trunc(newTune / 100);
3313
3327
  syxStatus.fineTune = newTune % 100;
@@ -5225,6 +5239,7 @@ function setTimeToInternal(time, ticks = void 0) {
5225
5239
  break;
5226
5240
  case "Controller Change": {
5227
5241
  const { controller, value, channel } = analyzed;
5242
+ if (channel >= channelsToSave) break;
5228
5243
  if (this._midiData.isMultiPort && track.channels.size === 0) break;
5229
5244
  if (controller === MIDIControllers.resetAllControllers) {
5230
5245
  resetAllControllers(channel);
@@ -14032,7 +14047,7 @@ var MIDIChannel = class {
14032
14047
  this.currentTuning = this._drumChannel ? channelSystem.fineTune : globalSystem.fineTune + globalMIDI.fineTune + channelSystem.fineTune + channelMIDI.fineTune;
14033
14048
  const currentPanNormalized = globalSystem.pan + globalMIDI.pan + channelSystem.pan;
14034
14049
  this.currentPan = currentPanNormalized * 500;
14035
- this.currentGain = SPESSASYNTH_GAIN_FACTOR * globalSystem.gain * globalMIDI.gain * channelSystem.gain;
14050
+ this.currentGain = SPESSASYNTH_GAIN_FACTOR * globalSystem.gain * Math.pow(globalMIDI.volume, 2) * channelSystem.gain;
14036
14051
  }
14037
14052
  /**
14038
14053
  * Sets the channel to a given MIDI patch.
@@ -14341,8 +14356,7 @@ function universalSystemExclusive(syx, channelOffset = 0) {
14341
14356
  break;
14342
14357
  case 1: {
14343
14358
  const vol = syx[5] << 7 | syx[4];
14344
- const gain = Math.pow(vol / 16383, 2);
14345
- this.setMIDIParameter("gain", gain);
14359
+ this.setMIDIParameter("volume", vol / 16383);
14346
14360
  SpessaLog.gmInfo("Master Volume", vol);
14347
14361
  break;
14348
14362
  }
@@ -14508,12 +14522,17 @@ function rolandSystemExclusive(syx, channelOffset = 0) {
14508
14522
  const a2 = syx[5];
14509
14523
  const a3 = syx[6];
14510
14524
  const data = Math.min(syx[7], 127);
14511
- if (a1 === 0 && a2 === 0 && a3 === 127 && data === 0) {
14525
+ if (a1 === 0 && a2 === 0 && a3 === 127 && (data === 0 || data === 1)) {
14526
+ if (data === 1) {
14527
+ SpessaLog.gsInfo("Mode", "Double Module");
14528
+ while (this.midiChannels.length < 32) this.createMIDIChannel(true);
14529
+ }
14512
14530
  SpessaLog.coolInfo("MIDI System", "Roland GS");
14513
14531
  this.reset("gs");
14514
14532
  return;
14515
14533
  }
14516
- if (a1 === 64) {
14534
+ if (a1 === 64 || a1 === 80) {
14535
+ if (a1 === 80) channelOffset += 16;
14517
14536
  if (a2 === 0) {
14518
14537
  switch (a3) {
14519
14538
  case 0: {
@@ -14524,7 +14543,7 @@ function rolandSystemExclusive(syx, channelOffset = 0) {
14524
14543
  }
14525
14544
  case 4:
14526
14545
  SpessaLog.gsInfo("Master Volume", data);
14527
- this.setMIDIParameter("gain", data / 127);
14546
+ this.setMIDIParameter("volume", data / 127);
14528
14547
  break;
14529
14548
  case 5: {
14530
14549
  const transpose = data - 64;
@@ -14872,6 +14891,7 @@ function rolandSystemExclusive(syx, channelOffset = 0) {
14872
14891
  if (a2 >> 4 === 1) {
14873
14892
  const channel = MIDIUtils.syxToChannel(a2 & 15) + channelOffset;
14874
14893
  const ch = this.midiChannels[channel];
14894
+ if (!ch) SpessaLog.gsFail(`Patch Parameter for ${channel}`, syx, "Invalid channel number");
14875
14895
  switch (a3) {
14876
14896
  default:
14877
14897
  SpessaLog.gsFail(`Patch Part Parameter for ${channel}`, [a3]);
@@ -15046,7 +15066,7 @@ function rolandSystemExclusive(syx, channelOffset = 0) {
15046
15066
  SpessaLog.gsFail("Patch Parameter", syx);
15047
15067
  return;
15048
15068
  }
15049
- if (a1 === 65) {
15069
+ if (a1 === 65 || a1 === 81) {
15050
15070
  if (this.systemParameters.drumLock) return;
15051
15071
  const map = (a2 >> 4) + 1;
15052
15072
  const drumKey = a3;
@@ -15057,7 +15077,8 @@ function rolandSystemExclusive(syx, channelOffset = 0) {
15057
15077
  return;
15058
15078
  case 0: {
15059
15079
  const patchName = readBinaryString(syx, 12, 7);
15060
- SpessaLog.gsInfo(`Patch Name for MAP${map}`, patchName);
15080
+ SpessaLog.gsInfo(`Drum Map Name for MAP${map}`, patchName);
15081
+ this.callEvent("displayMessage", [...syx]);
15061
15082
  break;
15062
15083
  }
15063
15084
  case 1: {
@@ -15132,12 +15153,10 @@ function rolandSystemExclusive(syx, channelOffset = 0) {
15132
15153
  return;
15133
15154
  }
15134
15155
  case 69:
15135
- if (syx[4] === 16) if (syx[5] === 0) this.callEvent("displayMessage", [...syx]);
15136
- else if (syx[5] === 1) this.callEvent("displayMessage", [...syx]);
15137
- else SpessaLog.gsFail("Display Data", syx);
15156
+ if (syx[4] === 16) this.callEvent("displayMessage", [...syx]);
15138
15157
  return;
15139
15158
  case 22: if (syx[4] === 16) {
15140
- this.setMIDIParameter("gain", syx[7] / 100);
15159
+ this.setMIDIParameter("volume", syx[7] / 100);
15141
15160
  SpessaLog.coolInfo("Roland Master Volume Control", syx[7]);
15142
15161
  return;
15143
15162
  } else SpessaLog.unsupported("Roland", syx);
@@ -15171,12 +15190,12 @@ function yamahaSystemExclusive(syx, channelOffset = 0) {
15171
15190
  }
15172
15191
  break;
15173
15192
  case 4:
15174
- this.setMIDIParameter("gain", data / 127);
15193
+ this.setMIDIParameter("volume", data / 127);
15175
15194
  SpessaLog.xgInfo("Master Volume", data);
15176
15195
  break;
15177
15196
  case 5: {
15178
15197
  const vol = 127 - data;
15179
- this.setMIDIParameter("gain", vol / 127);
15198
+ this.setMIDIParameter("volume", vol / 127);
15180
15199
  SpessaLog.xgInfo("Master Attenuation", data);
15181
15200
  break;
15182
15201
  }
@@ -15236,6 +15255,10 @@ function yamahaSystemExclusive(syx, channelOffset = 0) {
15236
15255
  SpessaLog.xgInfo(`Mono/poly on ${channel}`, poly ? "POLY" : "MONO");
15237
15256
  break;
15238
15257
  }
15258
+ case 6:
15259
+ ch.setMIDIParameter("assignMode", data);
15260
+ SpessaLog.xgInfo(`Same Note Number Key On Assign on ${channel}`, data);
15261
+ break;
15239
15262
  case 7: {
15240
15263
  const drums = data !== 0;
15241
15264
  ch.setDrums(drums);
@@ -19751,7 +19774,7 @@ var SpessaSynthDelay = class {
19751
19774
  //#endregion
19752
19775
  //#region src/synthesizer/audio_engine/parameters/midi.ts
19753
19776
  const DEFAULT_GLOBAL_MIDI_PARAMETERS = {
19754
- gain: 1,
19777
+ volume: 1,
19755
19778
  pan: 0,
19756
19779
  keyShift: 0,
19757
19780
  fineTune: 0,
@@ -20149,7 +20172,7 @@ var SynthesizerCore = class {
20149
20172
  reset(system = "gs") {
20150
20173
  this.callEvent("reset", system);
20151
20174
  this.setMIDIParameter("system", system);
20152
- this.setMIDIParameter("gain", 1);
20175
+ this.setMIDIParameter("volume", 1);
20153
20176
  this.setMIDIParameter("pan", 0);
20154
20177
  this.setMIDIParameter("keyShift", 0);
20155
20178
  this.setMIDIParameter("fineTune", 0);