spessasynth_lib 3.23.3 → 3.23.8

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.
Files changed (40) hide show
  1. package/@types/midi_parser/basic_midi.d.ts +41 -21
  2. package/@types/midi_parser/midi_builder.d.ts +0 -4
  3. package/@types/midi_parser/midi_data.d.ts +38 -20
  4. package/@types/sequencer/sequencer.d.ts +2 -1
  5. package/@types/soundfont/basic_soundfont/basic_sample.d.ts +5 -0
  6. package/@types/soundfont/basic_soundfont/basic_soundfont.d.ts +8 -0
  7. package/@types/soundfont/basic_soundfont/modulator.d.ts +2 -1
  8. package/@types/soundfont/dls/articulator_converter.d.ts +9 -0
  9. package/@types/soundfont/dls/dls_sample.d.ts +0 -5
  10. package/@types/soundfont/dls/dls_sources.d.ts +5 -0
  11. package/@types/soundfont/read_sf2/samples.d.ts +0 -1
  12. package/@types/synthetizer/synth_event_handler.d.ts +5 -0
  13. package/@types/utils/byte_functions/string.d.ts +5 -0
  14. package/midi_parser/basic_midi.js +269 -109
  15. package/midi_parser/midi_builder.js +1 -106
  16. package/midi_parser/midi_data.js +140 -90
  17. package/midi_parser/midi_loader.js +2 -0
  18. package/midi_parser/rmidi_writer.js +4 -4
  19. package/package.json +1 -1
  20. package/sequencer/sequencer.js +3 -2
  21. package/soundfont/basic_soundfont/basic_sample.js +15 -6
  22. package/soundfont/basic_soundfont/basic_soundfont.js +64 -0
  23. package/soundfont/basic_soundfont/modulator.js +4 -2
  24. package/soundfont/basic_soundfont/riff_chunk.js +1 -1
  25. package/soundfont/basic_soundfont/write_dls/art2.js +17 -0
  26. package/soundfont/basic_soundfont/write_dls/ins.js +2 -2
  27. package/soundfont/basic_soundfont/write_dls/modulator_converter.js +12 -6
  28. package/soundfont/basic_soundfont/write_dls/rgn2.js +30 -25
  29. package/soundfont/basic_soundfont/write_dls/wave.js +8 -3
  30. package/soundfont/basic_soundfont/write_dls/write_dls.js +3 -2
  31. package/soundfont/basic_soundfont/write_dls/wsmp.js +2 -2
  32. package/soundfont/dls/articulator_converter.js +12 -10
  33. package/soundfont/dls/dls_sample.js +9 -8
  34. package/soundfont/dls/dls_sources.js +36 -1
  35. package/soundfont/dls/read_articulation.js +3 -15
  36. package/soundfont/dls/read_instrument.js +3 -14
  37. package/synthetizer/synth_event_handler.js +38 -1
  38. package/synthetizer/synth_soundfont_manager.js +0 -0
  39. package/synthetizer/worklet_processor.min.js +10 -10
  40. package/utils/byte_functions/string.js +9 -0
@@ -5,118 +5,167 @@
5
5
  export class MidiData
