spessasynth_lib 3.23.6 → 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.
@@ -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++)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spessasynth_lib",
3
- "version": "3.23.6",
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 =>
@@ -62,13 +62,14 @@ export class DLSSample extends BasicSample
62
62
  }
63
63
  return this.compressedData;
64
64
  }
65
- const uint8 = new Uint8Array(this.sampleData.length * 2);
66
- for (let i = 0; i < this.sampleData.length; i++)
67
- {
68
- const sample = Math.floor(this.sampleData[i] * 32768);
69
- uint8[i * 2] = sample & 0xFF; // lower byte
70
- uint8[i * 2 + 1] = (sample >> 8) & 0xFF; // upper byte
71
- }
72
- return uint8;
65
+ return super.getRawData();
66
+ // const uint8 = new Uint8Array(this.sampleData.length * 2);
67
+ // for (let i = 0; i < this.sampleData.length; i++)
68
+ // {
69
+ // const sample = Math.floor(this.sampleData[i] * 32768);
70
+ // uint8[i * 2] = sample & 0xFF; // lower byte
71
+ // uint8[i * 2 + 1] = (sample >> 8) & 0xFF; // upper byte
72
+ // }
73
+ // return uint8;
73
74
  }
74
75
  }
@@ -161,12 +161,33 @@ export class EventHandler
161
161
  {
162
162
  setTimeout(() =>
163
163
  {
164
- Object.values(this.events[name]).forEach(ev => ev(eventData));
164
+ Object.values(this.events[name]).forEach(ev =>
165
+ {
166
+ try
167
+ {
168
+ ev(eventData);
169
+ }
170
+ catch (e)
171
+ {
172
+ console.error(`Error while executing an event callback for ${name}:`, e);
173
+ }
174
+ });
165
175
  }, this.timeDelay * 1000);
166
176
  }
167
177
  else
168
178
  {
169
- Object.values(this.events[name]).forEach(ev => ev(eventData));
179
+ Object.values(this.events[name]).forEach(ev =>
180
+ {
181
+ try
182
+ {
183
+ ev(eventData);
184
+ }
185
+ catch (e)
186
+ {
187
+ console.error(`Error while executing an event callback for ${name}:`, e);
188
+ }
189
+ }
190
+ );
170
191
  }
171
192
  }
172
193
  }
File without changes