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,53 @@
1
+ export const SongChangeType = {
2
+ backwards: 0, // no additional data
3
+ forwards: 1, // no additional data
4
+ shuffleOn: 2, // no additional data
5
+ shuffleOff: 3, // no additional data
6
+ index: 4 // songIndex<number>
7
+ };
8
+
9
+ /**
10
+ * @enum {number}
11
+ * @property {number} loadNewSongList - 0 -> [...song<MIDI>]
12
+ * @property {number} pause - 1 -> isFinished<boolean>
13
+ * @property {number} stop - 2 -> (no data)
14
+ * @property {number} play - 3 -> resetTime<boolean>
15
+ * @property {number} setTime - 4 -> time<number>
16
+ * @property {number} changeMIDIMessageSending - 5 -> sendMIDIMessages<boolean>
17
+ * @property {number} setPlaybackRate - 6 -> playbackRate<number>
18
+ * @property {number} setLoop - 7 -> [loop<boolean>, count<number>]
19
+ * @property {number} changeSong - 8 -> [changeType<SongChangeType>, data<number>]
20
+ * @property {number} getMIDI - 9 -> (no data)
21
+ * @property {number} setSkipToFirstNote -10 -> skipToFirstNoteOn<boolean>
22
+ * @property {number} setPreservePlaybackState -11 -> preservePlaybackState<boolean>
23
+ */
24
+ export const SpessaSynthSequencerMessageType = {
25
+ loadNewSongList: 0,
26
+ pause: 1,
27
+ stop: 2,
28
+ play: 3,
29
+ setTime: 4,
30
+ changeMIDIMessageSending: 5,
31
+ setPlaybackRate: 6,
32
+ setLoop: 7,
33
+ changeSong: 8,
34
+ getMIDI: 9,
35
+ setSkipToFirstNote: 10,
36
+ setPreservePlaybackState: 11
37
+ };
38
+
39
+ /**
40
+ *
41
+ * @enum {number}
42
+ */
43
+ export const SpessaSynthSequencerReturnMessageType = {
44
+ midiEvent: 0, // [...midiEventBytes<number>]
45
+ songChange: 1, // [songIndex<number>, isAutoPlayed<boolean>]
46
+ timeChange: 2, // newTime<number>
47
+ pause: 3, // no data
48
+ getMIDI: 4, // midiData<MIDI>
49
+ midiError: 5, // errorMSG<string>
50
+ metaEvent: 6, // [event<MIDIMessage>, trackNum<number>]
51
+ loopCountChange: 7, // newLoopCount<number>
52
+ songListChange: 8 // songListData<MIDIData[]>
53
+ };
@@ -0,0 +1,38 @@
1
+ ## This is the main synthesizer folder.
2
+
3
+ The code here is responsible for making the actual sound.
4
+ This is the heart of the SpessaSynth library.
5
+
6
+ - `audio_engine` - the core synthesis engine, it theoretically can run on non-browser environments.
7
+ - `audio_effects` - the WebAudioAPI audio effects.
8
+ - `worklet_wrapper` - the wrapper for the core synthesis engine using audio worklets.
9
+
10
+ `worklet_processor.min.js` - the minified worklet processor code to import.
11
+
12
+ # About the message protocol
13
+ Since spessasynth_lib runs in the audioWorklet thread, here is an explanation of how it works:
14
+
15
+ There's one processor per synthesizer, with a `MessagePort` for communication.
16
+ Each processor has a single `SpessaSynthequencer` instance that is idle by default.
17
+
18
+ The `Synthetizer`,
19
+ `Sequencer` and `SoundFontManager` classes are all interfaces
20
+ that do not do anything except sending the commands to te processor.
21
+
22
+ The synthesizer sends the commands (note on, off, etc.) directly to the processor where they are processed and executed.
23
+
24
+ The sequencer sends the commands through the connected synthesizer's messagePort, which then get processed as sequencer messages and routed properly.
25
+
26
+
27
+ ## How it works in spessasynth_lib
28
+ Both `Synthetizer` and `Sequencer` are essentially "remote control"
29
+ for the actual sequencer and synthesizer in the audio worklet thread (here)
30
+ These core components are wrapped in the AudioWorkletProcessor, which is receiving both commands and data (MIDIs, sound banks)
31
+ through the message port, and sends data back (events, time changes, status changes, etc.).
32
+
33
+ For example,
34
+ the playback to WebMIDIAPI is actually the sequencer in the worklet thread
35
+ playing back the sequence and then postMessaging the commands through the synthesizer to the sequencer
36
+ which actually sends them to the specified output.
37
+
38
+ The wonders of separate audio thread...
@@ -0,0 +1,25 @@
1
+ import { DEFAULT_CHORUS_CONFIG } from "./fancy_chorus.js";
2
+
3
+ /**
4
+ * @typedef {Object} SynthConfig
5
+ * @property {boolean?} chorusEnabled - indicates if the chorus effect is enabled.
6
+ * @property {ChorusConfig?} chorusConfig - the configuration for chorus. Pass undefined to use defaults
7
+ * @property {boolean?} reverbEnabled - indicates if the reverb effect is enabled.
8
+ * @property {AudioBuffer?} reverbImpulseResponse - the impulse response for the reverb. Pass undefined to use defaults
9
+ * @property {{
10
+ * worklet: function(context: object, name: string, options?: Object)
11
+ * }?} audioNodeCreators - custom audio node creation functions for Web Audio wrappers.
12
+ */
13
+
14
+
15
+ /**
16
+ * @type {SynthConfig}
17
+ */
18
+ export const DEFAULT_SYNTH_CONFIG = {
19
+ chorusEnabled: true,
20
+ chorusConfig: DEFAULT_CHORUS_CONFIG,
21
+
22
+ reverbEnabled: true,
23
+ reverbImpulseResponse: undefined, // will load the integrated one
24
+ audioNodeCreators: undefined
25
+ };
@@ -0,0 +1,162 @@
1
+ /**
2
+ * fancy_chorus.js
3
+ * purpose: creates a simple chorus effect node
4
+ */
5
+
6
+ /**
7
+ * @typedef {{
8
+ * oscillator: OscillatorNode,
9
+ * oscillatorGain: GainNode,
10
+ * delay: DelayNode
11
+ * }} ChorusNode
12
+ */
13
+
14
+ /**
15
+ * @typedef {Object} ChorusConfig
16
+ * @property {number?} nodesAmount - the amount of delay nodes (for each channel) and the corresponding oscillators
17
+ * @property {number?} defaultDelay - the initial delay, in seconds
18
+ * @property {number?} delayVariation - the difference between delays in the delay nodes
19
+ * @property {number?} stereoDifference - the difference of delays between two channels (added to the left channel and subtracted from the right)
20
+ * @property {number?} oscillatorFrequency - the initial delay time oscillator frequency, in Hz.
21
+ * @property {number?} oscillatorFrequencyVariation - the difference between frequencies of oscillators, in Hz
22
+ * @property {number?} oscillatorGain - how much will oscillator alter the delay in delay nodes, in seconds
23
+ */
24
+
25
+ const NODES_AMOUNT = 4;
26
+ const DEFAULT_DELAY = 0.03;
27
+ const DELAY_VARIATION = 0.01;
28
+ const STEREO_DIFF = 0.02;
29
+
30
+ const OSC_FREQ = 0.2;
31
+ const OSC_FREQ_VARIATION = 0.05;
32
+ const OSC_GAIN = 0.003;
33
+
34
+ export const DEFAULT_CHORUS_CONFIG = {
35
+ nodesAmount: NODES_AMOUNT,
36
+ defaultDelay: DEFAULT_DELAY,
37
+ delayVariation: DELAY_VARIATION,
38
+ stereoDifference: STEREO_DIFF,
39
+ oscillatorFrequency: OSC_FREQ,
40
+ oscillatorFrequencyVariation: OSC_FREQ_VARIATION,
41
+ oscillatorGain: OSC_GAIN
42
+ };
43
+
44
+ export class FancyChorus
45
+ {
46
+ /**
47
+ * Creates a fancy chorus effect
48
+ * @param output {AudioNode}
49
+ * @param config {ChorusConfig}
50
+ */
51
+ constructor(output, config = DEFAULT_CHORUS_CONFIG)
52
+ {
53
+ const context = output.context;
54
+
55
+ this.input = context.createChannelSplitter(2);
56
+
57
+ const merger = context.createChannelMerger(2);
58
+
59
+ /**
60
+ * @type {ChorusNode[]}
61
+ */
62
+ const chorusNodesLeft = [];
63
+ /**
64
+ * @type {ChorusNode[]}
65
+ */
66
+ const chorusNodesRight = [];
67
+ let freq = config.oscillatorFrequency;
68
+ let delay = config.defaultDelay;
69
+ for (let i = 0; i < config.nodesAmount; i++)
70
+ {
71
+ // left node
72
+ this.createChorusNode(
73
+ freq,
74
+ delay - config.stereoDifference,
75
+ chorusNodesLeft,
76
+ 0,
77
+ merger,
78
+ 0,
79
+ context,
80
+ config
81
+ );
82
+ // right node
83
+ this.createChorusNode(
84
+ freq,
85
+ delay + config.stereoDifference,
86
+ chorusNodesRight,
87
+ 1,
88
+ merger,
89
+ 1,
90
+ context,
91
+ config
92
+ );
93
+ freq += config.oscillatorFrequencyVariation;
94
+ delay += config.delayVariation;
95
+ }
96
+
97
+ merger.connect(output);
98
+ this.merger = merger;
99
+ this.chorusLeft = chorusNodesLeft;
100
+ this.chorusRight = chorusNodesRight;
101
+ }
102
+
103
+ delete()
104
+ {
105
+ this.input.disconnect();
106
+ delete this.input;
107
+ this.merger.disconnect();
108
+ delete this.merger;
109
+ for (const chorusLeftElement of this.chorusLeft)
110
+ {
111
+ chorusLeftElement.delay.disconnect();
112
+ chorusLeftElement.oscillator.disconnect();
113
+ chorusLeftElement.oscillatorGain.disconnect();
114
+ delete chorusLeftElement.delay;
115
+ delete chorusLeftElement.oscillatorGain;
116
+ delete chorusLeftElement.oscillatorGain;
117
+ }
118
+ for (const chorusRightElement of this.chorusRight)
119
+ {
120
+ chorusRightElement.delay.disconnect();
121
+ chorusRightElement.oscillator.disconnect();
122
+ chorusRightElement.oscillatorGain.disconnect();
123
+ delete chorusRightElement.delay;
124
+ delete chorusRightElement.oscillatorGain;
125
+ delete chorusRightElement.oscillatorGain;
126
+ }
127
+ }
128
+
129
+ /**
130
+ * @param freq {number}
131
+ * @param delay {number}
132
+ * @param list {ChorusNode[]}
133
+ * @param input {number}
134
+ * @param output {AudioNode}
135
+ * @param outputNum {number}
136
+ * @param context {BaseAudioContext}
137
+ * @param config {ChorusConfig}
138
+ */
139
+ createChorusNode(freq, delay, list, input, output, outputNum, context, config)
140
+ {
141
+ const oscillator = context.createOscillator();
142
+ oscillator.type = "sine";
143
+ oscillator.frequency.value = freq;
144
+ const gainNode = context.createGain();
145
+ gainNode.gain.value = config.oscillatorGain;
146
+ const delayNode = context.createDelay();
147
+ delayNode.delayTime.value = delay;
148
+
149
+ oscillator.connect(gainNode);
150
+ gainNode.connect(delayNode.delayTime);
151
+ oscillator.start(context.currentTime /*+ delay*/);
152
+
153
+ this.input.connect(delayNode, input);
154
+ delayNode.connect(output, 0, outputNum);
155
+
156
+ list.push({
157
+ oscillator: oscillator,
158
+ oscillatorGain: gainNode,
159
+ delay: delayNode
160
+ });
161
+ }
162
+ }