454 lines
13 KiB
HTML
454 lines
13 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
|
<meta charset="utf-8">
|
|
<meta http-equiv="X-UA-Compatible" content="IE=11">
|
|
<meta property="og:image" content="./banner.png">
|
|
<meta name="description" content="Minki's Module Collection is a selection of tracker music from various sources. This page offers the ability to play back these modules.">
|
|
<meta name="keywords" content="mueller_minki, Mueller's Software, Amiga, Amiga Module, Tracker, Music Tracker, Music, Minki's Module Collection, Module Collection, Music Collection">
|
|
<title>ModPlayer</title>
|
|
<style>
|
|
body {
|
|
font-family: Arial, sans-serif;
|
|
background: #111;
|
|
color: #f4f4f4;
|
|
margin: 0;
|
|
padding: 0;
|
|
display: flex;
|
|
flex-direction: row;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.sidebar {
|
|
width: 200px;
|
|
background: #1a1a1a;
|
|
padding: 20px;
|
|
box-sizing: border-box;
|
|
flex-shrink: 0;
|
|
height: 100vh;
|
|
}
|
|
|
|
.sidebar h2 {
|
|
font-size: 1.1rem;
|
|
margin: 0 0 10px;
|
|
}
|
|
|
|
.sidebar ul {
|
|
list-style: none;
|
|
padding-left: 10px;
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.sidebar li {
|
|
cursor: pointer;
|
|
padding: 5px;
|
|
margin: 2px 0;
|
|
}
|
|
|
|
.sidebar li:hover {
|
|
background-color: #333;
|
|
}
|
|
|
|
.content {
|
|
flex: 1;
|
|
min-width: 300px;
|
|
padding: 20px;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.player {
|
|
max-width: 600px;
|
|
margin: auto;
|
|
background: #1e1e1e;
|
|
padding: 20px;
|
|
border-radius: 8px;
|
|
box-shadow: 0 0 10px rgba(0,0,0,0.5);
|
|
}
|
|
|
|
h1 {
|
|
font-size: 1.5rem;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
a#play {
|
|
display: inline-block;
|
|
background: #2196F3;
|
|
color: white;
|
|
padding: 8px 16px;
|
|
text-decoration: none;
|
|
border-radius: 5px;
|
|
margin: 10px 0;
|
|
transition: background 0.3s;
|
|
}
|
|
|
|
a#play:hover {
|
|
background: #1976D2;
|
|
}
|
|
|
|
input[type="range"] {
|
|
width: 100%;
|
|
margin: 10px 0;
|
|
}
|
|
|
|
select {
|
|
width: 100%;
|
|
padding: 5px;
|
|
background: #333;
|
|
color: white;
|
|
border: 1px solid #555;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
small {
|
|
color: #888;
|
|
display: block;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
#playlist {
|
|
margin-top: 20px;
|
|
padding-left: 0;
|
|
}
|
|
|
|
#playlist li {
|
|
cursor: pointer;
|
|
padding: 8px 10px;
|
|
list-style: none;
|
|
border-bottom: 1px solid #333;
|
|
}
|
|
|
|
#playlist li:hover {
|
|
background-color: #333;
|
|
}
|
|
|
|
#playlist li.active {
|
|
background-color: #444;
|
|
font-weight: bold;
|
|
}
|
|
|
|
@media (max-width: 700px) {
|
|
body {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.sidebar {
|
|
width: 100%;
|
|
height: auto; /* Reset height for mobile */
|
|
}
|
|
|
|
.content {
|
|
padding: 10px;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<div class="sidebar">
|
|
<h2>Mod Collections</h2>
|
|
<div id="collectionList"></div>
|
|
</div>
|
|
|
|
<div class="content">
|
|
<div class="player">
|
|
<h1>Playing: <span id="modfilename">Select a module...</span></h1>
|
|
|
|
<strong><a href="javascript:pauseButton()" id="play">Load a module</a></strong><br>
|
|
Title: <span id="title"> </span><br>
|
|
Duration: <span id="duration"> </span>
|
|
<input id="seekbar" type="range" min="0" max="100" value="0" onchange="player.seek(this.value);" />
|
|
|
|
<div id="subsongs" style="display:none">
|
|
Subsongs:
|
|
<select id="subsong" onchange="selectSubsong()"></select>
|
|
</div>
|
|
|
|
<ul id="playlist"></ul>
|
|
|
|
<small id="library-version"> </small>
|
|
<small>ModPlayer web project version: 0.1.7 (2025-07-26T03:08:30.629763Z)<br><br>(c) 2025 Minki the Avali - <a href="https://www.wtfpl.net/txt/copying/">WTFPL-2</a></small>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="./chiptune2.js"></script>
|
|
<script src="./libopenmpt.js" async onload="initPlayer()"></script>
|
|
|
|
<script>
|
|
let moduleCollections = {
|
|
"Volume_A": {
|
|
"1": [
|
|
"Volume_A/1/2010.s3m",
|
|
"Volume_A/1/aaaa.mod",
|
|
"Volume_A/1/afloat.mod",
|
|
"Volume_A/1/gtasa.it",
|
|
"Volume_A/1/music1.s3m",
|
|
"Volume_A/1/MYPT2.MOD",
|
|
"Volume_A/1/NOWHAT.MOD",
|
|
"Volume_A/1/silence.mod",
|
|
"Volume_A/1/TRANSISR.IT",
|
|
"Volume_A/1/TRPNEVER.MOD",
|
|
"Volume_A/1/validanc.mod",
|
|
"Volume_A/1/whatname.s3m",
|
|
"Volume_A/1/yosheek.it"
|
|
],
|
|
"2": [
|
|
"Volume_A/2/5kb.mod",
|
|
"Volume_A/2/Allnite.mod",
|
|
"Volume_A/2/bid.mod",
|
|
"Volume_A/2/deflcrap.mod",
|
|
"Volume_A/2/droppy.it",
|
|
"Volume_A/2/fastfunk.xm",
|
|
"Volume_A/2/freehugs.xm",
|
|
"Volume_A/2/IhmeLuon.mod",
|
|
"Volume_A/2/Party.mod",
|
|
"Volume_A/2/Spacdeli.mod",
|
|
"Volume_A/2/Winners.mod"
|
|
],
|
|
"3": [
|
|
"Volume_A/3/1990chip.mod",
|
|
"Volume_A/3/chippie.xm",
|
|
"Volume_A/3/dotcom.mod",
|
|
"Volume_A/3/eek.it",
|
|
"Volume_A/3/golden4.mod",
|
|
"Volume_A/3/groove.mod",
|
|
"Volume_A/3/k21.mod",
|
|
"Volume_A/3/ohbdisco.it",
|
|
"Volume_A/3/rustruin.it",
|
|
"Volume_A/3/techno2.mod",
|
|
"Volume_A/3/wtaf.mod",
|
|
"Volume_A/3/z.xm"
|
|
]
|
|
},
|
|
"Volume_B": {
|
|
"1": [
|
|
"Volume_B/1/asm98.xm",
|
|
"Volume_B/1/chip-meltdown.mod",
|
|
"Volume_B/1/chirpychip.mod",
|
|
"Volume_B/1/enchantment-tsp.mod",
|
|
"Volume_B/1/funky_blend.mod",
|
|
"Volume_B/1/lizardking_-_master_and_servant.mod",
|
|
"Volume_B/1/monkeydroid.mod",
|
|
"Volume_B/1/mortal_mario.mod",
|
|
"Volume_B/1/online_pizza.mod",
|
|
"Volume_B/1/spill_the_beans.mod",
|
|
"Volume_B/1/technix_-_too_late.mod",
|
|
"Volume_B/1/wonderfull_summer.xm"
|
|
],
|
|
"2": [
|
|
"Volume_B/2/cyborgjeff-91groove.mod",
|
|
"Volume_B/2/cyborg_jeff_-_paradise.mod",
|
|
"Volume_B/2/realize.mod",
|
|
"Volume_B/2/smooth_dancer.mod",
|
|
"Volume_B/2/technix-bubble_bobble.mod",
|
|
"Volume_B/2/technix_-_cabbage.mod",
|
|
"Volume_B/2/technix-moonwalk.mod",
|
|
"Volume_B/2/technix_-_outrun.mod",
|
|
"Volume_B/2/trade.xm",
|
|
"Volume_B/2/unsuspected_o.mod",
|
|
"Volume_B/2/what_is_love_g.s3m"
|
|
],
|
|
"3": [
|
|
"Volume_B/3/28.mod",
|
|
"Volume_B/3/an_empty_cup_of_tea.mod",
|
|
"Volume_B/3/april.mod",
|
|
"Volume_B/3/chippy_nr_152.mod",
|
|
"Volume_B/3/hi-del.mod",
|
|
"Volume_B/3/high3.s3m",
|
|
"Volume_B/3/kaos_och_dekadens.mod",
|
|
"Volume_B/3/mastodontti.mod",
|
|
"Volume_B/3/noone_51_1998.mod",
|
|
"Volume_B/3/nv1bb.s3m",
|
|
"Volume_B/3/PHASE.MOD",
|
|
"Volume_B/3/tce_msx1.mod",
|
|
"Volume_B/3/_warhawk.xm"
|
|
]
|
|
},
|
|
"Volume_C": {
|
|
"1": [
|
|
"Volume_C/1/afraid_.mod",
|
|
"Volume_C/1/bsx-ten8.xm",
|
|
"Volume_C/1/captain_america.xm",
|
|
"Volume_C/1/dance_mania.mod",
|
|
"Volume_C/1/dubmood_-_a_message_to_you_mo.xm",
|
|
"Volume_C/1/full_metal_trousers.mod",
|
|
"Volume_C/1/hitmix2.xm",
|
|
"Volume_C/1/itspheno.mod"
|
|
],
|
|
"2": [
|
|
"Volume_C/2/12th_street_rag.mod",
|
|
"Volume_C/2/afterglow.mod",
|
|
"Volume_C/2/ida_ohb-idk.it",
|
|
"Volume_C/2/megatec_bestechno.mod",
|
|
"Volume_C/2/mexy_-_i_like_to_move_it_remix.mod",
|
|
"Volume_C/2/omen_no_-_funkypiano.mod",
|
|
"Volume_C/2/popcorn_world_remix.xm"
|
|
],
|
|
"3": [
|
|
"Volume_C/3/64_alive.mod",
|
|
"Volume_C/3/bevcop1.s3m",
|
|
"Volume_C/3/doop_remix.s3m",
|
|
"Volume_C/3/external.xm",
|
|
"Volume_C/3/mip4_intro.mod",
|
|
"Volume_C/3/oistein_eide_-_illusions.mod",
|
|
"Volume_C/3/pink_n_fluffy.xm",
|
|
"Volume_C/3/rg-random.xm",
|
|
"Volume_C/3/still_chippy.mod",
|
|
"Volume_C/3/subways.mod",
|
|
"Volume_C/3/techlort.s3m"
|
|
]
|
|
}
|
|
};
|
|
|
|
let moduleList = [];
|
|
let currentIndex = 0;
|
|
let player;
|
|
let progressInterval = null;
|
|
|
|
function pauseButton() {
|
|
const button = document.getElementById('play');
|
|
if (player.togglePause()) {
|
|
button.innerHTML = "Pause";
|
|
} else {
|
|
button.innerHTML = "Play";
|
|
}
|
|
}
|
|
|
|
function selectSubsong() {
|
|
const select = document.getElementById('subsong');
|
|
const subsong = select.options[select.selectedIndex].value;
|
|
player.selectSubsong(subsong);
|
|
updateDuration();
|
|
}
|
|
|
|
function updateDuration() {
|
|
const sec_num = player.duration();
|
|
const minutes = Math.floor(sec_num / 60);
|
|
let seconds = Math.floor(sec_num % 60);
|
|
if (seconds < 10) seconds = '0' + seconds;
|
|
document.getElementById('duration').innerHTML = `${minutes}:${seconds}`;
|
|
document.getElementById('seekbar').max = sec_num;
|
|
}
|
|
|
|
function setMetadata(filename) {
|
|
const metadata = player.metadata();
|
|
document.getElementById('title').innerHTML = metadata.title || filename;
|
|
|
|
const subsongs = player.subsongs();
|
|
const select = document.getElementById('subsong');
|
|
select.innerHTML = '';
|
|
document.getElementById('subsongs').style.display = (subsongs.length > 1) ? 'block' : 'none';
|
|
|
|
if (subsongs.length > 1) {
|
|
const allOption = document.createElement('option');
|
|
allOption.textContent = 'Play all subsongs';
|
|
allOption.value = -1;
|
|
select.appendChild(allOption);
|
|
|
|
subsongs.forEach((s, i) => {
|
|
const opt = document.createElement('option');
|
|
opt.textContent = s;
|
|
opt.value = i;
|
|
select.appendChild(opt);
|
|
});
|
|
|
|
select.selectedIndex = 0;
|
|
player.selectSubsong(-1);
|
|
}
|
|
|
|
updateDuration();
|
|
|
|
const stack = stackSave();
|
|
document.getElementById('library-version').innerHTML =
|
|
'Library version: libopenmpt ' + UTF8ToString(libopenmpt._openmpt_get_string(asciiToStack('library_version')))
|
|
+ ' (' + UTF8ToString(libopenmpt._openmpt_get_string(asciiToStack('build'))) + ')';
|
|
stackRestore(stack);
|
|
}
|
|
|
|
function loadAndPlay(index) {
|
|
if (progressInterval) clearInterval(progressInterval);
|
|
|
|
currentIndex = index;
|
|
const path = moduleList[index];
|
|
document.getElementById('modfilename').innerText = path;
|
|
|
|
highlightPlaylistItem(index);
|
|
|
|
player.load(path, function(buffer) {
|
|
if (player.play(buffer)) {
|
|
document.getElementById('play').innerHTML = "Pause";
|
|
} else {
|
|
document.getElementById('play').innerHTML = "Play";
|
|
}
|
|
|
|
setMetadata(path);
|
|
|
|
progressInterval = setInterval(() => {
|
|
const currentPos = player.getPosition();
|
|
const max = parseFloat(document.getElementById('seekbar').max);
|
|
document.getElementById('seekbar').value = currentPos;
|
|
|
|
if (currentPos >= max - 0.5) {
|
|
clearInterval(progressInterval);
|
|
if (currentIndex + 1 < moduleList.length) {
|
|
loadAndPlay(currentIndex + 1);
|
|
} else {
|
|
player.stop();
|
|
document.getElementById('play').innerText = "Play";
|
|
}
|
|
}
|
|
}, 500);
|
|
});
|
|
}
|
|
|
|
function highlightPlaylistItem(index) {
|
|
const items = document.querySelectorAll('#playlist li');
|
|
items.forEach((item, i) => {
|
|
item.classList.toggle('active', i === index);
|
|
});
|
|
}
|
|
|
|
function updatePlaylist(list) {
|
|
moduleList = list;
|
|
const playlist = document.getElementById('playlist');
|
|
playlist.innerHTML = '';
|
|
moduleList.forEach((filename, index) => {
|
|
const li = document.createElement('li');
|
|
li.textContent = filename;
|
|
li.onclick = () => loadAndPlay(index);
|
|
playlist.appendChild(li);
|
|
});
|
|
loadAndPlay(0);
|
|
}
|
|
|
|
function renderCollections() {
|
|
const container = document.getElementById('collectionList');
|
|
for (let collection in moduleCollections) {
|
|
const title = document.createElement('div');
|
|
title.innerHTML = `<strong>${collection}</strong>`;
|
|
container.appendChild(title);
|
|
|
|
const diskList = document.createElement('ul');
|
|
for (let disk in moduleCollections[collection]) {
|
|
const li = document.createElement('li');
|
|
li.textContent = "Disk " + disk;
|
|
li.onclick = () => updatePlaylist(moduleCollections[collection][disk]);
|
|
diskList.appendChild(li);
|
|
}
|
|
container.appendChild(diskList);
|
|
}
|
|
}
|
|
|
|
function initPlayer() {
|
|
libopenmpt.onRuntimeInitialized = () => {
|
|
player = new ChiptuneJsPlayer(new ChiptuneJsConfig(-1));
|
|
renderCollections();
|
|
};
|
|
}
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|
|
|