split javascript out into xm.js

Change-Id: I81718be257163f66ed88c6debbcc90c276a16fb0
This commit is contained in:
Andy Sloane 2015-10-22 06:51:13 -07:00
commit a0a2d4446e
2 changed files with 246 additions and 123 deletions

View file

@ -1,132 +1,13 @@
<head>
<title>js xm</title>
<script>
var _note_names = ["C-", "C#", "D-", "D#", "E-", "F-", "F#", "G-", "G#", "A-", "A#", "B-"];
function prettify_note(note) {
if (note == -1) return "---";
return _note_names[note%12] + ~~(note/12);
}
function prettify_number(num) {
if (num == -1) return "--";
if (num < 10) return "0" + num;
return num;
}
function prettify_effect(t, p) {
if (t == -1) t = "-"; else t = t.toString(16);
if (p == -1) p = "--";
else if (p < 16) p = '0' + p.toString(16);
else p = p.toString(16);
return "" + t + p
}
function prettify_notedata(note, inst, vol, efftype, effparam) {
return (prettify_note(note) + " " + prettify_number(inst) + " "
+ prettify_number(vol) + " "
+ prettify_effect(efftype, effparam));
}
function playXM(arrayBuf) {
var dv = new DataView(arrayBuf);
window.dv = dv;
var name = "";
for (var i = 17; i < 37; i++) {
var c = dv.getUint8(i);
if (c == 0) break;
name += String.fromCharCode(c);
}
var hlen = dv.getUint32(0x3c, true) + 0x3c;
var songlen = dv.getUint16(0x40, true);
var looppos = dv.getUint16(0x42, true);
var nchan = dv.getUint16(0x44, true);
var npat = dv.getUint16(0x46, true);
var ninst = dv.getUint16(0x48, true);
var flags = dv.getUint16(0x4a, true);
var tempo = dv.getUint16(0x4c, true);
var bpm = dv.getUint16(0x4e, true);
console.log("header len " + hlen);
console.log("songlen %d, %d channels, %d patterns, %d instruments", songlen, nchan, npat, ninst);
console.log("loop @%d", looppos);
console.log("flags=%d tempo %d bpm %d", flags, tempo, bpm);
songpats = [];
for (var i = 0; i < songlen; i++) {
songpats.push(dv.getUint8(0x50 + i));
}
console.log("song patterns: ", songpats);
var idx = hlen;
var patterns = [];
for (var i = 0; i < 1; i++) {
var pattern = []
var patheaderlen = dv.getUint32(idx, true);
var patrows = dv.getUint16(idx + 5, true);
var patsize = dv.getUint16(idx + 7, true);
console.log("pattern %d: %d bytes, %d rows", i, patsize, patrows);
idx += 9;
for (var j = 0; j < patrows; j++) {
row = [];
pretty_row = [];
for (var k = 0; k < nchan; k++) {
var byte0 = dv.getUint8(idx); idx++;
var note = -1, inst = -1, vol = -1, efftype = -1, effparam = -1;
if (byte0 & 0x80) {
if (byte0 & 0x01) {
note = dv.getUint8(idx); idx++;
}
if (byte0 & 0x02) {
inst = dv.getUint8(idx); idx++;
}
if (byte0 & 0x04) {
vol = dv.getUint8(idx); idx++;
}
if (byte0 & 0x08) {
efftype = dv.getUint8(idx); idx++;
}
if (byte0 & 0x10) {
effparam = dv.getUint8(idx); idx++;
}
} else {
note = byte0;
inst = dv.getUint8(idx); idx++;
vol = dv.getUint8(idx); idx++;
efftype = dv.getUint8(idx); idx++;
effparam = dv.getUint8(idx); idx++;
}
pretty_row.push(prettify_notedata(note, inst, vol, efftype, effparam));
row.push([note, inst, vol, efftype, effparam]);
}
console.log(pretty_row.join(" "));
pattern.push(row);
}
patterns.push(pattern);
}
var debug = document.getElementById("debug");
console.log("loaded \"" + name + "\"");
debug.innerHTML = name;
}
var xmReq = new XMLHttpRequest();
xmReq.open("GET", "kamel.xm", true);
xmReq.responseType = "arraybuffer";
xmReq.onload = function (xmEvent) {
var arrayBuffer = xmReq.response;
if (arrayBuffer) {
playXM(arrayBuffer);
}
}
xmReq.send(null);
</script>
<script src="xm.js"></script>
</head>
<body>
<pre>
todo:
- load xm using XHR
- load instruments
- load samples
- open audio and start generating
</pre>
<pre id='debug'>
</pre>