6
6
  {
7
7
  /**
8
- * @param midi {BasicMIDI}
8
+ * The time division of the sequence, representing the number of ticks per beat.
9
+ * @type {number}
10
+ */
11
+ timeDivision = 0;
12
+
13
+ /**
14
+ * The duration of the sequence, in seconds.
15
+ * @type {number}
16
+ */
17
+ duration = 0;
18
+
19
+ /**
20
+ * The tempo changes in the sequence, ordered from the last change to the first.
21
+ * Each change is represented by an object with a tick position and a tempo value in beats per minute.
22
+ * @type {{ticks: number, tempo: number}[]}
23
+ */
24
+ tempoChanges = [{ ticks: 0, tempo: 120 }];
25
+
26
+ /**
27
+ * A string containing the copyright information for the MIDI sequence.
28
+ * @type {string}
29
+ */
30
+ copyright = "";
31
+
32
+ /**
33
+ * The number of tracks in the MIDI sequence.
34
+ * @type {number}
35
+ */
36
+ tracksAmount = 0;
37
+
38
+ /**
39
+ * An array containing the lyrics of the sequence, stored as binary chunks (Uint8Array).
40
+ * @type {Uint8Array[]}
41
+ */
42
+ lyrics = [];
43
+
44
+ /**
45
+ * The tick position of the first note-on event in the MIDI sequence.
46
+ * @type {number}
47
+ */
48
+ firstNoteOn = 0;
49
+
50
+ /**
51
+ * The MIDI key range used in the sequence, represented by a minimum and maximum note value.
52
+ * @type {{min: number, max: number}}
53
+ */
54
+ keyRange = { min: 0, max: 127 };
55
+
56
+ /**
57
+ * The tick position of the last voice event (such as note-on, note-off, or control change) in the sequence.
58
+ * @type {number}
59
+ */
60
+ lastVoiceEventTick = 0;
61
+
62
+ /**
63
+ * An array of MIDI port numbers used by each track in the sequence.
64
+ * @type {number[]}
65
+ */
66
+ midiPorts = [0];
67
+
68
+ /**
69
+ * An array of channel offsets for each MIDI port, using the SpessaSynth method.
70
+ * @type {number[]}
71
+ */
72
+ midiPortChannelOffsets = [0];
73
+
74
+ /**
75
+ * A list of sets, where each set contains the MIDI channels used by each track in the sequence.
76
+ * @type {Set<number>[]}
77
+ */
78
+ usedChannelsOnTrack = [];
79
+
80
+ /**
81
+ * The loop points (in ticks) of the sequence, including both start and end points.
82
+ * @type {{start: number, end: number}}
83
+ */
84
+ loop = { start: 0, end: 0 };
85
+
86
+ /**
87
+ * The name of the MIDI sequence.
88
+ * @type {string}
89
+ */
90
+ midiName = "";
91
+
92
+ /**
93
+ * A boolean indicating if the sequence's name is the same as the file name.
94
+ * @type {boolean}
95
+ */
96
+ midiNameUsesFileName = false;
97
+
98
+ /**
99
+ * The file name of the MIDI sequence, if provided by the MIDI class.
100
+ * @type {string}
101
+ */
102
+ fileName = "";
103
+
104
+ /**
105
+ * The raw, encoded MIDI name, represented as a Uint8Array.
106
+ * @type {Uint8Array}
107
+ */
108
+ rawMidiName = undefined;
109
+
110
+ /**
111
+ * A boolean indicating if the MIDI file contains an embedded soundfont.
112
+ * If the embedded soundfont is undefined, this will be false.
113
+ * @type {boolean}
114
+ */
115
+ isEmbedded = false;
116
+
117
+ /**
118
+ * The MIDI file's format, which can be 0, 1, or 2, indicating the type of the MIDI file.
119
+ * @type {number}
120
+ */
121
+ format = 0;
122
+
123
+ /**
124
+ * The RMID (Resource Interchangeable MIDI) info data, if the file is RMID formatted.
125
+ * Otherwise, this field is undefined.
126
+ * @type {Object<string, IndexedByteArray>}
127
+ */
128
+ RMIDInfo = {};
129
+
130
+ /**
131
+ * The bank offset used for RMID files.
132
+ * @type {number}
133
+ */
134
+ bankOffset = 0;
135
+
136
+ /**
137
+ * Constructor that copies data from a BasicMIDI instance, except for tracks and embeddedSoundFont.
138
+ * @param {BasicMIDI} midi - The BasicMIDI instance to copy data from.
9
139
  */
10
140
  constructor(midi)
11
141
  {
12
- /**
13
- * The time division of the sequence
14
- * @type {number}
15
- */
16
142
  this.timeDivision = midi.timeDivision;
17
- /**
18
- * The duration of the sequence, in seconds
19
- * @type {number}
20
- */
21
143
  this.duration = midi.duration;
22
- /**
23
- * The tempo changes in the sequence, ordered from last to first
24
- * @type {{ticks: number, tempo: number}[]}
25
- */
26
144
  this.tempoChanges = midi.tempoChanges;
27
- /**
28
- * Contains the copyright strings
29
- * @type {string}
30
- */
31
145
  this.copyright = midi.copyright;
32
-
33
- /**
34
- * The amount of tracks in the sequence
35
- * @type {number}
36
- */
37
146
  this.tracksAmount = midi.tracksAmount;
38
-
39
- /**
40
- * The lyrics of the sequence as binary chunks
41
- * @type {Uint8Array[]}
42
- */
43
147
  this.lyrics = midi.lyrics;
44
-
45
148
  this.firstNoteOn = midi.firstNoteOn;
46
-
47
- /**
48
- * The MIDI's key range
49
- * @type {{min: number, max: number}}
50
- */
51
149
  this.keyRange = midi.keyRange;
52
-
53
- /**
54
- * The last voice (note on, off, cc change etc.) event tick
55
- * @type {number}
56
- */
57
150
  this.lastVoiceEventTick = midi.lastVoiceEventTick;
58
-
59
- /**
60
- * Midi port numbers for each track
61
- * @type {number[]}
62
- */
63
151
  this.midiPorts = midi.midiPorts;
64
-
65
- /**
66
- * Channel offsets for each port, using the SpessaSynth method
67
- * @type {number[]}
68
- */
69
152
  this.midiPortChannelOffsets = midi.midiPortChannelOffsets;
70
-
71
- /**
72
- * All channels that each track uses
73
- * @type {Set<number>[]}
74
- */
75
153
  this.usedChannelsOnTrack = midi.usedChannelsOnTrack;
76
-
77
- /**
78
- * The loop points (in ticks) of the sequence
79
- * @type {{start: number, end: number}}
80
- */
81
154
  this.loop = midi.loop;
82
-
83
- /**
84
- * The sequence's name
85
- * @type {string}
86
- */
87
155
  this.midiName = midi.midiName;
88
-
89
- /**
90
- * The file name of the sequence, if provided in the MIDI class
91
- * @type {string}
92
- */
156
+ this.midiNameUsesFileName = midi.midiNameUsesFileName;
93
157
  this.fileName = midi.fileName;
94
-
95
- /**
96
- * The raw, encoded MIDI name.
97
- * @type {Uint8Array}
98
- */
99
158
  this.rawMidiName = midi.rawMidiName;
100
-
101
- /**
102
- * Indicates if the midi has an embedded soundfont
103
- * @type {boolean}
104
- */
105
- this.isEmbedded = midi.embeddedSoundFont !== undefined;
106
-
107
- /**
108
- * The RMID Info data if RMID, otherwise undefined
109
- * @type {Object<string, IndexedByteArray>}
110
- */
159
+ this.format = midi.format;
111
160
  this.RMIDInfo = midi.RMIDInfo;
112
- /**
113
- * The bank offset for RMIDI
114
- * @type {number}
115
- */
116
161
  this.bankOffset = midi.bankOffset;
162
+
163
+ // Set isEmbedded based on the presence of an embeddedSoundFont
164
+ this.isEmbedded = midi.embeddedSoundFont !== undefined;
117
165
  }
118
166
  }
