2020-03-09 22:13:53 +01:00
|
|
|
PleromaModPlayer = function (attachment) {
|
2020-11-16 05:36:54 +01:00
|
|
|
this.file = "";
|
|
|
|
if (attachment.querySelector("a")) {
|
|
|
|
this.file = attachment.querySelector("a").href;
|
|
|
|
} else if (attachment.querySelector("audio")) {
|
|
|
|
this.file = attachment.querySelector("audio").src;
|
|
|
|
}
|
2020-03-09 22:13:53 +01:00
|
|
|
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;
|
2020-04-28 02:22:32 +02:00
|
|
|
this.duration = 0;
|
2020-03-09 22:13:53 +01:00
|
|
|
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");
|
2020-11-16 05:36:54 +01:00
|
|
|
if (!this.songName) {
|
|
|
|
const audio = this.attachment.querySelector("audio");
|
|
|
|
this.songName = document.createElement("a");
|
|
|
|
this.songName.href = this.file;
|
2021-06-10 23:51:37 +02:00
|
|
|
audio.style.display = "none";
|
2020-11-16 05:36:54 +01:00
|
|
|
}
|
2020-03-09 22:13:53 +01:00
|
|
|
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 = " ▶ ";
|
2021-06-10 23:51:37 +02:00
|
|
|
this.playButton.classList.add("btn");
|
|
|
|
this.playButton.classList.add("button-default");
|
2020-03-09 22:13:53 +01:00
|
|
|
this.playButton.disabled = !window.CompatAudioContext;
|
|
|
|
controls.appendChild(this.playButton);
|
|
|
|
|
|
|
|
this.stopButton = document.createElement("button");
|
|
|
|
this.stopButton.onclick = () => { this.stop() };
|
|
|
|
this.stopButton.innerText = " ■ ";
|
2021-06-10 23:51:37 +02:00
|
|
|
this.stopButton.classList.add("btn");
|
|
|
|
this.stopButton.classList.add("button-default");
|
2020-03-09 22:13:53 +01:00
|
|
|
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() };
|
2020-04-28 02:22:32 +02:00
|
|
|
this.seekBar.onkeydown = () => { this.stop() };
|
|
|
|
this.seekBar.onkeyup = () => { this.play() };
|
2020-03-09 22:13:53 +01:00
|
|
|
controls.appendChild(this.seekBar);
|
2020-05-02 21:49:01 +02:00
|
|
|
|
2020-04-28 02:22:32 +02:00
|
|
|
this.volumeBar = document.createElement("input");
|
2020-05-02 21:49:01 +02:00
|
|
|
this.volumeBar.setAttribute("class", "mod-volumebar hidden");
|
2020-04-28 02:22:32 +02:00
|
|
|
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) };
|
2020-05-02 21:49:01 +02:00
|
|
|
|
|
|
|
this.volumeButton = document.createElement("button");
|
2021-06-10 23:51:37 +02:00
|
|
|
this.volumeButton.classList.add("btn");
|
|
|
|
this.volumeButton.classList.add("button-default");
|
2020-05-02 21:49:01 +02:00
|
|
|
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);
|
2020-03-09 22:13:53 +01:00
|
|
|
|
|
|
|
this.attachment.appendChild(controls);
|
|
|
|
|
2020-05-02 21:49:01 +02:00
|
|
|
this.attachment.appendChild(this.volumeBar);
|
|
|
|
|
2020-03-09 22:13:53 +01:00
|
|
|
this.patternCanvas = document.createElement("canvas");
|
|
|
|
this.patternCanvas.setAttribute("class", "mod-patterns");
|
|
|
|
this.attachment.appendChild(this.patternCanvas);
|
|
|
|
|
|
|
|
this.instruments = document.createElement("div");
|
2020-05-15 17:20:51 +02:00
|
|
|
this.instruments.setAttribute("class", "mod-instruments collapsed");
|
2020-03-09 22:13:53 +01:00
|
|
|
this.attachment.appendChild(this.instruments);
|
2020-05-15 17:20:51 +02:00
|
|
|
|
|
|
|
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);
|
2020-03-09 22:13:53 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
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);
|
2020-04-28 02:22:32 +02:00
|
|
|
this.duration = Math.round(this.replay.calculateSongDuration() / rate);
|
2020-03-09 22:13:53 +01:00
|
|
|
this.samplePosition = 0;
|
|
|
|
this.seekBar.value = 0;
|
2020-04-28 02:22:32 +02:00
|
|
|
this.seekBar.max = this.duration;
|
2020-03-09 22:13:53 +01:00
|
|
|
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 ? " " : "") + idx.toString(16);
|
|
|
|
instruments = instruments + instrument + " " + name.replace(/\s/g, " ") + "<br/>";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.instruments.innerHTML = instruments;
|
|
|
|
this.channel = 0;
|
|
|
|
this.clearDisplay();
|
|
|
|
this.updateDisplay();
|
|
|
|
},
|
|
|
|
|
|
|
|
function updateDisplay (count) {
|
|
|
|
if (count) {
|
|
|
|
this.samplePosition += count;
|
|
|
|
}
|
2020-04-28 02:22:32 +02:00
|
|
|
this.seekBar.value = this.samplePosition / PleromaModTracker.player.getSamplingRate() % this.duration;
|
2020-03-09 22:13:53 +01:00
|
|
|
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 () {
|
2021-06-10 23:51:37 +02:00
|
|
|
const posts = document.querySelectorAll(".StatusContent");
|
2020-03-09 22:13:53 +01:00
|
|
|
for (const post of posts) {
|
|
|
|
this.handlePost(post);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
function handlePost (postElement) {
|
2021-06-10 23:51:37 +02:00
|
|
|
if (!postElement.querySelector) {
|
|
|
|
console.warn("element?", postElement);
|
|
|
|
return;
|
2020-03-09 22:13:53 +01:00
|
|
|
}
|
2021-06-10 23:51:37 +02:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2020-03-09 22:13:53 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
function catchUp () {
|
|
|
|
for (const attachment of this.toUpdate) {
|
|
|
|
new PleromaModPlayer(attachment);
|
|
|
|
}
|
|
|
|
this.toUpdate = [];
|
|
|
|
},
|
|
|
|
|
2020-05-28 23:54:37 +02:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2020-03-09 22:13:53 +01:00
|
|
|
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; });
|
|
|
|
|
2020-05-02 21:49:01 +02:00
|
|
|
PleromaModLoader.registerMod(PleromaModTracker);
|