spessasynth_lib 3.24.26 → 3.24.28

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/README.md CHANGED
@@ -32,13 +32,28 @@ document.getElementById("button").onclick = async () =>
32
32
 
33
33
  ## Current Features
34
34
 
35
+ ### Numerous Format Support
36
+ Supported formats list:
37
+ - `.mid` - Standard MIDI File
38
+ - `.kar` - Soft Karaoke MIDI File
39
+ - `.sf2` - SoundFont2 File
40
+ - `.sf3` - SoundFont2 Compressed File
41
+ - `.sfogg` - SF2Pack With Vorbis Compression
42
+ - `.dls` - Downloadable Sounds Levels 1 & 2 (as well as Mobile DLS)
43
+ - `.rmi` - RIFF MIDI File
44
+ - `.rmi` - RIFF MIDI File With Embedded DLS
45
+ - `.rmi` - [RIFF MIDI File With Embedded SF2](https://github.com/spessasus/sf2-rmidi-specification)
46
+
47
+ *With [an easy way of converting between them!](https://github.com/spessasus/SpessaSynth/wiki/Converting-Between-Formats)*
48
+
35
49
  ### Easy Integration
36
- - **Modular design:** Easy integration into other projects (load what you need)
37
- - **[Detailed documentation:](https://github.com/spessasus/SpessaSynth/wiki/Home)** With [examples!](https://github.com/spessasus/SpessaSynth/wiki/Usage-As-Library#examples)
38
- - **Easy to Use:** basic setup is just [two lines of code!](https://github.com/spessasus/SpessaSynth/wiki/Usage-As-Library#minimal-setup)
39
- - **No dependencies:** _batteries included!_
50
+ - **Modular design:** *Easy integration into other projects (load what you need)*
51
+ - **[Detailed documentation:](https://github.com/spessasus/SpessaSynth/wiki/Home)** *With [examples!](https://github.com/spessasus/SpessaSynth/wiki/Usage-As-Library#examples)*
52
+ - **Flexible:** *It's not just a MIDI player!*
53
+ - **Easy to Use:** *Basic setup is just [two lines of code!](https://github.com/spessasus/SpessaSynth/wiki/Usage-As-Library#minimal-setup)*
54
+ - **No dependencies:** *Batteries included!*
40
55
 
41
- ### Powerful SoundFont Synthesizer
56
+ ### Powerful Synthesizer
42
57
  - Suitable for both **real-time** and **offline** synthesis
43
58
  - **Excellent SoundFont support:**
44
59
  - **Full Generator Support**
@@ -63,19 +78,22 @@ document.getElementById("button").onclick = async () =>
63
78
  - **[Custom modulators for additional controllers](https://github.com/spessasus/SpessaSynth/wiki/Modulator-Class#default-modulators):** *Why not?*
64
79
  - **Written using AudioWorklets:**
65
80
  - Runs in a **separate thread** for maximum performance
81
+ - Doesn't stop playing even when the main thread is frozen
66
82
  - Supported by all modern browsers
67
83
  - **Unlimited channel count:** Your CPU is the limit!
68
84
  - **Excellent MIDI Standards Support:**
69
85
  - **MIDI Controller Support:** Default supported controllers [here](https://github.com/spessasus/SpessaSynth/wiki/MIDI-Implementation#supported-controllers)
86
+ - **Portamento Support:** Glide the notes!
87
+ - **Sound Controllers:** Real-time filter and envelope control!
70
88
  - **MIDI Tuning Standard Support:** [more info here](https://github.com/spessasus/SpessaSynth/wiki/MIDI-Implementation#midi-tuning-standard)
71
89
  - [Full **RPN** and limited **NRPN** support](https://github.com/spessasus/SpessaSynth/wiki/MIDI-Implementation#supported-registered-parameters)
72
90
  - Supports some [**Roland GS** and **Yamaha XG** system exclusives](https://github.com/spessasus/SpessaSynth/wiki/MIDI-Implementation#supported-system-exclusives)
73
- - **High-performance mode:** Play Rush E! _note: may kill your browser ;)_
91
+ - **High-performance mode:** Play Rush E! *note: may kill your browser ;)*
74
92
 
75
- ### Built-in Powerful and Fast Sequencer
76
- - **Supports MIDI formats 0, 1, and 2:** _note: format 2 support is experimental as it's very, very rare_
93
+ ### Powerful and Fast MIDI Sequencer
94
+ - **Supports MIDI formats 0, 1, and 2:** *note: format 2 support is experimental as it's very, very rare.*
77
95
  - **[Multi-Port MIDI](https://github.com/spessasus/SpessaSynth/wiki/About-Multi-Port) support:** More than 16 channels!
78
- - **Smart preloading:** Only preloads the samples used in the MIDI file for smooth playback (down to key and velocity!)
96
+ - **Smart preloading:** Only preloads the samples used in the MIDI file for smooth playback *(down to key and velocity!)*
79
97
  - **Lyrics support:** Add karaoke to your program!
80
98
  - **Raw lyrics available:** Decode in any encoding! *(Kanji? No problem!)*
81
99
  - **Runs in Audio Thread as well:** Never blocks the main thread
@@ -89,7 +107,7 @@ document.getElementById("button").onclick = async () =>
89
107
  - **Used channels on track:** Quickly determine which channels are used
90
108
  - **Key range detection:** Detect the key range of the MIDI
91
109
  - **Easy MIDI editing:** Use [helper functions](https://github.com/spessasus/SpessaSynth/wiki/Writing-MIDI-Files#modifymidi) to modify the song to your needs!
92
- - **Loop detection:** Automatically detects loops in MIDIs (e.g., from _Touhou Project_)
110
+ - **Loop detection:** Automatically detects loops in MIDIs (e.g., from *Touhou Project*)
93
111
  - **First note detection:** Skip unnecessary silence at the start by jumping to the first note!
94
112
  - **Lyrics support:** Both regular MIDI and .kar files!
95
113
  - **[Write MIDI files from scratch](https://github.com/spessasus/SpessaSynth/wiki/Creating-MIDI-Files)**
package/index.js CHANGED
@@ -8,7 +8,10 @@ import { Generator } from "./soundfont/basic_soundfont/generator.js";
8
8
  import { Modulator } from "./soundfont/basic_soundfont/modulator.js";
9
9
  import { BasicPresetZone } from "./soundfont/basic_soundfont/basic_zones.js";
10
10
  import { BasicPreset } from "./soundfont/basic_soundfont/basic_preset.js";
11
+ import { BasicMIDI } from "./midi_parser/basic_midi.js";
12
+ import { MidiMessage } from "./midi_parser/midi_message.js";
11
13
  import { MIDI } from './midi_parser/midi_loader.js';
14
+ import { RMIDINFOChunks } from "./midi_parser/rmidi_writer.js";
12
15
  import { MIDIticksToSeconds } from './midi_parser/basic_midi.js';
13
16
  import { MIDIBuilder } from "./midi_parser/midi_builder.js";
14
17
  import { Synthetizer, VOICE_CAP, DEFAULT_PERCUSSION } from './synthetizer/synthetizer.js';
@@ -32,19 +35,28 @@ import { MIDIDeviceHandler} from "./external_midi/midi_handler.js";
32
35
  import { WebMidiLinkHandler} from "./external_midi/web_midi_link.js";
33
36
  import { formatTime, formatTitle, consoleColors, arrayToHexString } from './utils/other.js';
34
37
  import { readBytesAsUintBigEndian } from './utils/byte_functions/big_endian.js';
38
+ import { readBytesAsString } from "./utils/byte_functions/string.js";
39
+ import { readLittleEndian } from "./utils/byte_functions/little_endian.js";
40
+ import { readVariableLengthQuantity } from "./utils/byte_functions/variable_length_quantity.js";
35
41
  import { modulatorSources } from "./soundfont/basic_soundfont/modulator.js";
36
42
  import { NON_CC_INDEX_OFFSET } from "./synthetizer/worklet_system/worklet_utilities/controller_tables.js";
37
43
  import { ALL_CHANNELS_OR_DIFFERENT_ACTION } from './synthetizer/worklet_system/message_protocol/worklet_message.js';
44
+ import { DEFAULT_SYNTH_CONFIG } from "./synthetizer/audio_effects/effects_config.js";
38
45
  import { trimSoundfont} from "./soundfont/basic_soundfont/write_sf2/soundfont_trimmer.js";
39
46
  import { WORKLET_URL_ABSOLUTE } from './synthetizer/worklet_url.js';
47
+ import { SynthesizerSnapshot} from "./synthetizer/worklet_system/snapshot/synthesizer_snapshot.js";
48
+ import { ChannelSnapshot } from "./synthetizer/worklet_system/snapshot/channel_snapshot.js";
40
49
 
41
50
  // Export modules
42
51
  export {
43
52
  // Synthesizer and Sequencer
44
53
  Sequencer,
45
54
  Synthetizer,
55
+ SynthesizerSnapshot,
56
+ ChannelSnapshot,
46
57
  DEFAULT_PERCUSSION,
47
58
  VOICE_CAP,
59
+ DEFAULT_SYNTH_CONFIG,
48
60
 
49
61
  // SoundFont
50
62
  BasicSoundFont,
@@ -61,13 +73,16 @@ export {
61
73
 
62
74
  // MIDI
63
75
  MIDI,
76
+ BasicMIDI,
64
77
  MIDIBuilder,
65
78
  IndexedByteArray,
79
+ MidiMessage,
66
80
  writeMIDIFile,
67
81
  writeRMIDI,
68
82
  applySnapshotToMIDI,
69
83
  modifyMIDI,
70
84
  MIDIticksToSeconds,
85
+ RMIDINFOChunks,
71
86
 
72
87
  // Utilities
73
88
  audioBufferToWav,
@@ -86,7 +101,12 @@ export {
86
101
  consoleColors,
87
102
  formatTitle,
88
103
  formatTime,
104
+
89
105
  readBytesAsUintBigEndian,
106
+ readBytesAsString,
107
+ readLittleEndian,
108
+ readVariableLengthQuantity,
109
+
90
110
  NON_CC_INDEX_OFFSET,
91
111
  ALL_CHANNELS_OR_DIFFERENT_ACTION,
92
112
  WORKLET_URL_ABSOLUTE
@@ -92,9 +92,6 @@ export class BasicMIDI extends MIDISequenceData
92
92
  * @type {boolean}
93
93
  */
94
94
  let karaokeHasTitle = false;
95
- let portOffset = 0;
96
- this.midiPorts = [];
97
- this.midiPortChannelOffsets = [];
98
95
 
99
96
  this.keyRange = { max: 0, min: 127 };
100
97
 
@@ -126,7 +123,6 @@ export class BasicMIDI extends MIDISequenceData
126
123
  {
127
124
  const track = this.tracks[i];
128
125
  const usedChannels = new Set();
129
- this.midiPorts.push(-1);
130
126
  let trackHasVoiceMessages = false;
131
127
 
132
128
  for (const e of track)
@@ -230,16 +226,6 @@ export class BasicMIDI extends MIDISequenceData
230
226
  e.messageData.currentIndex = 0;
231
227
  break;
232
228
 
233
- case messageTypes.midiPort:
234
- const port = e.messageData[0];
235
- this.midiPorts[i] = port;
236
- if (this.midiPortChannelOffsets[port] === undefined)
237
- {
238
- this.midiPortChannelOffsets[port] = portOffset;
239
- portOffset += 16;
240
- }
241
- break;
242
-
243
229
  case messageTypes.copyright:
244
230
  if (!copyrightDetected)
245
231
  {
@@ -399,6 +385,33 @@ export class BasicMIDI extends MIDISequenceData
399
385
  consoleColors.recognized
400
386
  );
401
387
 
388
+ // determine ports
389
+ let portOffset = 0;
390
+ this.midiPorts = [];
391
+ this.midiPortChannelOffsets = [];
392
+ for (let trackNum = 0; trackNum < this.tracks.length; trackNum++)
393
+ {
394
+ this.midiPorts.push(-1);
395
+ if (this.usedChannelsOnTrack[trackNum].size === 0)
396
+ {
397
+ continue;
398
+ }
399
+ for (const e of this.tracks[trackNum])
400
+ {
401
+ if (e.messageStatusByte !== messageTypes.midiPort)
402
+ {
403
+ continue;
404
+ }
405
+ const port = e.messageData[0];
406
+ this.midiPorts[trackNum] = port;
407
+ if (this.midiPortChannelOffsets[port] === undefined)
408
+ {
409
+ this.midiPortChannelOffsets[port] = portOffset;
410
+ portOffset += 16;
411
+ }
412
+ }
413
+ }
414
+
402
415
  // fix midi ports:
403
416
  // midi tracks without ports will have a value of -1
404
417
  // if all ports have a value of -1, set it to 0,
@@ -408,15 +421,21 @@ export class BasicMIDI extends MIDISequenceData
408
421
  // but leave the conductor track with no port pref.
409
422
  // this spessasynth to reserve the first 16 channels for the conductor track
410
423
  // (which doesn't play anything) and use the additional 16 for the actual ports.
411
- let defaultPort = 0;
424
+ let defaultPort = Infinity;
412
425
  for (let port of this.midiPorts)
413
426
  {
414
427
  if (port !== -1)
415
428
  {
416
- defaultPort = port;
417
- break;
429
+ if (defaultPort > port)
430
+ {
431
+ defaultPort = port;
432
+ }
418
433
  }
419
434
  }
435
+ if (defaultPort === Infinity)
436
+ {
437
+ defaultPort = 0;
438
+ }
420
439
  this.midiPorts = this.midiPorts.map(port => port === -1 ? defaultPort : port);
421
440
  // add fake port if empty
422
441
  if (this.midiPortChannelOffsets.length === 0)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spessasynth_lib",
3
- "version": "3.24.26",
3
+ "version": "3.24.28",
4
4
  "description": "MIDI and SoundFont2/DLS library with no compromises",
5
5
  "browser": "index.js",
6
6
  "type": "module",
@@ -11,6 +11,7 @@
11
11
  "type": "git",
12
12
  "url": "git+https://github.com/spessasus/SpessaSynth.git"
13
13
  },
14
+ "main": "index.js",
14
15
  "keywords": [
15
16
  "soundfont",
16
17
  "synthesizer",
@@ -99,7 +99,7 @@ export class Sequencer
99
99
 
100
100
  /**
101
101
  * Fires on meta-event
102
- * @type {Object<string, function([number, Uint8Array])>}
102
+ * @type {Object<string, function([number, Uint8Array, number])>}
103
103
  */
104
104
  onMetaEvent = {};
105
105
 
@@ -438,7 +438,7 @@ export class Sequencer
438
438
 
439
439
  /**
440
440
  * Adds a new event that gets called when a meta-event occurs
441
- * @param callback {function([number, Uint8Array])} the meta-event type and its data
441
+ * @param callback {function([number, Uint8Array, number])} the meta-event type, its data and the track number
442
442
  * @param id {string} must be unique
443
443
  */
444
444
  addOnMetaEvent(callback, id)
@@ -171,7 +171,10 @@ export function _processEvent(event, trackIndex)
171
171
  }
172
172
  if (statusByteData.status >= 0 && statusByteData.status < 0x80)
173
173
  {
174
- this.post(WorkletSequencerReturnMessageType.metaEvent, [event.messageStatusByte, event.messageData]);
174
+ this.post(
175
+ WorkletSequencerReturnMessageType.metaEvent,
176
+ [event.messageStatusByte, event.messageData, trackIndex]
177
+ );
175
178
  }
176
179
  }
177
180
 
@@ -47,6 +47,6 @@ export const WorkletSequencerReturnMessageType = {
47
47
  pause: 4, // no data
48
48
  getMIDI: 5, // midiData<MIDI>
49
49
  midiError: 6, // errorMSG<string>
50
- metaEvent: 7, // [messageType<number>, messageData<Uint8Array>]
50
+ metaEvent: 7, // [messageType<number>, messageData<Uint8Array>, trackNum<number>]
51
51
  loopCountChange: 8 // newLoopCount<number>
52
52
  };