spessasynth_lib 3.16.5 → 3.20.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/@types/index.d.ts +5 -4
- package/@types/midi_parser/basic_midi.d.ts +125 -0
- package/@types/midi_parser/midi_builder.d.ts +69 -0
- package/@types/midi_parser/midi_data.d.ts +2 -2
- package/@types/midi_parser/midi_editor.d.ts +4 -4
- package/@types/midi_parser/midi_loader.d.ts +3 -100
- package/@types/midi_parser/midi_writer.d.ts +2 -2
- package/@types/midi_parser/rmidi_writer.d.ts +3 -3
- package/@types/midi_parser/used_keys_loaded.d.ts +2 -2
- package/@types/sequencer/sequencer.d.ts +1 -1
- package/@types/soundfont/basic_soundfont/basic_sample.d.ts +2 -2
- package/@types/soundfont/basic_soundfont/basic_zone.d.ts +12 -12
- package/@types/soundfont/basic_soundfont/basic_zones.d.ts +4 -0
- package/@types/soundfont/basic_soundfont/riff_chunk.d.ts +6 -0
- package/@types/soundfont/basic_soundfont/write_sf2/soundfont_trimmer.d.ts +2 -2
- package/@types/soundfont/dls/articulator_converter.d.ts +10 -0
- package/@types/soundfont/dls/dls_destinations.d.ts +29 -0
- package/@types/soundfont/dls/dls_preset.d.ts +13 -0
- package/@types/soundfont/dls/dls_sample.d.ts +18 -0
- package/@types/soundfont/dls/dls_soundfont.d.ts +31 -0
- package/@types/soundfont/dls/dls_sources.d.ts +22 -0
- package/@types/soundfont/dls/dls_zone.d.ts +22 -0
- package/@types/soundfont/dls/read_articulation.d.ts +12 -0
- package/@types/soundfont/dls/read_instrument.d.ts +5 -0
- package/@types/soundfont/dls/read_instrument_list.d.ts +5 -0
- package/@types/soundfont/dls/read_lart.d.ts +7 -0
- package/@types/soundfont/dls/read_region.d.ts +7 -0
- package/@types/soundfont/dls/read_samples.d.ts +5 -0
- package/@types/soundfont/load_soundfont.d.ts +6 -0
- package/@types/soundfont/read_sf2/generators.d.ts +18 -5
- package/@types/soundfont/read_sf2/modulators.d.ts +1 -0
- package/@types/soundfont/soundfont.d.ts +2 -1
- package/@types/synthetizer/synthetizer.d.ts +2 -2
- package/@types/utils/byte_functions/little_endian.d.ts +1 -1
- package/README.md +27 -15
- package/index.js +6 -4
- package/midi_parser/basic_midi.js +146 -0
- package/midi_parser/midi_builder.js +281 -0
- package/midi_parser/midi_data.js +1 -1
- package/midi_parser/midi_editor.js +2 -2
- package/midi_parser/midi_loader.js +38 -56
- package/midi_parser/midi_writer.js +1 -1
- package/midi_parser/rmidi_writer.js +2 -2
- package/midi_parser/used_keys_loaded.js +1 -1
- package/package.json +1 -1
- package/sequencer/sequencer.js +1 -1
- package/sequencer/worklet_sequencer/song_control.js +3 -3
- package/sequencer/worklet_sequencer/worklet_sequencer.js +1 -1
- package/soundfont/README.md +6 -2
- package/soundfont/basic_soundfont/basic_sample.js +3 -3
- package/soundfont/basic_soundfont/basic_zone.js +28 -28
- package/soundfont/basic_soundfont/basic_zones.js +15 -19
- package/soundfont/basic_soundfont/riff_chunk.js +20 -4
- package/soundfont/basic_soundfont/write_sf2/soundfont_trimmer.js +1 -1
- package/soundfont/dls/articulator_converter.js +311 -0
- package/soundfont/dls/dls_destinations.js +38 -0
- package/soundfont/dls/dls_preset.js +32 -0
- package/soundfont/dls/dls_sample.js +58 -0
- package/soundfont/dls/dls_soundfont.js +150 -0
- package/soundfont/dls/dls_sources.js +26 -0
- package/soundfont/dls/dls_zone.js +75 -0
- package/soundfont/dls/read_articulation.js +327 -0
- package/soundfont/dls/read_instrument.js +100 -0
- package/soundfont/dls/read_instrument_list.js +17 -0
- package/soundfont/dls/read_lart.js +35 -0
- package/soundfont/dls/read_region.js +129 -0
- package/soundfont/dls/read_samples.js +174 -0
- package/soundfont/load_soundfont.js +21 -0
- package/soundfont/read_sf2/generators.js +41 -6
- package/soundfont/read_sf2/instruments.js +2 -2
- package/soundfont/read_sf2/modulators.js +8 -8
- package/soundfont/read_sf2/presets.js +7 -7
- package/soundfont/read_sf2/samples.js +8 -8
- package/soundfont/read_sf2/zones.js +5 -5
- package/soundfont/soundfont.js +8 -3
- package/synthetizer/synthetizer.js +1 -1
- package/synthetizer/worklet_processor.min.js +10 -7
- package/synthetizer/worklet_system/main_processor.js +1 -2
- package/synthetizer/worklet_system/worklet_methods/program_control.js +6 -3
- package/synthetizer/worklet_system/worklet_methods/worklet_soundfont_manager/worklet_soundfont_manager.js +5 -5
- package/utils/buffer_to_wav.js +5 -26
- package/utils/byte_functions/little_endian.js +1 -1
- /package/@types/{midi_handler → external_midi}/midi_handler.d.ts +0 -0
- /package/@types/{midi_handler → external_midi}/web_midi_link.d.ts +0 -0
- /package/{midi_handler → external_midi}/README.md +0 -0
- /package/{midi_handler → external_midi}/midi_handler.js +0 -0
- /package/{midi_handler → external_midi}/web_midi_link.js +0 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { readLittleEndian } from '../../utils/byte_functions/little_endian.js'
|
|
2
|
+
import { findRIFFListType, readRIFFChunk } from '../basic_soundfont/riff_chunk.js'
|
|
3
|
+
import { Generator, generatorTypes } from '../read_sf2/generators.js'
|
|
4
|
+
import { DLSZone } from './dls_zone.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @this {DLSSoundFont}
|
|
8
|
+
* @param chunk {RiffChunk}
|
|
9
|
+
* @returns {DLSZone}
|
|
10
|
+
*/
|
|
11
|
+
export function readRegion(chunk)
|
|
12
|
+
{
|
|
13
|
+
// regions are essentially instrument zones
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* read chunks in the region
|
|
17
|
+
* @type {RiffChunk[]}
|
|
18
|
+
*/
|
|
19
|
+
const regionChunks = [];
|
|
20
|
+
while(chunk.chunkData.length > chunk.chunkData.currentIndex)
|
|
21
|
+
{
|
|
22
|
+
regionChunks.push(readRIFFChunk(chunk.chunkData));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// region header
|
|
26
|
+
const regionHeader = regionChunks.find(c => c.header === "rgnh");
|
|
27
|
+
// key range
|
|
28
|
+
const keyMin = readLittleEndian(regionHeader.chunkData, 2);
|
|
29
|
+
const keyMax = readLittleEndian(regionHeader.chunkData, 2);
|
|
30
|
+
// vel range
|
|
31
|
+
const velMin = readLittleEndian(regionHeader.chunkData, 2);
|
|
32
|
+
const velMax = readLittleEndian(regionHeader.chunkData, 2);
|
|
33
|
+
|
|
34
|
+
const zone = new DLSZone(
|
|
35
|
+
{min: keyMin, max: keyMax},
|
|
36
|
+
{min: velMin, max: velMax}
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
// fusOptions: no idea about that one???
|
|
40
|
+
readLittleEndian(regionHeader.chunkData, 2);
|
|
41
|
+
|
|
42
|
+
// keyGroup: essentially exclusive class
|
|
43
|
+
const exclusive = readLittleEndian(regionHeader.chunkData, 2);
|
|
44
|
+
if(exclusive !== 0)
|
|
45
|
+
{
|
|
46
|
+
zone.generators.push(new Generator(generatorTypes.exclusiveClass, exclusive));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// lart
|
|
50
|
+
const lart = findRIFFListType(regionChunks, "lart");
|
|
51
|
+
const lar2 = findRIFFListType(regionChunks, "lar2");
|
|
52
|
+
this.readLart(lart, lar2, zone);
|
|
53
|
+
|
|
54
|
+
// wsmpl: wave sample chunk
|
|
55
|
+
zone.isGlobal = false;
|
|
56
|
+
const waveSampleChunk = regionChunks.find(c => c.header === "wsmp");
|
|
57
|
+
// cbSize
|
|
58
|
+
readLittleEndian(waveSampleChunk.chunkData, 4);
|
|
59
|
+
const originalKey = readLittleEndian(waveSampleChunk.chunkData, 2);
|
|
60
|
+
// pitch correction is read from the wave wsmpl chunk
|
|
61
|
+
readLittleEndian(waveSampleChunk.chunkData, 2);
|
|
62
|
+
|
|
63
|
+
// gain correction: Each unit of gain represents 1/655360 dB
|
|
64
|
+
const gainCorrection = readLittleEndian(waveSampleChunk.chunkData, 4);
|
|
65
|
+
// convert to signed and turn into attenuation (invert)
|
|
66
|
+
const dbCorrection = (gainCorrection | 0) / -655360;
|
|
67
|
+
// convert to centibels
|
|
68
|
+
const attenuation = (dbCorrection * 10) / 0.4; // make sure to apply EMU correction
|
|
69
|
+
|
|
70
|
+
// skip options
|
|
71
|
+
readLittleEndian(waveSampleChunk.chunkData, 4);
|
|
72
|
+
|
|
73
|
+
// read loop count (always one or zero)
|
|
74
|
+
const loopsAmount = readLittleEndian(waveSampleChunk.chunkData, 4);
|
|
75
|
+
let loopingMode;
|
|
76
|
+
const loop = {start: 0, end: 0};
|
|
77
|
+
if(loopsAmount === 0)
|
|
78
|
+
{
|
|
79
|
+
// no loop
|
|
80
|
+
loopingMode = 0;
|
|
81
|
+
}
|
|
82
|
+
else
|
|
83
|
+
{
|
|
84
|
+
// ignore cbSize
|
|
85
|
+
readLittleEndian(waveSampleChunk.chunkData, 4);
|
|
86
|
+
// loop type: loop normally or loop until release (like soundfont)
|
|
87
|
+
const loopType = readLittleEndian(waveSampleChunk.chunkData, 4); // why is it long???
|
|
88
|
+
if(loopType === 0)
|
|
89
|
+
{
|
|
90
|
+
loopingMode = 1;
|
|
91
|
+
}
|
|
92
|
+
else
|
|
93
|
+
{
|
|
94
|
+
loopingMode = 3;
|
|
95
|
+
}
|
|
96
|
+
loop.start = readLittleEndian(waveSampleChunk.chunkData, 4);
|
|
97
|
+
const loopLength = readLittleEndian(waveSampleChunk.chunkData, 4);
|
|
98
|
+
loop.end = loop.start + loopLength;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// wave link
|
|
102
|
+
const waveLinkChunk = regionChunks.find(c => c.header === "wlnk");
|
|
103
|
+
if(waveLinkChunk === undefined)
|
|
104
|
+
{
|
|
105
|
+
// no wave link = no sample. What? Why is it even here then????
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// flags
|
|
110
|
+
readLittleEndian(waveLinkChunk.chunkData, 2);
|
|
111
|
+
// phasse group
|
|
112
|
+
readLittleEndian(waveLinkChunk.chunkData, 2);
|
|
113
|
+
// channel
|
|
114
|
+
readLittleEndian(waveLinkChunk.chunkData, 4);
|
|
115
|
+
// sampleID
|
|
116
|
+
const sampleID = readLittleEndian(waveLinkChunk.chunkData, 4);
|
|
117
|
+
const sample = this.samples[sampleID];
|
|
118
|
+
if(sample === undefined)
|
|
119
|
+
{
|
|
120
|
+
throw new Error("Invalid sample ID!");
|
|
121
|
+
}
|
|
122
|
+
zone.setWavesample(
|
|
123
|
+
attenuation, loopingMode,
|
|
124
|
+
loop,
|
|
125
|
+
originalKey,
|
|
126
|
+
sample,
|
|
127
|
+
sampleID);
|
|
128
|
+
return zone;
|
|
129
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { findRIFFListType, readRIFFChunk } from '../basic_soundfont/riff_chunk.js'
|
|
2
|
+
import { readBytesAsString } from '../../utils/byte_functions/string.js'
|
|
3
|
+
import {
|
|
4
|
+
SpessaSynthGroupCollapsed,
|
|
5
|
+
SpessaSynthGroupEnd,
|
|
6
|
+
SpessaSynthInfo, SpessaSynthWarn,
|
|
7
|
+
} from '../../utils/loggin.js'
|
|
8
|
+
import { consoleColors } from '../../utils/other.js'
|
|
9
|
+
import { readLittleEndian, signedInt16 } from '../../utils/byte_functions/little_endian.js'
|
|
10
|
+
import { DLSSample } from './dls_sample.js'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @this {DLSSoundFont}
|
|
14
|
+
* @param waveListChunk {RiffChunk}
|
|
15
|
+
*/
|
|
16
|
+
export function readDLSSamples(waveListChunk)
|
|
17
|
+
{
|
|
18
|
+
SpessaSynthGroupCollapsed("%cLoading Wave samples...",
|
|
19
|
+
consoleColors.recognized);
|
|
20
|
+
let sampleID = 0;
|
|
21
|
+
while(waveListChunk.chunkData.currentIndex < waveListChunk.chunkData.length)
|
|
22
|
+
{
|
|
23
|
+
const waveChunk = readRIFFChunk(waveListChunk.chunkData);
|
|
24
|
+
this.verifyHeader(waveChunk, "LIST");
|
|
25
|
+
this.verifyText(readBytesAsString(waveChunk.chunkData, 4), "wave");
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @type {RiffChunk[]}
|
|
29
|
+
*/
|
|
30
|
+
const waveChunks = [];
|
|
31
|
+
while(waveChunk.chunkData.currentIndex < waveChunk.chunkData.length)
|
|
32
|
+
{
|
|
33
|
+
waveChunks.push(readRIFFChunk(waveChunk.chunkData));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const fmtChunk = waveChunks.find(c => c.header === "fmt ");
|
|
37
|
+
if(!fmtChunk)
|
|
38
|
+
{
|
|
39
|
+
throw new Error("No fmt chunk in the wave file!");
|
|
40
|
+
}
|
|
41
|
+
const waveFormat = readLittleEndian(fmtChunk.chunkData, 2);
|
|
42
|
+
if(waveFormat !== 1)
|
|
43
|
+
{
|
|
44
|
+
throw new Error("Only PCM format in WAVE is supported.");
|
|
45
|
+
}
|
|
46
|
+
const channelsAmount = readLittleEndian(fmtChunk.chunkData, 2);
|
|
47
|
+
if(channelsAmount !== 1)
|
|
48
|
+
{
|
|
49
|
+
throw new Error("Only mono samples are supported.");
|
|
50
|
+
}
|
|
51
|
+
const sampleRate = readLittleEndian(fmtChunk.chunkData, 4);
|
|
52
|
+
// skip avg bytes
|
|
53
|
+
readLittleEndian(fmtChunk.chunkData, 4);
|
|
54
|
+
// blockAlign
|
|
55
|
+
readLittleEndian(fmtChunk.chunkData, 2);
|
|
56
|
+
// it's bits per sample because one channel
|
|
57
|
+
const wBitsPerSample = readLittleEndian(fmtChunk.chunkData, 2);
|
|
58
|
+
const bytesPerSample = wBitsPerSample / 8;
|
|
59
|
+
|
|
60
|
+
const maxSampleValue = Math.pow(2, bytesPerSample * 8 - 1); // Max value for the sample
|
|
61
|
+
const maxUnsigned = Math.pow(2, bytesPerSample * 8);
|
|
62
|
+
|
|
63
|
+
let normalizationFactor;
|
|
64
|
+
let isUnsigned = false;
|
|
65
|
+
|
|
66
|
+
if (wBitsPerSample === 8)
|
|
67
|
+
{
|
|
68
|
+
normalizationFactor = 255; // For 8-bit normalize from 0-255
|
|
69
|
+
isUnsigned = true;
|
|
70
|
+
}
|
|
71
|
+
else
|
|
72
|
+
{
|
|
73
|
+
normalizationFactor = maxSampleValue; // For 16-bit normalize from -32768 to 32767
|
|
74
|
+
}
|
|
75
|
+
// read the data
|
|
76
|
+
const dataChunk = waveChunks.find(c => c.header === "data");
|
|
77
|
+
if(!dataChunk)
|
|
78
|
+
{
|
|
79
|
+
throw new Error("No data chunk in the wave chunk!");
|
|
80
|
+
}
|
|
81
|
+
const sampleLength = dataChunk.size / bytesPerSample;
|
|
82
|
+
const sampleData = new Float32Array(sampleLength);
|
|
83
|
+
for (let i = 0; i < sampleData.length; i++)
|
|
84
|
+
{
|
|
85
|
+
// read
|
|
86
|
+
let sample = readLittleEndian(dataChunk.chunkData, bytesPerSample);
|
|
87
|
+
// turn into signed
|
|
88
|
+
if (isUnsigned)
|
|
89
|
+
{
|
|
90
|
+
// normalize unsigned 8-bit sample
|
|
91
|
+
sampleData[i] = (sample / normalizationFactor) - 0.5;
|
|
92
|
+
}
|
|
93
|
+
else
|
|
94
|
+
{
|
|
95
|
+
// normalize signed 16-bit sample
|
|
96
|
+
if (sample >= maxSampleValue)
|
|
97
|
+
{
|
|
98
|
+
sample -= maxUnsigned;
|
|
99
|
+
}
|
|
100
|
+
sampleData[i] = sample / normalizationFactor;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// sane defaults
|
|
105
|
+
let sampleKey = 60;
|
|
106
|
+
let samplePitch = 0;
|
|
107
|
+
let sampleLoopStart = 0;
|
|
108
|
+
let sampleLoopEnd = sampleData.length - 1;
|
|
109
|
+
|
|
110
|
+
// read wsmp
|
|
111
|
+
const wsmpChunk = waveChunks.find(c => c.header === "wsmp")
|
|
112
|
+
if(wsmpChunk)
|
|
113
|
+
{
|
|
114
|
+
// skip cbsize
|
|
115
|
+
readLittleEndian(wsmpChunk.chunkData, 4);
|
|
116
|
+
sampleKey = readLittleEndian(wsmpChunk.chunkData, 2);
|
|
117
|
+
// section 1.14.2: Each relative pitch unit represents 1/65536 cents.
|
|
118
|
+
// but that doesn't seem to be true for this one: it's just cents.
|
|
119
|
+
samplePitch = signedInt16(
|
|
120
|
+
wsmpChunk.chunkData[wsmpChunk.chunkData.currentIndex++],
|
|
121
|
+
wsmpChunk.chunkData[wsmpChunk.chunkData.currentIndex++]
|
|
122
|
+
);
|
|
123
|
+
// gain is handled in regions as initialAttenuation
|
|
124
|
+
readLittleEndian(wsmpChunk.chunkData, 4);
|
|
125
|
+
// no idea about ful options
|
|
126
|
+
readLittleEndian(wsmpChunk.chunkData, 4);
|
|
127
|
+
const loopsAmount = readLittleEndian(wsmpChunk.chunkData, 4);
|
|
128
|
+
if(loopsAmount === 1)
|
|
129
|
+
{
|
|
130
|
+
// skip size and type
|
|
131
|
+
readLittleEndian(wsmpChunk.chunkData, 8);
|
|
132
|
+
sampleLoopStart = readLittleEndian(wsmpChunk.chunkData, 4);
|
|
133
|
+
const loopSize = readLittleEndian(wsmpChunk.chunkData, 4);
|
|
134
|
+
sampleLoopEnd = sampleLoopStart + loopSize;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else
|
|
138
|
+
{
|
|
139
|
+
SpessaSynthWarn("No wsmp chunk in wave... using sane defaults.")
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// read sample name
|
|
143
|
+
const waveInfo = findRIFFListType(waveChunks, "INFO");
|
|
144
|
+
let sampleName = `Unnamed ${sampleID}`;
|
|
145
|
+
if(waveInfo)
|
|
146
|
+
{
|
|
147
|
+
let infoChunk = readRIFFChunk(waveInfo.chunkData);
|
|
148
|
+
while(infoChunk.header !== "INAM" && waveInfo.chunkData.currentIndex < waveInfo.chunkData.length)
|
|
149
|
+
{
|
|
150
|
+
infoChunk = readRIFFChunk(waveInfo.chunkData);
|
|
151
|
+
}
|
|
152
|
+
if(infoChunk.header === "INAM")
|
|
153
|
+
{
|
|
154
|
+
sampleName = readBytesAsString(infoChunk.chunkData, infoChunk.size).trim();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
this.samples.push(new DLSSample(
|
|
159
|
+
sampleName,
|
|
160
|
+
sampleRate,
|
|
161
|
+
sampleKey,
|
|
162
|
+
samplePitch,
|
|
163
|
+
sampleLoopStart,
|
|
164
|
+
sampleLength,
|
|
165
|
+
sampleData
|
|
166
|
+
));
|
|
167
|
+
|
|
168
|
+
sampleID++;
|
|
169
|
+
SpessaSynthInfo(`%cLoaded sample %c${sampleName}`,
|
|
170
|
+
consoleColors.info,
|
|
171
|
+
consoleColors.recognized);
|
|
172
|
+
}
|
|
173
|
+
SpessaSynthGroupEnd();
|
|
174
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { IndexedByteArray } from '../utils/indexed_array.js'
|
|
2
|
+
import { readBytesAsString } from '../utils/byte_functions/string.js'
|
|
3
|
+
import { DLSSoundFont } from './dls/dls_soundfont.js'
|
|
4
|
+
import { SoundFont2 } from './soundfont.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Loads a soundfont file
|
|
8
|
+
* @param buffer {ArrayBuffer}
|
|
9
|
+
* @returns {BasicSoundFont}
|
|
10
|
+
*/
|
|
11
|
+
export function loadSoundFont(buffer)
|
|
12
|
+
{
|
|
13
|
+
const check = buffer.slice(8, 12);
|
|
14
|
+
const a = new IndexedByteArray(check);
|
|
15
|
+
const id = readBytesAsString(a, 4, undefined, false).toLowerCase();
|
|
16
|
+
if(id === "dls ")
|
|
17
|
+
{
|
|
18
|
+
return new DLSSoundFont(buffer);
|
|
19
|
+
}
|
|
20
|
+
return new SoundFont2(buffer, false);
|
|
21
|
+
}
|
|
@@ -149,6 +149,39 @@ generatorLimits[generatorTypes.exclusiveClass] = {min: 0, max: 99999, def: 0};
|
|
|
149
149
|
generatorLimits[generatorTypes.overridingRootKey] = {min: 0-1, max: 127, def: -1};
|
|
150
150
|
|
|
151
151
|
|
|
152
|
+
export class Generator
|
|
153
|
+
{
|
|
154
|
+
/**
|
|
155
|
+
* Constructs a new generator
|
|
156
|
+
* @param type {generatorTypes|number}
|
|
157
|
+
* @param value {number}
|
|
158
|
+
*/
|
|
159
|
+
constructor(type = generatorTypes.INVALID, value = 0)
|
|
160
|
+
{
|
|
161
|
+
this.generatorType = type;
|
|
162
|
+
if(value === undefined)
|
|
163
|
+
{
|
|
164
|
+
throw new Error("No value provided.");
|
|
165
|
+
}
|
|
166
|
+
const lim = generatorLimits[type];
|
|
167
|
+
this.generatorValue = Math.round(value);
|
|
168
|
+
if(lim !== undefined)
|
|
169
|
+
{
|
|
170
|
+
this.generatorValue = Math.max(lim.min, Math.min(lim.max, this.generatorValue));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* The generator's enum number
|
|
175
|
+
* @type {generatorTypes|number}
|
|
176
|
+
*/
|
|
177
|
+
generatorType = generatorTypes.INVALID;
|
|
178
|
+
/**
|
|
179
|
+
* The generator's 16-bit value
|
|
180
|
+
* @type {number}
|
|
181
|
+
*/
|
|
182
|
+
generatorValue = 0;
|
|
183
|
+
}
|
|
184
|
+
|
|
152
185
|
/**
|
|
153
186
|
* @param generatorType {number}
|
|
154
187
|
* @param presetGens {Generator[]}
|
|
@@ -173,19 +206,21 @@ export function addAndClampGenerator(generatorType, presetGens, instrumentGens)
|
|
|
173
206
|
return Math.max(limits.min, Math.min(limits.max, instruValue + presetValue));
|
|
174
207
|
}
|
|
175
208
|
|
|
176
|
-
|
|
177
|
-
|
|
209
|
+
export class ReadGenerator extends Generator
|
|
210
|
+
{
|
|
178
211
|
/**
|
|
179
212
|
* Creates a generator
|
|
180
213
|
* @param dataArray {IndexedByteArray}
|
|
181
214
|
*/
|
|
182
|
-
constructor(dataArray)
|
|
215
|
+
constructor(dataArray)
|
|
216
|
+
{
|
|
217
|
+
super();
|
|
183
218
|
// 4 bytes:
|
|
184
219
|
// type, type, type, value
|
|
185
220
|
const i = dataArray.currentIndex;
|
|
186
221
|
/**
|
|
187
|
-
* @type {generatorTypes}
|
|
188
|
-
|
|
222
|
+
* @type {generatorTypes|number}
|
|
223
|
+
*/
|
|
189
224
|
this.generatorType = (dataArray[i + 1] << 8) | dataArray[i];
|
|
190
225
|
this.generatorValue = signedInt16(dataArray[i + 2], dataArray[i + 3]);
|
|
191
226
|
dataArray.currentIndex += 4;
|
|
@@ -202,7 +237,7 @@ export function readGenerators(generatorChunk)
|
|
|
202
237
|
let gens = [];
|
|
203
238
|
while(generatorChunk.chunkData.length > generatorChunk.chunkData.currentIndex)
|
|
204
239
|
{
|
|
205
|
-
gens.push(new
|
|
240
|
+
gens.push(new ReadGenerator(generatorChunk.chunkData));
|
|
206
241
|
}
|
|
207
242
|
if(gens.length > 1)
|
|
208
243
|
{
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {RiffChunk} from "../basic_soundfont/riff_chunk.js";
|
|
2
2
|
import {InstrumentZone} from "./zones.js";
|
|
3
|
-
import {
|
|
3
|
+
import {readLittleEndian} from "../../utils/byte_functions/little_endian.js";
|
|
4
4
|
import { readBytesAsString } from '../../utils/byte_functions/string.js'
|
|
5
5
|
import { BasicInstrument } from '../basic_soundfont/basic_instrument.js'
|
|
6
6
|
|
|
@@ -19,7 +19,7 @@ export class Instrument extends BasicInstrument
|
|
|
19
19
|
{
|
|
20
20
|
super();
|
|
21
21
|
this.instrumentName = readBytesAsString(instrumentChunk.chunkData, 20).trim();
|
|
22
|
-
this.instrumentZoneIndex =
|
|
22
|
+
this.instrumentZoneIndex = readLittleEndian(instrumentChunk.chunkData, 2);
|
|
23
23
|
this.instrumentZonesAmount = 0;
|
|
24
24
|
}
|
|
25
25
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {signedInt16,
|
|
1
|
+
import {signedInt16, readLittleEndian} from "../../utils/byte_functions/little_endian.js";
|
|
2
2
|
import { IndexedByteArray } from '../../utils/indexed_array.js';
|
|
3
3
|
import { generatorTypes } from './generators.js'
|
|
4
4
|
import { midiControllers } from '../../midi_parser/midi_message.js'
|
|
@@ -15,7 +15,8 @@ export const modulatorSources = {
|
|
|
15
15
|
channelPressure: 13,
|
|
16
16
|
pitchWheel: 14,
|
|
17
17
|
pitchWheelRange: 16,
|
|
18
|
-
link: 127
|
|
18
|
+
link: 127,
|
|
19
|
+
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
export const modulatorCurveTypes = {
|
|
@@ -54,11 +55,11 @@ export class Modulator{
|
|
|
54
55
|
}
|
|
55
56
|
else
|
|
56
57
|
{
|
|
57
|
-
this.modulatorSource =
|
|
58
|
-
this.modulatorDestination =
|
|
58
|
+
this.modulatorSource = readLittleEndian(dataArray, 2);
|
|
59
|
+
this.modulatorDestination = readLittleEndian(dataArray, 2);
|
|
59
60
|
this.transformAmount = signedInt16(dataArray[dataArray.currentIndex++], dataArray[dataArray.currentIndex++]);
|
|
60
|
-
this.modulationSecondarySrc =
|
|
61
|
-
this.transformType =
|
|
61
|
+
this.modulationSecondarySrc = readLittleEndian(dataArray, 2);
|
|
62
|
+
this.transformType = readLittleEndian(dataArray, 2);
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
if(this.modulatorDestination > 58)
|
|
@@ -142,7 +143,7 @@ export class Modulator{
|
|
|
142
143
|
}
|
|
143
144
|
}
|
|
144
145
|
|
|
145
|
-
function getModSourceEnum(curveType, polarity, direction, isCC, index)
|
|
146
|
+
export function getModSourceEnum(curveType, polarity, direction, isCC, index)
|
|
146
147
|
{
|
|
147
148
|
return (curveType << 10) | (polarity << 9) | (direction << 8) | (isCC << 7) | index;
|
|
148
149
|
}
|
|
@@ -189,7 +190,6 @@ export const defaultModulators = [
|
|
|
189
190
|
}),
|
|
190
191
|
|
|
191
192
|
// reverb effects to send
|
|
192
|
-
// 1000 to align with the reverbSend (overriding it works anyways)
|
|
193
193
|
new Modulator({srcEnum: 0x00DB, dest: generatorTypes.reverbEffectsSend, amt: 200, secSrcEnum: 0x0, transform: 0}),
|
|
194
194
|
|
|
195
195
|
// chorus effects to send
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {RiffChunk} from "../basic_soundfont/riff_chunk.js";
|
|
2
2
|
import {PresetZone} from "./zones.js";
|
|
3
|
-
import {
|
|
3
|
+
import {readLittleEndian} from "../../utils/byte_functions/little_endian.js";
|
|
4
4
|
import { readBytesAsString } from '../../utils/byte_functions/string.js'
|
|
5
5
|
import { BasicPreset } from '../basic_soundfont/basic_preset.js'
|
|
6
6
|
|
|
@@ -21,14 +21,14 @@ export class Preset extends BasicPreset
|
|
|
21
21
|
.trim()
|
|
22
22
|
.replace(/\d{3}:\d{3}/, ""); // remove those pesky "000:001"
|
|
23
23
|
|
|
24
|
-
this.program =
|
|
25
|
-
this.bank =
|
|
26
|
-
this.presetZoneStartIndex =
|
|
24
|
+
this.program = readLittleEndian(presetChunk.chunkData, 2);
|
|
25
|
+
this.bank = readLittleEndian(presetChunk.chunkData, 2);
|
|
26
|
+
this.presetZoneStartIndex = readLittleEndian(presetChunk.chunkData, 2);
|
|
27
27
|
|
|
28
28
|
// read the dwords
|
|
29
|
-
this.library =
|
|
30
|
-
this.genre =
|
|
31
|
-
this.morphology =
|
|
29
|
+
this.library = readLittleEndian(presetChunk.chunkData, 4);
|
|
30
|
+
this.genre = readLittleEndian(presetChunk.chunkData, 4);
|
|
31
|
+
this.morphology = readLittleEndian(presetChunk.chunkData, 4);
|
|
32
32
|
this.presetZonesAmount = 0;
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { RiffChunk } from '../basic_soundfont/riff_chunk.js'
|
|
2
2
|
import { IndexedByteArray } from '../../utils/indexed_array.js'
|
|
3
|
-
import {
|
|
3
|
+
import { readLittleEndian, signedInt8 } from '../../utils/byte_functions/little_endian.js'
|
|
4
4
|
import { stbvorbis } from '../../externals/stbvorbis_sync/stbvorbis_sync.min.js'
|
|
5
5
|
import { SpessaSynthWarn } from '../../utils/loggin.js'
|
|
6
6
|
import { readBytesAsString } from '../../utils/byte_functions/string.js'
|
|
@@ -239,19 +239,19 @@ function readSample(index, sampleHeaderData, smplArrayData, isDataRaw) {
|
|
|
239
239
|
let sampleName = readBytesAsString(sampleHeaderData, 20);
|
|
240
240
|
|
|
241
241
|
// read the sample start index
|
|
242
|
-
let sampleStartIndex =
|
|
242
|
+
let sampleStartIndex = readLittleEndian(sampleHeaderData, 4) * 2;
|
|
243
243
|
|
|
244
244
|
// read the sample end index
|
|
245
|
-
let sampleEndIndex =
|
|
245
|
+
let sampleEndIndex = readLittleEndian(sampleHeaderData, 4) * 2;
|
|
246
246
|
|
|
247
247
|
// read the sample looping start index
|
|
248
|
-
let sampleLoopStartIndex =
|
|
248
|
+
let sampleLoopStartIndex = readLittleEndian(sampleHeaderData, 4) * 2;
|
|
249
249
|
|
|
250
250
|
// read the sample looping end index
|
|
251
|
-
let sampleLoopEndIndex =
|
|
251
|
+
let sampleLoopEndIndex = readLittleEndian(sampleHeaderData, 4) * 2;
|
|
252
252
|
|
|
253
253
|
// read the sample rate
|
|
254
|
-
let sampleRate =
|
|
254
|
+
let sampleRate = readLittleEndian(sampleHeaderData, 4);
|
|
255
255
|
|
|
256
256
|
// read the original sample pitch
|
|
257
257
|
let samplePitch = sampleHeaderData[sampleHeaderData.currentIndex++];
|
|
@@ -266,8 +266,8 @@ function readSample(index, sampleHeaderData, smplArrayData, isDataRaw) {
|
|
|
266
266
|
|
|
267
267
|
|
|
268
268
|
// read the link to the other channel
|
|
269
|
-
let sampleLink =
|
|
270
|
-
let sampleType =
|
|
269
|
+
let sampleLink = readLittleEndian(sampleHeaderData, 2);
|
|
270
|
+
let sampleType = readLittleEndian(sampleHeaderData, 2);
|
|
271
271
|
|
|
272
272
|
|
|
273
273
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {readLittleEndian} from "../../utils/byte_functions/little_endian.js";
|
|
2
2
|
import {IndexedByteArray} from "../../utils/indexed_array.js";
|
|
3
3
|
import {RiffChunk} from "../basic_soundfont/riff_chunk.js";
|
|
4
4
|
import {Generator, generatorTypes} from "./generators.js";
|
|
@@ -20,8 +20,8 @@ export class InstrumentZone extends BasicInstrumentZone
|
|
|
20
20
|
constructor(dataArray)
|
|
21
21
|
{
|
|
22
22
|
super();
|
|
23
|
-
this.generatorZoneStartIndex =
|
|
24
|
-
this.modulatorZoneStartIndex =
|
|
23
|
+
this.generatorZoneStartIndex = readLittleEndian(dataArray, 2);
|
|
24
|
+
this.modulatorZoneStartIndex = readLittleEndian(dataArray, 2);
|
|
25
25
|
this.modulatorZoneSize = 0;
|
|
26
26
|
this.generatorZoneSize = 0;
|
|
27
27
|
this.isGlobal = true;
|
|
@@ -146,8 +146,8 @@ export class PresetZone extends BasicPresetZone
|
|
|
146
146
|
constructor(dataArray)
|
|
147
147
|
{
|
|
148
148
|
super();
|
|
149
|
-
this.generatorZoneStartIndex =
|
|
150
|
-
this.modulatorZoneStartIndex =
|
|
149
|
+
this.generatorZoneStartIndex = readLittleEndian(dataArray, 2);
|
|
150
|
+
this.modulatorZoneStartIndex = readLittleEndian(dataArray, 2);
|
|
151
151
|
this.modulatorZoneSize = 0;
|
|
152
152
|
this.generatorZoneSize = 0;
|
|
153
153
|
this.isGlobal = true;
|
package/soundfont/soundfont.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { IndexedByteArray } from '../utils/indexed_array.js'
|
|
2
2
|
import {readSamples} from "./read_sf2/samples.js";
|
|
3
|
-
import {
|
|
3
|
+
import { readLittleEndian } from '../utils/byte_functions/little_endian.js'
|
|
4
4
|
import { readGenerators, Generator } from './read_sf2/generators.js'
|
|
5
5
|
import {readInstrumentZones, InstrumentZone, readPresetZones} from "./read_sf2/zones.js";
|
|
6
6
|
import { readPresets } from "./read_sf2/presets.js";
|
|
@@ -23,10 +23,15 @@ export class SoundFont2 extends BasicSoundFont
|
|
|
23
23
|
/**
|
|
24
24
|
* Initializes a new SoundFont2 Parser and parses the given data array
|
|
25
25
|
* @param arrayBuffer {ArrayBuffer}
|
|
26
|
+
* @param warnDeprecated {boolean}
|
|
26
27
|
*/
|
|
27
|
-
constructor(arrayBuffer)
|
|
28
|
+
constructor(arrayBuffer, warnDeprecated = true)
|
|
28
29
|
{
|
|
29
30
|
super();
|
|
31
|
+
if(warnDeprecated)
|
|
32
|
+
{
|
|
33
|
+
console.warn("Using the constructor directly is deprecated. Use loadSoundFont instead.");
|
|
34
|
+
}
|
|
30
35
|
this.dataArray = new IndexedByteArray(arrayBuffer);
|
|
31
36
|
SpessaSynthGroup("%cParsing SoundFont...", consoleColors.info);
|
|
32
37
|
if(!this.dataArray)
|
|
@@ -66,7 +71,7 @@ export class SoundFont2 extends BasicSoundFont
|
|
|
66
71
|
{
|
|
67
72
|
case "ifil":
|
|
68
73
|
case "iver":
|
|
69
|
-
text = `${
|
|
74
|
+
text = `${readLittleEndian(chunk.chunkData, 2)}.${readLittleEndian(chunk.chunkData, 2)}`;
|
|
70
75
|
break;
|
|
71
76
|
|
|
72
77
|
case "icmt":
|
|
@@ -21,7 +21,7 @@ import { SoundfontManager } from './synth_soundfont_manager.js'
|
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* @typedef {Object} StartRenderingDataConfig
|
|
24
|
-
* @property {
|
|
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
27
|
*/
|