KEXEL's picture
1.1
b0bfea8 verified
import { combineArrays, IndexedByteArray } from "../../../utils/indexed_array.js";
import { RiffChunk, writeRIFFChunk } from "../riff_chunk.js";
import { writeStringAsBytes } from "../../../utils/byte_functions/string.js";
import { consoleColors } from "../../../utils/other.js";
import { getIGEN } from "./igen.js";
import { getSDTA } from "./sdta.js";
import { getSHDR } from "./shdr.js";
import { getIMOD } from "./imod.js";
import { getIBAG } from "./ibag.js";
import { getINST } from "./inst.js";
import { getPGEN } from "./pgen.js";
import { getPMOD } from "./pmod.js";
import { getPBAG } from "./pbag.js";
import { getPHDR } from "./phdr.js";
import { writeWord } from "../../../utils/byte_functions/little_endian.js";
import { SpessaSynthGroupCollapsed, SpessaSynthGroupEnd, SpessaSynthInfo } from "../../../utils/loggin.js";
/**
* @typedef {Object} SoundFont2WriteOptions
* @property {boolean} compress - if the soundfont should be compressed with the Ogg Vorbis codec
* @property {number} compressionQuality - the vorbis compression quality, from -0.1 to 1
* @property {EncodeVorbisFunction|undefined} compressionFunction - the encode vorbis function.
* Can be undefined if not compressed.
*/
/**
* @type {SoundFont2WriteOptions}
*/
const DEFAULT_WRITE_OPTIONS = {
compress: false,
compressionQuality: 0.5,
compressionFunction: undefined
};
/**
* Write the soundfont as an .sf2 file. This method is DESTRUCTIVE
* @this {BasicSoundBank}
* @param {SoundFont2WriteOptions} options
* @returns {Uint8Array}
*/
export function write(options = DEFAULT_WRITE_OPTIONS)
{
if (options.compress)
{
if (typeof options.compressionFunction !== "function")
{
throw new TypeError("No compression function supplied but compression enabled.");
}
}
SpessaSynthGroupCollapsed(
"%cSaving soundfont...",
consoleColors.info
);
SpessaSynthInfo(
`%cCompression: %c${options?.compress || "false"}%c quality: %c${options?.compressionQuality || "none"}`,
consoleColors.info,
consoleColors.recognized,
consoleColors.info,
consoleColors.recognized
);
SpessaSynthInfo(
"%cWriting INFO...",
consoleColors.info
);
/**
* Write INFO
* @type {IndexedByteArray[]}
*/
const infoArrays = [];
this.soundFontInfo["ISFT"] = "SpessaSynth"; // ( ͡° ͜ʖ ͡°)
if (options?.compress)
{
this.soundFontInfo["ifil"] = "3.0"; // set version to 3
}
for (const [type, data] of Object.entries(this.soundFontInfo))
{
if (type === "ifil" || type === "iver")
{
const major = parseInt(data.split(".")[0]);
const minor = parseInt(data.split(".")[1]);
const ckdata = new IndexedByteArray(4);
writeWord(ckdata, major);
writeWord(ckdata, minor);
infoArrays.push(writeRIFFChunk(new RiffChunk(
type,
4,
ckdata
)));
}
else if (type === "DMOD")
{
infoArrays.push(writeRIFFChunk(new RiffChunk(
type,
data.length,
data
)));
}
else
{
const arr = new IndexedByteArray(data.length);
writeStringAsBytes(arr, data);
infoArrays.push(writeRIFFChunk(new RiffChunk(
type,
data.length,
arr
)));
}
}
const combined = combineArrays([
new IndexedByteArray([73, 78, 70, 79]), // INFO
...infoArrays
]);
const infoChunk = writeRIFFChunk(new RiffChunk("LIST", combined.length, combined));
SpessaSynthInfo(
"%cWriting SDTA...",
consoleColors.info
);
// write sdta
const smplStartOffsets = [];
const smplEndOffsets = [];
const sdtaChunk = getSDTA.call(
this,
smplStartOffsets,
smplEndOffsets,
options?.compress,
options?.compressionQuality ?? 0.5,
options.compressionFunction
);
SpessaSynthInfo(
"%cWriting PDTA...",
consoleColors.info
);
// write pdta
// go in reverse so the indexes are correct
// instruments
SpessaSynthInfo(
"%cWriting SHDR...",
consoleColors.info
);
const shdrChunk = getSHDR.call(this, smplStartOffsets, smplEndOffsets);
SpessaSynthInfo(
"%cWriting IGEN...",
consoleColors.info
);
const igenChunk = getIGEN.call(this);
SpessaSynthInfo(
"%cWriting IMOD...",
consoleColors.info
);
const imodChunk = getIMOD.call(this);
SpessaSynthInfo(
"%cWriting IBAG...",
consoleColors.info
);
const ibagChunk = getIBAG.call(this);
SpessaSynthInfo(
"%cWriting INST...",
consoleColors.info
);
const instChunk = getINST.call(this);
// presets
const pgenChunk = getPGEN.call(this);
SpessaSynthInfo(
"%cWriting PMOD...",
consoleColors.info
);
const pmodChunk = getPMOD.call(this);
SpessaSynthInfo(
"%cWriting PBAG...",
consoleColors.info
);
const pbagChunk = getPBAG.call(this);
SpessaSynthInfo(
"%cWriting PHDR...",
consoleColors.info
);
const phdrChunk = getPHDR.call(this);
// combine in the sfspec order
const pdtadata = combineArrays([
new IndexedByteArray([112, 100, 116, 97]), // "pdta"
phdrChunk,
pbagChunk,
pmodChunk,
pgenChunk,
instChunk,
ibagChunk,
imodChunk,
igenChunk,
shdrChunk
]);
const pdtaChunk = writeRIFFChunk(new RiffChunk(
"LIST",
pdtadata.length,
pdtadata
));
SpessaSynthInfo(
"%cWriting the output file...",
consoleColors.info
);
// finally, combine everything
const riffdata = combineArrays([
new IndexedByteArray([115, 102, 98, 107]), // "sfbk"
infoChunk,
sdtaChunk,
pdtaChunk
]);
const main = writeRIFFChunk(new RiffChunk(
"RIFF",
riffdata.length,
riffdata
));
SpessaSynthInfo(
`%cSaved succesfully! Final file size: %c${main.length}`,
consoleColors.info,
consoleColors.recognized
);
SpessaSynthGroupEnd();
return main;
}