spessasynth_core 1.0.11 → 1.1.0

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
@@ -2,7 +2,7 @@
2
2
  A SoundFont2 synthesizer library, made for use with node.js.
3
3
  A fork of [SpessaSynth](https://github.com/spessasus/SpessaSynth).
4
4
 
5
- [Jump to documentation](#api-reference)
5
+ [Jump to the API reference](#api-reference)
6
6
 
7
7
  `npm install --save spessasynth_core`
8
8
 
@@ -19,7 +19,7 @@ A fork of [SpessaSynth](https://github.com/spessasus/SpessaSynth).
19
19
  - No SoundFont size limit
20
20
  - No dependencies
21
21
 
22
- ### Example: render midi file to wav
22
+ ### Example: Render a MIDI file to a .wav file
23
23
  ```js
24
24
  const fs = require('fs');
25
25
  // spessasynth_core is an es6 module
@@ -53,6 +53,89 @@ import("spessasynth_core").then(core => {
53
53
  });
54
54
  ```
55
55
 
56
+ ### Example 2: play a MIDI file to speakers
57
+ via npm package `speaker`
58
+ ```js
59
+ const fs = require('fs');
60
+ const Speaker = require("speaker");
61
+ const SAMPLE_RATE = 44100; // hertz
62
+ const BLOCK_SIZE = 128; // samples
63
+
64
+ // usage: node test.js <sf path> <midi path>
65
+ import("spessasynth_core").then(core => {
66
+ // Disable logging
67
+ core.SpessaSynthLogging(false, false, false, false);
68
+
69
+ // Read arguments and load the input files
70
+ const [,, soundfontName, midiName] = process.argv;
71
+ const soundfont = fs.readFileSync(soundfontName);
72
+ const mid = new core.MIDI(fs.readFileSync(midiName)); // parse the midi
73
+
74
+
75
+ // Initialize synth and sequencer
76
+ const synth = new core.Synthesizer(soundfont, SAMPLE_RATE, BLOCK_SIZE);
77
+ const seq = new core.Sequencer(synth);
78
+
79
+ // Load new song and disable the loop
80
+ seq.loadNewSongList([mid]);
81
+ seq.loop = false;
82
+
83
+ // make the program stop after the sequence is finished
84
+ const time = seq.duration + 1
85
+ let isPlaying = true;
86
+ setTimeout(() => isPlaying = false, time * 1000);
87
+ console.log(`Playing "${mid.midiName || "<unnamed song>"}"`);
88
+
89
+ // Calculate length and allocate buffers
90
+ const outLeft = new Float32Array(BLOCK_SIZE);
91
+ const outRight = new Float32Array(BLOCK_SIZE);
92
+
93
+ // Wait for sf3 support to load and render
94
+ synth.sf3supportReady.then(() => {
95
+ const speakerOut = new Speaker({
96
+ channels: 2,
97
+ bitDepth: 16,
98
+ sampleRate: SAMPLE_RATE,
99
+ });
100
+
101
+ function render()
102
+ {
103
+ // Render audio samples from the synthesizer
104
+ synth.render([outLeft, outRight]);
105
+
106
+ // Prepare buffer for WAV output
107
+ const wavData = new Int16Array(outLeft.length * 2); // 2 channels
108
+
109
+ // Interleave audio data
110
+ let offset = 0;
111
+ for (let i = 0; i < outLeft.length; i++)
112
+ {
113
+ // Float ranges from -1 to 1, int16 ranges from -32768 to 32767, convert it here
114
+ const sampleL = Math.max(-1, Math.min(1, outLeft[i])) * 32767;
115
+ const sampleR = Math.max(-1, Math.min(1, outRight[i])) * 32767;
116
+
117
+ // Interleave data: L, R, L, R, etc...
118
+ wavData[offset++] = sampleL;
119
+ wavData[offset++] = sampleR;
120
+ }
121
+ // Write WAV data to speaker
122
+ speakerOut.write(wavData);
123
+
124
+ if(isPlaying)
125
+ {
126
+ setImmediate(render);
127
+ }
128
+ else
129
+ {
130
+ process.exit(0);
131
+ }
132
+ }
133
+
134
+ render();
135
+ });
136
+ });
137
+ ```
138
+
56
139
  ## API reference
57
140
  ### Contents
58
141
  - [Importing the library](#importing-the-library)
@@ -207,12 +290,12 @@ synth.muteChannel(channel, isMuted);
207
290
  - channel - the channel to mute/unmute. Usually ranges from 0 to 15, but it depends on the channel count.
208
291
  - isMuted - if the channel should be muted. boolean.
209
292
 
210
- ### transpose
293
+ ### transposeAllChannels
211
294
 
212
295
  Transposes the synth up or down in semitones. Floating point values can be used for more precise tuning.
213
296
 
214
297
  ```js
215
- synth.transpose(semitones);
298
+ synth.transposeAllChannels(semitones);
216
299
  ```
217
300
 
218
301
  - semitones - the amount of semitones to transpose the synth by. Can be positive or negative or zero. Zero resets the pitch.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spessasynth_core",
3
- "version": "1.0.11",
3
+ "version": "1.1.0",
4
4
  "description": "SoundFont2 synthesizer library for node.js",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -12,9 +12,10 @@ import { SpessaSynthInfo } from '../../../utils/loggin.js'
12
12
  import { SYNTHESIZER_GAIN } from '../../synthesizer.js'
13
13
 
14
14
  /**
15
- * @param channel {number}
16
- * @param controllerNumber {number}
17
- * @param controllerValue {number}
15
+ * Changes a MIDI controller
16
+ * @param channel {number} - The MIDI Channel to use
17
+ * @param controllerNumber {number} - The MIDI controller number
18
+ * @param controllerValue {number} - The new value
18
19
  * @this {Synthesizer}
19
20
  */
20
21
  export function controllerChange(channel, controllerNumber, controllerValue)
@@ -141,6 +142,7 @@ export function controllerChange(channel, controllerNumber, controllerValue)
141
142
  }
142
143
 
143
144
  /**
145
+ * Resets all controllers and programs
144
146
  * @this {Synthesizer}
145
147
  */
146
148
  export function resetAllControllers()
@@ -176,7 +178,7 @@ export function resetAllControllers()
176
178
 
177
179
  /**
178
180
  * Resets all controllers for channel
179
- * @param channel {number}
181
+ * @param channel {number} - The MIDI Channel to use
180
182
  * @this {Synthesizer}
181
183
  */
182
184
  export function resetControllers(channel)
@@ -248,7 +250,8 @@ export function resetParameters(channel)
248
250
  }
249
251
 
250
252
  /**
251
- * @param volume {number} 0-1
253
+ * Sets the main volume of the synthesizer
254
+ * @param volume {number} - 0-1 the volume
252
255
  * @this {Synthesizer}
253
256
  */
254
257
  export function setMainVolume(volume)
@@ -258,10 +261,10 @@ export function setMainVolume(volume)
258
261
  }
259
262
 
260
263
  /**
261
- * @param pan {number} -1 to 1
264
+ * Sets the master stereo panning of the synthesizer
265
+ * @param pan {number} - -1 to 1 the pan, -1 is left, 0 is the middle, 1 is right
262
266
  * @this {Synthesizer}
263
267
  */
264
-
265
268
  export function setMasterPan(pan)
266
269
  {
267
270
  this.pan = pan;
@@ -272,8 +275,9 @@ export function setMasterPan(pan)
272
275
  }
273
276
 
274
277
  /**
275
- * @param channel {number}
276
- * @param isMuted {boolean}
278
+ * Mutes/unmutes a channel
279
+ * @param channel {number} - The MIDI channel to use
280
+ * @param isMuted {boolean} - If the channel should be muted or not
277
281
  * @this {Synthesizer}
278
282
  */
279
283
  export function muteChannel(channel, isMuted)
@@ -3,9 +3,9 @@ import { consoleColors } from '../../../utils/other.js'
3
3
  import { SpessaSynthInfo, SpessaSynthWarn } from '../../../utils/loggin.js'
4
4
 
5
5
  /**
6
- * Release a note
7
- * @param channel {number}
8
- * @param midiNote {number}
6
+ * Releases a note
7
+ * @param channel {number} - The MIDI channel to use
8
+ * @param midiNote {number} - The MIDI key number
9
9
  * @this {Synthesizer}
10
10
  */
11
11
  export function noteOff(channel, midiNote)
@@ -46,8 +46,8 @@ export function noteOff(channel, midiNote)
46
46
 
47
47
  /**
48
48
  * Stops a note nearly instantly
49
- * @param channel {number}
50
- * @param midiNote {number}
49
+ * @param channel {number} - The MIDI channel to use
50
+ * @param midiNote {number} - The MIDI key number
51
51
  * @this {Synthesizer}
52
52
  */
53
53
  export function killNote(channel, midiNote)
@@ -64,8 +64,8 @@ export function killNote(channel, midiNote)
64
64
 
65
65
  /**
66
66
  * stops all notes
67
- * @param channel {number}
68
- * @param force {boolean}
67
+ * @param channel {number} - The MIDI channel to use
68
+ * @param force {boolean} - If the notes should stop instantly or release normally
69
69
  * @this {Synthesizer}
70
70
  */
71
71
  export function stopAll(channel, force = false)
@@ -91,7 +91,7 @@ export function stopAll(channel, force = false)
91
91
 
92
92
  /**
93
93
  * @this {Synthesizer}
94
- * @param force {boolean}
94
+ * @param force {boolean} - If the notes should stop instantly or release normally
95
95
  */
96
96
  export function stopAllChannels(force = false)
97
97
  {
@@ -5,11 +5,11 @@ import { VOICE_CAP } from "../../synthesizer.js";
5
5
  import { SpessaSynthWarn } from '../../../utils/loggin.js'
6
6
 
7
7
  /**
8
- * Append the voices
9
- * @param channel {number}
10
- * @param midiNote {number}
11
- * @param velocity {number}
12
- * @param enableDebugging {boolean}
8
+ * Starts playing a MIDI note
9
+ * @param channel {number} - The MIDI Channel to use
10
+ * @param midiNote {number} - The MIDI key number
11
+ * @param velocity {number} - The velocity (how hard is the key pressed)
12
+ * @param enableDebugging {boolean} - used internally, ignore
13
13
  * @this {Synthesizer}
14
14
  */
15
15
  export function noteOn(channel, midiNote, velocity, enableDebugging = false)
@@ -5,12 +5,11 @@ import { generatorTypes } from '../../../soundfont/chunk/generators.js'
5
5
 
6
6
  /**
7
7
  * executes a program change
8
- * @param channel {number}
9
- * @param programNumber {number}
10
- * @param userChange {boolean}
8
+ * @param channel {number} - The MIDI Channel to use
9
+ * @param programNumber {number} - The MIDI program number
11
10
  * @this {Synthesizer}
12
11
  */
13
- export function programChange(channel, programNumber, userChange=false)
12
+ export function programChange(channel, programNumber)
14
13
  {
15
14
  /**
16
15
  * @type {WorkletProcessorChannel}
@@ -48,8 +47,8 @@ export function setPreset(channel, preset)
48
47
 
49
48
  /**
50
49
  * Toggles drums on a given channel
51
- * @param channel {number}
52
- * @param isDrum {boolean}
50
+ * @param channel {number} - The MIDI Channel to use
51
+ * @param isDrum {boolean} - boolean, if the channel should be drums
53
52
  * @this {Synthesizer}
54
53
  */
55
54
  export function setDrums(channel, isDrum)
@@ -68,7 +67,8 @@ export function setDrums(channel, isDrum)
68
67
  }
69
68
 
70
69
  /**
71
- * @param buffer {ArrayBuffer}
70
+ * Reloads the soundfont, stops all voices
71
+ * @param buffer {ArrayBuffer} - the new soundfont buffer
72
72
  * @this {Synthesizer}
73
73
  */
74
74
  export function reloadSoundFont(buffer)
@@ -2,8 +2,8 @@ import { arrayToHexString, consoleColors } from '../../../utils/other.js'
2
2
  import { SpessaSynthInfo, SpessaSynthWarn } from '../../../utils/loggin.js'
3
3
  /**
4
4
  * Executes a system exclusive
5
- * @param messageData {number[]} - the message data without f0
6
- * @param channelOffset {number}
5
+ * @param messageData {number[]} - The binary message data without f0
6
+ * @param channelOffset {number} - Channel offset (for midi ports). Defaults to 0.
7
7
  * @this {Synthesizer}
8
8
  */
9
9
 
@@ -1,8 +1,8 @@
1
- import { customControllers, NON_CC_INDEX_OFFSET } from '../worklet_utilities/worklet_processor_channel.js'
2
- import { consoleColors } from '../../../utils/other.js'
3
- import { modulatorSources } from '../../../soundfont/chunk/modulators.js'
4
- import { computeModulators } from '../worklet_utilities/worklet_modulator.js'
5
- import { SpessaSynthInfo } from '../../../utils/loggin.js'
1
+ import {customControllers, NON_CC_INDEX_OFFSET} from '../worklet_utilities/worklet_processor_channel.js'
2
+ import {consoleColors} from '../../../utils/other.js'
3
+ import {modulatorSources} from '../../../soundfont/chunk/modulators.js'
4
+ import {computeModulators} from '../worklet_utilities/worklet_modulator.js'
5
+ import {SpessaSynthInfo} from '../../../utils/loggin.js'
6
6
 
7
7
  /**
8
8
  * Transposes all channels by given amount of semitones
@@ -22,9 +22,9 @@ export function transposeAllChannels(semitones, force = false)
22
22
  /**
23
23
  * Transposes the channel by given amount of semitones
24
24
  * @this {Synthesizer}
25
- * @param channel {number}
26
- * @param semitones {number} Can be float
27
- * @param force {boolean} defaults to false, if true transposes the channel even if it's a drum channel
25
+ * @param channel {number} - The MIDI Channel to use
26
+ * @param semitones {number} - Can be a float
27
+ * @param force {boolean} - defaults to false, if true transposes the channel even if it's a drum channel
28
28
  */
29
29
  export function transposeChannel(channel, semitones, force=false)
30
30
  {
@@ -99,7 +99,6 @@ export function setModulationDepth(channel, cents)
99
99
  */
100
100
  export function pitchWheel(channel, MSB, LSB)
101
101
  {
102
- const bend = (LSB | (MSB << 7));
103
- this.workletProcessorChannels[channel].midiControllers[NON_CC_INDEX_OFFSET + modulatorSources.pitchWheel] = bend;
102
+ this.workletProcessorChannels[channel].midiControllers[NON_CC_INDEX_OFFSET + modulatorSources.pitchWheel] = (LSB | (MSB << 7));
104
103
  this.workletProcessorChannels[channel].voices.forEach(v => computeModulators(v, this.workletProcessorChannels[channel].midiControllers));
105
104
  }
@@ -28,6 +28,7 @@ import { modulatorSources } from '../../../soundfont/chunk/modulators.js'
28
28
  */
29
29
 
30
30
  /**
31
+ * Adds a new channel
31
32
  * @this {Synthesizer}
32
33
  */
33
34
  export function addNewChannel()