soundsmith/smith.js

667 lines
26 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 SoundSmith = (function () {
function SoundSmith() {
this.player = null;
}
SoundSmith.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);
})];
});
});
};
SoundSmith.prototype.open = function (name, music, wb) {
return __awaiter(this, void 0, void 0, function () {
var _this = this;
var loaded, info, song, _a, wavebank, _b, controls, stop_1, play;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
this.name = name;
loaded = document.getElementById('loaded');
if (loaded) {
loaded.textContent = 'loading...';
}
info = document.getElementById('info');
_a = Handle.bind;
return [4, this.load(music)];
case 1:
song = new (_a.apply(Handle, [void 0, _c.sent()]))();
_b = Handle.bind;
return [4, this.load(wb)];
case 2:
wavebank = new (_b.apply(Handle, [void 0, _c.sent()]))();
if (this.player) {
this.player.stop();
}
this.player = new Player(song, wavebank, function (cur, max) {
if (info) {
if (max == 0) {
info.textContent = 'none';
}
else {
info.textContent = cur.toString(10) + ' / ' + max.toString(10);
}
}
});
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];
}
});
});
};
SoundSmith.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 SoundSmith;
}());
document.addEventListener('DOMContentLoaded', function () { return __awaiter(_this, void 0, void 0, function () {
var _this = this;
var ss, songs, list, _i, songs_1, song, row;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
ss = new SoundSmith();
return [4, ss.getSongList('songs.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.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, ss.open(target.dataset.name, target.dataset.music, target.dataset.wb)];
case 1:
_a.sent();
return [2];
}
});
}); });
list.appendChild(row);
}
return [2];
}
});
}); });
var Player = (function () {
function Player(music, wavebank, notice) {
var _this = this;
this.stereo = true;
this.timer = 0;
this.tempo = 0;
this.curRow = 0;
this.curPat = 0;
this.orders = [];
this.volTable = [];
this.rowOffset = 0;
this.numInst = 0;
this.ticksLeft = 0;
this.instruments = [];
this.compactTable = new Uint16Array(16);
this.stereoTable = new Uint16Array(16);
this.curInst = new Uint8Array(16);
this.arpeggio = new Uint8Array(16);
this.tone = new Uint8Array(16);
this.frequencies = [
0x0000, 0x0016, 0x0017, 0x0018, 0x001a, 0x001b, 0x001d, 0x001e,
0x0020, 0x0022, 0x0024, 0x0026, 0x0029, 0x002b, 0x002e, 0x0031,
0x0033, 0x0036, 0x003a, 0x003d, 0x0041, 0x0045, 0x0049, 0x004d,
0x0052, 0x0056, 0x005c, 0x0061, 0x0067, 0x006d, 0x0073, 0x007a,
0x0081, 0x0089, 0x0091, 0x009a, 0x00a3, 0x00ad, 0x00b7, 0x00c2,
0x00ce, 0x00d9, 0x00e6, 0x00f4, 0x0102, 0x0112, 0x0122, 0x0133,
0x0146, 0x015a, 0x016f, 0x0184, 0x019b, 0x01b4, 0x01ce, 0x01e9,
0x0206, 0x0225, 0x0246, 0x0269, 0x028d, 0x02b4, 0x02dd, 0x0309,
0x0337, 0x0368, 0x039c, 0x03d3, 0x040d, 0x044a, 0x048c, 0x04d1,
0x051a, 0x0568, 0x05ba, 0x0611, 0x066e, 0x06d0, 0x0737, 0x07a5,
0x081a, 0x0895, 0x0918, 0x09a2, 0x0a35, 0x0ad0, 0x0b75, 0x0c23,
0x0cdc, 0x0d9f, 0x0e6f, 0x0f4b, 0x1033, 0x112a, 0x122f, 0x1344,
0x1469, 0x15a0, 0x16e9, 0x1846, 0x19b7, 0x1b3f, 0x1cde, 0x1e95,
0x2066, 0x2254, 0x245e, 0x2688,
];
this.notice = notice;
this.es5503 = new ES5503(function (osc) { _this.irq(osc); });
this.loadWavebank(wavebank);
music.seek(6);
var blockLen = music.r16();
this.tempo = music.r16();
this.es5503.setFrequency(30, 0xfa);
this.es5503.setVolume(30, 0);
this.es5503.setPointer(30, 0);
this.es5503.setSize(30, 0);
this.es5503.setEnabled(0x3c);
this.es5503.setControl(30, 8);
music.seek(0x2c);
for (var i = 0; i < 15; i++) {
this.volTable.push(music.r16());
music.skip(0x1c);
}
music.seek(0x1d6);
var songLen = music.r16() & 0xff;
for (var i = 0; i < songLen; i++) {
this.orders.push(music.r8() * 64 * 14);
}
music.seek(0x258);
this.notes = music.read(blockLen);
this.effects1 = music.read(blockLen);
this.effects2 = music.read(blockLen);
for (var i = 0; i < 16; i++) {
this.stereoTable[i] = music.r16();
}
this.rowOffset = this.orders[this.curPat];
this.notice(this.curPat + 1, this.orders.length);
}
Player.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);
};
Player.prototype.stop = function () {
if (this.audioNode) {
this.audioNode.disconnect();
}
this.audioNode = undefined;
if (this.ctx) {
this.ctx.close();
}
this.ctx = undefined;
};
Player.prototype.loadWavebank = function (wavebank) {
wavebank.seek(0);
if (wavebank.r4() == 'GSWV') {
var ofs = wavebank.r16();
this.numInst = wavebank.r8();
wavebank.skip(this.numInst * 10);
for (var i = 0; i < this.numInst; i++) {
var instLen = (wavebank.r8() + wavebank.r8()) * 6;
this.instruments.push(wavebank.read(instLen));
}
wavebank.seek(ofs);
var tbl = new Uint8Array(0x10000);
tbl.set(wavebank.read(wavebank.length - ofs));
}
else {
wavebank.seek(0);
this.numInst = wavebank.r16() & 0xff;
this.es5503.setRam(wavebank.read(0x10000));
wavebank.seek(0x10022);
for (var i = 0; i < this.numInst; i++) {
this.instruments.push(wavebank.read(12));
wavebank.skip(0x50);
}
wavebank.skip(0x3c);
for (var i = 0; i < 16; i++) {
this.compactTable[i] = wavebank.r16();
}
}
};
Player.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;
}
}
};
Player.prototype.irq = function (osc) {
if (osc != 30) {
this.es5503.go(osc);
return;
}
this.timer++;
if (this.timer == this.tempo) {
this.timer = 0;
for (var oscillator = 0; oscillator < 14; oscillator++) {
var semitone = this.notes[this.rowOffset];
if (semitone == 0 || (semitone & 0x80)) {
this.rowOffset++;
if (semitone == 0x80) {
this.es5503.setControl(oscillator * 2, 1);
this.es5503.setControl(oscillator * 2 + 1, 1);
}
else if (semitone == 0x81) {
this.curRow = 0x3f;
}
}
else {
var fx = this.effects1[this.rowOffset];
if (fx & 0xf0) {
this.curInst[oscillator] = (fx >> 4) - 1;
}
var inst = this.curInst[oscillator];
var volume = this.volTable[inst] >> 1;
fx &= 0xf;
if (fx == 0) {
this.arpeggio[oscillator] = this.effects2[this.rowOffset];
this.tone[oscillator] = semitone;
}
else {
this.arpeggio[oscillator] = 0;
if (fx == 3) {
volume = this.effects2[this.rowOffset] >> 1;
this.es5503.setVolume(oscillator * 2, volume);
this.es5503.setVolume(oscillator * 2 + 1, volume);
}
else if (fx == 6) {
volume -= this.effects2[this.rowOffset] >> 1;
volume = Math.max(volume, 0);
this.es5503.setVolume(oscillator * 2, volume);
this.es5503.setVolume(oscillator * 2 + 1, volume);
}
else if (fx == 5) {
volume += this.effects2[this.rowOffset] >> 1;
volume = Math.min(volume, 0x7f);
this.es5503.setVolume(oscillator * 2, volume);
this.es5503.setVolume(oscillator * 2 + 1, volume);
}
else if (fx == 0xf) {
this.tempo = this.effects2[this.rowOffset];
}
}
var addr = oscillator * 2;
this.es5503.stop(addr);
this.es5503.stop(addr + 1);
if (inst < this.numInst) {
var x = 0;
while (this.instruments[inst][x] < semitone) {
x += 6;
}
var oscAptr = this.instruments[inst][x + 1];
var oscAsiz = this.instruments[inst][x + 2];
var oscActl = this.instruments[inst][x + 3] & 0xf;
if (this.stereoTable[oscillator]) {
oscActl |= 0x10;
}
while (this.instruments[inst][x] != 0x7f) {
x += 6;
}
x += 6;
while (this.instruments[inst][x] < semitone) {
x += 6;
}
var oscBptr = this.instruments[inst][x + 1];
var oscBsiz = this.instruments[inst][x + 2];
var oscBctl = this.instruments[inst][x + 3] & 0xf;
if (this.stereoTable[oscillator]) {
oscBctl |= 0x10;
}
var freq = this.frequencies[semitone] >>
this.compactTable[inst];
this.es5503.setFrequency(addr, freq);
this.es5503.setFrequency(addr + 1, freq);
this.es5503.setVolume(addr, volume);
this.es5503.setVolume(addr + 1, volume);
this.es5503.setPointer(addr, oscAptr);
this.es5503.setPointer(addr + 1, oscBptr);
this.es5503.setSize(addr, oscAsiz);
this.es5503.setSize(addr + 1, oscBsiz);
this.es5503.setControl(addr, oscActl);
this.es5503.setControl(addr + 1, oscBctl);
}
this.rowOffset++;
}
}
this.curRow++;
if (this.curRow < 0x40) {
return;
}
this.curRow = 0;
this.curPat++;
if (this.curPat < this.orders.length) {
this.notice(this.curPat + 1, this.orders.length);
this.rowOffset = this.orders[this.curPat];
return;
}
this.notice(0, 0);
this.stop();
return;
}
else {
for (var oscillator = 0; oscillator < 14; oscillator++) {
var a = this.arpeggio[oscillator];
if (a) {
switch (this.timer % 6) {
case 1:
case 4:
this.tone[oscillator] += a >> 4;
break;
case 2:
case 5:
this.tone[oscillator] += a & 0xf;
break;
case 0:
case 3:
this.tone[oscillator] -= a >> 4;
this.tone[oscillator] -= a & 0xf;
break;
}
var freq = this.frequencies[this.tone[oscillator]] >>
this.compactTable[oscillator];
var addr = oscillator * 2;
this.es5503.setFrequency(addr, freq);
this.es5503.setFrequency(addr + 1, freq);
}
}
}
};
return Player;
}());
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=smith.js.map