furnace-module-interface
Version:
An interface for loading, processing and saving furnace modules in an object-oriented manner. Fully typed via TypeScript.
120 lines (119 loc) • 5.64 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FurnaceSong = void 0;
const channels_1 = require("./channels");
const defaults_1 = require("./defaults");
class FurnaceSong {
constructor() {
this.timebase = defaults_1.defaults.timebase;
this.speed1 = defaults_1.defaults.speed1;
this.speed2 = defaults_1.defaults.speed2;
this.highlightA = defaults_1.defaults.highlightA;
this.highlightB = defaults_1.defaults.highlightB;
this.initialArpeggioTime = 1;
this.ticksPerSecond = defaults_1.defaults.tps;
this.virtualTempoNumerator = defaults_1.defaults.tempo;
this.virtualTempoDenominator = defaults_1.defaults.tempo;
this.name = defaults_1.defaults.songName;
this.comment = defaults_1.defaults.songComment;
this.rows = defaults_1.defaults.rows;
this.channels = null;
this.channelInfo = [];
}
static create() {
return new FurnaceSong();
}
static fromHelper(helper, info, version) {
const song = FurnaceSong.create();
if (FurnaceSong.magic !== helper.getASCII(4)) {
throw new Error("Expected format magic string \"" + FurnaceSong.magic + "\" when loading a subsong block.");
}
helper.skipBytes(4);
song.timebase = helper.readUInt8() + 1;
song.speed1 = helper.readUInt8();
song.speed2 = helper.readUInt8();
song.initialArpeggioTime = helper.readUInt8();
song.ticksPerSecond = helper.readFloat32LE();
song.rows = helper.readUInt16LE();
const numOrders = helper.readUInt16LE();
if (song.rows > 256) {
throw new Error("Expected at most 256 rows in a pattern, but " + song.rows + " were loaded.");
}
if (numOrders > 256) {
throw new Error("Expected at most 256 orders in a subsong, but " + numOrders + " were loaded.");
}
song.highlightA = helper.readUInt8();
song.highlightB = helper.readUInt8();
song.virtualTempoNumerator = helper.readUInt16LE();
song.virtualTempoDenominator = helper.readUInt16LE();
song.name = helper.readString("utf-8");
song.comment = helper.readString("utf-8");
song.fetchChannels(helper, version, (0, channels_1.computeChannelCount)(info), numOrders);
return song;
}
fetchChannels(helper, version, channelCount, orderCount) {
for (let i = 0; i < channelCount; i++) {
this.channelInfo.push(new channels_1.FurnaceChannelInfo());
}
for (let i = 0; i < this.channelInfo.length; i++) {
this.channelInfo[i].orders = [...helper.readBuffer(orderCount),].map((v) => v & (version < 80 ? 0x7F : 0xFF));
}
for (let i = 0; i < this.channelInfo.length; i++) {
this.channelInfo[i].effects = helper.readUInt8();
}
for (let i = 0; i < this.channelInfo.length; i++) {
this.channelInfo[i].hidden = helper.readUInt8() === 0;
}
for (let i = 0; i < this.channelInfo.length; i++) {
this.channelInfo[i].collapsed = helper.readUInt8() === 0;
}
for (let i = 0; i < this.channelInfo.length; i++) {
this.channelInfo[i].longname = helper.readString("ascii");
}
for (let i = 0; i < this.channelInfo.length; i++) {
this.channelInfo[i].shortname = helper.readString("ascii");
}
}
writeToBuffer(helper) {
const _begin = helper.getPosition(8);
helper.writeString(FurnaceSong.magic, "ascii", false);
helper.skipBytes(4);
helper.writeUInt8(this.timebase - 1);
helper.writeUInt8(this.speed1);
helper.writeUInt8(this.speed2);
helper.writeUInt8(this.initialArpeggioTime);
helper.writeFloat32LE(this.ticksPerSecond);
helper.writeUInt16LE(this.rows);
helper.writeUInt16LE(this.channelInfo[0].orders.length);
helper.writeUInt8(this.highlightA);
helper.writeUInt8(this.highlightB);
helper.writeUInt16LE(this.virtualTempoNumerator);
helper.writeUInt16LE(this.virtualTempoDenominator);
helper.writeString(this.name, "utf-8", true);
helper.writeString(this.comment, "utf-8", true);
this.writeChannels(helper);
helper.writeUInt32LE(helper.getPosition() - _begin, _begin - 4);
}
writeChannels(helper) {
for (let i = 0; i < this.channelInfo.length; i++) {
helper.writeBuffer(Buffer.from(this.channelInfo[i].orders), 0, this.channelInfo[i].orders.length);
}
helper.writeBuffer(Buffer.from(this.channelInfo.map((c) => c.effects)), 0, this.channelInfo.length);
helper.writeBuffer(Buffer.from(this.channelInfo.map((c) => c.hidden ? 0 : 1)), 0, this.channelInfo.length);
helper.writeBuffer(Buffer.from(this.channelInfo.map((c) => c.collapsed ? 0 : 1)), 0, this.channelInfo.length);
for (let i = 0; i < this.channelInfo.length; i++) {
helper.writeString(this.channelInfo[i].longname, "ascii", true);
}
for (let i = 0; i < this.channelInfo.length; i++) {
helper.writeString(this.channelInfo[i].shortname, "ascii", true);
}
}
getChannels(info) {
if (this.channels) {
return this.channels;
}
return this.channels = (0, channels_1.computeChannelObjects)(info, this.channelInfo);
}
}
exports.FurnaceSong = FurnaceSong;
FurnaceSong.magic = "SONG";