Split viewer code into trackview.js
Also disabled iOS hack, as it seems to be causing issues, and fixed a minor drag-n-drop bug (wouldn't load if track was already playing past length of newly loaded track). To play without a viewer, you still have to stub it out. Not sure the best way to handle that case yet.
This commit is contained in:
parent
4b6e75332e
commit
8e114a16da
4 changed files with 465 additions and 398 deletions
|
|
@ -2,6 +2,7 @@
|
||||||
<title>js xm</title>
|
<title>js xm</title>
|
||||||
<script src="http://a1k0n-pub.s3-website-us-west-1.amazonaws.com/xm/xmlist.js"></script>
|
<script src="http://a1k0n-pub.s3-website-us-west-1.amazonaws.com/xm/xmlist.js"></script>
|
||||||
<script src="xmeffects.js"></script>
|
<script src="xmeffects.js"></script>
|
||||||
|
<script src="trackview.js"></script>
|
||||||
<script src="xm.js"></script>
|
<script src="xm.js"></script>
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
|
|
|
||||||
334
trackview.js
Normal file
334
trackview.js
Normal file
|
|
@ -0,0 +1,334 @@
|
||||||
|
(function (window, document) {
|
||||||
|
if (!window.XMPlayer) {
|
||||||
|
window.XMPlayer = {};
|
||||||
|
}
|
||||||
|
var player = window.XMPlayer;
|
||||||
|
|
||||||
|
if (!window.XMView) {
|
||||||
|
window.XMView = {};
|
||||||
|
}
|
||||||
|
var view = window.XMView;
|
||||||
|
|
||||||
|
var _pattern_cellwidth = 16 + 4 + 8 + 4 + 8 + 16 + 4;
|
||||||
|
var _scope_width = _pattern_cellwidth - 1;
|
||||||
|
var _pattern_border = 20;
|
||||||
|
|
||||||
|
view.init = init;
|
||||||
|
view.pushEvent = pushEvent;
|
||||||
|
view.pause = pause;
|
||||||
|
view.resume = resume;
|
||||||
|
view.stop = stop;
|
||||||
|
view.scope_width = _scope_width;
|
||||||
|
|
||||||
|
// Load font (ripped from FastTracker 2)
|
||||||
|
var fontimg = new window.Image();
|
||||||
|
fontimg.src = "ft2font.png";
|
||||||
|
|
||||||
|
var _fontmap_notes = [8*5, 8*22, 8*28];
|
||||||
|
var pat_canvas_patnum;
|
||||||
|
|
||||||
|
var audio_events = [];
|
||||||
|
var paused_events = [];
|
||||||
|
var shown_row;
|
||||||
|
|
||||||
|
// canvas to render patterns onto
|
||||||
|
var pat_canvas = document.createElement('canvas');
|
||||||
|
|
||||||
|
// pixel widths of each character in the proportional font
|
||||||
|
var _fontwidths = [
|
||||||
|
4, 7, 3, 6, 6, 6, 6, 5, 4, 5, 5, 5, 5, 5, 7, 7,
|
||||||
|
5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 6, 7, 7, 7, 7, 7,
|
||||||
|
4, 2, 5, 7, 7, 7, 7, 3, 4, 4, 6, 6, 3, 6, 2, 7,
|
||||||
|
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 2, 3, 6, 6, 6, 7,
|
||||||
|
7, 7, 7, 7, 7, 7, 7, 7, 7, 2, 7, 7, 7, 8, 8, 7,
|
||||||
|
7, 7, 7, 7, 8, 7, 7, 8, 8, 8, 7, 4, 7, 4, 4, 5,
|
||||||
|
3, 6, 6, 6, 6, 6, 4, 6, 6, 2, 4, 6, 2, 8, 6, 6,
|
||||||
|
6, 6, 4, 6, 4, 6, 7, 8, 7, 6, 6, 4, 2, 4, 4, 4];
|
||||||
|
|
||||||
|
var _bigfontwidths = [
|
||||||
|
4, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
|
||||||
|
15, 15, 15, 15, 15, 15, 15, 15, 13, 13, 13, 15, 15, 15, 15, 15,
|
||||||
|
4, 5, 12, 16, 15, 15, 16, 5, 8, 8, 13, 10, 6, 10, 5, 12,
|
||||||
|
15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 5, 6, 12, 10, 12, 15,
|
||||||
|
14, 15, 15, 15, 15, 15, 15, 15, 15, 5, 13, 15, 14, 15, 15, 15,
|
||||||
|
15, 16, 15, 15, 15, 15, 15, 15, 15, 15, 16, 7, 12, 7, 13, 15,
|
||||||
|
5, 13, 13, 13, 13, 13, 11, 13, 13, 5, 9, 13, 5, 16, 13, 13,
|
||||||
|
13, 13, 12, 13, 11, 13, 13, 16, 15, 13, 15, 9, 2, 9, 15, 15];
|
||||||
|
|
||||||
|
// draw FT2 proportional font text to a drawing context
|
||||||
|
// returns width rendered
|
||||||
|
function drawText(text, dx, dy, ctx) {
|
||||||
|
var dx0 = dx;
|
||||||
|
for (var i = 0; i < text.length; i++) {
|
||||||
|
var n = text.charCodeAt(i);
|
||||||
|
var sx = (n&63)*8;
|
||||||
|
var sy = (n>>6)*10 + 56;
|
||||||
|
var width = _fontwidths[n];
|
||||||
|
ctx.drawImage(fontimg, sx, sy, width, 10, dx, dy, width, 10);
|
||||||
|
dx += width + 1;
|
||||||
|
}
|
||||||
|
return dx - dx0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTextSize(text, widthtable) {
|
||||||
|
var width = 0;
|
||||||
|
for (var i = 0; i < text.length; i++) {
|
||||||
|
var n = text.charCodeAt(i);
|
||||||
|
width += widthtable[n] + 1;
|
||||||
|
}
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawBigText(text, dx, dy, ctx) {
|
||||||
|
var dx0 = dx;
|
||||||
|
for (var i = 0; i < text.length; i++) {
|
||||||
|
var n = text.charCodeAt(i);
|
||||||
|
var sx = (n&31)*16;
|
||||||
|
var sy = (n>>5)*20 + 96;
|
||||||
|
var width = _bigfontwidths[n];
|
||||||
|
ctx.drawImage(fontimg, sx, sy, width, 20, dx, dy, width, 20);
|
||||||
|
dx += width + 1;
|
||||||
|
}
|
||||||
|
return dx - dx0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function RenderPattern(canv, pattern) {
|
||||||
|
// a pattern consists of NxM cells which look like
|
||||||
|
// N-O II VV EFF
|
||||||
|
var cellwidth = _pattern_cellwidth;
|
||||||
|
canv.width = pattern[0].length * cellwidth + _pattern_border;
|
||||||
|
canv.height = pattern.length * 8;
|
||||||
|
var ctx = canv.getContext('2d');
|
||||||
|
ctx.fillcolor='#000';
|
||||||
|
ctx.fillRect(0, 0, canv.width, canv.height);
|
||||||
|
for (var j = 0; j < pattern.length; j++) {
|
||||||
|
var row = pattern[j];
|
||||||
|
var dy = j * 8;
|
||||||
|
// render row number
|
||||||
|
ctx.drawImage(fontimg, 8*(j>>4), 0, 8, 8, 2, dy, 8, 8);
|
||||||
|
ctx.drawImage(fontimg, 8*(j&15), 0, 8, 8, 10, dy, 8, 8);
|
||||||
|
|
||||||
|
for (var i = 0; i < row.length; i++) {
|
||||||
|
var dx = i*cellwidth + 2 + _pattern_border;
|
||||||
|
var data = row[i];
|
||||||
|
|
||||||
|
// render note
|
||||||
|
var note = data[0];
|
||||||
|
if (note < 0) {
|
||||||
|
ctx.drawImage(fontimg, 0, 8*5, 16, 8, dx, dy, 16, 8);
|
||||||
|
} else {
|
||||||
|
var octave = (note/12)|0;
|
||||||
|
var note_fontrow = _fontmap_notes[(octave/3)|0];
|
||||||
|
note = (note % (12*3))|0;
|
||||||
|
ctx.drawImage(fontimg, 16+16*note, note_fontrow, 16, 8, dx, dy, 16, 8);
|
||||||
|
}
|
||||||
|
dx += 20;
|
||||||
|
|
||||||
|
// render instrument
|
||||||
|
var inst = data[1];
|
||||||
|
if (inst != -1) { // no instrument = render nothing
|
||||||
|
if (inst > 15) {
|
||||||
|
ctx.drawImage(fontimg, 8*(inst>>4), 4*8, 4, 8, dx, dy, 4, 8);
|
||||||
|
}
|
||||||
|
ctx.drawImage(fontimg, 8*(inst&15), 4*8, 4, 8, dx+4, dy, 4, 8);
|
||||||
|
}
|
||||||
|
dx += 12;
|
||||||
|
|
||||||
|
// render volume
|
||||||
|
var vol = data[2];
|
||||||
|
if (vol < 0x10) {
|
||||||
|
ctx.drawImage(fontimg, 312, 0, 8, 8, dx, dy, 8, 8);
|
||||||
|
} else {
|
||||||
|
ctx.drawImage(fontimg, 8*(vol>>4) + 56*8, 4*8, 8, 8, dx, dy, 8, 8);
|
||||||
|
ctx.drawImage(fontimg, 8*(vol&15), 4*8, 4, 8, dx+4, dy, 4, 8);
|
||||||
|
}
|
||||||
|
dx += 8;
|
||||||
|
|
||||||
|
// render effect
|
||||||
|
var eff = data[3];
|
||||||
|
var effdata = data[4];
|
||||||
|
ctx.drawImage(fontimg, 8*eff + 16*8, 4*8, 8, 8, dx, dy, 8, 8);
|
||||||
|
dx += 8;
|
||||||
|
ctx.drawImage(fontimg, 8*(effdata>>4), 4*8, 4, 8, dx, dy, 4, 8);
|
||||||
|
ctx.drawImage(fontimg, 8*(effdata&15), 4*8, 4, 8, dx+4, dy, 4, 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function redrawScreen() {
|
||||||
|
if (audio_events.length === 0) return;
|
||||||
|
|
||||||
|
var e;
|
||||||
|
var t = player.audioctx.currentTime;
|
||||||
|
do {
|
||||||
|
e = audio_events.shift();
|
||||||
|
} while(e.t < t && audio_events.length > 0);
|
||||||
|
if (!e) return;
|
||||||
|
var VU = e.vu;
|
||||||
|
var scopes = e.scopes;
|
||||||
|
var ctx;
|
||||||
|
|
||||||
|
if (e.scopes) {
|
||||||
|
// update VU meters & oscilliscopes
|
||||||
|
var canvas = document.getElementById("vu");
|
||||||
|
ctx = canvas.getContext("2d");
|
||||||
|
ctx.fillStyle = '#000';
|
||||||
|
ctx.fillRect(0, 0, canvas.width, 64);
|
||||||
|
ctx.fillStyle = '#0f0';
|
||||||
|
ctx.strokeStyle = '#55acff';
|
||||||
|
for (var j = 0; j < player.xm.nchan; j++) {
|
||||||
|
var x = _pattern_border + j * _pattern_cellwidth;
|
||||||
|
// render channel number
|
||||||
|
drawText(''+j, x, 1, ctx);
|
||||||
|
|
||||||
|
// volume in dB as a green bar
|
||||||
|
var vu_y = -Math.log(VU[j])*10;
|
||||||
|
ctx.fillRect(x, vu_y, 2, 64-vu_y);
|
||||||
|
|
||||||
|
// oscilloscope
|
||||||
|
var scope = scopes[j];
|
||||||
|
if (scope) {
|
||||||
|
ctx.beginPath();
|
||||||
|
for (var k = 0; k < _scope_width; k++) {
|
||||||
|
ctx.lineTo(x + 1 + k, 32 - 16 * scope[k]);
|
||||||
|
}
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.row != shown_row) {
|
||||||
|
if (e.pat != pat_canvas_patnum) {
|
||||||
|
var p = player.xm.patterns[e.pat];
|
||||||
|
if (p) {
|
||||||
|
RenderPattern(pat_canvas, player.xm.patterns[e.pat]);
|
||||||
|
pat_canvas_patnum = e.pat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var gfx = document.getElementById("gfxpattern");
|
||||||
|
ctx = gfx.getContext('2d');
|
||||||
|
ctx.fillStyle = '#000';
|
||||||
|
ctx.fillRect(0, 0, gfx.width, gfx.height);
|
||||||
|
ctx.fillStyle = '#2a5684';
|
||||||
|
ctx.fillRect(0, gfx.height/2 - 4, gfx.width, 8);
|
||||||
|
ctx.globalCompositeOperation = 'lighten';
|
||||||
|
ctx.drawImage(pat_canvas, 0, gfx.height / 2 - 4 - 8*(e.row));
|
||||||
|
ctx.globalCompositeOperation = 'source-over';
|
||||||
|
shown_row = e.row;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audio_events.length > 0) {
|
||||||
|
var next_event = audio_events[0].t;
|
||||||
|
setTimeout(redrawScreen, 1000*(next_event - player.audioctx.currentTime));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
var title = document.getElementById("title");
|
||||||
|
// make title element fit text exactly, then render it
|
||||||
|
title.width = getTextSize(player.xm.songname, _bigfontwidths);
|
||||||
|
var ctx = title.getContext('2d');
|
||||||
|
drawBigText(player.xm.songname, 0, 1, ctx);
|
||||||
|
|
||||||
|
var instrlist = document.getElementById("instruments");
|
||||||
|
// clear instrument list if not already clear
|
||||||
|
while (instrlist.childNodes.length) {
|
||||||
|
instrlist.removeChild(instrlist.childNodes[0]);
|
||||||
|
}
|
||||||
|
var instrcols = ((player.xm.instruments.length + 7) / 8) | 0;
|
||||||
|
for (var i = 0; i < instrcols; i++) {
|
||||||
|
var canvas = document.createElement('canvas');
|
||||||
|
ctx = canvas.getContext('2d');
|
||||||
|
var instrcolumnwidth = 8*22;
|
||||||
|
canvas.width = instrcolumnwidth;
|
||||||
|
canvas.height = 8 * 10;
|
||||||
|
ctx.fillStyle = '#000';
|
||||||
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
|
var hasname = 0, hasdata = 0;
|
||||||
|
for (var j = 8*i; j < Math.min(8*i+8, player.xm.instruments.length); j++) {
|
||||||
|
var y = 10*(j - 8*i);
|
||||||
|
var n = (j+1).toString(16);
|
||||||
|
if (j < 15) n = '0' + n;
|
||||||
|
var data = player.xm.instruments[j].samples;
|
||||||
|
if (data) {
|
||||||
|
var len = data[0].len;
|
||||||
|
data = data[0].sampledata;
|
||||||
|
var scale = Math.ceil(len / instrcolumnwidth);
|
||||||
|
ctx.strokeStyle = '#55acff';
|
||||||
|
ctx.beginPath();
|
||||||
|
for (var k = 0; k < Math.min(len / scale, instrcolumnwidth - 20); k++) {
|
||||||
|
ctx.lineTo(k + 20, y + data[k*scale] * 4 + 4);
|
||||||
|
}
|
||||||
|
ctx.stroke();
|
||||||
|
hasdata++;
|
||||||
|
}
|
||||||
|
var name = player.xm.instruments[j].name;
|
||||||
|
ctx.globalCompositeOperation = 'lighten';
|
||||||
|
drawText(n, 1, y, ctx);
|
||||||
|
if (name !== '') {
|
||||||
|
drawText(player.xm.instruments[j].name, 20, y, ctx);
|
||||||
|
hasname++;
|
||||||
|
}
|
||||||
|
ctx.globalCompositeOperation = 'source-over';
|
||||||
|
}
|
||||||
|
if (hasname || hasdata) {
|
||||||
|
instrlist.appendChild(canvas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('vu').width = _pattern_border +
|
||||||
|
_pattern_cellwidth * player.xm.nchan;
|
||||||
|
var gfxpattern = document.getElementById("gfxpattern");
|
||||||
|
gfxpattern.width = _pattern_cellwidth * player.xm.nchan + _pattern_border;
|
||||||
|
|
||||||
|
// generate a fake audio event to render the initial paused screen
|
||||||
|
var scopes = [];
|
||||||
|
for (i = 0; i < player.xm.nchan; i++) {
|
||||||
|
scopes.push(new Float32Array(_scope_width));
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset display
|
||||||
|
shown_row = undefined;
|
||||||
|
pat_canvas_patnum = undefined;
|
||||||
|
|
||||||
|
audio_events.push({
|
||||||
|
t: 0, row: 0, pat: player.xm.songpats[0],
|
||||||
|
vu: new Float32Array(player.xm.nchan),
|
||||||
|
scopes: scopes
|
||||||
|
});
|
||||||
|
redrawScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
function pushEvent(e) {
|
||||||
|
audio_events.push(e);
|
||||||
|
if (audio_events.length == 1) {
|
||||||
|
requestAnimationFrame(redrawScreen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function pause() {
|
||||||
|
// grab all the audio events
|
||||||
|
var t = player.audioctx.currentTime;
|
||||||
|
while (audio_events.length > 0) {
|
||||||
|
var e = audio_events.shift();
|
||||||
|
e.t -= t;
|
||||||
|
paused_events.push(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resume() {
|
||||||
|
var t = player.audioctx.currentTime;
|
||||||
|
while (paused_events.length > 0) {
|
||||||
|
var e = paused_events.shift();
|
||||||
|
e.t += t;
|
||||||
|
audio_events.push(e);
|
||||||
|
}
|
||||||
|
requestAnimationFrame(redrawScreen);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stop() {
|
||||||
|
audio_events = [];
|
||||||
|
paused_events = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
})(window, document);
|
||||||
525
xm.js
525
xm.js
|
|
@ -3,157 +3,32 @@ if (!window.XMPlayer) {
|
||||||
window.XMPlayer = {};
|
window.XMPlayer = {};
|
||||||
}
|
}
|
||||||
var player = window.XMPlayer;
|
var player = window.XMPlayer;
|
||||||
player.allowDrop = allowDrop;
|
|
||||||
player.handleDrop = handleDrop;
|
if (!window.XMView) {
|
||||||
|
window.XMView = {};
|
||||||
|
}
|
||||||
|
var view = window.XMView;
|
||||||
|
|
||||||
player.PeriodForNote = PeriodForNote;
|
player.PeriodForNote = PeriodForNote;
|
||||||
player.prettify_effect = prettify_effect;
|
player.prettify_effect = prettify_effect;
|
||||||
|
player.playXM = playXM;
|
||||||
|
player.pauseXM = pauseXM;
|
||||||
|
player.stopXM = stopXM;
|
||||||
|
player.downloadXM = downloadXM;
|
||||||
player.cur_songpos = -1;
|
player.cur_songpos = -1;
|
||||||
player.cur_pat = -1;
|
player.cur_pat = -1;
|
||||||
player.cur_row = 64;
|
player.cur_row = 64;
|
||||||
player.cur_ticksamp = 0;
|
player.cur_ticksamp = 0;
|
||||||
player.cur_tick = 6;
|
player.cur_tick = 6;
|
||||||
player.xm = {}; // contains all song data
|
player.xm = {}; // contains all song data
|
||||||
|
|
||||||
var audioctx; // AudioContext
|
|
||||||
|
|
||||||
// Load font (ripped from FastTracker 2)
|
|
||||||
var fontimg = new Image();
|
|
||||||
fontimg.src = "ft2font.png";
|
|
||||||
|
|
||||||
// canvas to render patterns onto
|
|
||||||
var pat_canvas = document.createElement('canvas');
|
|
||||||
|
|
||||||
// for pretty-printing notes
|
// for pretty-printing notes
|
||||||
var _note_names = ["C-", "C#", "D-", "D#", "E-", "F-", "F#", "G-", "G#", "A-", "A#", "B-"];
|
var _note_names = [
|
||||||
|
"C-", "C#", "D-", "D#", "E-", "F-",
|
||||||
|
"F#", "G-", "G#", "A-", "A#", "B-"];
|
||||||
|
|
||||||
var f_smp = 44100; // updated by play callback, default value here
|
var f_smp = 44100; // updated by play callback, default value here
|
||||||
|
|
||||||
// pixel widths of each character in the proportional font
|
|
||||||
var _fontwidths = [
|
|
||||||
4, 7, 3, 6, 6, 6, 6, 5, 4, 5, 5, 5, 5, 5, 7, 7,
|
|
||||||
5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 6, 7, 7, 7, 7, 7,
|
|
||||||
4, 2, 5, 7, 7, 7, 7, 3, 4, 4, 6, 6, 3, 6, 2, 7,
|
|
||||||
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 2, 3, 6, 6, 6, 7,
|
|
||||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 2, 7, 7, 7, 8, 8, 7,
|
|
||||||
7, 7, 7, 7, 8, 7, 7, 8, 8, 8, 7, 4, 7, 4, 4, 5,
|
|
||||||
3, 6, 6, 6, 6, 6, 4, 6, 6, 2, 4, 6, 2, 8, 6, 6,
|
|
||||||
6, 6, 4, 6, 4, 6, 7, 8, 7, 6, 6, 4, 2, 4, 4, 4];
|
|
||||||
|
|
||||||
var _bigfontwidths = [
|
|
||||||
4, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
|
|
||||||
15, 15, 15, 15, 15, 15, 15, 15, 13, 13, 13, 15, 15, 15, 15, 15,
|
|
||||||
4, 5, 12, 16, 15, 15, 16, 5, 8, 8, 13, 10, 6, 10, 5, 12,
|
|
||||||
15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 5, 6, 12, 10, 12, 15,
|
|
||||||
14, 15, 15, 15, 15, 15, 15, 15, 15, 5, 13, 15, 14, 15, 15, 15,
|
|
||||||
15, 16, 15, 15, 15, 15, 15, 15, 15, 15, 16, 7, 12, 7, 13, 15,
|
|
||||||
5, 13, 13, 13, 13, 13, 11, 13, 13, 5, 9, 13, 5, 16, 13, 13,
|
|
||||||
13, 13, 12, 13, 11, 13, 13, 16, 15, 13, 15, 9, 2, 9, 15, 15];
|
|
||||||
|
|
||||||
// draw FT2 proportional font text to a drawing context
|
|
||||||
// returns width rendered
|
|
||||||
function DrawText(text, dx, dy, ctx) {
|
|
||||||
var dx0 = dx;
|
|
||||||
for (var i = 0; i < text.length; i++) {
|
|
||||||
var n = text.charCodeAt(i);
|
|
||||||
var sx = (n&63)*8;
|
|
||||||
var sy = (n>>6)*10 + 56;
|
|
||||||
var width = _fontwidths[n];
|
|
||||||
ctx.drawImage(fontimg, sx, sy, width, 10, dx, dy, width, 10);
|
|
||||||
dx += width + 1;
|
|
||||||
}
|
|
||||||
return dx - dx0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function TextSize(text, widthtable) {
|
|
||||||
var width = 0;
|
|
||||||
for (var i = 0; i < text.length; i++) {
|
|
||||||
var n = text.charCodeAt(i);
|
|
||||||
width += widthtable[n] + 1;
|
|
||||||
}
|
|
||||||
return width;
|
|
||||||
}
|
|
||||||
|
|
||||||
function DrawBigText(text, dx, dy, ctx) {
|
|
||||||
var dx0 = dx;
|
|
||||||
for (var i = 0; i < text.length; i++) {
|
|
||||||
var n = text.charCodeAt(i);
|
|
||||||
var sx = (n&31)*16;
|
|
||||||
var sy = (n>>5)*20 + 96;
|
|
||||||
var width = _bigfontwidths[n];
|
|
||||||
ctx.drawImage(fontimg, sx, sy, width, 20, dx, dy, width, 20);
|
|
||||||
dx += width + 1;
|
|
||||||
}
|
|
||||||
return dx - dx0;
|
|
||||||
}
|
|
||||||
|
|
||||||
var _fontmap_notes = [8*5, 8*22, 8*28];
|
|
||||||
var _pattern_cellwidth = 16 + 4 + 8 + 4 + 8 + 16 + 4;
|
|
||||||
var _scope_width = _pattern_cellwidth - 1;
|
|
||||||
var _pattern_border = 20;
|
|
||||||
var pat_canvas_patnum;
|
|
||||||
function RenderPattern(canv, pattern) {
|
|
||||||
// a pattern consists of NxM cells which look like
|
|
||||||
// N-O II VV EFF
|
|
||||||
var cellwidth = _pattern_cellwidth;
|
|
||||||
canv.width = pattern[0].length * cellwidth + _pattern_border;
|
|
||||||
canv.height = pattern.length * 8;
|
|
||||||
var ctx = canv.getContext('2d');
|
|
||||||
ctx.fillcolor='#000';
|
|
||||||
ctx.fillRect(0, 0, canv.width, canv.height);
|
|
||||||
for (var j = 0; j < pattern.length; j++) {
|
|
||||||
var row = pattern[j];
|
|
||||||
var dy = j * 8;
|
|
||||||
// render row number
|
|
||||||
ctx.drawImage(fontimg, 8*(j>>4), 0, 8, 8, 2, dy, 8, 8);
|
|
||||||
ctx.drawImage(fontimg, 8*(j&15), 0, 8, 8, 10, dy, 8, 8);
|
|
||||||
|
|
||||||
for (var i = 0; i < row.length; i++) {
|
|
||||||
var dx = i*cellwidth + 2 + _pattern_border;
|
|
||||||
var data = row[i];
|
|
||||||
|
|
||||||
// render note
|
|
||||||
var note = data[0];
|
|
||||||
if (note < 0) {
|
|
||||||
ctx.drawImage(fontimg, 0, 8*5, 16, 8, dx, dy, 16, 8);
|
|
||||||
} else {
|
|
||||||
var octave = (note/12)|0;
|
|
||||||
var note_fontrow = _fontmap_notes[(octave/3)|0];
|
|
||||||
note = (note % (12*3))|0;
|
|
||||||
ctx.drawImage(fontimg, 16+16*note, note_fontrow, 16, 8, dx, dy, 16, 8);
|
|
||||||
}
|
|
||||||
dx += 20;
|
|
||||||
|
|
||||||
// render instrument
|
|
||||||
var inst = data[1];
|
|
||||||
if (inst != -1) { // no instrument = render nothing
|
|
||||||
if (inst > 15) {
|
|
||||||
ctx.drawImage(fontimg, 8*(inst>>4), 4*8, 4, 8, dx, dy, 4, 8);
|
|
||||||
}
|
|
||||||
ctx.drawImage(fontimg, 8*(inst&15), 4*8, 4, 8, dx+4, dy, 4, 8);
|
|
||||||
}
|
|
||||||
dx += 12;
|
|
||||||
|
|
||||||
// render volume
|
|
||||||
var vol = data[2];
|
|
||||||
if (vol < 0x10) {
|
|
||||||
ctx.drawImage(fontimg, 312, 0, 8, 8, dx, dy, 8, 8);
|
|
||||||
} else {
|
|
||||||
ctx.drawImage(fontimg, 8*(vol>>4) + 56*8, 4*8, 8, 8, dx, dy, 8, 8);
|
|
||||||
ctx.drawImage(fontimg, 8*(vol&15), 4*8, 4, 8, dx+4, dy, 4, 8);
|
|
||||||
}
|
|
||||||
dx += 8;
|
|
||||||
|
|
||||||
// render effect
|
|
||||||
var eff = data[3];
|
|
||||||
var effdata = data[4];
|
|
||||||
ctx.drawImage(fontimg, 8*eff + 16*8, 4*8, 8, 8, dx, dy, 8, 8);
|
|
||||||
dx += 8;
|
|
||||||
ctx.drawImage(fontimg, 8*(effdata>>4), 4*8, 4, 8, dx, dy, 4, 8);
|
|
||||||
ctx.drawImage(fontimg, 8*(effdata&15), 4*8, 4, 8, dx+4, dy, 4, 8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function prettify_note(note) {
|
function prettify_note(note) {
|
||||||
if (note < 0) return "---";
|
if (note < 0) return "---";
|
||||||
if (note == 96) return "^^^";
|
if (note == 96) return "^^^";
|
||||||
|
|
@ -175,20 +50,20 @@ function prettify_effect(t, p) {
|
||||||
if (t >= 10) t = String.fromCharCode(55 + t);
|
if (t >= 10) t = String.fromCharCode(55 + t);
|
||||||
if (p < 16) p = '0' + p.toString(16);
|
if (p < 16) p = '0' + p.toString(16);
|
||||||
else p = p.toString(16);
|
else p = p.toString(16);
|
||||||
return t + p
|
return t + p;
|
||||||
}
|
}
|
||||||
|
|
||||||
function prettify_notedata(data) {
|
function prettify_notedata(data) {
|
||||||
return (prettify_note(data[0]) + " " + prettify_number(data[1]) + " "
|
return (prettify_note(data[0]) + " " + prettify_number(data[1]) + " " +
|
||||||
+ prettify_volume(data[2]) + " "
|
prettify_volume(data[2]) + " " +
|
||||||
+ prettify_effect(data[3], data[4]));
|
prettify_effect(data[3], data[4]));
|
||||||
}
|
}
|
||||||
|
|
||||||
function getstring(dv, offset, len) {
|
function getstring(dv, offset, len) {
|
||||||
var str = [];
|
var str = [];
|
||||||
for (var i = offset; i < offset+len; i++) {
|
for (var i = offset; i < offset+len; i++) {
|
||||||
var c = dv.getUint8(i);
|
var c = dv.getUint8(i);
|
||||||
if (c == 0) break;
|
if (c === 0) break;
|
||||||
str.push(String.fromCharCode(c));
|
str.push(String.fromCharCode(c));
|
||||||
}
|
}
|
||||||
return str.join('');
|
return str.join('');
|
||||||
|
|
@ -225,75 +100,6 @@ function PeriodForNote(ch, note) {
|
||||||
return 1920 - (note + ch.samp.note)*16 - ch.samp.fine / 8.0;
|
return 1920 - (note + ch.samp.note)*16 - ch.samp.fine / 8.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
var audio_events = [];
|
|
||||||
var shown_row = undefined;
|
|
||||||
function RedrawScreen() {
|
|
||||||
if (audio_events.length == 0) return;
|
|
||||||
|
|
||||||
var e;
|
|
||||||
var t = audioctx.currentTime;
|
|
||||||
do {
|
|
||||||
e = audio_events.shift();
|
|
||||||
} while(e.t < t && audio_events.length > 0);
|
|
||||||
if (e == undefined) return;
|
|
||||||
var VU = e.vu;
|
|
||||||
var scopes = e.scopes;
|
|
||||||
|
|
||||||
if (e.scopes != undefined) {
|
|
||||||
// update VU meters & oscilliscopes
|
|
||||||
var canvas = document.getElementById("vu");
|
|
||||||
var ctx = canvas.getContext("2d");
|
|
||||||
ctx.fillStyle = '#000';
|
|
||||||
ctx.fillRect(0, 0, canvas.width, 64);
|
|
||||||
ctx.fillStyle = '#0f0';
|
|
||||||
ctx.strokeStyle = '#55acff';
|
|
||||||
for (var j = 0; j < player.xm.nchan; j++) {
|
|
||||||
var x = _pattern_border + j * _pattern_cellwidth;
|
|
||||||
// render channel number
|
|
||||||
DrawText(''+j, x, 1, ctx);
|
|
||||||
|
|
||||||
// volume in dB as a green bar
|
|
||||||
var vu_y = -Math.log(VU[j])*10;
|
|
||||||
ctx.fillRect(x, vu_y, 2, 64-vu_y);
|
|
||||||
|
|
||||||
// oscilloscope
|
|
||||||
var scope = scopes[j];
|
|
||||||
if (scope != undefined) {
|
|
||||||
ctx.beginPath();
|
|
||||||
for (var k = 0; k < _scope_width; k++) {
|
|
||||||
ctx.lineTo(x + 1 + k, 32 - 16 * scope[k]);
|
|
||||||
}
|
|
||||||
ctx.stroke();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.row != shown_row) {
|
|
||||||
var gfx = document.getElementById("gfxpattern");
|
|
||||||
if (e.pat != pat_canvas_patnum) {
|
|
||||||
var p = player.xm.patterns[e.pat];
|
|
||||||
if (p != undefined) {
|
|
||||||
RenderPattern(pat_canvas, player.xm.patterns[e.pat]);
|
|
||||||
pat_canvas_patnum = e.pat;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var ctx = gfx.getContext('2d');
|
|
||||||
ctx.fillStyle = '#000';
|
|
||||||
ctx.fillRect(0, 0, gfx.width, gfx.height);
|
|
||||||
ctx.fillStyle = '#2a5684';
|
|
||||||
ctx.fillRect(0, gfx.height/2 - 4, gfx.width, 8);
|
|
||||||
ctx.globalCompositeOperation = 'lighten';
|
|
||||||
ctx.drawImage(pat_canvas, 0, gfx.height / 2 - 4 - 8*(e.row));
|
|
||||||
ctx.globalCompositeOperation = 'source-over';
|
|
||||||
shown_row = e.row;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (audio_events.length > 0) {
|
|
||||||
var next_event = audio_events[0].t;
|
|
||||||
setTimeout(RedrawScreen, 1000*(next_event - audioctx.currentTime));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function next_row() {
|
function next_row() {
|
||||||
if (player.cur_pat == -1 || player.cur_row >= player.xm.patterns[player.cur_pat].length) {
|
if (player.cur_pat == -1 || player.cur_row >= player.xm.patterns[player.cur_pat].length) {
|
||||||
player.cur_row = 0;
|
player.cur_row = 0;
|
||||||
|
|
@ -358,11 +164,11 @@ function next_row() {
|
||||||
} else if (v >= 0x60 && v < 0x70) { // volume slide down
|
} else if (v >= 0x60 && v < 0x70) { // volume slide down
|
||||||
ch.voleffectfn = function(ch) {
|
ch.voleffectfn = function(ch) {
|
||||||
ch.vol = Math.max(0, ch.vol - ch.voleffectdata);
|
ch.vol = Math.max(0, ch.vol - ch.voleffectdata);
|
||||||
}
|
};
|
||||||
} else if (v >= 0x70 && v < 0x80) { // volume slide up
|
} else if (v >= 0x70 && v < 0x80) { // volume slide up
|
||||||
ch.voleffectfn = function(ch) {
|
ch.voleffectfn = function(ch) {
|
||||||
ch.vol = Math.min(64, ch.vol + ch.voleffectdata);
|
ch.vol = Math.min(64, ch.vol + ch.voleffectdata);
|
||||||
}
|
};
|
||||||
} else if (v >= 0x80 && v < 0x90) { // fine volume slide down
|
} else if (v >= 0x80 && v < 0x90) { // fine volume slide down
|
||||||
ch.vol = Math.max(0, ch.vol - (v & 0x0f));
|
ch.vol = Math.max(0, ch.vol - (v & 0x0f));
|
||||||
} else if (v >= 0x90 && v < 0xa0) { // fine volume slide up
|
} else if (v >= 0x90 && v < 0xa0) { // fine volume slide up
|
||||||
|
|
@ -440,14 +246,14 @@ Envelope.prototype.Get = function(ticks) {
|
||||||
y0 = env[i+1];
|
y0 = env[i+1];
|
||||||
if (ticks < env[i]) {
|
if (ticks < env[i]) {
|
||||||
var x0 = env[i-2];
|
var x0 = env[i-2];
|
||||||
var y0 = env[i-1];
|
y0 = env[i-1];
|
||||||
var dx = env[i] - x0;
|
var dx = env[i] - x0;
|
||||||
var dy = env[i+1] - y0;
|
var dy = env[i+1] - y0;
|
||||||
return y0 + (ticks - x0) * dy / dx;
|
return y0 + (ticks - x0) * dy / dx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return y0;
|
return y0;
|
||||||
}
|
};
|
||||||
|
|
||||||
function EnvelopeFollower(env) {
|
function EnvelopeFollower(env) {
|
||||||
this.env = env;
|
this.env = env;
|
||||||
|
|
@ -470,7 +276,7 @@ EnvelopeFollower.prototype.Tick = function(release) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
};
|
||||||
|
|
||||||
function next_tick() {
|
function next_tick() {
|
||||||
player.cur_tick++;
|
player.cur_tick++;
|
||||||
|
|
@ -555,7 +361,7 @@ function MixChannelIntoBuf(ch, start, end, dataL, dataR) {
|
||||||
var volR = volE * (128 + p) * ch.vol / 8192.0;
|
var volR = volE * (128 + p) * ch.vol / 8192.0;
|
||||||
if (volL < 0) volL = 0;
|
if (volL < 0) volL = 0;
|
||||||
if (volR < 0) volR = 0;
|
if (volR < 0) volR = 0;
|
||||||
if (volR == 0 && volL == 0)
|
if (volR === 0 && volL === 0)
|
||||||
return;
|
return;
|
||||||
if (isNaN(volR) || isNaN(volL)) {
|
if (isNaN(volR) || isNaN(volL)) {
|
||||||
console.log("NaN volume!?", ch.number, volL, volR, volE, panE, ch.vol);
|
console.log("NaN volume!?", ch.number, volL, volR, volE, panE, ch.vol);
|
||||||
|
|
@ -598,65 +404,66 @@ function MixChannelIntoBuf(ch, start, end, dataL, dataR) {
|
||||||
// this is the inner loop of the player
|
// this is the inner loop of the player
|
||||||
|
|
||||||
// unrolled 8x
|
// unrolled 8x
|
||||||
|
var s, y;
|
||||||
for (; i + 7 < next_event; i+=8) {
|
for (; i + 7 < next_event; i+=8) {
|
||||||
var s = samp[k|0];
|
s = samp[k|0];
|
||||||
var y = f0 * (s + fs0) + f1*fs1 + f2*fs2;
|
y = f0 * (s + fs0) + f1*fs1 + f2*fs2;
|
||||||
fs2 = fs1; fs1 = y; fs0 = s;
|
fs2 = fs1; fs1 = y; fs0 = s;
|
||||||
k += dk;
|
k += dk;
|
||||||
dataL[i] += vL * y;
|
dataL[i] += vL * y;
|
||||||
dataR[i] += vR * y;
|
dataR[i] += vR * y;
|
||||||
Vrms += (vL + vR) * y * y;
|
Vrms += (vL + vR) * y * y;
|
||||||
|
|
||||||
var s = samp[k|0];
|
s = samp[k|0];
|
||||||
var y = f0 * (s + fs0) + f1*fs1 + f2*fs2;
|
y = f0 * (s + fs0) + f1*fs1 + f2*fs2;
|
||||||
fs2 = fs1; fs1 = y; fs0 = s;
|
fs2 = fs1; fs1 = y; fs0 = s;
|
||||||
k += dk;
|
k += dk;
|
||||||
dataL[i+1] += vL * y;
|
dataL[i+1] += vL * y;
|
||||||
dataR[i+1] += vR * y;
|
dataR[i+1] += vR * y;
|
||||||
Vrms += (vL + vR) * y * y;
|
Vrms += (vL + vR) * y * y;
|
||||||
|
|
||||||
var s = samp[k|0];
|
s = samp[k|0];
|
||||||
var y = f0 * (s + fs0) + f1*fs1 + f2*fs2;
|
y = f0 * (s + fs0) + f1*fs1 + f2*fs2;
|
||||||
fs2 = fs1; fs1 = y; fs0 = s;
|
fs2 = fs1; fs1 = y; fs0 = s;
|
||||||
k += dk;
|
k += dk;
|
||||||
dataL[i+2] += vL * y;
|
dataL[i+2] += vL * y;
|
||||||
dataR[i+2] += vR * y;
|
dataR[i+2] += vR * y;
|
||||||
Vrms += (vL + vR) * y * y;
|
Vrms += (vL + vR) * y * y;
|
||||||
|
|
||||||
var s = samp[k|0];
|
s = samp[k|0];
|
||||||
var y = f0 * (s + fs0) + f1*fs1 + f2*fs2;
|
y = f0 * (s + fs0) + f1*fs1 + f2*fs2;
|
||||||
fs2 = fs1; fs1 = y; fs0 = s;
|
fs2 = fs1; fs1 = y; fs0 = s;
|
||||||
k += dk;
|
k += dk;
|
||||||
dataL[i+3] += vL * y;
|
dataL[i+3] += vL * y;
|
||||||
dataR[i+3] += vR * y;
|
dataR[i+3] += vR * y;
|
||||||
Vrms += (vL + vR) * y * y;
|
Vrms += (vL + vR) * y * y;
|
||||||
|
|
||||||
var s = samp[k|0];
|
s = samp[k|0];
|
||||||
var y = f0 * (s + fs0) + f1*fs1 + f2*fs2;
|
y = f0 * (s + fs0) + f1*fs1 + f2*fs2;
|
||||||
fs2 = fs1; fs1 = y; fs0 = s;
|
fs2 = fs1; fs1 = y; fs0 = s;
|
||||||
k += dk;
|
k += dk;
|
||||||
dataL[i+4] += vL * y;
|
dataL[i+4] += vL * y;
|
||||||
dataR[i+4] += vR * y;
|
dataR[i+4] += vR * y;
|
||||||
Vrms += (vL + vR) * y * y;
|
Vrms += (vL + vR) * y * y;
|
||||||
|
|
||||||
var s = samp[k|0];
|
s = samp[k|0];
|
||||||
var y = f0 * (s + fs0) + f1*fs1 + f2*fs2;
|
y = f0 * (s + fs0) + f1*fs1 + f2*fs2;
|
||||||
fs2 = fs1; fs1 = y; fs0 = s;
|
fs2 = fs1; fs1 = y; fs0 = s;
|
||||||
k += dk;
|
k += dk;
|
||||||
dataL[i+5] += vL * y;
|
dataL[i+5] += vL * y;
|
||||||
dataR[i+5] += vR * y;
|
dataR[i+5] += vR * y;
|
||||||
Vrms += (vL + vR) * y * y;
|
Vrms += (vL + vR) * y * y;
|
||||||
|
|
||||||
var s = samp[k|0];
|
s = samp[k|0];
|
||||||
var y = f0 * (s + fs0) + f1*fs1 + f2*fs2;
|
y = f0 * (s + fs0) + f1*fs1 + f2*fs2;
|
||||||
fs2 = fs1; fs1 = y; fs0 = s;
|
fs2 = fs1; fs1 = y; fs0 = s;
|
||||||
k += dk;
|
k += dk;
|
||||||
dataL[i+6] += vL * y;
|
dataL[i+6] += vL * y;
|
||||||
dataR[i+6] += vR * y;
|
dataR[i+6] += vR * y;
|
||||||
Vrms += (vL + vR) * y * y;
|
Vrms += (vL + vR) * y * y;
|
||||||
|
|
||||||
var s = samp[k|0];
|
s = samp[k|0];
|
||||||
var y = f0 * (s + fs0) + f1*fs1 + f2*fs2;
|
y = f0 * (s + fs0) + f1*fs1 + f2*fs2;
|
||||||
fs2 = fs1; fs1 = y; fs0 = s;
|
fs2 = fs1; fs1 = y; fs0 = s;
|
||||||
k += dk;
|
k += dk;
|
||||||
dataL[i+7] += vL * y;
|
dataL[i+7] += vL * y;
|
||||||
|
|
@ -668,13 +475,13 @@ function MixChannelIntoBuf(ch, start, end, dataL, dataR) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (; i < next_event; i++) {
|
for (; i < next_event; i++) {
|
||||||
var s = samp[k|0];
|
s = samp[k|0];
|
||||||
// we low-pass filter here since we are resampling some arbitrary
|
// we low-pass filter here since we are resampling some arbitrary
|
||||||
// frequency to f_smp; this is an anti-aliasing filter and is
|
// frequency to f_smp; this is an anti-aliasing filter and is
|
||||||
// implemented as an IIR butterworth filter (usually we'd use an FIR
|
// implemented as an IIR butterworth filter (usually we'd use an FIR
|
||||||
// brick wall filter, but this is much simpler computationally and
|
// brick wall filter, but this is much simpler computationally and
|
||||||
// sounds fine)
|
// sounds fine)
|
||||||
var y = f0 * (s + fs0) + f1*fs1 + f2*fs2;
|
y = f0 * (s + fs0) + f1*fs1 + f2*fs2;
|
||||||
fs2 = fs1; fs1 = y; fs0 = s;
|
fs2 = fs1; fs1 = y; fs0 = s;
|
||||||
dataL[i] += vL * y;
|
dataL[i] += vL * y;
|
||||||
dataR[i] += vR * y;
|
dataR[i] += vR * y;
|
||||||
|
|
@ -692,19 +499,21 @@ function MixChannelIntoBuf(ch, start, end, dataL, dataR) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function audio_cb(e) {
|
function audio_cb(e) {
|
||||||
f_smp = audioctx.sampleRate;
|
f_smp = player.audioctx.sampleRate;
|
||||||
var time_sound_started = undefined;
|
var time_sound_started;
|
||||||
var buflen = e.outputBuffer.length;
|
var buflen = e.outputBuffer.length;
|
||||||
var dataL = e.outputBuffer.getChannelData(0);
|
var dataL = e.outputBuffer.getChannelData(0);
|
||||||
var dataR = e.outputBuffer.getChannelData(1);
|
var dataR = e.outputBuffer.getChannelData(1);
|
||||||
|
var i, j, k;
|
||||||
|
|
||||||
for (var i = 0; i < buflen; i++) {
|
for (i = 0; i < buflen; i++) {
|
||||||
dataL[i] = 0;
|
dataL[i] = 0;
|
||||||
dataR[i] = 0;
|
dataR[i] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
var offset = 0;
|
var offset = 0;
|
||||||
var ticklen = 0|(f_smp * 2.5 / player.xm.bpm);
|
var ticklen = 0|(f_smp * 2.5 / player.xm.bpm);
|
||||||
|
var _scope_width = view.scope_width || 0;
|
||||||
|
|
||||||
while(buflen > 0) {
|
while(buflen > 0) {
|
||||||
if (player.cur_pat == -1 || player.cur_ticksamp >= ticklen) {
|
if (player.cur_pat == -1 || player.cur_ticksamp >= ticklen) {
|
||||||
|
|
@ -713,12 +522,12 @@ function audio_cb(e) {
|
||||||
}
|
}
|
||||||
var tickduration = Math.min(buflen, ticklen - player.cur_ticksamp);
|
var tickduration = Math.min(buflen, ticklen - player.cur_ticksamp);
|
||||||
var VU = new Float32Array(player.xm.nchan);
|
var VU = new Float32Array(player.xm.nchan);
|
||||||
var scopes = undefined;
|
var scopes;
|
||||||
for (var j = 0; j < player.xm.nchan; j++) {
|
for (j = 0; j < player.xm.nchan; j++) {
|
||||||
var scope;
|
var scope;
|
||||||
if (tickduration >= 4*_scope_width) {
|
if (tickduration >= 4*_scope_width) {
|
||||||
scope = new Float32Array(_scope_width);
|
scope = new Float32Array(_scope_width);
|
||||||
for (var k = 0; k < _scope_width; k++) {
|
for (k = 0; k < _scope_width; k++) {
|
||||||
scope[k] = -dataL[offset+k*4] - dataR[offset+k*4];
|
scope[k] = -dataL[offset+k*4] - dataR[offset+k*4];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -728,14 +537,14 @@ function audio_cb(e) {
|
||||||
tickduration;
|
tickduration;
|
||||||
|
|
||||||
if (tickduration >= 4*_scope_width) {
|
if (tickduration >= 4*_scope_width) {
|
||||||
for (var k = 0; k < _scope_width; k++) {
|
for (k = 0; k < _scope_width; k++) {
|
||||||
scope[k] += dataL[offset+k*4] + dataR[offset+k*4];
|
scope[k] += dataL[offset+k*4] + dataR[offset+k*4];
|
||||||
}
|
}
|
||||||
if (scopes == undefined) scopes = [];
|
if (scopes === undefined) scopes = [];
|
||||||
scopes.push(scope);
|
scopes.push(scope);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
audio_events.push({
|
view.pushEvent({
|
||||||
t: e.playbackTime + (0.0 + offset) / f_smp,
|
t: e.playbackTime + (0.0 + offset) / f_smp,
|
||||||
vu: VU,
|
vu: VU,
|
||||||
scopes: scopes,
|
scopes: scopes,
|
||||||
|
|
@ -743,9 +552,6 @@ function audio_cb(e) {
|
||||||
pat: player.cur_pat,
|
pat: player.cur_pat,
|
||||||
row: player.cur_row - 1
|
row: player.cur_row - 1
|
||||||
});
|
});
|
||||||
if (audio_events.length == 1) {
|
|
||||||
requestAnimationFrame(RedrawScreen);
|
|
||||||
}
|
|
||||||
offset += tickduration;
|
offset += tickduration;
|
||||||
player.cur_ticksamp += tickduration;
|
player.cur_ticksamp += tickduration;
|
||||||
buflen -= tickduration;
|
buflen -= tickduration;
|
||||||
|
|
@ -755,20 +561,21 @@ function audio_cb(e) {
|
||||||
function ConvertSample(array, bits) {
|
function ConvertSample(array, bits) {
|
||||||
var len = array.length;
|
var len = array.length;
|
||||||
var acc = 0;
|
var acc = 0;
|
||||||
if (bits == 0) { // 8 bit sample
|
var samp, b, k;
|
||||||
var samp = new Float32Array(len);
|
if (bits === 0) { // 8 bit sample
|
||||||
for (var k = 0; k < len; k++) {
|
samp = new Float32Array(len);
|
||||||
|
for (k = 0; k < len; k++) {
|
||||||
acc += array[k];
|
acc += array[k];
|
||||||
var b = acc&255;
|
b = acc&255;
|
||||||
if (b & 128) b = b-256;
|
if (b & 128) b = b-256;
|
||||||
samp[k] = b / 128.0;
|
samp[k] = b / 128.0;
|
||||||
}
|
}
|
||||||
return samp;
|
return samp;
|
||||||
} else {
|
} else {
|
||||||
len /= 2;
|
len /= 2;
|
||||||
var samp = new Float32Array(len);
|
samp = new Float32Array(len);
|
||||||
for (var k = 0; k < len; k++) {
|
for (k = 0; k < len; k++) {
|
||||||
var b = array[k*2] + (array[k*2 + 1] << 8);
|
b = array[k*2] + (array[k*2 + 1] << 8);
|
||||||
if (b & 32768) b = b-65536;
|
if (b & 32768) b = b-65536;
|
||||||
acc = Math.max(-1, Math.min(1, acc + b / 32768.0));
|
acc = Math.max(-1, Math.min(1, acc + b / 32768.0));
|
||||||
samp[k] = acc;
|
samp[k] = acc;
|
||||||
|
|
@ -792,12 +599,13 @@ function UnrollSampleLoop(samp) {
|
||||||
data[i] = samp.sampledata[i];
|
data[i] = samp.sampledata[i];
|
||||||
}
|
}
|
||||||
for (var j = 0; j < nloops; j++) {
|
for (var j = 0; j < nloops; j++) {
|
||||||
|
var k;
|
||||||
if ((j&1) && pingpong) {
|
if ((j&1) && pingpong) {
|
||||||
for (var k = samp.looplen - 1; k >= 0; k--) {
|
for (k = samp.looplen - 1; k >= 0; k--) {
|
||||||
data[i++] = samp.sampledata[samp.loop + k];
|
data[i++] = samp.sampledata[samp.loop + k];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (var k = 0; k < samp.looplen; k++) {
|
for (k = 0; k < samp.looplen; k++) {
|
||||||
data[i++] = samp.sampledata[samp.loop + k];
|
data[i++] = samp.sampledata[samp.loop + k];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -824,7 +632,9 @@ function LoadXM(arrayBuf) {
|
||||||
player.xm.bpm = dv.getUint16(0x4e, true);
|
player.xm.bpm = dv.getUint16(0x4e, true);
|
||||||
player.xm.channelinfo = [];
|
player.xm.channelinfo = [];
|
||||||
|
|
||||||
for (var i = 0; i < player.xm.nchan; i++) {
|
var i, j, k;
|
||||||
|
|
||||||
|
for (i = 0; i < player.xm.nchan; i++) {
|
||||||
player.xm.channelinfo.push({
|
player.xm.channelinfo.push({
|
||||||
number: i,
|
number: i,
|
||||||
filterstate: new Float32Array(3),
|
filterstate: new Float32Array(3),
|
||||||
|
|
@ -838,7 +648,7 @@ function LoadXM(arrayBuf) {
|
||||||
retrig: 0,
|
retrig: 0,
|
||||||
vibratodepth: 1,
|
vibratodepth: 1,
|
||||||
vibratospeed: 1,
|
vibratospeed: 1,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
console.log("header len " + hlen);
|
console.log("header len " + hlen);
|
||||||
|
|
||||||
|
|
@ -847,23 +657,23 @@ function LoadXM(arrayBuf) {
|
||||||
console.log("flags=%d tempo %d bpm %d", player.xm.flags, player.xm.tempo, player.xm.bpm);
|
console.log("flags=%d tempo %d bpm %d", player.xm.flags, player.xm.tempo, player.xm.bpm);
|
||||||
|
|
||||||
player.xm.songpats = [];
|
player.xm.songpats = [];
|
||||||
for (var i = 0; i < songlen; i++) {
|
for (i = 0; i < songlen; i++) {
|
||||||
player.xm.songpats.push(dv.getUint8(0x50 + i));
|
player.xm.songpats.push(dv.getUint8(0x50 + i));
|
||||||
}
|
}
|
||||||
console.log("song patterns: ", player.xm.songpats);
|
console.log("song patterns: ", player.xm.songpats);
|
||||||
|
|
||||||
var idx = hlen;
|
var idx = hlen;
|
||||||
player.xm.patterns = [];
|
player.xm.patterns = [];
|
||||||
for (var i = 0; i < npat; i++) {
|
for (i = 0; i < npat; i++) {
|
||||||
var pattern = [];
|
var pattern = [];
|
||||||
var patheaderlen = dv.getUint32(idx, true);
|
var patheaderlen = dv.getUint32(idx, true);
|
||||||
var patrows = dv.getUint16(idx + 5, true);
|
var patrows = dv.getUint16(idx + 5, true);
|
||||||
var patsize = dv.getUint16(idx + 7, true);
|
var patsize = dv.getUint16(idx + 7, true);
|
||||||
console.log("pattern %d: %d bytes, %d rows", i, patsize, patrows);
|
console.log("pattern %d: %d bytes, %d rows", i, patsize, patrows);
|
||||||
idx += 9;
|
idx += 9;
|
||||||
for (var j = 0; patsize > 0 && j < patrows; j++) {
|
for (j = 0; patsize > 0 && j < patrows; j++) {
|
||||||
row = [];
|
row = [];
|
||||||
for (var k = 0; k < player.xm.nchan; k++) {
|
for (k = 0; k < player.xm.nchan; k++) {
|
||||||
var byte0 = dv.getUint8(idx); idx++;
|
var byte0 = dv.getUint8(idx); idx++;
|
||||||
var note = -1, inst = -1, vol = -1, efftype = 0, effparam = 0;
|
var note = -1, inst = -1, vol = -1, efftype = 0, effparam = 0;
|
||||||
if (byte0 & 0x80) {
|
if (byte0 & 0x80) {
|
||||||
|
|
@ -924,11 +734,11 @@ function LoadXM(arrayBuf) {
|
||||||
var env_pan_loop_end = dv.getUint8(idx+232);
|
var env_pan_loop_end = dv.getUint8(idx+232);
|
||||||
var vol_fadeout = dv.getUint16(idx+239, true);
|
var vol_fadeout = dv.getUint16(idx+239, true);
|
||||||
var env_vol = [];
|
var env_vol = [];
|
||||||
for (var j = 0; j < env_nvol*2; j++) {
|
for (j = 0; j < env_nvol*2; j++) {
|
||||||
env_vol.push(dv.getUint16(idx+129+j*2, true));
|
env_vol.push(dv.getUint16(idx+129+j*2, true));
|
||||||
}
|
}
|
||||||
var env_pan = [];
|
var env_pan = [];
|
||||||
for (var j = 0; j < env_npan*2; j++) {
|
for (j = 0; j < env_npan*2; j++) {
|
||||||
env_pan.push(dv.getUint16(idx+177+j*2, true));
|
env_pan.push(dv.getUint16(idx+177+j*2, true));
|
||||||
}
|
}
|
||||||
// FIXME: ignoring keymaps for now and assuming 1 sample / instrument
|
// FIXME: ignoring keymaps for now and assuming 1 sample / instrument
|
||||||
|
|
@ -939,7 +749,7 @@ function LoadXM(arrayBuf) {
|
||||||
idx += hdrsiz;
|
idx += hdrsiz;
|
||||||
var totalsamples = 0;
|
var totalsamples = 0;
|
||||||
var samps = [];
|
var samps = [];
|
||||||
for (var j = 0; j < nsamp; j++) {
|
for (j = 0; j < nsamp; j++) {
|
||||||
var samplen = dv.getUint32(idx, true);
|
var samplen = dv.getUint32(idx, true);
|
||||||
var samploop = dv.getUint32(idx+4, true);
|
var samploop = dv.getUint32(idx+4, true);
|
||||||
var samplooplen = dv.getUint32(idx+8, true);
|
var samplooplen = dv.getUint32(idx+8, true);
|
||||||
|
|
@ -963,7 +773,6 @@ function LoadXM(arrayBuf) {
|
||||||
'len': samplen, 'loop': samploop,
|
'len': samplen, 'loop': samploop,
|
||||||
'looplen': samplooplen, 'note': sampnote, 'fine': sampfinetune,
|
'looplen': samplooplen, 'note': sampnote, 'fine': sampfinetune,
|
||||||
'pan': samppan, 'type': samptype, 'vol': sampvol,
|
'pan': samppan, 'type': samptype, 'vol': sampvol,
|
||||||
'fine': sampfinetune,
|
|
||||||
'sampledata': ConvertSample(
|
'sampledata': ConvertSample(
|
||||||
new Uint8Array(arrayBuf, sampleoffset, samplen), samptype & 16),
|
new Uint8Array(arrayBuf, sampleoffset, samplen), samptype & 16),
|
||||||
};
|
};
|
||||||
|
|
@ -1031,195 +840,116 @@ function LoadXM(arrayBuf) {
|
||||||
|
|
||||||
console.log("loaded \"" + player.xm.songname + "\"");
|
console.log("loaded \"" + player.xm.songname + "\"");
|
||||||
|
|
||||||
var title = document.getElementById("title");
|
|
||||||
// make title element fit text exactly, then render it
|
|
||||||
title.width = TextSize(player.xm.songname, _bigfontwidths);
|
|
||||||
var ctx = title.getContext('2d');
|
|
||||||
DrawBigText(player.xm.songname, 0, 1, ctx);
|
|
||||||
|
|
||||||
var instrlist = document.getElementById("instruments");
|
|
||||||
// clear instrument list if not already clear
|
|
||||||
while (instrlist.childNodes.length) {
|
|
||||||
instrlist.removeChild(instrlist.childNodes[0]);
|
|
||||||
}
|
|
||||||
var instrcols = ((player.xm.instruments.length + 7) / 8) | 0;
|
|
||||||
for (var i = 0; i < instrcols; i++) {
|
|
||||||
var canvas = document.createElement('canvas');
|
|
||||||
var ctx = canvas.getContext('2d');
|
|
||||||
var instrcolumnwidth = 8*22;
|
|
||||||
canvas.width = instrcolumnwidth;
|
|
||||||
canvas.height = 8 * 10;
|
|
||||||
ctx.fillStyle = '#000';
|
|
||||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
||||||
var hasname = 0, hasdata = 0;
|
|
||||||
for (var j = 8*i; j < Math.min(8*i+8, player.xm.instruments.length); j++) {
|
|
||||||
var y = 10*(j - 8*i);
|
|
||||||
var n = (j+1).toString(16);
|
|
||||||
if (j < 15) n = '0' + n;
|
|
||||||
var data = player.xm.instruments[j].samples;
|
|
||||||
if (data) {
|
|
||||||
var len = data[0].len;
|
|
||||||
data = data[0].sampledata;
|
|
||||||
var scale = Math.ceil(len / instrcolumnwidth);
|
|
||||||
ctx.strokeStyle = '#55acff';
|
|
||||||
ctx.beginPath();
|
|
||||||
for (var k = 0; k < Math.min(len / scale, instrcolumnwidth - 20); k++) {
|
|
||||||
ctx.lineTo(k + 20, y + data[k*scale] * 4 + 4);
|
|
||||||
}
|
|
||||||
ctx.stroke();
|
|
||||||
hasdata++;
|
|
||||||
}
|
|
||||||
var name = player.xm.instruments[j].name;
|
|
||||||
ctx.globalCompositeOperation = 'lighten';
|
|
||||||
DrawText(n, 1, y, ctx);
|
|
||||||
if (name != '') {
|
|
||||||
DrawText(player.xm.instruments[j].name, 20, y, ctx);
|
|
||||||
hasname++;
|
|
||||||
}
|
|
||||||
ctx.globalCompositeOperation = 'source-over';
|
|
||||||
}
|
|
||||||
if (hasname || hasdata) {
|
|
||||||
instrlist.appendChild(canvas);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return player.xm;
|
return player.xm;
|
||||||
}
|
}
|
||||||
|
|
||||||
var jsNode, gainNode;
|
var jsNode, gainNode;
|
||||||
function InitAudio() {
|
function initAudio() {
|
||||||
if (audioctx == undefined) {
|
if (!player.audioctx) {
|
||||||
var audioContext = window.AudioContext || window.webkitAudioContext;
|
var audioContext = window.AudioContext || window.webkitAudioContext;
|
||||||
audioctx = new audioContext();
|
player.audioctx = new audioContext();
|
||||||
gainNode = audioctx.createGain();
|
gainNode = player.audioctx.createGain();
|
||||||
gainNode.gain.value = 0.1; // master volume
|
gainNode.gain.value = 0.1; // master volume
|
||||||
}
|
}
|
||||||
if (audioctx.createScriptProcessor == undefined) {
|
if (player.audioctx.createScriptProcessor === undefined) {
|
||||||
jsNode = audioctx.createJavaScriptNode(16384, 0, 2);
|
jsNode = player.audioctx.createJavaScriptNode(16384, 0, 2);
|
||||||
} else {
|
} else {
|
||||||
jsNode = audioctx.createScriptProcessor(16384, 0, 2);
|
jsNode = player.audioctx.createScriptProcessor(16384, 0, 2);
|
||||||
}
|
}
|
||||||
jsNode.onaudioprocess = audio_cb;
|
jsNode.onaudioprocess = audio_cb;
|
||||||
gainNode.connect(audioctx.destination);
|
gainNode.connect(player.audioctx.destination);
|
||||||
}
|
}
|
||||||
|
|
||||||
var playing = false;
|
var playing = false;
|
||||||
var paused_events = [];
|
function playXM() {
|
||||||
function PlayXM() {
|
|
||||||
if (!playing) {
|
if (!playing) {
|
||||||
// put paused events back into action, if any
|
// put paused events back into action, if any
|
||||||
var t = audioctx.currentTime;
|
view.resume();
|
||||||
while (paused_events.length > 0) {
|
|
||||||
var e = paused_events.shift();
|
|
||||||
e.t += t;
|
|
||||||
audio_events.push(e);
|
|
||||||
}
|
|
||||||
// start playing
|
// start playing
|
||||||
jsNode.connect(gainNode);
|
jsNode.connect(gainNode);
|
||||||
|
|
||||||
// hack to get iOS to play anything
|
// hack to get iOS to play anything
|
||||||
var temp_osc = audioctx.createOscillator();
|
/*
|
||||||
temp_osc.connect(audioctx.destination);
|
* this seems to cause other player issues... disabling for now
|
||||||
|
var temp_osc = player.audioctx.createOscillator();
|
||||||
|
temp_osc.connect(player.audioctx.destination);
|
||||||
if (temp_osc.noteOn) temp_osc.start = temp_osc.noteOn;
|
if (temp_osc.noteOn) temp_osc.start = temp_osc.noteOn;
|
||||||
temp_osc.frequency.value = 1;
|
temp_osc.frequency.value = 1;
|
||||||
temp_osc.start(0);
|
temp_osc.start(0);
|
||||||
setTimeout(10, function() {
|
setTimeout(10, function() {
|
||||||
temp_osc.disconnect();
|
temp_osc.disconnect();
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
requestAnimationFrame(RedrawScreen);
|
|
||||||
}
|
}
|
||||||
playing = true;
|
playing = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function PauseXM() {
|
function pauseXM() {
|
||||||
if (playing) {
|
if (playing) {
|
||||||
jsNode.disconnect(gainNode);
|
jsNode.disconnect(gainNode);
|
||||||
// grab all the audio events
|
view.pause();
|
||||||
var t = audioctx.currentTime;
|
|
||||||
while (audio_events.length > 0) {
|
|
||||||
var e = audio_events.shift();
|
|
||||||
e.t -= t;
|
|
||||||
paused_events.push(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
playing = false;
|
playing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function StopXM() {
|
function stopXM() {
|
||||||
if (playing) {
|
if (playing) {
|
||||||
jsNode.disconnect(gainNode);
|
jsNode.disconnect(gainNode);
|
||||||
playing = false;
|
playing = false;
|
||||||
audio_events = [];
|
|
||||||
paused_events = [];
|
|
||||||
}
|
}
|
||||||
player.cur_songpos = -1, player.cur_pat = -1, player.cur_row = 64, player.cur_ticksamp = 0;
|
player.cur_pat = -1;
|
||||||
InitAudio();
|
player.cur_row = 64;
|
||||||
|
player.cur_songpos = -1;
|
||||||
|
player.cur_ticksamp = 0;
|
||||||
|
initAudio();
|
||||||
}
|
}
|
||||||
|
|
||||||
function LoadXMAndInit(xmdata) {
|
function loadXMAndInit(xmdata) {
|
||||||
player.xm = LoadXM(xmdata);
|
player.xm = LoadXM(xmdata);
|
||||||
if (!player.xm) return;
|
if (!player.xm) return;
|
||||||
|
|
||||||
document.getElementById('vu').width = _pattern_border + _pattern_cellwidth * player.xm.nchan;
|
view.init();
|
||||||
var gfxpattern = document.getElementById("gfxpattern");
|
|
||||||
gfxpattern.width = _pattern_cellwidth * player.xm.nchan + _pattern_border;
|
var playbutton = document.getElementById("playpause");
|
||||||
var playbutton = document.getElementById('playpause');
|
|
||||||
playbutton.innerHTML='Play';
|
playbutton.innerHTML='Play';
|
||||||
playbutton.onclick = function() {
|
playbutton.onclick = function() {
|
||||||
if (playing) {
|
if (playing) {
|
||||||
PauseXM();
|
pauseXM();
|
||||||
playbutton.innerHTML='Play';
|
playbutton.innerHTML='Play';
|
||||||
} else {
|
} else {
|
||||||
PlayXM();
|
playXM();
|
||||||
playbutton.innerHTML='Pause';
|
playbutton.innerHTML='Pause';
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
playbutton.disabled = false;
|
playbutton.disabled = false;
|
||||||
// generate a fake audio event to render the initial paused screen
|
|
||||||
var scopes = [];
|
|
||||||
for (var i = 0; i < player.xm.nchan; i++) {
|
|
||||||
scopes.push(new Float32Array(_scope_width));
|
|
||||||
}
|
|
||||||
|
|
||||||
// reset display
|
|
||||||
shown_row = undefined;
|
|
||||||
pat_canvas_patnum = undefined;
|
|
||||||
|
|
||||||
audio_events.push({
|
|
||||||
t: 0, row: 0, pat: player.xm.songpats[0],
|
|
||||||
vu: new Float32Array(player.xm.nchan),
|
|
||||||
scopes: scopes
|
|
||||||
});
|
|
||||||
RedrawScreen();
|
|
||||||
|
|
||||||
return player.xm;
|
return player.xm;
|
||||||
}
|
}
|
||||||
|
|
||||||
function DownloadXM(uri) {
|
function downloadXM(uri) {
|
||||||
var xmReq = new XMLHttpRequest();
|
var xmReq = new XMLHttpRequest();
|
||||||
xmReq.open("GET", uri, true);
|
xmReq.open("GET", uri, true);
|
||||||
xmReq.responseType = "arraybuffer";
|
xmReq.responseType = "arraybuffer";
|
||||||
xmReq.onload = function (xmEvent) {
|
xmReq.onload = function (xmEvent) {
|
||||||
var arrayBuffer = xmReq.response;
|
var arrayBuffer = xmReq.response;
|
||||||
if (arrayBuffer) {
|
if (arrayBuffer) {
|
||||||
player.xm = LoadXMAndInit(arrayBuffer);
|
player.xm = loadXMAndInit(arrayBuffer);
|
||||||
} else {
|
} else {
|
||||||
console.log("unable to load", uri);
|
console.log("unable to load", uri);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
xmReq.send(null);
|
xmReq.send(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
function allowDrop(ev) {
|
player.allowDrop = function(ev) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
var elem = document.getElementById("playercontainer");
|
var elem = document.getElementById("playercontainer");
|
||||||
elem.className = (ev.type == "dragover" ? "draghover" : "playercontainer");
|
elem.className = (ev.type == "dragover" ? "draghover" : "playercontainer");
|
||||||
return false;
|
return false;
|
||||||
}
|
};
|
||||||
|
|
||||||
function handleDrop(e) {
|
player.handleDrop = function(e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var elem = document.getElementById("playercontainer");
|
var elem = document.getElementById("playercontainer");
|
||||||
|
|
@ -1228,13 +958,14 @@ function handleDrop(e) {
|
||||||
if (files.length < 1) return false;
|
if (files.length < 1) return false;
|
||||||
var reader = new FileReader();
|
var reader = new FileReader();
|
||||||
reader.onload = function(e) {
|
reader.onload = function(e) {
|
||||||
LoadXMAndInit(e.target.result);
|
stopXM();
|
||||||
}
|
loadXMAndInit(e.target.result);
|
||||||
|
};
|
||||||
reader.readAsArrayBuffer(files[0]);
|
reader.readAsArrayBuffer(files[0]);
|
||||||
return false;
|
return false;
|
||||||
}
|
};
|
||||||
|
|
||||||
function InitFilelist() {
|
function initFilelist() {
|
||||||
var el = document.getElementById('filelist');
|
var el = document.getElementById('filelist');
|
||||||
xmuris.forEach(function(entry) {
|
xmuris.forEach(function(entry) {
|
||||||
var a = document.createElement('a');
|
var a = document.createElement('a');
|
||||||
|
|
@ -1242,9 +973,9 @@ function InitFilelist() {
|
||||||
a.href = '#'+entry[1];
|
a.href = '#'+entry[1];
|
||||||
a.onclick = function() {
|
a.onclick = function() {
|
||||||
el.style.display = "none";
|
el.style.display = "none";
|
||||||
StopXM();
|
stopXM();
|
||||||
DownloadXM(baseuri + entry[1]);
|
downloadXM(baseuri + entry[1]);
|
||||||
}
|
};
|
||||||
el.appendChild(a);
|
el.appendChild(a);
|
||||||
el.appendChild(document.createElement('br'));
|
el.appendChild(document.createElement('br'));
|
||||||
});
|
});
|
||||||
|
|
@ -1255,20 +986,20 @@ function InitFilelist() {
|
||||||
} else {
|
} else {
|
||||||
el.style.display = "none";
|
el.style.display = "none";
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
InitAudio();
|
initAudio();
|
||||||
InitFilelist();
|
initFilelist();
|
||||||
|
|
||||||
var uri = location.hash.substr(1);
|
var uri = location.hash.substr(1);
|
||||||
if (uri == "") {
|
if (uri === "") {
|
||||||
uri = "kamel.xm";
|
uri = "kamel.xm";
|
||||||
}
|
}
|
||||||
if (!uri.startsWith("http")) {
|
if (!uri.startsWith("http")) {
|
||||||
uri = baseuri + uri;
|
uri = baseuri + uri;
|
||||||
}
|
}
|
||||||
DownloadXM(uri);
|
downloadXM(uri);
|
||||||
}
|
};
|
||||||
})(window || {}, document);
|
})(window, document);
|
||||||
|
|
|
||||||
|
|
@ -155,7 +155,7 @@ function eff_t0_e(ch, data) { // extended effects!
|
||||||
case 0x0c: // note cut handled in eff_t1_e
|
case 0x0c: // note cut handled in eff_t1_e
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.log("unimplemented extended effect E", ch.effect.toString(16));
|
console.log("unimplemented extended effect E", ch.effectdata.toString(16));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -293,4 +293,5 @@ player.effects_t1 = [ // effect functions on tick 1+
|
||||||
eff_unimplemented, // y
|
eff_unimplemented, // y
|
||||||
eff_unimplemented // z
|
eff_unimplemented // z
|
||||||
];
|
];
|
||||||
|
|
||||||
})(window || {});
|
})(window || {});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue