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.
- package/external_midi/README.md +4 -0
- package/external_midi/midi_handler.js +130 -0
- package/external_midi/web_midi_link.js +43 -0
- package/index.js +1 -56
- package/package.json +5 -2
- package/sequencer/README.md +32 -0
- package/sequencer/default_sequencer_options.js +8 -0
- package/sequencer/midi_data.js +63 -0
- package/sequencer/sequencer.js +808 -0
- package/sequencer/sequencer_message.js +53 -0
- package/synthetizer/README.md +38 -0
- package/synthetizer/audio_effects/effects_config.js +25 -0
- package/synthetizer/audio_effects/fancy_chorus.js +162 -0
- package/synthetizer/audio_effects/rb_compressed.min.js +1 -0
- package/synthetizer/audio_effects/reverb.js +35 -0
- package/synthetizer/audio_effects/reverb_as_binary.js +18 -0
- package/synthetizer/key_modifier_manager.js +113 -0
- package/synthetizer/sfman_message.js +9 -0
- package/synthetizer/synth_event_handler.js +217 -0
- package/synthetizer/synth_soundfont_manager.js +112 -0
- package/synthetizer/synthetizer.js +1033 -0
- package/synthetizer/worklet_message.js +120 -0
- package/synthetizer/worklet_processor.js +637 -0
- package/synthetizer/worklet_processor.min.js +22 -0
- package/synthetizer/worklet_processor.min.js.map +7 -0
- package/synthetizer/worklet_url.js +18 -0
- package/utils/buffer_to_wav.js +28 -0
- package/utils/fill_with_defaults.js +21 -0
- package/utils/other.js +11 -0
|
@@ -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
|
+
}
|