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:
`typescript
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));
}
`
API
#### Modules
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:
`typescript
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?
}
`
#### Subsongs
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.
#### Handling channels
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:
`typescript
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);
}
`
#### Handling instruments
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:
`typescript
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);
}
`
#### Sample formats
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:
`typescript
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.
#### Metadata
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.
#### Typings
furnace-module-interface provides full typings for all relevant user-faccing information. Please use Typescript to get the best experience!
Roadmap
furnace-module-interface follows the development of Furnace closely, and will periodically update to support the latest version.
License
This project is licensed under MIT.
Project status
Currently the project is in its very early days and is expected to be unstable and lack features.
Credits
- Aurora*Fields Main developer of the project
- tildearrow` Main developer of Furnace