pleroma-fe/instance/pleroma-mods/pleroma-mod-tracker/mod.js

259 lines
8.5 KiB
JavaScript

PleromaModPlayer = function (attachment) {
this.file = attachment.querySelector("a").href;
this.attachment = attachment.cloneNode(true);
this.attachment.setAttribute("class", "mod-player");
attachment.parentNode.replaceChild(this.attachment, attachment);
this.initComponents();
this.module = null;
this.replay = null;
this.patternDisplay = null;
this.samplePosition = 0;
this.channel = 0;
this.patternDisplay = new PatternDisplay(PleromaModTracker.chars.sheet, PleromaModTracker.chars.x, PleromaModTracker.chars.y);
this.patternCanvas.width = this.patternDisplay.getMaxWidth(PleromaModTracker.chars.x);
this.patternCanvas.height = this.patternDisplay.getMaxHeight();
this.loadFile();
};
[
function initComponents () {
if (!window.CompatAudioContext) {
const error = document.createElement("div");
error.setAttribute("class", "alert error");
error.innerText = "can't use webaudio. check if webaudio is enabled.";
this.attachment.appendChild(error);
return;
}
this.songName = this.attachment.querySelector("a");
this.songName.setAttribute("class", "mod-title");
const controls = document.createElement("div");
controls.setAttribute("class", "mod-controls");
this.playButton = document.createElement("button");
this.playButton.onclick = () => { this.play() };
this.playButton.innerText = " ▶ ";
this.playButton.disabled = !window.CompatAudioContext;
controls.appendChild(this.playButton);
this.stopButton = document.createElement("button");
this.stopButton.onclick = () => { this.stop() };
this.stopButton.innerText = " ■ ";
controls.appendChild(this.stopButton);
this.seekBar = document.createElement("input");
this.seekBar.setAttribute("class", "mod-seekbar");
this.seekBar.setAttribute("type", "range");
this.seekBar.onmousedown = () => { this.stop() };
this.seekBar.onmouseup = () => { this.play() };
controls.appendChild(this.seekBar);
this.attachment.appendChild(controls);
this.patternCanvas = document.createElement("canvas");
this.patternCanvas.setAttribute("class", "mod-patterns");
this.attachment.appendChild(this.patternCanvas);
this.instruments = document.createElement("div");
this.instruments.setAttribute("class", "mod-instruments");
this.attachment.appendChild(this.instruments);
},
function play () {
this.stop();
PleromaModTracker.currentDisplay = this;
PleromaModTracker.player.setAudioSource(this.replay);
this.samplePosition = this.replay.seek(this.seekBar.value * PleromaModTracker.player.getSamplingRate());
PleromaModTracker.setReverb(50);
PleromaModTracker.player.play();
},
function stop () {
PleromaModTracker.player.stop();
PleromaModTracker.currentDisplay = null;
},
function loadFile () {
window.fetch(this.file).then((response) => {
if (response.ok) {
response.arrayBuffer().then((buffer) => {
this.setModule(new Int8Array(buffer));
}).catch((error) => {
console.error("can't read module data");
console.error(error);
});
}
}).catch((error) => {
console.warn("can't load module");
console.warn(error);
});
},
function setModule (moduleData) {
this.module = new IBXMModule(moduleData);
const rate = PleromaModTracker.player.getSamplingRate();
this.replay = new IBXMReplay(this.module, rate);
const duration = Math.round(this.replay.calculateSongDuration() / rate);
this.samplePosition = 0;
this.seekBar.value = 0;
this.seekBar.max = duration;
this.songName.innerText = this.module.songName;
let instruments = "";
for (let idx = 1; idx < this.module.instruments.length; idx++) {
const name = this.module.instruments[idx].name;
if (name.trim().length > 0) {
const instrument = (idx < 16 ? "&nbsp;" : "") + idx.toString(16);
instruments = instruments + instrument + " " + name.replace(/\s/g, "&nbsp;") + "<br/>";
}
}
this.instruments.innerHTML = instruments;
this.channel = 0;
this.clearDisplay();
this.updateDisplay();
},
function updateDisplay (count) {
if (count) {
this.samplePosition += count;
}
this.seekBar.value = this.samplePosition / PleromaModTracker.player.getSamplingRate();
this.patternDisplay.display(this.module, this.replay, this.channel, this.patternCanvas);
},
function clearDisplay () {
const context = this.patternCanvas.getContext("2d");
context.fillStyle = "black";
context.fillRect(0, 0, this.patternCanvas.width, this.patternCanvas.height);
},
].forEach((fn) => { PleromaModPlayer.prototype[fn.name] = fn; });
PleromaModTracker = function () {
this.config = {
stylesheet: "style.css",
pattern: "\\.(xm|mod|s3m)$"
};
this.toUpdate = [];
this.ready = false;
};
[
function onMutation (mutation, observer) {
for (const addedNode of mutation.addedNodes) {
this.handlePost(addedNode);
}
},
function onReady () {
const posts = document.querySelectorAll(".status");
for (const post of posts) {
this.handlePost(post);
}
},
function handlePost (postElement) {
if (
postElement.querySelectorAll &&
postElement.querySelectorAll(".attachments").length > 0
) {
const attachments = this.getModuleAttachments(postElement);
for (const attachment of attachments) {
if (this.ready) {
new PleromaModPlayer(attachment);
} else {
this.toUpdate.push(attachment);
}
}
}
},
function getModuleAttachments (postElement) {
const result = [];
const nonGalleryElements = postElement.querySelectorAll(".non-gallery");
const regex = new RegExp(this.config.pattern, "i");
for (const nonGalleryElement of nonGalleryElements) {
if (
regex.test(nonGalleryElement.querySelector("a").href)
) {
result.push(nonGalleryElement);
}
}
return result;
},
function catchUp () {
for (const attachment of this.toUpdate) {
new PleromaModPlayer(attachment);
}
this.toUpdate = [];
},
function run () {
PleromaModLoader.includeModCss("pleroma-mod-tracker/" + this.config.stylesheet);
PleromaModTracker.chars = {
x: 8,
y: 16
};
if (!window.CompatAudioContext) {
this.ready = true;
this.catchUp();
return;
}
Promise.all([
PleromaModLoader.includeModScript("pleroma-mod-tracker/audio-player.js"),
PleromaModLoader.includeModScript("pleroma-mod-tracker/ibxm.js"),
PleromaModLoader.includeModScript("pleroma-mod-tracker/pattern-display.js"),
]).then(() => {
const imageCharset = new Image();
imageCharset.src = PleromaModLoader.getModDir() + "pleroma-mod-tracker/topaz8.png";
imageCharset.addEventListener("load", () => {
initCharset(imageCharset, PleromaModTracker.chars.x, PleromaModTracker.chars.y, (chs) => {
PleromaModTracker.chars.sheet = chs;
PleromaModTracker.player = new AudioPlayer(
PleromaModTracker.onPlayerUpdate,
PleromaModTracker.onEffect
);
this.ready = true;
this.catchUp();
});
});
});
},
].forEach((fn) => { PleromaModTracker.prototype[fn.name] = fn; });
window.CompatAudioContext = window.AudioContext || window.webkitAudioContext;
PleromaModTracker.currentDisplay = null;
[
function onPlayerUpdate (count) {
if (PleromaModTracker.currentDisplay) {
PleromaModTracker.currentDisplay.updateDisplay(count);
}
},
function setReverb (milliSeconds) {
const length = Math.round(PleromaModTracker.player.getSamplingRate() * milliSeconds / 1000);
PleromaModTracker.reverb = {
idx: 0,
length: length,
bufferLeft: new Float32Array(length),
bufferRight: new Float32Array(length)
};
},
function onEffect (mixBufferLeft, mixBufferRight, count) {
if (PleromaModTracker.reverb.length) {
const reverb = PleromaModTracker.reverb;
for (let mixIdx = 0; mixIdx < count; mixIdx++) {
mixBufferLeft[mixIdx] = (mixBufferLeft[mixIdx] * 3 + reverb.bufferRight[reverb.idx]) * 0.25;
mixBufferRight[mixIdx] = (mixBufferRight[mixIdx] * 3 + reverb.bufferLeft[reverb.idx]) * 0.25;
reverb.bufferLeft[reverb.idx] = mixBufferLeft[mixIdx];
reverb.bufferRight[reverb.idx] = mixBufferRight[mixIdx];
reverb.idx++;
if (reverb.idx >= reverb.length) {
reverb.idx = 0;
}
}
}
}
].forEach((fn) => { PleromaModTracker[fn.name] = fn; });
PleromaModLoader.registerMod(PleromaModTracker);