Separate html "shell" javascript; player now standalone

Closes #3.

The code which defines what the buttons do and downloads songs and so
forth are now completely separated from the player, which has the
following API:

 - `XMPlayer.initAudio` -> starts up audio context; it's available as
   `XMPlayer.audioctx`
 - `XMPlayer.loadXM(ArrayBuffer)` -> returns `true` if loaded, otherwise
   barfs randomly
 - `XMPlayer.playXM()` -> starts playing
 - `XMPlayer.pauseXM()` -> obvious
 - `XMPlayer.stopXM()` -> obvious; call this before loading a new one

Loading trackview.js is now optional; without it, the player won't do
any visualizations. Or, you can override the following to get callbacks:

 - `XMView.pushEvent(e)` -> push an audio event onto the queue. Called
   once per tick (about 50Hz, controlled by song). `e` contains fields:
   - `t` - audio timestamp
   - `vu` - Float32Array of RMS power (volume) for each channel
   - `scopes` - [Float32Array] of oscilloscope data, one array per
     channel; `XMView.scope_width` contains # of samples to produce here
   - `songpos` - position in the song (# patterns played)
   - `pat` - pattern number currently playing
   - `row` - row within pattern
 - `XMView.pause()` - pause visualization
 - `XMView.stop()` - stop/reset visualization
This commit is contained in:
Andy Sloane 2015-11-11 21:09:58 -08:00
commit 8c37c8d96b
4 changed files with 164 additions and 144 deletions

View file

@ -27,8 +27,8 @@ fontimg.src = "ft2font.png";
var _fontmap_notes = [8*5, 8*22, 8*28];
var pat_canvas_patnum;
var audio_events = [];
var paused_events = [];
var audio_events;
var paused_events;
var shown_row;
// canvas to render patterns onto
@ -156,19 +156,22 @@ function RenderPattern(canv, pattern) {
}
function redrawScreen() {
if (audio_events.length === 0) return;
var e;
var t = player.audioctx.currentTime;
do {
while (audio_events.length > 0 && audio_events[0].t < t) {
e = audio_events.shift();
} while(e.t < t && audio_events.length > 0);
if (!e) return;
}
if (!e) {
if (player.playing) {
window.requestAnimationFrame(redrawScreen);
}
return;
}
var VU = e.vu;
var scopes = e.scopes;
var ctx;
if (e.scopes) {
if (e.scopes !== undefined) {
// update VU meters & oscilliscopes
var canvas = document.getElementById("vu");
ctx = canvas.getContext("2d");
@ -217,9 +220,8 @@ function redrawScreen() {
shown_row = e.row;
}
if (audio_events.length > 0) {
var next_event = audio_events[0].t;
setTimeout(redrawScreen, 1000*(next_event - player.audioctx.currentTime));
if (player.playing) {
window.requestAnimationFrame(redrawScreen);
}
}
@ -291,6 +293,8 @@ function init() {
shown_row = undefined;
pat_canvas_patnum = undefined;
audio_events = [];
paused_events = [];
audio_events.push({
t: 0, row: 0, pat: player.xm.songpats[0],
vu: new Float32Array(player.xm.nchan),