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

326 lines
11 KiB
JavaScript

PleromaModPlayer = function (attachment) {
this.file = "";
if (attachment.querySelector("a")) {
this.file = attachment.querySelector("a").href;
} else if (attachment.querySelector("audio")) {
this.file = attachment.querySelector("audio").src;
}
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.duration = 0;
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");
if (!this.songName) {
const audio = this.attachment.querySelector("audio");
this.songName = document.createElement("a");
this.songName.href = this.file;
audio.style.display = "none";
}
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.classList.add("btn");
this.playButton.classList.add("button-default");
this.playButton.disabled = !window.CompatAudioContext;
controls.appendChild(this.playButton);
this.stopButton = document.createElement("button");
this.stopButton.onclick = () => { this.stop() };
this.stopButton.innerText = " ■ ";
this.stopButton.classList.add("btn");
this.stopButton.classList.add("button-default");
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() };
this.seekBar.onkeydown = () => { this.stop() };
this.seekBar.onkeyup = () => { this.play() };
controls.appendChild(this.seekBar);
this.volumeBar = document.createElement("input");
this.volumeBar.setAttribute("class", "mod-volumebar hidden");
this.volumeBar.setAttribute("type", "range");
this.volumeBar.value = localStorage.getItem('volume') === null ? 25 : localStorage.getItem('volume') * 100;
this.volumeBar.max = 100;
this.volumeBar.onmouseup = () => { localStorage.setItem('volume', this.volumeBar.value / 100) };
this.volumeBar.onkeyup = () => { localStorage.setItem('volume', this.volumeBar.value / 100) };
this.volumeBar.onmousemove = () => { PleromaModTracker.player.setVolume(this.volumeBar.value / 100) };
this.volumeButton = document.createElement("button");
this.volumeButton.classList.add("btn");
this.volumeButton.classList.add("button-default");
this.volumeButton.onclick = () => {
if (this.volumeBar.classList.contains("hidden")) {
this.volumeBar.classList.remove("hidden");
} else {
this.volumeBar.classList.add("hidden");
}
};
this.volumeButton.innerText = "🕩";
controls.appendChild(this.volumeButton);
this.attachment.appendChild(controls);
this.attachment.appendChild(this.volumeBar);
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 collapsed");
this.attachment.appendChild(this.instruments);
const linkExpand = document.createElement("a");
linkExpand.href = "#";
linkExpand.classList.add("cw-status-hider");
linkExpand.innerText = "show more";
linkExpand.onclick = (event) => {
if (this.instruments.classList.contains("collapsed")) {
this.instruments.classList.remove("collapsed");
linkExpand.innerText = "show less";
} else {
this.instruments.classList.add("collapsed");
linkExpand.innerText = "show more";
}
event.preventDefault();
};
this.attachment.appendChild(linkExpand);
},
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);
this.duration = Math.round(this.replay.calculateSongDuration() / rate);
this.samplePosition = 0;
this.seekBar.value = 0;
this.seekBar.max = this.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.duration;
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(".StatusContent");
for (const post of posts) {
this.handlePost(post);
}
},
function handlePost (postElement) {
if (!postElement.querySelector) {
console.warn("element?", postElement);
return;
}
const vueScope = PleromaModLoader.getVueScope(postElement.querySelector(".StatusContent"));
if (!vueScope || !vueScope._props.status) {
console.warn("vueScope?", vueScope);
return;
}
const attachments = vueScope.$children;
for (const attachment of attachments) {
if (attachment.$vnode.componentOptions.tag === "attachment") {
if (attachment._props.attachment.mimetype === "audio/x-mod") {
if (this.ready) {
new PleromaModPlayer(attachment.$el);
} else {
this.toUpdate.push(attachment.$el);
}
}
}
}
},
function catchUp () {
for (const attachment of this.toUpdate) {
new PleromaModPlayer(attachment);
}
this.toUpdate = [];
},
function onDestroy () {
const scripts = document.querySelectorAll("script");
for (const script of scripts) {
if (script.src && script.src.includes("pleroma-mod-tracker") && !script.src.endsWith("mod.js")) {
script.remove();
}
}
const stylesheets = document.querySelectorAll("link[rel=\"stylesheet\"]");
for (const style of stylesheets) {
if (style.href.endsWith("pleroma-mod-tracker/" + this.config.stylesheet)) {
style.remove();
}
}
},
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);