soundsmith/fta.js

532 lines
20 KiB
JavaScript

var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [0, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var _this = this;
var Song = (function () {
function Song() {
}
return Song;
}());
var FTA = (function () {
function FTA() {
this.player = null;
}
FTA.prototype.getSongList = function (path) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2, new Promise(function (resolve) {
var req = new XMLHttpRequest();
req.open('GET', path, true);
req.onload = function () {
resolve(JSON.parse(req.responseText));
};
req.send(null);
})];
});
});
};
FTA.prototype.open = function (name, music, wb, inst, delta) {
return __awaiter(this, void 0, void 0, function () {
var _this = this;
var loaded, song, _a, wavebank, _b, instdef, _c, controls, stop_1, play;
return __generator(this, function (_d) {
switch (_d.label) {
case 0:
this.name = name;
loaded = document.getElementById('loaded');
if (loaded) {
loaded.textContent = 'loading...';
}
_a = Handle.bind;
return [4, this.load(music)];
case 1:
song = new (_a.apply(Handle, [void 0, _d.sent()]))();
_b = Handle.bind;
return [4, this.load(wb)];
case 2:
wavebank = new (_b.apply(Handle, [void 0, _d.sent()]))();
_c = Handle.bind;
return [4, this.load(inst)];
case 3:
instdef = new (_c.apply(Handle, [void 0, _d.sent()]))();
if (this.player) {
this.player.stop();
}
this.player = new FTAPlayer(song, wavebank, instdef, delta);
if (loaded) {
loaded.textContent = name;
}
controls = document.getElementById('controls');
if (controls) {
while (controls.firstChild) {
controls.removeChild(controls.firstChild);
}
stop_1 = document.createElement('button');
stop_1.textContent = '\u23f9';
stop_1.addEventListener('click', function () {
if (_this.player) {
_this.player.stop();
}
});
controls.appendChild(stop_1);
play = document.createElement('button');
play.textContent = '\u25b6';
play.addEventListener('click', function () {
if (_this.player) {
_this.player.play();
}
});
controls.appendChild(play);
}
return [2];
}
});
});
};
FTA.prototype.load = function (file) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2, new Promise(function (resolve) {
var req = new XMLHttpRequest();
req.open('GET', file, true);
req.responseType = 'arraybuffer';
req.onload = function () {
if (req.response) {
resolve(new Uint8Array(req.response));
}
};
req.send(null);
})];
});
});
};
return FTA;
}());
document.addEventListener('DOMContentLoaded', function () { return __awaiter(_this, void 0, void 0, function () {
var _this = this;
var fta, songs, list, _i, songs_1, song, row;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
fta = new FTA();
return [4, fta.getSongList('ftasongs.json')];
case 1:
songs = _a.sent();
list = document.getElementById('songlist');
if (!list) {
return [2];
}
for (_i = 0, songs_1 = songs; _i < songs_1.length; _i++) {
song = songs_1[_i];
row = document.createElement('div');
row.dataset.name = song.name;
row.dataset.music = song.music;
row.dataset.wb = song.wb;
row.dataset.inst = song.inst;
row.dataset.delta = song.delta.toString(10);
row.appendChild(document.createTextNode(song.name));
row.addEventListener('click', function (event) { return __awaiter(_this, void 0, void 0, function () {
var target;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
target = event.target;
return [4, fta.open(target.dataset.name, target.dataset.music, target.dataset.wb, target.dataset.inst, parseInt(target.dataset.delta, 10))];
case 1:
_a.sent();
return [2];
}
});
}); });
list.appendChild(row);
}
return [2];
}
});
}); });
var Channel = (function () {
function Channel() {
this.ticks = 1;
this.offset = 0;
this.pos = 0;
this.notes = [];
this.osc = 0;
this.pointer = 0;
this.size = 0;
this.volume = 0;
this.panning = 0;
this.control = 0;
this.freq = 0;
}
return Channel;
}());
var FTAPlayer = (function () {
function FTAPlayer(music, wavebank, inst, delta) {
var _this = this;
this.stereo = true;
this.ticksLeft = 0;
this.channels = [];
this.active = [];
this.es5503 = new ES5503(function (osc) { _this.irq(osc); });
wavebank.seek(0);
this.es5503.setRam(wavebank.read(wavebank.length));
this.es5503.setEnabled(0x3e);
for (var i = 0; i < 32; i++) {
this.channels.push(new Channel());
}
inst.seek(0);
var base = inst.r16();
while (base != 0xffff) {
var ch = inst.r8();
this.active.push(ch);
this.channels[ch].osc = ch;
this.channels[ch].pointer = inst.r8();
this.channels[ch].size = inst.r8();
this.channels[ch].volume = inst.r8();
this.channels[ch].panning = inst.r8();
this.channels[ch].control = inst.r8();
music.seek(base - delta);
var channelLen = music.r16();
for (var i = 0; i < channelLen; i++) {
var freq = music.r16();
var time = music.r8();
this.channels[ch].notes.push({ freq: freq, time: time });
}
base = inst.r16();
}
inst.seek(0x46);
this.es5503.setFrequency(31, inst.r16());
this.es5503.setControl(31, 8);
}
FTAPlayer.prototype.play = function () {
var _this = this;
try {
this.ctx = new (window.AudioContext || window.webkitAudioContext)();
if (this.ctx == undefined) {
throw new Error("No audio support!");
}
}
catch (e) {
alert(e.message);
return;
}
this.audioNode = this.ctx.createScriptProcessor(0, 0, 2);
this.audioNode.onaudioprocess = function (evt) {
_this.render(evt);
};
this.audioNode.connect(this.ctx.destination);
};
FTAPlayer.prototype.stop = function () {
if (this.audioNode) {
this.audioNode.disconnect();
}
this.audioNode = undefined;
if (this.ctx) {
this.ctx.close();
}
this.ctx = undefined;
};
FTAPlayer.prototype.render = function (evt) {
var sampleRate = evt.outputBuffer.sampleRate;
var leftBuf = evt.outputBuffer.getChannelData(0);
var rightBuf = evt.outputBuffer.getChannelData(1);
for (var i = 0; i < evt.outputBuffer.length; i++) {
this.ticksLeft -= 26320;
if (this.ticksLeft <= 0) {
this.ticksLeft += sampleRate;
this.es5503.tick();
}
var _a = this.es5503.render(), left = _a[0], right = _a[1];
if (!this.stereo) {
leftBuf[i] = (left + right) * 0.707;
rightBuf[i] = leftBuf[i];
}
else {
leftBuf[i] = left;
rightBuf[i] = right;
}
}
};
FTAPlayer.prototype.irq = function (osc) {
if (osc != 31) {
var ch = this.channels[osc & 0xfc];
this.noteOff(ch);
this.noteOn(ch);
}
else {
for (var _i = 0, _a = this.active; _i < _a.length; _i++) {
var c = _a[_i];
var ch = this.channels[c];
ch.ticks--;
if (ch.ticks == 0) {
this.noteOff(ch);
ch.ticks = ch.notes[ch.pos].time;
ch.freq = ch.notes[ch.pos].freq;
ch.pos++;
if (ch.pos >= ch.notes.length) {
ch.pos = 0;
}
this.noteOn(ch);
ch.osc ^= 2;
}
}
}
};
FTAPlayer.prototype.noteOn = function (ch) {
this.es5503.setFrequency(ch.osc, ch.freq * 2);
this.es5503.setFrequency(ch.osc + 1, ch.freq * 2);
var vol = ch.volume & 0xf;
this.es5503.setVolume(ch.osc, vol << 3);
this.es5503.setVolume(ch.osc + 1, vol << 3);
this.es5503.setVolume(ch.osc ^ 2, vol << 3);
this.es5503.setVolume((ch.osc ^ 2) + 1, vol << 3);
this.es5503.setPointer(ch.osc, ch.pointer);
this.es5503.setPointer(ch.osc + 1, ch.pointer);
this.es5503.setSize(ch.osc, ch.size);
this.es5503.setSize(ch.osc + 1, ch.size);
var a = 2;
var b = 0x12;
if (ch.panning == 0) {
b = 2;
}
else if (ch.panning == 1) {
a = 0x12;
}
this.es5503.setControl(ch.osc, a | ch.control);
this.es5503.setControl(ch.osc + 1, b | ch.control);
};
FTAPlayer.prototype.noteOff = function (ch) {
this.es5503.setControl(ch.osc, 7);
this.es5503.setControl(ch.osc + 1, 7);
};
return FTAPlayer;
}());
var Mode;
(function (Mode) {
Mode[Mode["freeRun"] = 0] = "freeRun";
Mode[Mode["oneShot"] = 1] = "oneShot";
Mode[Mode["sync"] = 2] = "sync";
Mode[Mode["swap"] = 3] = "swap";
})(Mode || (Mode = {}));
var Oscillator = (function () {
function Oscillator() {
this.pointer = 0;
this.frequency = 0;
this.size = 0;
this.control = 1;
this.volume = 0;
this.data = 0;
this.resolution = 0;
this.accumulator = 0;
this.ptr = 0;
this.shift = 9;
this.max = 0xff;
}
return Oscillator;
}());
var ES5503 = (function () {
function ES5503(irq) {
this.waveTable = new Float32Array(0x10000);
this.oscillators = [];
this.enabled = 0;
this.waveSizes = [
0x100, 0x200, 0x400, 0x800, 0x1000, 0x2000, 0x4000, 0x8000,
];
this.waveMasks = [
0x1ff00, 0x1fe00, 0x1fc00, 0x1f800, 0x1f000, 0x1e000, 0x1c000, 0x18000,
];
this.irq = irq;
for (var i = 0; i < 32; i++) {
this.oscillators.push(new Oscillator());
}
}
ES5503.prototype.setEnabled = function (enabled) {
this.enabled = enabled >> 1;
};
ES5503.prototype.setRam = function (bank) {
for (var i = 0; i < bank.length; i++) {
this.waveTable[i] = (bank[i] - 128) / 128;
}
};
ES5503.prototype.setFrequency = function (osc, freq) {
this.oscillators[osc].frequency = freq;
};
ES5503.prototype.setVolume = function (osc, vol) {
this.oscillators[osc].volume = vol / 127;
};
ES5503.prototype.setPointer = function (osc, ptr) {
this.oscillators[osc].pointer = ptr << 8;
this.recalc(osc);
};
ES5503.prototype.setSize = function (osc, size) {
this.oscillators[osc].size = (size >> 3) & 7;
this.oscillators[osc].resolution = size & 7;
this.recalc(osc);
};
ES5503.prototype.setControl = function (osc, ctl) {
var prev = this.oscillators[osc].control & 1;
this.oscillators[osc].control = ctl;
var mode = (ctl >> 1) & 3;
if (!(ctl & 1) && prev) {
if (mode == Mode.sync) {
this.oscillators[osc ^ 1].control &= ~1;
this.oscillators[osc ^ 1].accumulator = 0;
}
this.oscillators[osc].accumulator = 0;
}
};
ES5503.prototype.stop = function (osc) {
this.oscillators[osc].control &= 0xf7;
this.oscillators[osc].control |= 1;
this.oscillators[osc].accumulator = 0;
};
ES5503.prototype.go = function (osc) {
this.oscillators[osc].control &= ~1;
};
ES5503.prototype.tick = function () {
for (var osc = 0; osc <= this.enabled; osc++) {
var cur = this.oscillators[osc];
if (!(cur.control & 1)) {
var base = cur.accumulator >> cur.shift;
var ofs = (base & cur.max) + cur.ptr;
cur.data = this.waveTable[ofs] * cur.volume;
cur.accumulator += cur.frequency;
if (this.waveTable[ofs] == -1) {
this.halted(osc, true);
}
else if (base >= cur.max) {
this.halted(osc, false);
}
}
}
};
ES5503.prototype.render = function () {
var left = 0;
var right = 0;
for (var osc = 0; osc <= this.enabled; osc++) {
var cur = this.oscillators[osc];
if (!(cur.control & 1)) {
if (cur.control & 0x10) {
right += cur.data;
}
else {
left += cur.data;
}
}
}
var spread = (this.enabled - 2) / 4;
return [left / spread, right / spread];
};
ES5503.prototype.recalc = function (osc) {
var cur = this.oscillators[osc];
cur.shift = (cur.resolution + 9) - cur.size;
cur.ptr = cur.pointer & this.waveMasks[cur.size];
cur.max = this.waveSizes[cur.size] - 1;
};
ES5503.prototype.halted = function (osc, interrupted) {
var cur = this.oscillators[osc];
var mode = (cur.control >> 1) & 3;
if (interrupted || mode != Mode.freeRun) {
cur.control |= 1;
}
else {
var base = (cur.accumulator >> cur.shift) - cur.max;
cur.accumulator = Math.max(base, 0) << cur.shift;
}
if (mode == Mode.swap) {
var swap = this.oscillators[osc ^ 1];
swap.control &= ~1;
swap.accumulator = 0;
}
if (cur.control & 8) {
this.irq(osc);
}
};
return ES5503;
}());
var Handle = (function () {
function Handle(data) {
this.pos = 0;
this.data = data;
this.length = data.length;
}
Handle.prototype.eof = function () {
return this.pos >= this.length;
};
Handle.prototype.r8 = function () {
return this.data[this.pos++];
};
Handle.prototype.r16 = function () {
var v = this.data[this.pos++];
v |= this.data[this.pos++] << 8;
return v;
};
Handle.prototype.r24 = function () {
var v = this.data[this.pos++];
v |= this.data[this.pos++] << 8;
v |= this.data[this.pos++] << 16;
return v;
};
Handle.prototype.r32 = function () {
var v = this.data[this.pos++];
v |= this.data[this.pos++] << 8;
v |= this.data[this.pos++] << 16;
v |= this.data[this.pos++] << 24;
return v >>> 0;
};
Handle.prototype.r4 = function () {
var r = '';
for (var i = 0; i < 4; i++) {
r += String.fromCharCode(this.data[this.pos++]);
}
return r;
};
Handle.prototype.seek = function (pos) {
this.pos = pos;
};
Handle.prototype.skip = function (len) {
this.pos += len;
};
Handle.prototype.tell = function () {
return this.pos;
};
Handle.prototype.read = function (len) {
var oldpos = this.pos;
this.pos += len;
return this.data.subarray(oldpos, this.pos);
};
return Handle;
}());
//# sourceMappingURL=fta.js.map