UNPKG

spessasynth_core

Version:

MIDI and SoundFont2/DLS library with no compromises

157 lines (140 loc) 5.05 kB
import { readLittleEndian, signedInt16 } from "../../utils/byte_functions/little_endian.js"; import { findRIFFListType, readRIFFChunk } from "../basic_soundfont/riff_chunk.js"; import { Generator } from "../basic_soundfont/generator.js"; import { generatorTypes } from "../basic_soundfont/generator_types.js"; import { SpessaSynthWarn } from "../../utils/loggin.js"; /** * @this {DLSSoundFont} * @param chunk {RiffChunk} * @param instrument {DLSInstrument} */ export function readRegion(chunk, instrument) { // regions are essentially instrument zones /** * read chunks in the region * @type {RiffChunk[]} */ const regionChunks = []; while (chunk.chunkData.length > chunk.chunkData.currentIndex) { regionChunks.push(readRIFFChunk(chunk.chunkData)); } // region header const regionHeader = regionChunks.find(c => c.header === "rgnh"); if (!regionHeader) { SpessaSynthWarn("Invalid DLS region: missing 'rgnh' chunk! Discarding..."); return; } // key range let keyMin = readLittleEndian(regionHeader.chunkData, 2); let keyMax = readLittleEndian(regionHeader.chunkData, 2); // vel range let velMin = readLittleEndian(regionHeader.chunkData, 2); let velMax = readLittleEndian(regionHeader.chunkData, 2); // a fix for not cool files if (velMin === 0 && velMax === 0) { velMax = 127; velMin = 0; } // cannot do the same to key zones sadly // create zone const zone = instrument.createZone(); // apply ranges zone.keyRange = { min: keyMin, max: keyMax }; zone.velRange = { min: velMin, max: velMax }; // fusOptions: no idea about that one??? readLittleEndian(regionHeader.chunkData, 2); // keyGroup: essentially exclusive class const exclusive = readLittleEndian(regionHeader.chunkData, 2); if (exclusive !== 0) { zone.addGenerators(new Generator(generatorTypes.exclusiveClass, exclusive)); } // lart const lart = findRIFFListType(regionChunks, "lart"); const lar2 = findRIFFListType(regionChunks, "lar2"); this.readLart(lart, lar2, zone); // wsmp: wave sample chunk const waveSampleChunk = regionChunks.find(c => c.header === "wsmp"); // cbSize readLittleEndian(waveSampleChunk.chunkData, 4); let originalKey = readLittleEndian(waveSampleChunk.chunkData, 2); // sFineTune let pitchCorrection = signedInt16( waveSampleChunk.chunkData[waveSampleChunk.chunkData.currentIndex++], waveSampleChunk.chunkData[waveSampleChunk.chunkData.currentIndex++] ); // gain correction: Each unit of gain represents 1/655360 dB // it is set after linking the sample const gainCorrection = readLittleEndian(waveSampleChunk.chunkData, 4); // convert to signed and turn into attenuation (invert) const dbCorrection = (gainCorrection | 0) / -655360; // skip options readLittleEndian(waveSampleChunk.chunkData, 4); // read loop count (always one or zero) const loopsAmount = readLittleEndian(waveSampleChunk.chunkData, 4); let loopingMode; const loop = { start: 0, end: 0 }; if (loopsAmount === 0) { // no loop loopingMode = 0; } else { // ignore cbSize readLittleEndian(waveSampleChunk.chunkData, 4); // loop type: loop normally or loop until release (like soundfont) const loopType = readLittleEndian(waveSampleChunk.chunkData, 4); // why is it long? if (loopType === 0) { loopingMode = 1; } else { loopingMode = 3; } loop.start = readLittleEndian(waveSampleChunk.chunkData, 4); const loopLength = readLittleEndian(waveSampleChunk.chunkData, 4); loop.end = loop.start + loopLength; } // wave link const waveLinkChunk = regionChunks.find(c => c.header === "wlnk"); if (waveLinkChunk === undefined) { // No wave link means no sample. What? Why is it even here then? return undefined; } // flags readLittleEndian(waveLinkChunk.chunkData, 2); // phase group readLittleEndian(waveLinkChunk.chunkData, 2); // channel readLittleEndian(waveLinkChunk.chunkData, 4); // sampleID const sampleID = readLittleEndian(waveLinkChunk.chunkData, 4); // noinspection JSValidateTypes /** * @type {DLSSample} */ const sample = this.samples[sampleID]; if (sample === undefined) { throw new Error("Invalid sample ID!"); } // this correction overrides the sample gain correction const actualDbCorrection = dbCorrection || sample.sampleDbAttenuation; // convert to centibels const attenuation = (actualDbCorrection * 10) / 0.4; // make sure to apply EMU correction zone.setWavesample( attenuation, loopingMode, loop, originalKey, sample, sampleID, pitchCorrection ); }