119
167
 
168
+
120
169
  /**
121
170
  *
122
171
  * @type {MidiData}
@@ -143,6 +192,7 @@ export const DUMMY_MIDI_DATA = {
143
192
  timeDivision: 0,
144
193
  keyRange: { min: 0, max: 127 },
145
194
  isEmbedded: false,
146
- RMIDInfo: undefined,
147
- bankOffset: 0
195
+ RMIDInfo: {},
196
+ bankOffset: 0,
197
+ midiNameUsesFileName: false
148
198
  };
@@ -582,6 +582,7 @@ class MIDI extends BasicMIDI
582
582
 
583
583
  this.fileName = fileName;
584
584
  this.midiName = this.midiName.trim();
585
+ this.midiNameUsesFileName = false;
585
586
  // if midiName is "", use the file name
586
587
  if (this.midiName.length === 0)
587
588
  {
@@ -590,6 +591,7 @@ class MIDI extends BasicMIDI
590
591
  consoleColors.info
591
592
  );
592
593
  this.midiName = formatTitle(fileName);
594
+ this.midiNameUsesFileName = true;
593
595
  // encode it too
594
596
  this.rawMidiName = new Uint8Array(this.midiName.length);
595
597
  for (let i = 0; i < this.midiName.length; i++)
@@ -1,7 +1,7 @@
1
1
  import { combineArrays, IndexedByteArray } from "../utils/indexed_array.js";
2
2
  import { writeMIDIFile } from "./midi_writer.js";
3
3
  import { writeRIFFOddSize } from "../soundfont/basic_soundfont/riff_chunk.js";
4
- import { getStringBytes } from "../utils/byte_functions/string.js";
4
+ import { getStringBytes, getStringBytesZero } from "../utils/byte_functions/string.js";
5
5
  import { messageTypes, midiControllers, MidiMessage } from "./midi_message.js";
6
6
  import { DEFAULT_PERCUSSION } from "../synthetizer/synthetizer.js";
7
7
  import { getGsOn } from "./midi_editor.js";
@@ -409,7 +409,7 @@ export function writeRMIDI(
409
409
  minute: "numeric"
410
410
  });
411
411
  infoContent.push(
412
- writeRIFFOddSize(RMIDINFOChunks.creationDate, getStringBytes(today), true)
412
+ writeRIFFOddSize(RMIDINFOChunks.creationDate, getStringBytesZero(today), true)
413
413
  );
414
414
  }
415
415
  // comment
@@ -471,7 +471,7 @@ export function writeRMIDI(
471
471
  // use midi copyright if possible
472
472
  const copyright = mid.copyright.length > 0 ? mid.copyright : DEFAULT_COPYRIGHT;
473
473
  infoContent.push(
474
- writeRIFFOddSize(RMIDINFOChunks.copyright, getStringBytes(copyright))
474
+ writeRIFFOddSize(RMIDINFOChunks.copyright, getStringBytesZero(copyright))
475
475
  );
476
476
  }
477
477
 
@@ -488,7 +488,7 @@ export function writeRMIDI(
488
488
  encoding = FORCED_ENCODING;
489
489
  }
490
490
  // encoding
491
- infoContent.push(writeRIFFOddSize(RMIDINFOChunks.encoding, getStringBytes(encoding)));
491
+ infoContent.push(writeRIFFOddSize(RMIDINFOChunks.encoding, getStringBytesZero(encoding)));
492
492
 
493
493
  // combine and write out
494
494
  const infodata = combineArrays(infoContent);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spessasynth_lib",
3
- "version": "3.23.3",
3
+ "version": "3.23.8",
4
4
  "description": "MIDI and SoundFont2/DLS library with no compromises",
5
5
  "browser": "index.js",
6
6
  "types": "@types/index.d.ts",
@@ -8,6 +8,7 @@ import {
8
8
  } from "./worklet_sequencer/sequencer_message.js";
9
9
  import { SpessaSynthWarn } from "../utils/loggin.js";
10
10
  import { DUMMY_MIDI_DATA, MidiData } from "../midi_parser/midi_data.js";
11
+ import { BasicMIDI } from "../midi_parser/basic_midi.js";
11
12
 
12
13
  /**
13
14
  * sequencer.js
@@ -95,7 +96,7 @@ export class Sequencer
95
96
  this.absoluteStartTime = this.synth.currentTime;
96
97
 
97
98
  /**
98
- * @type {function(MIDI)}
99
+ * @type {function(BasicMIDI)}
99
100
  * @private
100
101
  */
