butter smooth screen updates, longer audio buffer
This commit is contained in:
parent
949a7c89c5
commit
c238aabd66
2 changed files with 67 additions and 60 deletions
|
|
@ -14,9 +14,6 @@ code: <a href="http://github.com/a1k0n/jsxm/">github.com/a1k0n/jsxm</a>
|
||||||
todo:
|
todo:
|
||||||
- file picker, upload more xms to a host we can access cross-origin
|
- file picker, upload more xms to a host we can access cross-origin
|
||||||
- make display even more faithfully FT2-like
|
- 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
|
- render instrument list
|
||||||
- oscilloscopes
|
- oscilloscopes
|
||||||
- fix bugs!
|
- fix bugs!
|
||||||
|
|
|
||||||
124
xm.js
124
xm.js
|
|
@ -1,27 +1,9 @@
|
||||||
|
var audioctx;
|
||||||
var songname = '';
|
var songname = '';
|
||||||
var fontimg = new Image();
|
var fontimg = new Image();
|
||||||
var pat_canvas = document.createElement('canvas');
|
var pat_canvas = document.createElement('canvas');
|
||||||
fontimg.onload = function() {
|
fontimg.onload = function() {
|
||||||
/* NO!!!!!
|
// FIXME: don't attempt to render until this loads
|
||||||
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;
|
|
||||||
*/
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fontimg.src = "ft2font.png";
|
fontimg.src = "ft2font.png";
|
||||||
|
|
@ -174,9 +156,56 @@ function PeriodForNote(ch, note) {
|
||||||
return 1920 - note*16 - ch.inst.fine / 8.0;
|
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 + '<br>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_songpos = -1, cur_pat = -1, cur_row = 64, cur_ticksamp = 0;
|
||||||
var cur_tick = 6;
|
var cur_tick = 6;
|
||||||
var patdisplay = [];
|
|
||||||
function next_row() {
|
function next_row() {
|
||||||
if (cur_pat == -1 || cur_row >= patterns[cur_pat].length) {
|
if (cur_pat == -1 || cur_row >= patterns[cur_pat].length) {
|
||||||
cur_row = 0;
|
cur_row = 0;
|
||||||
|
|
@ -553,6 +582,7 @@ function MixChannelIntoBuf(ch, start, end, dataL, dataR) {
|
||||||
|
|
||||||
function audio_cb(e) {
|
function audio_cb(e) {
|
||||||
f_smp = audioctx.sampleRate;
|
f_smp = audioctx.sampleRate;
|
||||||
|
var time_sound_started = undefined;
|
||||||
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);
|
||||||
|
|
@ -564,53 +594,33 @@ function audio_cb(e) {
|
||||||
|
|
||||||
var offset = 0;
|
var offset = 0;
|
||||||
var ticklen = 0|(f_smp * 2.5 / bpm);
|
var ticklen = 0|(f_smp * 2.5 / bpm);
|
||||||
var VU = new Float32Array(nchan);
|
|
||||||
|
|
||||||
while(buflen > 0) {
|
while(buflen > 0) {
|
||||||
if (cur_ticksamp >= ticklen) {
|
if (cur_pat == -1 || cur_ticksamp >= ticklen) {
|
||||||
next_tick(f_smp);
|
next_tick(f_smp);
|
||||||
cur_ticksamp -= ticklen;
|
cur_ticksamp -= ticklen;
|
||||||
}
|
}
|
||||||
var tickduration = Math.min(buflen, ticklen - cur_ticksamp);
|
var tickduration = Math.min(buflen, ticklen - cur_ticksamp);
|
||||||
|
var VU = new Float32Array(nchan);
|
||||||
for (var j = 0; j < nchan; j++) {
|
for (var j = 0; j < nchan; j++) {
|
||||||
VU[j] += MixChannelIntoBuf(
|
VU[j] = MixChannelIntoBuf(
|
||||||
channelinfo[j], offset, offset + tickduration, dataL, dataR);
|
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;
|
offset += tickduration;
|
||||||
cur_ticksamp += tickduration;
|
cur_ticksamp += tickduration;
|
||||||
buflen -= 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 + '<br>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
|
function eff_t0_0(ch, data) { // arpeggio
|
||||||
|
|
@ -1106,7 +1116,7 @@ function playXM(arrayBuf) {
|
||||||
audioctx = new audioContext();
|
audioctx = new audioContext();
|
||||||
gainNode = audioctx.createGain();
|
gainNode = audioctx.createGain();
|
||||||
gainNode.gain.value = 0.1; // master volume
|
gainNode.gain.value = 0.1; // master volume
|
||||||
jsNode = audioctx.createScriptProcessor(4096, 0, 2);
|
jsNode = audioctx.createScriptProcessor(16384, 0, 2);
|
||||||
jsNode.onaudioprocess = audio_cb;
|
jsNode.onaudioprocess = audio_cb;
|
||||||
jsNode.connect(gainNode);
|
jsNode.connect(gainNode);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue