spessasynth_lib 3.20.19 → 3.20.20
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/@types/index.d.ts +2 -1
- package/@types/midi_parser/basic_midi.d.ts +7 -7
- package/@types/synthetizer/synthetizer.d.ts +5 -0
- package/@types/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.d.ts +5 -0
- package/README.md +3 -2
- package/index.js +2 -0
- package/midi_parser/basic_midi.js +24 -25
- package/midi_parser/midi_builder.js +2 -2
- package/midi_parser/midi_loader.js +2 -2
- package/package.json +1 -1
- package/sequencer/worklet_sequencer/play.js +2 -21
- package/sequencer/worklet_sequencer/process_tick.js +4 -2
- package/sequencer/worklet_sequencer/song_control.js +4 -2
- package/sequencer/worklet_sequencer/worklet_sequencer.js +3 -0
- package/synthetizer/synthetizer.js +1 -0
- package/synthetizer/worklet_processor.min.js +6 -6
- package/synthetizer/worklet_system/main_processor.js +10 -6
- package/synthetizer/worklet_system/worklet_methods/data_entry.js +20 -3
- package/synthetizer/worklet_system/worklet_methods/reset_controllers.js +1 -0
- package/synthetizer/worklet_system/worklet_methods/snapshot.js +2 -0
- package/synthetizer/worklet_system/worklet_methods/voice_control.js +2 -1
- package/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +2 -0
package/@types/index.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { writeMIDIFile } from './midi_parser/midi_writer.js';
|
|
|
14
14
|
import { writeRMIDI } from './midi_parser/rmidi_writer.js';
|
|
15
15
|
import { applySnapshotToMIDI } from './midi_parser/midi_editor.js';
|
|
16
16
|
import { modifyMIDI } from './midi_parser/midi_editor.js';
|
|
17
|
+
import { MIDIticksToSeconds } from './midi_parser/basic_midi.js';
|
|
17
18
|
import { audioBufferToWav } from './utils/buffer_to_wav.js';
|
|
18
19
|
import { SpessaSynthLogging } from './utils/loggin.js';
|
|
19
20
|
import { SpessaSynthGroup } from './utils/loggin.js';
|
|
@@ -34,4 +35,4 @@ import { readBytesAsUintBigEndian } from './utils/byte_functions/big_endian.js';
|
|
|
34
35
|
import { NON_CC_INDEX_OFFSET } from './synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js';
|
|
35
36
|
import { ALL_CHANNELS_OR_DIFFERENT_ACTION } from './synthetizer/worklet_system/message_protocol/worklet_message.js';
|
|
36
37
|
import { WORKLET_URL_ABSOLUTE } from './synthetizer/worklet_url.js';
|
|
37
|
-
export { Sequencer, Synthetizer, DEFAULT_PERCUSSION, VOICE_CAP, BasicSoundFont, loadSoundFont, trimSoundfont, modulatorSources, encodeVorbis, MIDI, MIDIBuilder, IndexedByteArray, writeMIDIFile, writeRMIDI, applySnapshotToMIDI, modifyMIDI, audioBufferToWav, SpessaSynthLogging, SpessaSynthGroup, SpessaSynthTable, SpessaSynthGroupEnd, SpessaSynthInfo, SpessaSynthWarn, SpessaSynthGroupCollapsed, midiControllers, messageTypes, MIDIDeviceHandler, WebMidiLinkHandler, arrayToHexString, consoleColors, formatTitle, formatTime, readBytesAsUintBigEndian, NON_CC_INDEX_OFFSET, ALL_CHANNELS_OR_DIFFERENT_ACTION, WORKLET_URL_ABSOLUTE };
|
|
38
|
+
export { Sequencer, Synthetizer, DEFAULT_PERCUSSION, VOICE_CAP, BasicSoundFont, loadSoundFont, trimSoundfont, modulatorSources, encodeVorbis, MIDI, MIDIBuilder, IndexedByteArray, writeMIDIFile, writeRMIDI, applySnapshotToMIDI, modifyMIDI, MIDIticksToSeconds, audioBufferToWav, SpessaSynthLogging, SpessaSynthGroup, SpessaSynthTable, SpessaSynthGroupEnd, SpessaSynthInfo, SpessaSynthWarn, SpessaSynthGroupCollapsed, midiControllers, messageTypes, MIDIDeviceHandler, WebMidiLinkHandler, arrayToHexString, consoleColors, formatTitle, formatTime, readBytesAsUintBigEndian, NON_CC_INDEX_OFFSET, ALL_CHANNELS_OR_DIFFERENT_ACTION, WORKLET_URL_ABSOLUTE };
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts ticks to time in seconds
|
|
3
|
+
* @param ticks {number} time in MIDI ticks
|
|
4
|
+
* @param mid {BasicMIDI} the MIDI
|
|
5
|
+
* @returns {number} time in seconds
|
|
6
|
+
*/
|
|
7
|
+
export function MIDIticksToSeconds(ticks: number, mid: BasicMIDI): number;
|
|
1
8
|
export class BasicMIDI {
|
|
2
9
|
/**
|
|
3
10
|
* The time division of the sequence
|
|
@@ -115,11 +122,4 @@ export class BasicMIDI {
|
|
|
115
122
|
* @type {MidiMessage[][]}
|
|
116
123
|
*/
|
|
117
124
|
tracks: MidiMessage[][];
|
|
118
|
-
/**
|
|
119
|
-
* Converts ticks to time in seconds
|
|
120
|
-
* @param ticks {number} time in MIDI ticks
|
|
121
|
-
* @returns {number} time in seconds
|
|
122
|
-
* @protected
|
|
123
|
-
*/
|
|
124
|
-
protected _ticksToSeconds(ticks: number): number;
|
|
125
125
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* @property {BasicMIDI} parsedMIDI - the MIDI to render
|
|
8
8
|
* @property {SynthesizerSnapshot} snapshot - the snapshot to apply
|
|
9
9
|
* @property {boolean|undefined} oneOutput - if synth should use one output with 32 channels (2 audio channels for each midi channel). this disables chorus and reverb.
|
|
10
|
+
* @property {number|undefined} loopCount - the times to loop the song
|
|
10
11
|
*/
|
|
11
12
|
export const WORKLET_PROCESSOR_NAME: "spessasynth-worklet-system";
|
|
12
13
|
export const VOICE_CAP: 450;
|
|
@@ -312,6 +313,10 @@ export type StartRenderingDataConfig = {
|
|
|
312
313
|
* - if synth should use one output with 32 channels (2 audio channels for each midi channel). this disables chorus and reverb.
|
|
313
314
|
*/
|
|
314
315
|
oneOutput: boolean | undefined;
|
|
316
|
+
/**
|
|
317
|
+
* - the times to loop the song
|
|
318
|
+
*/
|
|
319
|
+
loopCount: number | undefined;
|
|
315
320
|
};
|
|
316
321
|
import { EventHandler } from './synth_event_handler.js';
|
|
317
322
|
import { SoundfontManager } from './synth_soundfont_manager.js';
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @property {number} channelTransposeKeyShift - key shift of the channel
|
|
8
8
|
* @property {Int8Array} channelOctaveTuning - the tuning for octave on this channel
|
|
9
|
+
* @property {Int16Array} keyCentTuning - tuning of individual keys in cents
|
|
9
10
|
* @property {boolean} holdPedal - indicates whether the hold pedal is active
|
|
10
11
|
* @property {boolean} drumChannel - indicates whether the channel is a drum channel
|
|
11
12
|
*
|
|
@@ -77,6 +78,10 @@ export type WorkletProcessorChannel = {
|
|
|
77
78
|
* - the tuning for octave on this channel
|
|
78
79
|
*/
|
|
79
80
|
channelOctaveTuning: Int8Array;
|
|
81
|
+
/**
|
|
82
|
+
* - tuning of individual keys in cents
|
|
83
|
+
*/
|
|
84
|
+
keyCentTuning: Int16Array;
|
|
80
85
|
/**
|
|
81
86
|
* - indicates whether the hold pedal is active
|
|
82
87
|
*/
|
package/README.md
CHANGED
|
@@ -41,7 +41,8 @@ document.getElementById("button").onclick = async () => {
|
|
|
41
41
|
- **SoundFont3 Support:** Play compressed SoundFonts!
|
|
42
42
|
- **Experimental SF2Pack Support:** Play soundfonts compressed with BASSMIDI! (*Note: only works with vorbis compression*)
|
|
43
43
|
- **Can load very large SoundFonts:** up to 4GB! *Note: Only Firefox handles this well; Chromium has a hard-coded memory limit*
|
|
44
|
-
|
|
44
|
+
- **Soundfont manager:** Stack multiple soundfonts!
|
|
45
|
+
- **DLS Level 1 and 2 Support:** *internally converted to sf2*
|
|
45
46
|
- **Reverb and chorus support:** [customizable!](https://github.com/spessasus/SpessaSynth/wiki/Synthetizer-Class#effects-configuration-object)
|
|
46
47
|
- **Export audio files** using [OfflineAudioContext](https://developer.mozilla.org/en-US/docs/Web/API/OfflineAudioContext)
|
|
47
48
|
- **[Custom modulators for additional controllers](https://github.com/spessasus/SpessaSynth/wiki/Modulator-Class#default-modulators):** Why not?
|
|
@@ -101,7 +102,7 @@ document.getElementById("button").onclick = async () => {
|
|
|
101
102
|
|
|
102
103
|
#### Read and play DLS Level 1 or 2 files
|
|
103
104
|
- Read DLS (DownLoadable Sounds) files as SF2 files!
|
|
104
|
-
- **Works like a normal soundfont:** *Saving is still [just one function!](https://github.com/spessasus/SpessaSynth/wiki/SoundFont2-Class#write)*
|
|
105
|
+
- **Works like a normal soundfont:** *Saving it as sf2 is still [just one function!](https://github.com/spessasus/SpessaSynth/wiki/SoundFont2-Class#write)*
|
|
105
106
|
- Converts articulators to both **modulators** and **generators**!
|
|
106
107
|
- Works with both unsigned 8-bit samples and signed 16-bit samples!
|
|
107
108
|
- **Covers special generator cases:** *such as modLfoToPitch*!
|
package/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { loadSoundFont } from "./soundfont/load_soundfont.js";
|
|
3
3
|
import { BasicSoundFont } from "./soundfont/basic_soundfont/basic_soundfont.js";
|
|
4
4
|
import { MIDI } from './midi_parser/midi_loader.js';
|
|
5
|
+
import { MIDIticksToSeconds } from './midi_parser/basic_midi.js';
|
|
5
6
|
import { MIDIBuilder } from "./midi_parser/midi_builder.js";
|
|
6
7
|
import { Synthetizer, VOICE_CAP, DEFAULT_PERCUSSION } from './synthetizer/synthetizer.js';
|
|
7
8
|
import { Sequencer } from './sequencer/sequencer.js';
|
|
@@ -54,6 +55,7 @@ export {
|
|
|
54
55
|
writeRMIDI,
|
|
55
56
|
applySnapshotToMIDI,
|
|
56
57
|
modifyMIDI,
|
|
58
|
+
MIDIticksToSeconds,
|
|
57
59
|
|
|
58
60
|
// Utilities
|
|
59
61
|
audioBufferToWav,
|
|
@@ -124,31 +124,30 @@ export class BasicMIDI
|
|
|
124
124
|
*/
|
|
125
125
|
this.tracks = [];
|
|
126
126
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
return totalSeconds;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Converts ticks to time in seconds
|
|
131
|
+
* @param ticks {number} time in MIDI ticks
|
|
132
|
+
* @param mid {BasicMIDI} the MIDI
|
|
133
|
+
* @returns {number} time in seconds
|
|
134
|
+
*/
|
|
135
|
+
export function MIDIticksToSeconds(ticks, mid) {
|
|
136
|
+
let totalSeconds = 0;
|
|
137
|
+
|
|
138
|
+
while (ticks > 0)
|
|
139
|
+
{
|
|
140
|
+
// tempo changes are reversed so the first element is the last tempo change
|
|
141
|
+
// and the last element is the first tempo change
|
|
142
|
+
// (always at tick 0 and tempo 120)
|
|
143
|
+
// find the last tempo change that has occurred
|
|
144
|
+
let tempo = mid.tempoChanges.find(v => v.ticks < ticks);
|
|
145
|
+
|
|
146
|
+
// calculate the difference and tempo time
|
|
147
|
+
let timeSinceLastTempo = ticks - tempo.ticks;
|
|
148
|
+
totalSeconds += (timeSinceLastTempo * 60) / (tempo.tempo * mid.timeDivision);
|
|
149
|
+
ticks -= timeSinceLastTempo;
|
|
151
150
|
}
|
|
152
151
|
|
|
153
|
-
|
|
152
|
+
return totalSeconds;
|
|
154
153
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BasicMIDI } from './basic_midi.js'
|
|
1
|
+
import { BasicMIDI, MIDIticksToSeconds } from './basic_midi.js'
|
|
2
2
|
import { messageTypes, MidiMessage } from './midi_message.js'
|
|
3
3
|
import { IndexedByteArray } from '../utils/indexed_array.js'
|
|
4
4
|
import { readBytesAsUintBigEndian } from '../utils/byte_functions/big_endian.js'
|
|
@@ -101,7 +101,7 @@ export class MIDIBuilder extends BasicMIDI
|
|
|
101
101
|
|
|
102
102
|
// reverse tempo and compute duration
|
|
103
103
|
this.tempoChanges.reverse();
|
|
104
|
-
this.duration =
|
|
104
|
+
this.duration = MIDIticksToSeconds(this.lastVoiceEventTick, this);
|
|
105
105
|
|
|
106
106
|
// fix midi ports:
|
|
107
107
|
// midi tracks without ports will have a value of -1
|
|
@@ -8,7 +8,7 @@ import { readBytesAsUintBigEndian } from '../utils/byte_functions/big_endian.js'
|
|
|
8
8
|
import { readBytesAsString } from '../utils/byte_functions/string.js'
|
|
9
9
|
import { readLittleEndian } from '../utils/byte_functions/little_endian.js'
|
|
10
10
|
import { RMIDINFOChunks } from './rmidi_writer.js'
|
|
11
|
-
import { BasicMIDI } from './basic_midi.js'
|
|
11
|
+
import { BasicMIDI, MIDIticksToSeconds } from './basic_midi.js'
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* midi_loader.js
|
|
@@ -573,7 +573,7 @@ class MIDI extends BasicMIDI
|
|
|
573
573
|
* The total playback time, in seconds
|
|
574
574
|
* @type {number}
|
|
575
575
|
*/
|
|
576
|
-
this.duration =
|
|
576
|
+
this.duration = MIDIticksToSeconds(this.lastVoiceEventTick, this);
|
|
577
577
|
|
|
578
578
|
SpessaSynthGroupEnd();
|
|
579
579
|
SpessaSynthInfo(`%cMIDI file parsed. Total tick time: %c${this.lastVoiceEventTick}%c, total seconds time: %c${this.duration}`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getEvent, messageTypes, midiControllers } from '../../midi_parser/midi_message.js'
|
|
2
2
|
import { WorkletSequencerReturnMessageType } from './sequencer_message.js'
|
|
3
3
|
import { MIDI_CHANNEL_COUNT } from '../../synthetizer/synthetizer.js'
|
|
4
|
+
import { MIDIticksToSeconds } from '../../midi_parser/basic_midi.js'
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
// an array with preset default values
|
|
@@ -271,26 +272,6 @@ export function play(resetTime = false)
|
|
|
271
272
|
this.setProcessHandler();
|
|
272
273
|
}
|
|
273
274
|
|
|
274
|
-
/**
|
|
275
|
-
* Coverts ticks to time in seconds
|
|
276
|
-
* @param changes {{tempo: number, ticks: number}[]}
|
|
277
|
-
* @param ticks {number}
|
|
278
|
-
* @param division {number}
|
|
279
|
-
* @returns {number}
|
|
280
|
-
*/
|
|
281
|
-
export function ticksToSeconds(changes, ticks, division)
|
|
282
|
-
{
|
|
283
|
-
if (ticks <= 0) {
|
|
284
|
-
return 0;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// find the last tempo change that has occured
|
|
288
|
-
let tempo = changes.find(v => v.ticks < ticks);
|
|
289
|
-
|
|
290
|
-
let timeSinceLastTempo = ticks - tempo.ticks;
|
|
291
|
-
return ticksToSeconds(changes, ticks - timeSinceLastTempo, division) + (timeSinceLastTempo * 60) / (tempo.tempo * division);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
275
|
/**
|
|
295
276
|
* @this {WorkletSequencer}
|
|
296
277
|
* @param ticks {number}
|
|
@@ -302,7 +283,7 @@ export function setTimeTicks(ticks)
|
|
|
302
283
|
this.pausedTime = undefined;
|
|
303
284
|
this.post(
|
|
304
285
|
WorkletSequencerReturnMessageType.timeChange,
|
|
305
|
-
currentTime -
|
|
286
|
+
currentTime - MIDIticksToSeconds(ticks, this.midiData)
|
|
306
287
|
);
|
|
307
288
|
const isNotFinished = this._playTo(0, ticks);
|
|
308
289
|
this._recalculateStartTime(this.playedTime);
|
|
@@ -37,16 +37,18 @@ export function _processTick()
|
|
|
37
37
|
this.playedTime += this.oneTickToSeconds * (eventNext.ticks - event.ticks);
|
|
38
38
|
|
|
39
39
|
// loop
|
|
40
|
-
if((this.midiData.loop.end <= event.ticks) && this.loop)
|
|
40
|
+
if((this.midiData.loop.end <= event.ticks) && this.loop && this.currentLoopCount > 0)
|
|
41
41
|
{
|
|
42
|
+
this.currentLoopCount--;
|
|
42
43
|
this.setTimeTicks(this.midiData.loop.start);
|
|
43
44
|
return;
|
|
44
45
|
}
|
|
45
46
|
// if song has ended
|
|
46
47
|
else if(current >= this.duration)
|
|
47
48
|
{
|
|
48
|
-
if(this.loop)
|
|
49
|
+
if(this.loop && this.currentLoopCount > 0)
|
|
49
50
|
{
|
|
51
|
+
this.currentLoopCount--;
|
|
50
52
|
this.setTimeTicks(this.midiData.loop.start);
|
|
51
53
|
return;
|
|
52
54
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { WorkletSequencerReturnMessageType } from './sequencer_message.js'
|
|
2
2
|
import { consoleColors, formatTime } from '../../utils/other.js'
|
|
3
3
|
import { SpessaSynthGroupCollapsed, SpessaSynthGroupEnd, SpessaSynthInfo, SpessaSynthWarn } from '../../utils/loggin.js'
|
|
4
|
-
import { ticksToSeconds } from './play.js'
|
|
5
4
|
import { MidiData } from '../../midi_parser/midi_data.js'
|
|
6
5
|
import { MIDI } from '../../midi_parser/midi_loader.js'
|
|
7
6
|
import { getUsedProgramsAndKeys } from '../../midi_parser/used_keys_loaded.js'
|
|
7
|
+
import { MIDIticksToSeconds } from '../../midi_parser/basic_midi.js'
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* @param trackNum {number}
|
|
@@ -53,6 +53,8 @@ export function loadNewSequence(parsedMidi)
|
|
|
53
53
|
*/
|
|
54
54
|
this.midiData = parsedMidi;
|
|
55
55
|
|
|
56
|
+
this.currentLoopCount = this.loopCount;
|
|
57
|
+
|
|
56
58
|
// check for embedded soundfont
|
|
57
59
|
if(this.midiData.embeddedSoundFont !== undefined)
|
|
58
60
|
{
|
|
@@ -108,7 +110,7 @@ export function loadNewSequence(parsedMidi)
|
|
|
108
110
|
* @type {number}
|
|
109
111
|
*/
|
|
110
112
|
this.duration = this.midiData.duration;
|
|
111
|
-
this.firstNoteTime =
|
|
113
|
+
this.firstNoteTime = MIDIticksToSeconds(this.midiData.firstNoteOn, this.midiData);
|
|
112
114
|
SpessaSynthInfo(`%cTotal song time: ${formatTime(Math.ceil(this.duration)).time}`, consoleColors.recognized);
|
|
113
115
|
|
|
114
116
|
this.post(WorkletSequencerReturnMessageType.songChange, [new MidiData(this.midiData), this.songIndex]);
|
|
@@ -24,6 +24,7 @@ import { SoundfontManager } from './synth_soundfont_manager.js'
|
|
|
24
24
|
* @property {BasicMIDI} parsedMIDI - the MIDI to render
|
|
25
25
|
* @property {SynthesizerSnapshot} snapshot - the snapshot to apply
|
|
26
26
|
* @property {boolean|undefined} oneOutput - if synth should use one output with 32 channels (2 audio channels for each midi channel). this disables chorus and reverb.
|
|
27
|
+
* @property {number|undefined} loopCount - the times to loop the song
|
|
27
28
|
*/
|
|
28
29
|
|
|
29
30
|
export const WORKLET_PROCESSOR_NAME = "spessasynth-worklet-system";
|