Skip to content

Commit

Permalink
Allow beat saber song directories as well as zip. clean formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
joshie committed Apr 23, 2019
1 parent 504be0f commit 8b66c3c
Showing 1 changed file with 136 additions and 113 deletions.
249 changes: 136 additions & 113 deletions bstoaud.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const fs = require('fs');
const StreamZip = require('node-stream-zip');
const JSZip = require("jszip");
const recursive = require("recursive-readdir");

const moggHeaderPath = __dirname + '/datafiles/moggheader';
const moggHeaderSize = fs.statSync(moggHeaderPath).size;
Expand All @@ -9,131 +10,153 @@ var audicaFiles = {
'song.mid': fs.readFileSync(__dirname + '/datafiles/midifile')
}

const zip = new StreamZip({
file: process.argv[2],
storeEntries: true
});
// Directory, Zip, or Bail
var isDirectory = false
if (typeof process.argv[2] === 'undefined') {
console.log('This tool requires a Beat Saber song zip or directory to run');
process.exit(0);
} else if (!(process.argv[2].toLowerCase().endsWith('.zip'))) {
if (fs.statSync(process.argv[2]).isDirectory()) {
isDirectory = true
} else {
console.log('Argument must be a zipfile or directory');
process.exit(0);
}
}

var songDesc = {
"moggSong": "song.moggsong",
"midiFile": "song.mid",
"fusionSpatialized": "fusion/guns/default/drums_default_spatial.fusion",
"fusionUnspatialized": "fusion/guns/default/drums_default_sub.fusion",
"sustainSongRight": "song_sustain_r.moggsong",
"sustainSongLeft": "song_sustain_l.moggsong",
"fxSong": "song_extras.moggsong",
"songEndEvent": "event:/song_end/song_end_A",
"prerollSeconds": 3.0,
"useMidiForCues": false,
"hidden": false
"moggSong": "song.moggsong",
"midiFile": "song.mid",
"fusionSpatialized": "fusion/guns/default/drums_default_spatial.fusion",
"fusionUnspatialized": "fusion/guns/default/drums_default_sub.fusion",
"sustainSongRight": "song_sustain_r.moggsong",
"sustainSongLeft": "song_sustain_l.moggsong",
"fxSong": "song_extras.moggsong",
"songEndEvent": "event:/song_end/song_end_A",
"prerollSeconds": 3.0,
"useMidiForCues": false,
"hidden": false
};