101
102
  this._getMIDIResolve = undefined;
@@ -466,7 +467,7 @@ export class Sequencer
466
467
  case WorkletSequencerReturnMessageType.getMIDI:
467
468
  if (this._getMIDIResolve)
468
469
  {
469
- this._getMIDIResolve(messageData);
470
+ this._getMIDIResolve(BasicMIDI.copyFrom(messageData));
470
471
  }
471
472
  }
472
473
  }
@@ -87,6 +87,12 @@ export class BasicSample
87
87
  * @type {number}
88
88
  */
89
89
  this.useCount = 0;
90
+
91
+ /**
92
+ * The sample's audio data
93
+ * @type {Float32Array}
94
+ */
95
+ this.sampleData = undefined;
90
96
  }
91
97
 
92
98
  /**
@@ -94,9 +100,14 @@ export class BasicSample
94
100
  */
95
101
  getRawData()
96
102
  {
97
- const e = new Error("Not implemented");
98
- e.name = "NotImplementedError";
99
- throw e;
103
+ const uint8 = new Uint8Array(this.sampleData.length * 2);
104
+ for (let i = 0; i < this.sampleData.length; i++)
105
+ {
106
+ const sample = Math.floor(this.sampleData[i] * 32768);
107
+ uint8[i * 2] = sample & 0xFF; // lower byte
108
+ uint8[i * 2 + 1] = (sample >> 8) & 0xFF; // upper byte
109
+ }
110
+ return uint8;
100
111
  }
