From c238aabd661f543ba77721ff07e3c1bceb1d9bcf Mon Sep 17 00:00:00 2001 From: Andy Sloane Date: Fri, 30 Oct 2015 21:25:21 -0700 Subject: [PATCH] butter smooth screen updates, longer audio buffer --- index.html | 3 -- xm.js | 124 +++++++++++++++++++++++++++++------------------------ 2 files changed, 67 insertions(+), 60 deletions(-) diff --git a/index.html b/index.html index eed5fbe..3784657 100644 --- a/index.html +++ b/index.html @@ -14,9 +14,6 @@ code: github.com/a1k0n/jsxm todo: - file picker, upload more xms to a host we can access cross-origin - make display even more faithfully FT2-like - - sync display & sound to 60Hz precision (right now it only updates at - about 10Hz) - - buffer the song more after that's done - render instrument list - oscilloscopes - fix bugs! diff --git a/xm.js b/xm.js index 673bf07..e7be221 100644 --- a/xm.js +++ b/xm.js @@ -1,27 +1,9 @@ +var audioctx; var songname = ''; var fontimg = new Image(); var pat_canvas = document.createElement('canvas'); fontimg.onload = function() { - /* NO!!!!! - var canv = document.createElement('canvas'); - canv.width = fontimg.width; - canv.height = fontimg.height; - var c = canv.getContext('2d'); - c.drawImage(fontimg, 0, 0); - var image_data = c.getImageData(0, 0, fontimg.width, fontimg.height); - var idx = 0; - var data = image_data.data; - for(var i = 0; i < fontimg.height; i++) { - for(var j = 0; j < fontimg.width; j++) { - // image_data.data[((i*(img.width*4)) + (j*4) + 3)] = 127; - if (data[idx] == 0) - data[idx+3] = 0; - idx += 4; - } - } - c.putImageData(image_data, 0, 0); - fontimg = canv; - */ + // FIXME: don't attempt to render until this loads }; fontimg.src = "ft2font.png"; @@ -174,9 +156,56 @@ function PeriodForNote(ch, note) { return 1920 - note*16 - ch.inst.fine / 8.0; } +var audio_events = []; +var shown_row = undefined; +function RedrawScreen() { + 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; + + // update VU meters + var canvas = document.getElementById("vu"); + var ctx = canvas.getContext("2d"); + ctx.fillStyle = '#000'; + ctx.fillRect(0, 0, 16 * nchan, 64); + ctx.fillStyle = '#0f0'; + for (var j = 0; j < nchan; j++) { + var y = -Math.log(VU[j])*10; + ctx.fillRect(j*16, y, 15, 64-y); + } + + var debug = document.getElementById("debug"); + debug.innerHTML = songname + '
pat ' + e.pat + ' row ' + (e.row); + + if (e.row != shown_row) { + var gfx = document.getElementById("gfxpattern"); + if (e.pat != pat_canvas_patnum) { + RenderPattern(pat_canvas, 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)); + } +} + var cur_songpos = -1, cur_pat = -1, cur_row = 64, cur_ticksamp = 0; var cur_tick = 6; -var patdisplay = []; function next_row() { if (cur_pat == -1 || cur_row >= patterns[cur_pat].length) { cur_row = 0; @@ -553,6 +582,7 @@ function MixChannelIntoBuf(ch, start, end, dataL, dataR) { function audio_cb(e) { f_smp = audioctx.sampleRate; + var time_sound_started = undefined; var buflen = e.outputBuffer.length; var dataL = e.outputBuffer.getChannelData(0); var dataR = e.outputBuffer.getChannelData(1); @@ -564,53 +594,33 @@ function audio_cb(e) { var offset = 0; var ticklen = 0|(f_smp * 2.5 / bpm); - var VU = new Float32Array(nchan); while(buflen > 0) { - if (cur_ticksamp >= ticklen) { + if (cur_pat == -1 || cur_ticksamp >= ticklen) { next_tick(f_smp); cur_ticksamp -= ticklen; } var tickduration = Math.min(buflen, ticklen - cur_ticksamp); + var VU = new Float32Array(nchan); for (var j = 0; j < nchan; j++) { - VU[j] += MixChannelIntoBuf( - channelinfo[j], offset, offset + tickduration, dataL, dataR); + VU[j] = MixChannelIntoBuf( + channelinfo[j], offset, offset + tickduration, dataL, dataR) / + tickduration; + } + audio_events.push({ + t: e.playbackTime + (0.0 + offset) / f_smp, + vu: VU, + songpos: cur_songpos, + pat: cur_pat, + row: cur_row - 1 + }); + if (audio_events.length == 1) { + requestAnimationFrame(RedrawScreen); } offset += tickduration; cur_ticksamp += tickduration; buflen -= tickduration; } - - window.requestAnimationFrame(function() { - // update VU meters - var canvas = document.getElementById("vu"); - var ctx = canvas.getContext("2d"); - ctx.fillStyle = '#000'; - ctx.fillRect(0, 0, 16 * nchan, 64); - ctx.fillStyle = '#0f0'; - for (var j = 0; j < nchan; j++) { - var rms = VU[j] / e.outputBuffer.length; - var y = -Math.log(rms)*10; - ctx.fillRect(j*16, y, 15, 64-y); - } - - var debug = document.getElementById("debug"); - debug.innerHTML = songname + '
pat ' + cur_pat + ' row ' + (cur_row-1); - - var gfx = document.getElementById("gfxpattern"); - if (cur_pat != pat_canvas_patnum) { - RenderPattern(pat_canvas, patterns[cur_pat]); - pat_canvas_patnum = cur_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*(cur_row - 1)); - ctx.globalCompositeOperation = 'source-over'; - }); } function eff_t0_0(ch, data) { // arpeggio @@ -1106,7 +1116,7 @@ function playXM(arrayBuf) { audioctx = new audioContext(); gainNode = audioctx.createGain(); gainNode.gain.value = 0.1; // master volume - jsNode = audioctx.createScriptProcessor(4096, 0, 2); + jsNode = audioctx.createScriptProcessor(16384, 0, 2); jsNode.onaudioprocess = audio_cb; jsNode.connect(gainNode);