split javascript out into xm.js
Change-Id: I81718be257163f66ed88c6debbcc90c276a16fb0
This commit is contained in:
parent
733fd861d3
commit
a0a2d4446e
2 changed files with 246 additions and 123 deletions
127
index.html
127
index.html
|
|
@ -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
242
xm.js
Normal 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);
|
||||
Loading…
Reference in a new issue