101
112
 
102
113
  /**
@@ -133,8 +144,6 @@ export class BasicSample
133
144
  */
134
145
  getAudioData()
135
146
  {
136
- const e = new Error("Not implemented");
137
- e.name = "NotImplementedError";
138
- throw e;
147
+ return this.sampleData;
139
148
  }
140
149
  }
@@ -3,6 +3,11 @@ import { consoleColors } from "../../utils/other.js";
3
3
  import { write } from "./write_sf2/write.js";
4
4
  import { defaultModulators, Modulator } from "./modulator.js";
5
5
  import { writeDLS } from "./write_dls/write_dls.js";
6
+ import { BasicSample } from "./basic_sample.js";
7
+ import { BasicInstrumentZone, BasicPresetZone } from "./basic_zones.js";
8
+ import { Generator, generatorTypes } from "./generator.js";
9
+ import { BasicInstrument } from "./basic_instrument.js";
10
+ import { BasicPreset } from "./basic_preset.js";
6
11
 
7
12
  class BasicSoundFont
8
13
  {
@@ -75,6 +80,65 @@ class BasicSoundFont
75
80
  return new BasicSoundFont({ presets: presets, info: mainSf.soundFontInfo });
76
81
  }
77
82
 
83
+ /**
84
+ * Creates a simple soundfont with one saw wave preset.
85
+ * @returns {ArrayBufferLike}
86
+ */
87
+ static getDummySoundfontFile()
88
+ {
89
+ const font = new BasicSoundFont();
90
+ const sample = new BasicSample(
91
+ "Saw",
92
+ 44100,
93
+ 65,
94
+ 20,
95
+ 0,
96
+ 0,
97
+ 0,
98
+ 127
99
+ );
100
+ sample.sampleData = new Float32Array(128);
101
+ for (let i = 0; i < 128; i++)
102
+ {
103
+ sample.sampleData[i] = (i / 128) * 2 - 1;
104
+ }
105
+ font.samples.push(sample);
106
+
107
+ const gZone = new BasicInstrumentZone();
108
+ gZone.isGlobal = true;
109
+ gZone.generators.push(new Generator(generatorTypes.initialAttenuation, 375));
110
+ gZone.generators.push(new Generator(generatorTypes.releaseVolEnv, -1000));
111
+ gZone.generators.push(new Generator(generatorTypes.sampleModes, 1));
112
+
113
+ const zone1 = new BasicInstrumentZone();
114
+ zone1.sample = sample;
115
+
116
+ const zone2 = new BasicInstrumentZone();
117
+ zone2.sample = sample;
118
+ zone2.generators.push(new Generator(generatorTypes.fineTune, -9));
119
+
120
+
121
+ const inst = new BasicInstrument();
122
+ inst.instrumentName = "Saw Wave";
123
+ inst.instrumentZones.push(gZone);
124
+ inst.instrumentZones.push(zone1);
125
+ inst.instrumentZones.push(zone2);
126
+ font.instruments.push(inst);
127
+
128
+ const pZone = new BasicPresetZone();
129
+ pZone.instrument = inst;
130
+
131
+ const preset = new BasicPreset(font.defaultModulators);
132
+ preset.presetName = "Saw Wave";
133
+ preset.presetZones.push(pZone);
134
+ font.presets.push(preset);
135
+
136
+ font.soundFontInfo["ifil"] = "2.1";
137
+ font.soundFontInfo["isng"] = "EMU8000";
138
+ font.soundFontInfo["INAM"] = "Dummy";
139
+ return font.write().buffer;
140
+ }
141
+
78
142
  removeUnusedElements()
