An interface for loading, processing and saving furnace modules in an object-oriented manner. Fully typed via TypeScript.
npm install furnace-module-interface
Here is an example of a typical usecase:
import fsp from "fs/promises";
import { loadFurnaceModule, deflateBuffer } from "furnace-module-interface";
const file = await fsp.readFile("testfile.fur")
const module = await loadFurnaceModule(file);
if(module instanceof FurnaceSongModule) {
// only modify name IF this is a furnace song module (.fur extension should always be that!)
module.info.name = "Example module name";
const newdata = module.getAsBuffer();
await fsp.writeFile("editedtestfile.fur", deflateBuffer(newdata));
}
There are 3 types of furnace modules: FurnaceSongModule
for full songs, FurnaceInstrumentModule
for instrument module files, and FurnaceWavetableModule
for wavetable module files. furnace-module-interface supports all 3, but they have many differences. Checking modules you get by calling loadFurnaceModule
is recommended as a way to ensure you are processing the right version:
import { loadFurnaceModule } from "furnace-module-interface";
const module = await loadFurnaceModule(fileBuffer);
if(module instanceof FurnaceSongModule) {
// process song module
} else if(module instanceof FurnaceInstrumentModule) {
// process instrument module
} else if(module instanceof FurnaceWavetableModule) {
// process wavetable module
} else {
// no idea what we got. Maybe this is a future version with even more modules?
}
dev95 introduced subsongs, but before they were not supported. furnace-module-interface pretends the main subsong is just like any subsong, even in older versions. Check FurnaceSongModule.songs[0]
for that. The first subsong is handled in a special way when loading or saving, other indices work as subsongs normalöly.
Channels are internally handled by using "systems" and "chips", but to access their properties, its more advantageous to treat them as fully independent entities. furnace-module-interface handles this via creating an intermediate object, that processes the properties of channels into convenient objects. This means however, that some properties are not changeable, while others are. To load channels, you can simply do this:
import { loadFurnaceModule, FurnaceInstrumentTypeEnum } from "furnace-module-interface";
const module = await loadFurnaceModule(fileBuffer);
if(module instanceof FurnaceSongModule) {
const song = module.info.songs[0];
const channels = song.getChannels(module.info);
}
Because of compatibility issues, instrument handling is a little weird. You are required to create a "view", that makes it easier to access relevant information about an instrument. You can create any number of views, but modifying one view can affect another. This is particularly a case for OPLL vs other FM instruments. Here is an example:
import { loadFurnaceModule, FurnaceInstrumentTypeEnum } from "furnace-module-interface";
const module = await loadFurnaceModule(fileBuffer);
if(module instanceof FurnaceSongModule) {
const instrument = module.info.instruments[0];
const view = instrument.getView(FurnaceInstrumentTypeEnum.OPMOPN);
}
A simple interface for sample conversions are provided. They can be converted between native format and 16-bit signed PCM. This allows easy handling of sample data without having to deal with the format specifics. Here is an example of how you might handle that in your program:
import { loadFurnaceModule, FurnaceInstrumentTypeEnum } from "furnace-module-interface";
const module = await loadFurnaceModule(fileBuffer);
if(module instanceof FurnaceSongModule) {
const sample = module.info.samples[0];
const rawSamples = sample.data.convertToArray();
/**
* manipulate sample data here!!!
*/
sample.data.convertFromArray(rawSamples);
}
Accessing raw sample data is not recommended, as it is easier to get it wrong.
Module metadata is a furnace-module-interface and is ignored by Furnace. This is not intended to be used to store critical information about modules. Instead, it is a tool that can be used to store information with a module that may be used for any personal purpose. This format is not formally documented yet.
furnace-module-interface provides full typings for all relevant user-faccing information. Please use Typescript to get the best experience!
furnace-module-interface follows the development of Furnace closely, and will periodically update to support the latest version.
This project is licensed under MIT.
Currently the project is in its very early days and is expected to be unstable and lack features.
-
Aurora*Fields
Main developer of the project -
tildearrow
Main developer of Furnace