Skip to content

Commit cf9591c

Browse files
committed
updated: latest version with local module loading
1 parent a6ff36d commit cf9591c

File tree

6 files changed

+98
-84
lines changed

6 files changed

+98
-84
lines changed

index.html

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,21 @@
1414
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
1515
<link rel="stylesheet" href="https://code.getmdl.io/1.3.0/material.indigo-deep_purple.min.css">
1616
<style type="text/css">
17+
input[type="file"] {
18+
visibility: hidden;
19+
width: 0;
20+
}
1721
/* mdl */
1822
.page-content {
1923
display: flex;
2024
justify-content: center;
2125
padding: 40px;
2226
}
2327

28+
.mdl-navigation.controls {
29+
gap: 20px;
30+
}
31+
2432
a.mdl-navigation__link.selected:before {
2533
font-family: 'Material Icons';
2634
font-weight: 400;
@@ -91,12 +99,15 @@
9199
<div class="mdl-layout-spacer"></div>
92100
<!-- Navigation. We hide it in small screens. -->
93101
<nav class="mdl-navigation controls">
94-
<!-- <a class="mdl-navigation__link mdl-navigation__link--icon github" href="https://github.com/warpdesign/modplayer-js"><i class="material-icons">link</i><span>GitHub</span></a> -->
95-
<button class="mdl-button mdl-js-button mdl-button--icon play" style="display:none;" onclick="togglePlay()">
96-
<i class="material-icons">play_arrow</i>
102+
<button class="mdl-button mdl-js-button play mdl-button--raised mdl-button--accent" style="display:none;" onclick="togglePlay()">
103+
<i class="material-icons">play_arrow</i><span>Play</span>
104+
</button>
105+
<button class="mdl-button mdl-js-button stop mdl-button--raised mdl-button--accent" style="display:none;" onclick="stop()">
106+
<i class="material-icons">stop</i>Stop
97107
</button>
98-
<button class="mdl-button mdl-js-button mdl-button--icon play" style="display:none;" onclick="stop()">
99-
<i class="material-icons">stop</i>
108+
<button class="mdl-button mdl-js-button eject mdl-button--raised mdl-button--accent" style="display:none;" onclick="window.choose_local_file.click()">
109+
<input type="file" id="choose_local_file" onchange="loadLocalFile(this.files[0]);this.value = null;"/>
110+
<i class="material-icons">eject</i>Load
100111
</button>
101112
<div id="loader" class="mdl-spinner mdl-js-spinner is-active"></div>
102113
</nav>
@@ -169,15 +180,7 @@ <h2 class="mdl-card__title-text" title="Click here to load another module"><div>
169180
</div>
170181
</div>
171182
</div>
172-
<!-- <div class="mdl-card__actions mdl-card--border">
173-
<a class="mdl-button mdl-button--colored mdl-js-button mdl-js-ripple-effect">
174-
Get Started
175-
</a>
176-
</div> -->
177183
<div class="mdl-card__menu">
178-
<!-- <button class="mdl-button mdl-button--icon mdl-js-button mdl-js-ripple-effect">
179-
<i class="material-icons">attachment</i>
180-
</button> -->
181184
</div>
182185
</div>
183186
</div>

js/main.js

Lines changed: 34 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,15 @@ window.onload = function () {
3636

3737
toast = new Toast('info-snackbar');
3838

39+
document.addEventListener('moduleLoadError', () => {
40+
toast.show(`Error loading module: are you sure this is a Sound Tracker module file?`);
41+
removeLoader()
42+
})
43+
3944
document.addEventListener('moduleLoaded', (event) => {
40-
const split = moduleList[selectedMod].file.split('#'),
41-
name = split.length > 1 && split[1] || split[0];
45+
const name = event.data.title || "No Title"
4246

43-
toast.show(`Module loaded: ${name}`);
47+
toast.show(`Module loaded: ${event.data.title || "No Name"}`);
4448

4549
const samples = event.data.samples;
4650
let str = '';
@@ -54,17 +58,16 @@ window.onload = function () {
5458

5559
document.querySelector('.song-title').innerText = event.data.title;
5660
document.querySelector('.title').innerText = name;
57-
document.querySelector('.author').innerText = moduleList[selectedMod].author;
61+
document.querySelector('.author').innerText = moduleList[selectedMod]?.author || 'Unknown Author';
5862
document.querySelector('.song-length').innerText = event.data.length;
5963
document.querySelector('.song-samples').innerText = event.data.samples.length;
6064
document.querySelector('.song-positions').innerText = event.data.positions;
6165
document.querySelector('.song-patterns').innerText = event.data.patterns;
6266

63-
document.querySelector('#loader').classList.remove('is-active');
67+
document.querySelector('a.mdl-navigation__link.selected')?.classList.remove('selected');
68+
typeof selectedMod === 'number' && document.querySelector(`a.mdl-navigation__link.mod_${selectedMod}`).classList.add('selected');
6469

65-
document.querySelectorAll('.controls button').forEach((button) => {
66-
button.style.display = 'inline-block';
67-
});
70+
removeLoader();
6871

6972
togglePlayButton();
7073

@@ -117,37 +120,6 @@ window.onload = function () {
117120
ModPlayer.setPlayingChannels(channelsPlaying);
118121
});
119122

120-
// function drawBars(amplitudeArray) {
121-
// var bufferLength = amplitudeArray.length;
122-
// ctx.fillStyle = 'rgb(0, 0, 0)';
123-
// ctx.fillRect(0, 0, canvasWidth, canvasHeight);
124-
125-
// var barWidth = (canvasWidth / bufferLength) * 2.5 - 1;
126-
// barWidth *= 2;
127-
// var barHeight;
128-
// var x = 0;
129-
130-
// for (var i = 0; i < bufferLength; i++) {
131-
// barHeight = amplitudeArray[i];
132-
133-
// ctx.fillStyle = 'rgb(' + (barHeight + 100) + ',50,50)';
134-
// ctx.fillRect(x, canvasHeight - barHeight / 2, barWidth, barHeight / 2);
135-
136-
// x += barWidth;
137-
// }
138-
// }
139-
140-
// function drawOscillo(amplitudeArray) {
141-
// ctx.clearRect(0, 0, canvasWidth, canvasHeight);
142-
143-
// for (var i = 0; i < amplitudeArray.length; i++) {
144-
// var value = amplitudeArray[i] / 256;
145-
// var y = canvasHeight - (canvasHeight * value) - 1;
146-
// ctx.fillStyle = '#000000';
147-
// ctx.fillRect(i, y, 1, 1);
148-
// }
149-
// }
150-
151123
ModPlayer.init({
152124
canvas: canvas,
153125
audioWorkletSupport: audioWorkletSupport
@@ -158,8 +130,17 @@ window.onload = function () {
158130
});
159131
}
160132

133+
function removeLoader() {
134+
document.querySelector('#loader').classList.remove('is-active');
135+
136+
document.querySelectorAll('.controls button').forEach((button) => {
137+
button.style.display = 'inline-block';
138+
});
139+
}
140+
161141
function togglePlayButton() {
162142
document.querySelector('button.play i').innerText = ModPlayer.playing && 'pause' || 'play_arrow';
143+
document.querySelector('button.play span').innerText = ModPlayer.playing && 'Pause' || 'Play';
163144
}
164145

165146
function togglePlay() {
@@ -172,12 +153,17 @@ function stop() {
172153
togglePlayButton();
173154
}
174155

175-
function loadModule(moduleIndex, hideDrawer = true) {
176-
var moduleName = moduleList[moduleIndex].file;
156+
function loadLocalFile(file) {
157+
console.log("should load local file", file);
158+
loadModule(file, false)
159+
}
160+
161+
function loadModule(module, hideDrawer = true) {
162+
var moduleName = typeof module === 'number' && moduleList[module].file;
177163

178-
selectedMod = moduleIndex;
164+
selectedMod = module;
179165

180-
if (ModPlayer.ready && moduleName) {
166+
if (ModPlayer.ready && (moduleName || module)) {
181167
// I guess that's the best way to programmatically hide the drawer
182168
// since MDL does not provide any API to do that
183169
if (hideDrawer) {
@@ -190,10 +176,12 @@ function loadModule(moduleIndex, hideDrawer = true) {
190176
button.style.display = 'none';
191177
});
192178

193-
document.querySelector('a.mdl-navigation__link.selected').classList.toggle('selected');
194-
document.querySelector(`a.mdl-navigation__link.mod_${moduleIndex}`).classList.add('selected');
179+
const file = moduleName ?
180+
(moduleName.match(/^http/) ? moduleName : prefix + moduleName)
181+
:
182+
module;
195183

196-
ModPlayer.loadModule(moduleName.match(/^http/) ? moduleName : prefix + moduleName)
184+
ModPlayer.loadModule(file)
197185
.catch(err => {
198186
toast.show(`Error loading module: ${err}`);
199187
});

js/mod-processor-es5.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

js/mod-processor.js

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -205,10 +205,25 @@ class PTModuleProcessor extends AudioWorkletProcessor{
205205
}
206206
}
207207

208+
isModule(buffer) {
209+
// Very basic mod file detection, maybe we could check for M.K signature
210+
// at offset 1080 instead
211+
return buffer.byteLength > 20 && new DataView(buffer).getUint8(0x13) === 0;
212+
}
213+
208214
prepareModule(buffer) {
209215
console.log('Decoding module data...');
216+
217+
if (!this.isModule(buffer)) {
218+
this.postMessage({
219+
message: 'moduleLoadError',
220+
})
221+
return
222+
}
223+
210224
this.ready = false;
211225
this.init();
226+
212227
this.buffer = buffer;
213228
this.name = BinUtils.readAscii(this.buffer, 20);
214229

@@ -361,8 +376,6 @@ class PTModuleProcessor extends AudioWorkletProcessor{
361376
}
362377

363378
this.decodeRow();
364-
365-
// console.log('** next row !', this.row.toString(16).padStart(2, "0"));
366379
}
367380
}
368381
}
@@ -372,7 +385,6 @@ class PTModuleProcessor extends AudioWorkletProcessor{
372385

373386
// Loop ? Use loop parameter
374387
if (this.position > this.positions.length - 1) {
375-
debugger;
376388
console.log('Warning: last position reached, going back to 0');
377389
this.position = 0;
378390
}
@@ -382,8 +394,6 @@ class PTModuleProcessor extends AudioWorkletProcessor{
382394
}
383395

384396
this.pattern = this.positions[this.position];
385-
386-
console.log('** position', this.position, 'pattern:', this.pattern);
387397
}
388398

389399
decodeRow() {
@@ -448,7 +458,6 @@ class PTModuleProcessor extends AudioWorkletProcessor{
448458
try {
449459
Effects[channel.cmd](this, channel);
450460
} catch (err) {
451-
debugger;
452461
console.warn(`effect not implemented: ${channel.cmd.toString(16).padStart(2, '0')}/${channel.data.toString(16).padStart(2, '0')}`);
453462
}
454463
}
@@ -472,9 +481,9 @@ class PTModuleProcessor extends AudioWorkletProcessor{
472481
data: null
473482
};
474483

475-
if (sample.finetune) {
476-
debugger;
477-
}
484+
// if (sample.finetune) {
485+
// debugger;
486+
// }
478487

479488
// Existing mod players seem to play a sample only once if repeatLength is set to 2
480489
if (sample.repeatLength === 2) {
@@ -730,7 +739,6 @@ const Effects = {
730739
* Set Vibrato waveform + retrigger
731740
*/
732741
0xE4(Module, channel) {
733-
debugger;
734742
if (!Module.ticks) {
735743
// channel.vform = //
736744
}

js/modplayer.js

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const ModPlayer = {
2020
return this.createContext();
2121
},
2222

23-
async loadModule(url) {
23+
async loadModule(module) {
2424
if (!this.ready) {
2525
return;
2626
} else {
@@ -36,7 +36,7 @@ const ModPlayer = {
3636
this.createContext();
3737
}
3838

39-
const buffer = await this.loadBinary(url);
39+
const buffer = typeof module === 'string' ? await this.loadBinaryFromURL(module) : await this.loadBinaryFromLocalFile(module);
4040
this.postMessage({
4141
message: 'loadModule',
4242
buffer: buffer
@@ -45,13 +45,26 @@ const ModPlayer = {
4545
this.ready = true;
4646
},
4747

48-
async loadBinary(url) {
48+
async loadBinaryFromURL(url) {
4949
const response = await betterFetch(url);
5050
const buffer = await response.arrayBuffer();
5151

5252
return buffer;
5353
},
5454

55+
async loadBinaryFromLocalFile(file) {
56+
return new Promise((resolve, reject) => {
57+
var reader = new FileReader();
58+
reader.onload = function(e) {
59+
resolve(e.target.result);
60+
};
61+
reader.onerror = function(e) {
62+
reject('Error : ' + e.type);;
63+
};
64+
reader.readAsArrayBuffer(file);
65+
});
66+
},
67+
5568
createContext() {
5669
console.log('Creating audio context...');
5770
this.context = new (window.AudioContext || window.webkitAudioContext)();
@@ -138,7 +151,7 @@ const ModPlayer = {
138151

139152
handleMessage(message) {
140153
switch (message.data.message) {
141-
case 'moduleLoaded':
154+
case 'moduleLoaded': {
142155
this.loaded = true;
143156
const event = new Event('moduleLoaded');
144157
event.data = message.data.data;
@@ -147,11 +160,18 @@ const ModPlayer = {
147160
if (!this.playing) {
148161
this.renderScope();
149162
}
150-
break;
163+
}
164+
break;
151165

152166
case 'toggleLowPass':
153167
this.setLowPass(message.data.data.activate);
154168
break;
169+
170+
default: {
171+
this.loaded = true;
172+
const event = new Event(message.data.message);
173+
document.dispatchEvent(event);
174+
}
155175
}
156176
},
157177

@@ -308,8 +328,6 @@ const ModPlayer = {
308328
this.ctx.strokeStyle = style;
309329
this.ctx.fillStyle = style;
310330

311-
// this.ctx.beginPath();
312-
313331
while (timeData[risingEdge] > 0 &&
314332
risingEdge <= this.canvasWidth &&
315333
risingEdge < timeData.length) {
@@ -329,12 +347,8 @@ const ModPlayer = {
329347

330348
for (let x = risingEdge; x < timeData.length && x - risingEdge < this.canvasWidth; x++) {
331349
const y = this.canvasHeight - (((timeData[x] + 1) / 2) * this.canvasHeight);
332-
// this.ctx.moveTo(x - risingEdge + i * this.canvasWidth, y-1);
333-
// this.ctx.lineTo(x - risingEdge + i * this.canvasWidth, y);
334350
this.ctx.fillRect(x - risingEdge + pos * this.canvasWidth, y, 1, 1);
335351
}
336-
337-
// this.ctx.stroke();
338352
});
339353
}
340354
}

package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
"description": "JavaScript player for SoundTracker/Noisetracker mod files using the webaudio AudioWorklet API.",
55
"main": "js/modplayer.js",
66
"scripts": {
7-
"build": "microbundle",
8-
"dev": "microbundle watch",
7+
"build": "microbundle -i js/mod-processor.js -o dist && cp dist/modplayer-js.js js/mod-processor-es5.js",
8+
"server": "http-server",
99
"test": "echo \"Error: no test specified\" && exit 1"
1010
},
1111
"repository": {
@@ -28,6 +28,7 @@
2828
},
2929
"homepage": "https://github.com/warpdesign/modplayer-js#readme",
3030
"devDependencies": {
31-
"microbundle": "^0.6.0"
31+
"http-server": "^14.1.1",
32+
"microbundle": "^0.15.1"
3233
}
3334
}

0 commit comments

Comments
 (0)