79
143
  {
80
144
  this.instruments.forEach(i =>
@@ -108,14 +108,16 @@ export class Modulator
108
108
  /**
109
109
  * @param mod1 {Modulator}
110
110
  * @param mod2 {Modulator}
111
+ * @param checkAmount {boolean}
111
112
  * @returns {boolean}
112
113
  */
113
- static isIdentical(mod1, mod2)
114
+ static isIdentical(mod1, mod2, checkAmount = false)
114
115
  {
115
116
  return (mod1.sourceEnum === mod2.sourceEnum)
116
117
  && (mod1.modulatorDestination === mod2.modulatorDestination)
117
118
  && (mod1.secondarySourceEnum === mod2.secondarySourceEnum)
118
- && (mod1.transformType === mod2.transformType);
119
+ && (mod1.transformType === mod2.transformType)
120
+ && (!checkAmount || (mod1.transformAmount === mod2.transformAmount));
119
121
  }
120
122
 
121
123
  /**
@@ -101,7 +101,7 @@ export function writeRIFFOddSize(header, data, addZeroByte = false, isList = fal
101
101
  if (addZeroByte)
102
102
  {
103
103
  const tempData = new Uint8Array(data.length + 1);
104
- tempData.set(data, 0);
104
+ tempData.set(data);
105
105
  data = tempData;
106
106
  }
107
107
  let offset = 8;
@@ -5,6 +5,13 @@ import { Generator, generatorTypes } from "../generator.js";
5
5
  import { writeDword } from "../../../utils/byte_functions/little_endian.js";
6
6
  import { consoleColors } from "../../../utils/other.js";
7
7
  import { SpessaSynthInfo, SpessaSynthWarn } from "../../../utils/loggin.js";
8
+ import { Modulator } from "../modulator.js";
9
+ import {
10
+ DEFAULT_DLS_CHORUS,
11
+ DEFAULT_DLS_REVERB,
12
+ DLS_1_NO_VIBRATO_MOD,
13
+ DLS_1_NO_VIBRATO_PRESSURE
14
+ } from "../../dls/dls_sources.js";
8
15
 
9
16
  const invalidGeneratorTypes = new Set([
10
17
  generatorTypes.sampleModes,
@@ -108,6 +115,16 @@ export function writeArticulator(zone)
108
115
  */
109
116
  const modulators = zone.modulators.reduce((arrs, m) =>
110
117
  {
118
+ // do not write the default DLS modulators
119
+ if (
120
+ Modulator.isIdentical(m, DEFAULT_DLS_CHORUS, true) ||
121
+ Modulator.isIdentical(m, DEFAULT_DLS_REVERB, true) ||
122
+ Modulator.isIdentical(m, DLS_1_NO_VIBRATO_MOD, true) ||
123
+ Modulator.isIdentical(m, DLS_1_NO_VIBRATO_PRESSURE, true)
124
+ )
125
+ {
126
+ return arrs;
127
+ }
111
128
  const art = getDLSArticulatorFromSf2Modulator(m);
112
129
  if (art !== undefined)
113
130
  {
@@ -3,7 +3,7 @@ import { combineZones } from "./combine_zones.js";
3
3
  import { writeRIFFOddSize } from "../riff_chunk.js";
4
4
  import { writeDword } from "../../../utils/byte_functions/little_endian.js";
5
5
  import { writeDLSRegion } from "./rgn2.js";
6
- import { getStringBytes } from "../../../utils/byte_functions/string.js";
6
+ import { getStringBytesZero } from "../../../utils/byte_functions/string.js";
7
7
  import { writeArticulator } from "./art2.js";
8
8
  import { SpessaSynthGroupCollapsed, SpessaSynthGroupEnd } from "../../../utils/loggin.js";
9
9
  import { consoleColors } from "../../../utils/other.js";
@@ -84,7 +84,7 @@ export function writeIns(preset)
84
84
  // writeINFO
85
85
  const inam = writeRIFFOddSize(
86
86
  "INAM",
87
- getStringBytes(preset.presetName)
87
+ getStringBytesZero(preset.presetName)
88
88
  );
89
89
  const info = writeRIFFOddSize(
90
90
  "INFO",
@@ -257,7 +257,7 @@ export function getDLSArticulatorFromSf2Modulator(mod)
257
257
  return undefined;
258
258
  }
259
259
  let source = getDLSSourceFromSf2Source(mod.sourceUsesCC, mod.sourceIndex);
260
- let sourceCurve = mod.sourceCurveType;
260
+ let sourceTransformType = mod.sourceCurveType;
261
261
  let sourceBipolar = mod.sourcePolarity;
262
262
  let sourceDirection = mod.sourceDirection;
263
263
  if (source === undefined)
@@ -265,8 +265,13 @@ export function getDLSArticulatorFromSf2Modulator(mod)
265
265
  SpessaSynthWarn(`Invalid source: ${mod.sourceIndex}, CC: ${mod.sourceUsesCC}`);
266
266
  return undefined;
267
267
  }
268
+ // attenuation is the opposite of gain. Invert
269
+ if (mod.modulatorDestination === generatorTypes.initialAttenuation)
270
+ {
271
+ sourceDirection = sourceDirection === 1 ? 0 : 1;
272
+ }
268
273
  let control = getDLSSourceFromSf2Source(mod.secSrcUsesCC, mod.secSrcIndex);
269
- let controlCurve = mod.secSrcCurveType;
274
+ let controlTransformType = mod.secSrcCurveType;
270
275
  let controlBipolar = mod.secSrcPolarity;
271
276
  let controlDirection = mod.secSrcDirection;
272
277
  if (control === undefined)
@@ -288,12 +293,12 @@ export function getDLSArticulatorFromSf2Modulator(mod)
288
293
  amt = specialCombo.amt;
289
294
  // move source to control
290
295
  control = source;
291
- controlCurve = sourceCurve;
296
+ controlTransformType = sourceTransformType;
292
297
  controlBipolar = sourceBipolar;
293
298
  controlDirection = sourceDirection;
294
299
 
295
300
  // set source as static as it's either: env, lfo or keynum
296
- sourceCurve = modulatorCurveTypes.linear;
301
+ sourceTransformType = modulatorCurveTypes.linear;
297
302
  sourceBipolar = specialCombo.isBipolar ? 1 : 0;
298
303
  sourceDirection = 0;
299
304
  source = specialCombo.source;
@@ -307,11 +312,12 @@ export function getDLSArticulatorFromSf2Modulator(mod)
307
312
 
308
313
  // source curve type maps to desfont curve type in section 2.10, table 9
309
314
  let transform = 0;
310
- transform |= controlCurve << 4;
315
+ transform |= controlTransformType << 4;
311
316
  transform |= controlBipolar << 8;
312
317
  transform |= controlDirection << 9;
313
318
 
314
- transform |= sourceCurve << 10;
319
+ // use use the source curve in output transform
320
+ transform |= sourceTransformType;
315
321
  transform |= sourceBipolar << 14;
316
322
  transform |= sourceDirection << 15;
317
323
  return new Articulator(
@@ -14,14 +14,14 @@ import { writeArticulator } from "./art2.js";
14
14
  export function writeDLSRegion(zone, globalZone)
15
15
  {
16
16
  // region header
17
- const rgnhData = new IndexedByteArray(14);
17
+ const rgnhData = new IndexedByteArray(12);
18
18
  // keyRange
19
19
  writeWord(rgnhData, Math.max(zone.keyRange.min, 0));
20
20
  writeWord(rgnhData, zone.keyRange.max);
21
21
  // velRange
22
22
  writeWord(rgnhData, Math.max(zone.velRange.min, 0));
23
23
  writeWord(rgnhData, zone.velRange.max);
24
- // fusOptions
24
+ // fusOptions: 0 it seems
25
25
  writeWord(rgnhData, 0);
26
26
  // keyGroup (exclusive class)
27
27
  const exclusive = zone.getGeneratorValue(generatorTypes.exclusiveClass, 0);
@@ -71,21 +71,22 @@ export function writeDLSRegion(zone, globalZone)
71
71
  const wlnkData = new IndexedByteArray(12);
72
72
  writeWord(wlnkData, 0); // fusOptions
73
73
  writeWord(wlnkData, 0); // usPhaseGroup
74
- let sampleType = 0;
75
- switch (zone.sample.sampleType)
76
- {
77
- default:
78
- case 1:
79
- case 4:
80
- // mono/left
81
- sampleType = 0;
82
- break;
83
-
84
- case 2:
85
- // right
86
- sampleType = 1;
87
- }
88
- writeDword(wlnkData, sampleType); // ulChannel
74
+ // let sampleType = 0;
75
+ // switch (zone.sample.sampleType)
76
+ // {
77
+ // default:
78
+ // case 1:
79
+ // case 4:
80
+ // // mono/left
81
+ // sampleType = 0;
82
+ // break;
83
+ //
84
+ // case 2:
85
+ // // right
86
+ // sampleType = 1;
87
+ // }
88
+ // 1 means that the first bit is on so mono/left
89
+ writeDword(wlnkData, 1); // ulChannel
89
90
  writeDword(wlnkData, this.samples.indexOf(zone.sample)); // ulTableIndex
90
91
  const wlnk = writeRIFFOddSize(
91
92
  "wlnk",
@@ -93,14 +94,18 @@ export function writeDLSRegion(zone, globalZone)
93
94
  );
94
95
 
95
96
  // art
96
- const art2 = writeArticulator(zone);
97
-
98
- const lar2 = writeRIFFOddSize(
99
- "lar2",
100
- art2,
101
- false,
102
- true
103
- );
97
+ let lar2 = new IndexedByteArray(0);
98
+ if (zone.modulators.length + zone.generators.length > 0)
99
+ {
100
+ const art2 = writeArticulator(zone);
101
+
102
+ lar2 = writeRIFFOddSize(
103
+ "lar2",
104
+ art2,
105
+ false,
106
+ true
107
+ );
108
+ }
104
109
 
105
110
  return writeRIFFOddSize(
106
111
  "rgn2",