var difficultyMap = {
const difficultyMap = {
'beginner.cues': ['easy','normal'],
'moderate.cues': ['normal','hard'],
'advanced.cues': ['hard','expert'],
'expert.cues': ['expert','expertplus']
}
var difficultyIndex = 0;

var filePath = {};
var cues = {};

zip.on('ready', () => {
Object.keys(zip.entries()).forEach(function(entry) {
filePath[entry.replace(/.*\//,'').toLowerCase()] = entry;
});

var songInfo = JSON.parse(zip.entryDataSync(filePath['info.json']));
var infoPath = filePath['info.json'].replace(/[^/]*$/,'');

filePath = {}
var re = new RegExp("^"+infoPath.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'));
Object.keys(zip.entries()).forEach(function(entry) {
filePath[entry.replace(re,'').toLowerCase()] = entry;
});

var oggPath = songInfo.difficultyLevels[0].audioPath.toLowerCase();
var offset = songInfo.difficultyLevels[0].offset || 0;

songDesc.songID = songInfo.songName.toLowerCase() + songInfo.songSubName.toLowerCase() + songInfo.authorName.toLowerCase();
songDesc.songID = songDesc.songID.replace(/[^a-z0-9]/gi,'');
songDesc.title = songInfo.songName;
songDesc.artist = songInfo.songSubName + '';
if (songDesc.artist == '')
songDesc.artist = songInfo.authorName;
songDesc.tempo = songInfo.beatsPerMinute;
songDesc.offset = offset;

if (typeof songInfo.previewStartTime === 'number') {
songDesc.previewStartSeconds = songInfo.previewStartTime;
} else {
songDesc.previewStartSeconds = 12;
}

audicaFiles['song.desc'] = JSON.stringify(songDesc,0,2);
if (isDirectory) {
recursive(process.argv[2], function (err, files) {
fs.reader = fs.readFileSync;
createAudica(fs,files);
});
} else {
const zip = new StreamZip({
file: process.argv[2],
storeEntries: true
});

zip.on('ready', () => {
zip.reader = zip.entryDataSync;
createAudica(zip,Object.keys(zip.entries()));
});
}

function createAudica(o,files) {
var filePath = {};

files.forEach(function(entry) {
filePath[entry.replace(/.*\//,'').toLowerCase()] = entry;
});

audicaFiles['song.mid'].writeUIntBE(Math.round(60000000/songDesc.tempo),41,3);
audicaFiles['song.mogg'] = Buffer.concat([fs.readFileSync(moggHeaderPath),zip.entryDataSync(filePath[oggPath])]);
var infoPath = filePath['info.json'].replace(/[^/]*$/,'');
var re = new RegExp("^"+infoPath.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'));

songInfo.difficultyLevels.forEach(function(l) {
if (l.difficulty.toLowerCase() === 'expertplus')
difficultyIndex = 1;
cues[l.difficulty.toLowerCase()] = {cues: []};
files.forEach(function(entry) {
filePath[entry.replace(re,'').toLowerCase()] = entry;
});

var dedupe = {};
var songInfo = JSON.parse(o.reader(filePath['info.json']));
var oggPath = songInfo.difficultyLevels[0].audioPath.toLowerCase();
var offset = songInfo.difficultyLevels[0].offset || 0;

songDesc.songID = songInfo.songName.toLowerCase() + songInfo.songSubName.toLowerCase() + songInfo.authorName.toLowerCase();
songDesc.songID = songDesc.songID.replace(/[^a-z0-9]/gi,'');
songDesc.title = songInfo.songName;
songDesc.artist = songInfo.songSubName + '';
if (songDesc.artist == '')
songDesc.artist = songInfo.authorName;
songDesc.tempo = songInfo.beatsPerMinute;
songDesc.offset = offset;

if (typeof songInfo.previewStartTime === 'number') {
songDesc.previewStartSeconds = songInfo.previewStartTime;
} else {
songDesc.previewStartSeconds = 12;
}

audicaFiles['song.desc'] = JSON.stringify(songDesc,0,2);

audicaFiles['song.mid'].writeUIntBE(Math.round(60000000/songDesc.tempo),41,3);
audicaFiles['song.mogg'] = Buffer.concat([fs.readFileSync(moggHeaderPath),o.reader(filePath[oggPath])]);

var cues = {};
var difficultyIndex = 0;

songInfo.difficultyLevels.forEach(function(l) {
if (l.difficulty.toLowerCase() === 'expertplus')
difficultyIndex = 1;
cues[l.difficulty.toLowerCase()] = {cues: []};

var dedupe = {};

if(l.jsonPath.toLowerCase() in filePath) {
JSON.parse(zip.entryDataSync(filePath[l.jsonPath.toLowerCase()]))._notes.forEach(function(n) {
if (n._type <= 1) {
var tick = Math.round(n._time * 480);
var handType = Math.abs(n._type-1) + 1;
if(!dedupe[tick + '_' + handType]) {
cues[l.difficulty.toLowerCase()].cues.push({
tick: tick,
tickLength: 120,
pitch: 28 + 12 * n._lineLayer + n._lineIndex,
velocity: 60,
gridOffset: {
x: 0.0,
y: 0.0
},
handType: handType,
behavior: 0
});
dedupe[tick + '_' + handType] = true;
}
if(l.jsonPath.toLowerCase() in filePath) {
JSON.parse(o.reader(filePath[l.jsonPath.toLowerCase()]))._notes.forEach(function(n) {
if (n._type <= 1) {
var tick = Math.round(n._time * 480);
var handType = Math.abs(n._type-1) + 1;
if(!dedupe[tick + '_' + handType]) {
cues[l.difficulty.toLowerCase()].cues.push({
tick: tick,
tickLength: 120,
pitch: 28 + 12 * n._lineLayer + n._lineIndex,
velocity: 60,
gridOffset: {
x: 0.0,
y: 0.0
},
handType: handType,
behavior: 0
});
dedupe[tick + '_' + handType] = true;
}
});
} else {
console.log(l.difficulty + " difficulty is defined but json for difficulty is missing, skipping");
}
});

Object.keys(difficultyMap).forEach(function(cueFile) {
if(typeof cues[difficultyMap[cueFile][difficultyIndex]] === 'object')
audicaFiles[cueFile] = JSON.stringify(cues[difficultyMap[cueFile][difficultyIndex]],0,2);
});

fs.readdirSync(__dirname + '/audicatemplate').forEach(function(f) {
audicaFiles[f] = fs.readFileSync(__dirname + '/audicatemplate/' + f);
});

zipName = songDesc.songID + '.audica'

var ozip = new JSZip();
Object.keys(audicaFiles).forEach(function(fileName) {
ozip.file(fileName,audicaFiles[fileName]);
});

ozip
.generateNodeStream({type:'nodebuffer',streamFiles:false})
.pipe(fs.createWriteStream(zipName))
.on('finish', function () {
// JSZip generates a readable stream with a "end" event,
// but is piped here in a writable stream which emits a "finish" event.
console.log(zipName + " written.");
});

zip.close()
});
}
});
} else {
console.log(l.difficulty + " difficulty is defined but json for difficulty is missing, skipping");
}
});

Object.keys(difficultyMap).forEach(function(cueFile) {
if(typeof cues[difficultyMap[cueFile][difficultyIndex]] === 'object')
audicaFiles[cueFile] = JSON.stringify(cues[difficultyMap[cueFile][difficultyIndex]],0,2);
});

fs.readdirSync(__dirname + '/audicatemplate').forEach(function(f) {
audicaFiles[f] = fs.readFileSync(__dirname + '/audicatemplate/' + f);
});

zipName = songDesc.songID + '.audica'

var ozip = new JSZip();
Object.keys(audicaFiles).forEach(function(fileName) {
ozip.file(fileName,audicaFiles[fileName]);
});

ozip
.generateNodeStream({type:'nodebuffer',streamFiles:false})
.pipe(fs.createWriteStream(zipName))
.on('finish', function () {
console.log(zipName + " written.");
});
}

0 comments on commit 8b66c3c

Please sign in to comment.