popntowav/popnchart.js
2020-03-17 20:41:05 +00:00

124 lines
4.1 KiB
JavaScript

const fs = require("fs");
class PopnChart {
//offsetKeysounds indicates that any keysound index references
//within the chart may need to be decremented by one to account
//for the bgtrack not being at the start of the 2dx container.
constructor(filename, offsetKeysounds=false) {
this.filename = filename;
this.data = fs.readFileSync(filename);
//Check if the chart is newstyle or old>
//Needed to know event lengths.
let newFormat = this.checkFormat();
this.events = [];
//This loop reads through the entire file,
//rather than ending on an endofsong event.
let offset = 0;
while (offset < this.data.length) {
const eventOffset = this.data.readInt32LE(offset);
offset += 5;
const eventFlag = this.data.readInt8(offset);
offset += 1;
let eventParam = 0;
let eventValue = 0;
//In regular events, param and value are 1 byte.
//However on keysound events, the first 4 bits
//are used for the param, while the proceeding
//12 bits are used for the value.
let joined = this.data.slice(offset, offset+2);
offset += 2;
if (eventFlag === 2 || eventFlag === 7) {
//Endianness needs flipped.
//This is a terrible way of doing this, I think.
joined.swap16();
const hx = joined.toString("hex");
eventParam = parseInt(hx.slice(1, 4), 16);
eventValue = parseInt(hx.slice(0, 1), 16);
} else {
eventParam = joined.readUInt8(0);
eventValue = joined.readUInt8(1);
}
//Long note data isn't needed for GSTs, however it's here.
if (newFormat) {
const longNoteData = this.data.readInt32LE(offset);
offset += 4;
}
this.events.push([eventOffset, eventFlag, eventParam, eventValue]);
}
this.bpm = 0;
this.bpmTransitions = [];
this.playEvents = [];
this.uniqueKeysounds = [];
this.notecount = 0;
const sampleColumns = [0, 0, 0, 0, 0, 0, 0, 0, 0];
for (const event of this.events) {
let [offset, eventType, param, value] = event;
if (eventType == 7 || eventType == 2) {
if (this.uniqueKeysounds.indexOf(param) == -1) {
this.uniqueKeysounds.push(param);
}
}
switch (eventType) {
case 1:
//Playable note event.
//This if is overzealous, just trying to stop BG tracks from being played twice.
if (sampleColumns[param] != 0) {
this.playEvents.push([offset, sampleColumns[param]]);
}
this.notecount += 1;
break;
case 2:
//Sample change event.
if (offsetKeysounds) {
param -= 1;
}
sampleColumns[value] = param;
break;
case 3:
//BG track start event.
this.playEvents.push([offset, 0]);
break;
case 4:
//BPM change event.
this.bpm = param;
this.bpmTransitions.push(param);
break;
case 7:
//BG sample event.
if (offsetKeysounds) {
param -= 1;
}
this.playEvents.push([offset, param]);
}
}
}
checkFormat() {
if (this.data.readInt8(16) == 69) {
return true;
} else if (this.data.readInt8(12) == 69) {
return false;
} else {
throw "Chart format not supported.";
}
}
}
module.exports = PopnChart;