spessasynth_lib 3.26.0 → 3.26.2

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.
@@ -0,0 +1,4 @@
1
+ ## This is the MIDI handling folder.
2
+
3
+ The code here is respnsible for dealing with MIDI Inputs and outputs
4
+ and also for the WebMidiLink functionality.
@@ -0,0 +1,130 @@
1
+ import { Synthetizer } from "../synthetizer/synthetizer.js";
2
+ import { SpessaSynthCoreUtils as util } from "spessasynth_core";
3
+ import { consoleColors } from "../utils/other.js";
4
+
5
+ /**
6
+ * midi_handler.js
7
+ * purpose: handles the connection between MIDI devices and synthesizer/sequencer via Web MIDI API
8
+ */
9
+
10
+ const NO_INPUT = null;
11
+
12
+ // noinspection JSUnusedGlobalSymbols
13
+ /**
14
+ * A class for handling MIDI devices
15
+ */
16
+ export class MIDIDeviceHandler
17
+ {
18
+ /**
19
+ * @returns {Promise<boolean>} if succeeded
20
+ */
21
+ async createMIDIDeviceHandler()
22
+ {
23
+ /**
24
+ * @type {MIDIInput}
25
+ */
26
+ this.selectedInput = NO_INPUT;
27
+ /**
28
+ * @type {MIDIOutput}
29
+ */
30
+ this.selectedOutput = NO_INPUT;
31
+ if (navigator.requestMIDIAccess)
32
+ {
33
+ // prepare the midi access
34
+ try
35
+ {
36
+ const response = await navigator.requestMIDIAccess({ sysex: true, software: true });
37
+ this.inputs = response.inputs;
38
+ this.outputs = response.outputs;
39
+ util.SpessaSynthInfo("%cMIDI handler created!", consoleColors.recognized);
40
+ return true;
41
+ }
42
+ catch (e)
43
+ {
44
+ util.SpessaSynthWarn(`Could not get MIDI Devices:`, e);
45
+ this.inputs = [];
46
+ this.outputs = [];
47
+ return false;
48
+ }
49
+ }
50
+ else
51
+ {
52
+ util.SpessaSynthWarn("Web MIDI Api not supported!", consoleColors.unrecognized);
53
+ this.inputs = [];
54
+ this.outputs = [];
55
+ return false;
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Connects the sequencer to a given MIDI output port
61
+ * @param output {MIDIOutput}
62
+ * @param seq {Sequencer}
63
+ */
64
+ connectMIDIOutputToSeq(output, seq)
65
+ {
66
+ this.selectedOutput = output;
67
+ seq.connectMidiOutput(output);
68
+ util.SpessaSynthInfo(
69
+ `%cPlaying MIDI to %c${output.name}`,
70
+ consoleColors.info,
71
+ consoleColors.recognized
72
+ );
73
+ }
74
+
75
+ /**
76
+ * Disconnects a midi output port from the sequencer
77
+ * @param seq {Sequencer}
78
+ */
79
+ disconnectSeqFromMIDI(seq)
80
+ {
81
+ this.selectedOutput = NO_INPUT;
82
+ seq.connectMidiOutput(undefined);
83
+ util.SpessaSynthInfo(
84
+ "%cDisconnected from MIDI out.",
85
+ consoleColors.info
86
+ );
87
+ }
88
+
89
+ /**
90
+ * Connects a MIDI input to the synthesizer
91
+ * @param input {MIDIInput}
92
+ * @param synth {Synthetizer}
93
+ */
94
+ connectDeviceToSynth(input, synth)
95
+ {
96
+ this.selectedInput = input;
97
+ input.onmidimessage = event =>
98
+ {
99
+ synth.sendMessage(event.data);
100
+ };
101
+ util.SpessaSynthInfo(
102
+ `%cListening for messages on %c${input.name}`,
103
+ consoleColors.info,
104
+ consoleColors.recognized
105
+ );
106
+ }
107
+
108
+ /**
109
+ * @param input {MIDIInput}
110
+ */
111
+ disconnectDeviceFromSynth(input)
112
+ {
113
+ this.selectedInput = NO_INPUT;
114
+ input.onmidimessage = undefined;
115
+ util.SpessaSynthInfo(
116
+ `%cDisconnected from %c${input.name}`,
117
+ consoleColors.info,
118
+ consoleColors.recognized
119
+ );
120
+ }
121
+
122
+ disconnectAllDevicesFromSynth()
123
+ {
124
+ this.selectedInput = NO_INPUT;
125
+ for (const i of this.inputs)
126
+ {
127
+ i[1].onmidimessage = undefined;
128
+ }
129
+ }
130
+ }
@@ -0,0 +1,43 @@
1
+ import { Synthetizer } from "../synthetizer/synthetizer.js";
2
+ import { consoleColors } from "../utils/other.js";
3
+ import { SpessaSynthCoreUtils } from "spessasynth_core";
4
+
5
+ /**
6
+ * web_midi_link.js
7
+ * purpose: handles the web midi link connection to the synthesizer
8
+ * https://www.g200kg.com/en/docs/webmidilink/
9
+ */
10
+
11
+ export class WebMIDILinkHandler
12
+ {
13
+ /**
14
+ * @param synth {Synthetizer} the synth to play to
15
+ */
16
+ constructor(synth)
17
+ {
18
+
19
+ window.addEventListener("message", msg =>
20
+ {
21
+ if (typeof msg.data !== "string")
22
+ {
23
+ return;
24
+ }
25
+ /**
26
+ * @type {string[]}
27
+ */
28
+ const data = msg.data.split(",");
29
+ if (data[0] !== "midi")
30
+ {
31
+ return;
32
+ }
33
+
34
+ data.shift(); // remove MIDI
35
+
36
+ const midiData = data.map(byte => parseInt(byte, 16));
37
+
38
+ synth.sendMessage(midiData);
39
+ });
40
+
41
+ SpessaSynthCoreUtils.SpessaSynthInfo("%cWeb MIDI Link handler created!", consoleColors.recognized);
42
+ }
43
+ }
package/index.js CHANGED
@@ -1,31 +1,5 @@
1
1
  // Import modules
2
- import {
3
- ALL_CHANNELS_OR_DIFFERENT_ACTION,
4
- BasicInstrument,
5
- BasicInstrumentZone,
6
- BasicMIDI,
7
- BasicPreset,
8
- BasicPresetZone,
9
- BasicSample,
10
- BasicSoundBank,
11
- ChannelSnapshot,
12
- DEFAULT_PERCUSSION,
13
- Generator,
14
- IndexedByteArray,
15
- loadSoundFont,
16
- messageTypes,
17
- MIDI,
18
- MIDIBuilder,
19
- midiControllers,
20
- MIDIMessage,
21
- Modulator,
22
- modulatorSources,
23
- NON_CC_INDEX_OFFSET,
24
- RMIDINFOChunks,
25
- SpessaSynthLogging,
26
- SynthesizerSnapshot,
27
- VOICE_CAP
28
- } from "spessasynth_core";
2
+
29
3
  import { Synthetizer } from "./synthetizer/synthetizer.js";
30
4
  import { Sequencer } from "./sequencer/sequencer.js";
31
5
  import { audioBufferToWav } from "./utils/buffer_to_wav.js";
@@ -39,39 +13,10 @@ export {
39
13
  // Synthesizer and Sequencer
40
14
  Sequencer,
41
15
  Synthetizer,
42
- SynthesizerSnapshot,
43
- ChannelSnapshot,
44
- DEFAULT_PERCUSSION,
45
- VOICE_CAP,
46
16
  DEFAULT_SYNTH_CONFIG,
47
- ALL_CHANNELS_OR_DIFFERENT_ACTION,
48
- NON_CC_INDEX_OFFSET,
49
-
50
- // SoundFont
51
- BasicSoundBank,
52
- BasicSample,
53
- BasicInstrumentZone,
54
- BasicInstrument,
55
- BasicPreset,
56
- BasicPresetZone,
57
- Generator,
58
- Modulator,
59
- loadSoundFont,
60
- modulatorSources,
61
-
62
- // MIDI
63
- MIDI,
64
- BasicMIDI,
65
- MIDIBuilder,
66
- MIDIMessage,
67
- RMIDINFOChunks,
68
17
 
69
18
  // Utilities
70
- IndexedByteArray,
71
19
  audioBufferToWav,
72
- SpessaSynthLogging,
73
- midiControllers,
74
- messageTypes,
75
20
  MIDIDeviceHandler,
76
21
  WebMIDILinkHandler,
77
22
  WORKLET_URL_ABSOLUTE
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spessasynth_lib",
3
- "version": "3.26.0",
3
+ "version": "3.26.2",
4
4
  "description": "MIDI and SoundFont2/DLS library for the browsers with no compromises",
5
5
  "browser": "index.js",
6
6
  "type": "module",
@@ -45,7 +45,10 @@
45
45
  "index.js",
46
46
  "LICENSE",
47
47
  "README.md",
48
- "src"
48
+ "sequencer",
49
+ "synthetizer",
50
+ "utils",
51
+ "external_midi"
49
52
  ],
50
53
  "dependencies": {
51
54
  "spessasynth_core": "latest"
@@ -0,0 +1,32 @@
1
+ ## This is the sequencer's folder.
2
+
3
+ The code here is responsible for playing back the parsed MIDI sequence with the synthesizer.
4
+
5
+ - `sequencer_engine` - the core sequencer engine, currently runs in audio worklet
6
+
7
+ ### Message protocol:
8
+
9
+ #### Message structure
10
+
11
+ ```js
12
+ const message = {
13
+ messageType: number, // WorkletSequencerMessageType
14
+ messageData: any // any
15
+ }
16
+ ```
17
+
18
+ #### To worklet
19
+
20
+ Sequencer uses `Synthetizer`'s `post` method to post a message with `messageData` set to
21
+ `workletMessageType.sequencerSpecific`.
22
+ The `messageData` is set to the sequencer's message.
23
+
24
+ #### From worklet
25
+
26
+ `WorkletSequencer` uses `SpessaSynthProcessor`'s post to send a message with `messageData` set to
27
+ `returnMessageType.sequencerSpecific`.
28
+ The `messageData` is set to the sequencer's return message.
29
+
30
+ ### Process tick
31
+
32
+ `processTick` is called every time the `process` method is called via `SpessaSynthProcessor.processTickCallback`.
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @type {SequencerOptions}
3
+ */
4
+ export const DEFAULT_SEQUENCER_OPTIONS = {
5
+ skipToFirstNoteOn: true,
6
+ autoPlay: true,
7
+ preservePlaybackState: false
8
+ };
@@ -0,0 +1,63 @@
1
+ import { BasicMIDI, MIDISequenceData } from "spessasynth_core";
2
+
3
+ /**
4
+ * A simplified version of the MIDI, accessible at all times from the Sequencer.
5
+ * Use getMIDI() to get the actual sequence.
6
+ * This class contains all properties that MIDI does, except for tracks and the embedded soundfont.
7
+ */
8
+ export class MIDIData extends MIDISequenceData
9
+ {
10
+
11
+ /**
12
+ * A boolean indicating if the MIDI file contains an embedded soundfont.
13
+ * If the embedded soundfont is undefined, this will be false.
14
+ * @type {boolean}
15
+ */
16
+ isEmbedded = false;
17
+
18
+ /**
19
+ * Constructor that copies data from a BasicMIDI instance.
20
+ * @param {BasicMIDI} midi - The BasicMIDI instance to copy data from.
21
+ */
22
+ constructor(midi)
23
+ {
24
+ super();
25
+ this._copyFromSequence(midi);
26
+
27
+ // Set isEmbedded based on the presence of an embeddedSoundFont
28
+ this.isEmbedded = midi.embeddedSoundFont !== undefined;
29
+ }
30
+ }
31
+
32
+
33
+ /**
34
+ * Temporary MIDI data used when the MIDI is not loaded.
35
+ * @type {MIDIData}
36
+ */
37
+ export const DUMMY_MIDI_DATA = {
38
+ duration: 99999,
39
+ firstNoteOn: 0,
40
+ loop: {
41
+ start: 0,
42
+ end: 123456
43
+ },
44
+
45
+ lastVoiceEventTick: 123456,
46
+ lyrics: [],
47
+ copyright: "",
48
+ midiPorts: [],
49
+ midiPortChannelOffsets: [],
50
+ tracksAmount: 0,
51
+ tempoChanges: [{ ticks: 0, tempo: 120 }],
52
+ fileName: "NOT_LOADED.mid",
53
+ midiName: "Loading...",
54
+ rawMidiName: new Uint8Array([76, 111, 97, 100, 105, 110, 103, 46, 46, 46]), // "Loading..."
55
+ usedChannelsOnTrack: [],
56
+ timeDivision: 0,
57
+ keyRange: { min: 0, max: 127 },
58
+ isEmbedded: false,
59
+ RMIDInfo: {},
60
+ bankOffset: 0,
61
+ midiNameUsesFileName: false,
62
+ format: 0
63
+ };