|
|
import { findRIFFListType, readRIFFChunk } from "../basic_soundfont/riff_chunk.js"; |
|
|
import { readBytesAsString } from "../../utils/byte_functions/string.js"; |
|
|
import { |
|
|
SpessaSynthGroupCollapsed, |
|
|
SpessaSynthGroupEnd, |
|
|
SpessaSynthInfo, |
|
|
SpessaSynthWarn |
|
|
} from "../../utils/loggin.js"; |
|
|
import { consoleColors } from "../../utils/other.js"; |
|
|
import { readLittleEndian, signedInt16 } from "../../utils/byte_functions/little_endian.js"; |
|
|
import { DLSSample } from "./dls_sample.js"; |
|
|
|
|
|
const W_FORMAT_TAG = { |
|
|
PCM: 0x01, |
|
|
ALAW: 0x6 |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function readPCM(dataChunk, bytesPerSample) |
|
|
{ |
|
|
const maxSampleValue = Math.pow(2, bytesPerSample * 8 - 1); |
|
|
const maxUnsigned = Math.pow(2, bytesPerSample * 8); |
|
|
|
|
|
let normalizationFactor; |
|
|
let isUnsigned = false; |
|
|
|
|
|
if (bytesPerSample === 1) |
|
|
{ |
|
|
normalizationFactor = 255; |
|
|
isUnsigned = true; |
|
|
} |
|
|
else |
|
|
{ |
|
|
normalizationFactor = maxSampleValue; |
|
|
} |
|
|
const sampleLength = dataChunk.size / bytesPerSample; |
|
|
const sampleData = new Float32Array(sampleLength); |
|
|
for (let i = 0; i < sampleData.length; i++) |
|
|
{ |
|
|
|
|
|
let sample = readLittleEndian(dataChunk.chunkData, bytesPerSample); |
|
|
|
|
|
if (isUnsigned) |
|
|
{ |
|
|
|
|
|
sampleData[i] = (sample / normalizationFactor) - 0.5; |
|
|
} |
|
|
else |
|
|
{ |
|
|
|
|
|
if (sample >= maxSampleValue) |
|
|
{ |
|
|
sample -= maxUnsigned; |
|
|
} |
|
|
sampleData[i] = sample / normalizationFactor; |
|
|
} |
|
|
} |
|
|
return sampleData; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function readALAW(dataChunk, bytesPerSample) |
|
|
{ |
|
|
const sampleLength = dataChunk.size / bytesPerSample; |
|
|
const sampleData = new Float32Array(sampleLength); |
|
|
for (let i = 0; i < sampleData.length; i++) |
|
|
{ |
|
|
|
|
|
const input = readLittleEndian(dataChunk.chunkData, bytesPerSample); |
|
|
|
|
|
|
|
|
|
|
|
let sample = input ^ 0x55; |
|
|
|
|
|
|
|
|
sample &= 0x7F; |
|
|
|
|
|
|
|
|
let exponent = sample >> 4; |
|
|
|
|
|
let mantissa = sample & 0xF; |
|
|
if (exponent > 0) |
|
|
{ |
|
|
mantissa += 16; |
|
|
} |
|
|
|
|
|
mantissa = (mantissa << 4) + 0x8; |
|
|
if (exponent > 1) |
|
|
{ |
|
|
mantissa = mantissa << (exponent - 1); |
|
|
} |
|
|
|
|
|
const s16sample = input > 127 ? mantissa : -mantissa; |
|
|
|
|
|
|
|
|
sampleData[i] = s16sample / 32678; |
|
|
} |
|
|
return sampleData; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function readDLSSamples(waveListChunk) |
|
|
{ |
|
|
SpessaSynthGroupCollapsed( |
|
|
"%cLoading Wave samples...", |
|
|
consoleColors.recognized |
|
|
); |
|
|
let sampleID = 0; |
|
|
while (waveListChunk.chunkData.currentIndex < waveListChunk.chunkData.length) |
|
|
{ |
|
|
const waveChunk = readRIFFChunk(waveListChunk.chunkData); |
|
|
this.verifyHeader(waveChunk, "LIST"); |
|
|
this.verifyText(readBytesAsString(waveChunk.chunkData, 4), "wave"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const waveChunks = []; |
|
|
while (waveChunk.chunkData.currentIndex < waveChunk.chunkData.length) |
|
|
{ |
|
|
waveChunks.push(readRIFFChunk(waveChunk.chunkData)); |
|
|
} |
|
|
|
|
|
const fmtChunk = waveChunks.find(c => c.header === "fmt "); |
|
|
if (!fmtChunk) |
|
|
{ |
|
|
throw new Error("No fmt chunk in the wave file!"); |
|
|
} |
|
|
|
|
|
const waveFormat = readLittleEndian(fmtChunk.chunkData, 2); |
|
|
const channelsAmount = readLittleEndian(fmtChunk.chunkData, 2); |
|
|
if (channelsAmount !== 1) |
|
|
{ |
|
|
throw new Error(`Only mono samples are supported. Fmt reports ${channelsAmount} channels`); |
|
|
} |
|
|
const sampleRate = readLittleEndian(fmtChunk.chunkData, 4); |
|
|
|
|
|
readLittleEndian(fmtChunk.chunkData, 4); |
|
|
|
|
|
readLittleEndian(fmtChunk.chunkData, 2); |
|
|
|
|
|
const wBitsPerSample = readLittleEndian(fmtChunk.chunkData, 2); |
|
|
const bytesPerSample = wBitsPerSample / 8; |
|
|
|
|
|
|
|
|
let failed = false; |
|
|
const dataChunk = waveChunks.find(c => c.header === "data"); |
|
|
if (!dataChunk) |
|
|
{ |
|
|
this.parsingError("No data chunk in the WAVE chunk!"); |
|
|
} |
|
|
let sampleData; |
|
|
switch (waveFormat) |
|
|
{ |
|
|
default: |
|
|
failed = true; |
|
|
sampleData = new Float32Array(dataChunk.size / bytesPerSample); |
|
|
break; |
|
|
|
|
|
case W_FORMAT_TAG.PCM: |
|
|
sampleData = readPCM(dataChunk, bytesPerSample); |
|
|
break; |
|
|
|
|
|
case W_FORMAT_TAG.ALAW: |
|
|
sampleData = readALAW(dataChunk, bytesPerSample); |
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
const waveInfo = findRIFFListType(waveChunks, "INFO"); |
|
|
let sampleName = `Unnamed ${sampleID}`; |
|
|
if (waveInfo) |
|
|
{ |
|
|
let infoChunk = readRIFFChunk(waveInfo.chunkData); |
|
|
while (infoChunk.header !== "INAM" && waveInfo.chunkData.currentIndex < waveInfo.chunkData.length) |
|
|
{ |
|
|
infoChunk = readRIFFChunk(waveInfo.chunkData); |
|
|
} |
|
|
if (infoChunk.header === "INAM") |
|
|
{ |
|
|
sampleName = readBytesAsString(infoChunk.chunkData, infoChunk.size).trim(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
let sampleKey = 60; |
|
|
let samplePitch = 0; |
|
|
let sampleLoopStart = 0; |
|
|
let sampleLoopEnd = sampleData.length - 1; |
|
|
let sampleDbAttenuation = 0; |
|
|
|
|
|
|
|
|
const wsmpChunk = waveChunks.find(c => c.header === "wsmp"); |
|
|
if (wsmpChunk) |
|
|
{ |
|
|
|
|
|
readLittleEndian(wsmpChunk.chunkData, 4); |
|
|
sampleKey = readLittleEndian(wsmpChunk.chunkData, 2); |
|
|
|
|
|
|
|
|
samplePitch = signedInt16( |
|
|
wsmpChunk.chunkData[wsmpChunk.chunkData.currentIndex++], |
|
|
wsmpChunk.chunkData[wsmpChunk.chunkData.currentIndex++] |
|
|
); |
|
|
|
|
|
|
|
|
const samplePitchSemitones = Math.trunc(samplePitch / 100); |
|
|
sampleKey += samplePitchSemitones; |
|
|
samplePitch -= samplePitchSemitones * 100; |
|
|
|
|
|
|
|
|
|
|
|
const gainCorrection = readLittleEndian(wsmpChunk.chunkData, 4); |
|
|
|
|
|
sampleDbAttenuation = (gainCorrection | 0) / -655360; |
|
|
|
|
|
readLittleEndian(wsmpChunk.chunkData, 4); |
|
|
const loopsAmount = readLittleEndian(wsmpChunk.chunkData, 4); |
|
|
if (loopsAmount === 1) |
|
|
{ |
|
|
|
|
|
readLittleEndian(wsmpChunk.chunkData, 8); |
|
|
sampleLoopStart = readLittleEndian(wsmpChunk.chunkData, 4); |
|
|
const loopSize = readLittleEndian(wsmpChunk.chunkData, 4); |
|
|
sampleLoopEnd = sampleLoopStart + loopSize; |
|
|
} |
|
|
} |
|
|
else |
|
|
{ |
|
|
SpessaSynthWarn("No wsmp chunk in wave... using sane defaults."); |
|
|
} |
|
|
|
|
|
if (failed) |
|
|
{ |
|
|
console.error(`Failed to load '${sampleName}': Unsupported format: (${waveFormat})`); |
|
|
} |
|
|
|
|
|
this.samples.push(new DLSSample( |
|
|
sampleName, |
|
|
sampleRate, |
|
|
sampleKey, |
|
|
samplePitch, |
|
|
sampleLoopStart, |
|
|
sampleLoopEnd, |
|
|
sampleData, |
|
|
sampleDbAttenuation |
|
|
)); |
|
|
|
|
|
|
|
|
sampleID++; |
|
|
SpessaSynthInfo( |
|
|
`%cLoaded sample %c${sampleName}`, |
|
|
consoleColors.info, |
|
|
consoleColors.recognized |
|
|
); |
|
|
} |
|
|
SpessaSynthGroupEnd(); |
|
|
} |