242
xm.js Normal file
View file

@ -0,0 +1,242 @@
var _note_names = ["C-", "C#", "D-", "D#", "E-", "F-", "F#", "G-", "G#", "A-", "A#", "B-"];
function prettify_note(note) {
if (note <= 0) return "---";
if (note == 97) return "^^^";
note += 11;
return _note_names[note%12] + ~~(note/12);
}
function prettify_number(num) {
if (num == -1) return "--";
if (num < 10) return "0" + num;
return num;
}
function prettify_effect(t, p) {
if (t == -1) t = "-"; else t = t.toString(16);
if (p == -1) p = "--";
else if (p < 16) p = '0' + p.toString(16);
else p = p.toString(16);
return "" + t + p
}
function prettify_notedata(note, inst, vol, efftype, effparam) {
return (prettify_note(note) + " " + prettify_number(inst) + " "
+ prettify_number(vol) + " "
+ prettify_effect(efftype, effparam));
}
function getstring(dv, offset, len) {
var str = []
for (var i = offset; i < offset+len; i++) {
var c = dv.getUint8(i);
if (c == 0) break;
str.push(String.fromCharCode(c));
}
return str.join('');
}
channelinfo = []
cur_songpos = -1, cur_pat = -1, cur_row = 64, cur_ticksamp = 0;
function next_row(f_smp) {
if (cur_row >= 64) {
cur_row = 0;
cur_songpos++;
if (cur_songpos >= songpats.length)
cur_songpos = song_looppos;
cur_pat = songpats[cur_songpos];
}
var p = patterns[cur_pat];
var r = p[cur_row];
cur_row++;
for (var i = 0; i < cur_row.length; i++) {
// instrument trigger
if (r[i][1]) channelinfo[i].inst = r[i][1];
// note trigger
if (r[i][0]) {
if (r[i][0] == 97) {
// release note, FIXME once envelopes are implemented
} else {
// assume linear frequency table (flags header & 1 == 1)
var inst = channelinfo[i].inst;
var period = 7680 - r[i][0]*64 - instruments[inst].fine*0.5;
var freq = 8363 * ((4608 - period) / 768);
channelinfo[i].doff = freq / f_smp;
channelinfo[i].off = 0;
}
}
}
}
function next_tick(f_smp) {
// blah
}
function audio_cb(e) {
var f_smp = audioctx.sampleRate;
var buflen = e.outputBuffer.length;
var dataL = e.outputBuffer.getChannelData(0);
var dataR = e.outputBuffer.getChannelData(1);
var offset = 0;
var ticklen = f_smp * 2.5 / bpm;
while(buflen > 0) {
if (cur_ticksamp >= ticklen) {
next_tick(f_smp);
cur_ticksamp -= ticklen;
}
var tickduration = Math.min(buflen, ticklen - cur_ticksamp);
for (var i = 0; i < tickduration; i++) {
for (var j = 0; j < nchan; j++) {
}
}
cur_ticksamp += tickduration;
buflen -= tickduration;
}
console.log("buflen %d f_smp %d", buflen, f_smp);
}
function playXM(arrayBuf) {
var dv = new DataView(arrayBuf);
window.dv = dv;
var name = getstring(dv, 17, 20);
var hlen = dv.getUint32(0x3c, true) + 0x3c;
var songlen = dv.getUint16(0x40, true);
song_looppos = dv.getUint16(0x42, true);
nchan = dv.getUint16(0x44, true);
var npat = dv.getUint16(0x46, true);
var ninst = dv.getUint16(0x48, true);
var flags = dv.getUint16(0x4a, true);
tempo = dv.getUint16(0x4c, true);
bpm = dv.getUint16(0x4e, true);
for (var i = 0; i < nchan; i++) {
channelinfo.push({})
}
console.log("header len " + hlen);
console.log("songlen %d, %d channels, %d patterns, %d instruments", songlen, nchan, npat, ninst);
console.log("loop @%d", song_looppos);
console.log("flags=%d tempo %d bpm %d", flags, tempo, bpm);
songpats = [];
for (var i = 0; i < songlen; i++) {
songpats.push(dv.getUint8(0x50 + i));
}
console.log("song patterns: ", songpats);
var idx = hlen;
patterns = [];
for (var i = 0; i < npat; i++) {
var pattern = []
var patheaderlen = dv.getUint32(idx, true);
var patrows = dv.getUint16(idx + 5, true);
var patsize = dv.getUint16(idx + 7, true);
console.log("pattern %d: %d bytes, %d rows", i, patsize, patrows);
idx += 9;
for (var j = 0; j < patrows; j++) {
row = [];
pretty_row = [];
for (var k = 0; k < nchan; k++) {
var byte0 = dv.getUint8(idx); idx++;
var note = -1, inst = -1, vol = -1, efftype = -1, effparam = -1;
if (byte0 & 0x80) {
if (byte0 & 0x01) {
note = dv.getUint8(idx); idx++;
}
if (byte0 & 0x02) {
inst = dv.getUint8(idx); idx++;
}
if (byte0 & 0x04) {
vol = dv.getUint8(idx); idx++;
}
if (byte0 & 0x08) {
efftype = dv.getUint8(idx); idx++;
}
if (byte0 & 0x10) {
effparam = dv.getUint8(idx); idx++;
}
} else {
note = byte0;
inst = dv.getUint8(idx); idx++;
vol = dv.getUint8(idx); idx++;
efftype = dv.getUint8(idx); idx++;
effparam = dv.getUint8(idx); idx++;
}
pretty_row.push(prettify_notedata(note, inst, vol, efftype, effparam));
row.push([note, inst, vol, efftype, effparam]);
}
if (i == 0)
console.log(pretty_row.join(" "));
pattern.push(row);
}
patterns.push(pattern);
}
instruments = []
// now load instruments
for (i = 0; i < ninst; i++) {
var hdrsiz = dv.getUint32(idx, true);
var instname = getstring(dv, idx+0x4, 22);
var nsamp = dv.getUint16(idx+0x1b, true);
if (nsamp > 0) {
// FIXME: ignoring keymaps for now and assuming 1 sample / instrument
// var keymap = getarray(dv, idx+0x21);
var samphdrsiz = dv.getUint32(idx+0x1d, true);
console.log("hdrsiz %d; instrument %d: '%s' %d samples, samphdrsiz %d",
hdrsiz, i, instname, nsamp, samphdrsiz);
idx += hdrsiz;
for (var j = 0; j < nsamp; j++) {
var samplen = dv.getUint32(idx, true);
var samploop = dv.getUint32(idx+4, true);
var samplooplen = dv.getUint32(idx+8, true);
var sampvol = dv.getUint8(idx+12);
var sampfinetune = dv.getInt8(idx+13);
var samptype = dv.getUint8(idx+14);
var samppan = dv.getUint8(idx+15);
var sampnote = dv.getUint8(idx+16);
var sampname = getstring(dv, idx+18, 22);
var sampleoffset = idx + samphdrsiz;
console.log("sample %d: len %d name '%s' loop %d/%d vol %d",
j, samplen, sampname, samploop, samplooplen, sampvol);
console.log(" type %d note %s finetune %d pan %d",
samptype, prettify_note(sampnote), sampfinetune, samppan);
idx += samplen + samphdrsiz;
}
instruments.push({
'name': instname,
'off': sampleoffset, 'len': samplen, 'loop': samploop,
'looplen': samplooplen, 'note': sampnote, 'fine': sampfinetune,
'pan': samppan})
} else {
instruments.push(null);
}
}
audioctx = new webkitAudioContext();
gainNode = audioctx.createGain();
gainNode.gain.value = 1.0; // master volume
jsNode = audioctx.createScriptProcessor(4096, 0, 2);
jsNode.onaudioprocess = audio_cb;
// jsNode.connect(gainNode);
var debug = document.getElementById("debug");
console.log("loaded \"" + name + "\"");
debug.innerHTML = name;
// start playing
gainNode.connect(audioctx.destination);
}
var xmReq = new XMLHttpRequest();
xmReq.open("GET", "kamel.xm", true);
xmReq.responseType = "arraybuffer";
xmReq.onload = function (xmEvent) {
var arrayBuffer = xmReq.response;
if (arrayBuffer) {
playXM(arrayBuffer);
}
}
xmReq.send(null);