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 ? " " : "") + idx.toString(16); instruments = instruments + instrument + " " + name.replace(/\s/g, " ") + "
"; } } 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);