diff --git a/js/main.js b/js/main.js
index d051758..29424b6 100644
--- a/js/main.js
+++ b/js/main.js
@@ -1,12 +1,29 @@
var moduleList = [
- { file: 'agony.mod', author: 'Tim Wright' },
- { file: 'all_that_she_wants.mod', author: 'Crossair' },
- { file: 'bigtime.mod', author: 'ISO/Axis Group' },
- { file: 'cannonfodder.mod', author: 'John Hare' },
- { file: 'desert_strike.mod', author: 'Jason Whitley' },
- { file: 'LotusII.mod', author: 'Barry Leitch' },
- { file: 'projectx.mod', author: 'Allister Brimble' },
- { file: 'silkworm.mod', author: 'Barry Leitch' }
+ { file: 'https://api.modarchive.org/downloads.php?moduleid=91286#faggots_universe.mod', author: 'Deelite' },
+ { file: 'https://api.modarchive.org/downloads.php?moduleid=168739#neurodancer_-_quasar.mod', author: 'Neurodancer' },
+ { file: 'https://api.modarchive.org/downloads.php?moduleid=183672#tax_haven_dry_hump.mod', author: 'Curt Cool' },
+ { file: 'https://api.modarchive.org/downloads.php?moduleid=182057#h0ffman_-_drop_the_panic.mod', author: 'h0ffman' },
+ { file: 'https://api.modarchive.org/downloads.php?moduleid=168122#prodigy_-_downtown.mod', author: 'prodigy of oops' },
+ { file: 'https://api.modarchive.org/downloads.php?moduleid=172266#hoffman_-_the_hunter.mod', author: 'h0ffman' },
+ { file: 'https://api.modarchive.org/downloads.php?moduleid=65280#variations.mod', author: 'jogeir-liljedahl' },
+ { file: 'https://api.modarchive.org/downloads.php?moduleid=166686#wiklund_-_bonfire.mod', author: 'Wiklund' },
+ { file: 'https://api.modarchive.org/downloads.php?moduleid=55058#pinball_illusions.mod', author: 'Olof Gustafsson' },
+ { file: 'https://api.modarchive.org/downloads.php?moduleid=167668#vinnie_-_sweet_dreams.mod', author: 'vinnie/spaceballs' },
+ { file: 'https://api.modarchive.org/downloads.php?moduleid=172271#subi-king_of_boggle.mod', author: 'Subi/DESiRE' },
+ { file: 'https://api.modarchive.org/downloads.php?moduleid=171416#bass-1107.mod', author: 'Noiseless (cm/ao)' },
+ { file: 'https://api.modarchive.org/downloads.php?moduleid=168110#punnik_-_drum_bass.mod', author: 'punnik' },
+ { file: 'https://api.modarchive.org/downloads.php?moduleid=171616#dan_-_childs_philozophy.mod', author: 'dan / picco' },
+ { file: 'https://api.modarchive.org/downloads.php?moduleid=119303#boesendorfer_p_s_s.mod', author: 'romeoknight' },
+ { file: 'https://api.modarchive.org/downloads.php?moduleid=170637#ghost_in_the_cli.mod', author: 'h0ffman' },
+ { file: 'https://api.modarchive.org/downloads.php?moduleid=158057#alf_-_no-mercy.mod', author: 'alf/vtl' },
+ { file: 'https://api.modarchive.org/downloads.php?moduleid=105709#trans_atlantic.mod', author: 'Lizardking'},
+ { file: 'https://api.modarchive.org/downloads.php?moduleid=124303#agony_intro.mod', author: 'Tim Wright' },
+ { file: 'https://api.modarchive.org/downloads.php?moduleid=98051#big_time_sensuality.mod', author: 'ISO/Axis Group' },
+ { file: 'https://api.modarchive.org/downloads.php?moduleid=34568#CANNONFO.MOD', author: 'John Hare' },
+ { file: 'https://api.modarchive.org/downloads.php?moduleid=68835#desert_strike.mod', author: 'Jason Whitley' },
+ { file: 'https://api.modarchive.org/downloads.php?moduleid=87180#lotus2-title.mod', author: 'Barry Leitch' },
+ { file: 'https://api.modarchive.org/downloads.php?moduleid=56660#projectx.mod', author: 'Allister Brimble' },
+ { file: 'https://api.modarchive.org/downloads.php?moduleid=83115#silkwormtitle.mod', author: 'Barry Leitch' }
],
selectedMod = 0,
prefix = 'audio/',
@@ -14,17 +31,16 @@ var moduleList = [
window.onload = function () {
var canvas = document.getElementById('visualizer'),
- effect = 1,
- effects = [],
- ctx = canvas.getContext('2d'),
- canvasWidth = canvas.width,
- canvasHeight = canvas.height,
- channelsPlaying = [true, true, true, true];
+ channelsPlaying = [true, true, true, true],
+ audioWorkletSupport = !!AudioWorkletNode.toString().match(/native code/);
toast = new Toast('info-snackbar');
document.addEventListener('moduleLoaded', (event) => {
- toast.show(`Module loaded: ${moduleList[selectedMod].file}`);
+ const split = moduleList[selectedMod].file.split('#'),
+ name = split.length > 1 && split[1] || split[0];
+
+ toast.show(`Module loaded: ${name}`);
const samples = event.data.samples;
let str = '';
@@ -37,7 +53,7 @@ window.onload = function () {
document.querySelector('.sample-list').innerHTML = str;
document.querySelector('.song-title').innerText = event.data.title;
- document.querySelector('.title').innerText = moduleList[selectedMod].file;
+ document.querySelector('.title').innerText = name;
document.querySelector('.author').innerText = moduleList[selectedMod].author;
document.querySelector('.song-length').innerText = event.data.length;
document.querySelector('.song-samples').innerText = event.data.samples.length;
@@ -65,7 +81,9 @@ window.onload = function () {
if (i === selectedMod) {
options += ' selected';
}
- options += `">${module.file}`;
+ const split = module.file.split('#'),
+ name = split.length > 1 && split[1] || split[0];
+ options += `">${name}`;
});
modNav.innerHTML = options;
@@ -78,97 +96,61 @@ window.onload = function () {
document.querySelector('.mdl-layout__obfuscator').click();
});
- document.addEventListener('analyzer_ready', (event) => {
- requestAnimationFrame(() => {
- effects[effect](event.data);
- });
- });
-
- document.querySelector('.channel_control').addEventListener('click', (event) => {
- if (event.target.id && event.target.id.match(/channel-toggle/)) {
- var channel = event.target.id.substr(-1, 1),
- checked = event.target.hasAttribute('checked');
-
- channelsPlaying[channel - 1] = !channelsPlaying[channel - 1];
-
- ModPlayer.setPlayingChannels(channelsPlaying);
- }
- });
-
- canvas.onclick = () => {
- effect++;
- if (effect >= effects.length) {
- effect = 0;
- }
- };
-
- function drawBars(amplitudeArray) {
- var bufferLength = amplitudeArray.length;
- ctx.fillStyle = 'rgb(0, 0, 0)';
- ctx.fillRect(0, 0, canvasWidth, canvasHeight);
-
- var barWidth = (canvasWidth / bufferLength) * 2.5 - 1;
- barWidth *= 2;
- var barHeight;
- var x = 0;
-
- for (var i = 0; i < bufferLength; i++) {
- barHeight = amplitudeArray[i];
-
- ctx.fillStyle = 'rgb(' + (barHeight + 100) + ',50,50)';
- ctx.fillRect(x, canvasHeight - barHeight / 2, barWidth, barHeight / 2);
-
- x += barWidth;
- }
- }
-
- function drawOscillo(amplitudeArray) {
- ctx.clearRect(0, 0, canvasWidth, canvasHeight);
-
- for (var i = 0; i < amplitudeArray.length; i++) {
- var value = amplitudeArray[i] / 256;
- var y = canvasHeight - (canvasHeight * value) - 1;
- ctx.fillStyle = '#000000';
- ctx.fillRect(i, y, 1, 1);
+ canvas.addEventListener('click', (event) => {
+ const width = canvas.width / 4,
+ channel = Math.floor(event.offsetX / width);
+
+ // audioworklet mode shows the four channels
+ // scriptprocessor fallback groups 0-3 and 1-2 channels visually
+ if (audioWorkletSupport) {
+ channelsPlaying[channel] = !channelsPlaying[channel];
+ } else {
+ if (!channel) {
+ channelsPlaying[0] = !channelsPlaying[0];
+ channelsPlaying[3] = !channelsPlaying[3];
+ } else if (channel === 3) {
+ channelsPlaying[1] = !channelsPlaying[1];
+ channelsPlaying[2] = !channelsPlaying[2];
+ }
}
- }
- function drawOscillo2(amplitudeArray) {
- var bufferLength = amplitudeArray.length;
-
- ctx.fillStyle = "rgb(200, 200, 200)";
- ctx.fillRect(0, 0, canvas.width, canvas.height);
-
- ctx.lineWidth = 2;
- ctx.strokeStyle = "rgb(0, 0, 0)";
+ ModPlayer.setPlayingChannels(channelsPlaying);
+ });
- ctx.beginPath();
+ // function drawBars(amplitudeArray) {
+ // var bufferLength = amplitudeArray.length;
+ // ctx.fillStyle = 'rgb(0, 0, 0)';
+ // ctx.fillRect(0, 0, canvasWidth, canvasHeight);
- var sliceWidth = canvas.width * 1.0 / bufferLength;
- var x = 0;
+ // var barWidth = (canvasWidth / bufferLength) * 2.5 - 1;
+ // barWidth *= 2;
+ // var barHeight;
+ // var x = 0;
- for (var i = 0; i < bufferLength; i++) {
+ // for (var i = 0; i < bufferLength; i++) {
+ // barHeight = amplitudeArray[i];
- var v = amplitudeArray[i] / 128.0;
- var y = v * canvas.height / 2;
+ // ctx.fillStyle = 'rgb(' + (barHeight + 100) + ',50,50)';
+ // ctx.fillRect(x, canvasHeight - barHeight / 2, barWidth, barHeight / 2);
- if (i === 0) {
- ctx.moveTo(x, y);
- } else {
- ctx.lineTo(x, y);
- }
+ // x += barWidth;
+ // }
+ // }
- x += sliceWidth;
- }
-
- ctx.lineTo(canvas.width, canvas.height / 2);
- ctx.stroke();
- }
+ // function drawOscillo(amplitudeArray) {
+ // ctx.clearRect(0, 0, canvasWidth, canvasHeight);
- effects.push(drawBars, drawOscillo);
+ // for (var i = 0; i < amplitudeArray.length; i++) {
+ // var value = amplitudeArray[i] / 256;
+ // var y = canvasHeight - (canvasHeight * value) - 1;
+ // ctx.fillStyle = '#000000';
+ // ctx.fillRect(i, y, 1, 1);
+ // }
+ // }
ModPlayer.init({
- canvas: canvas
+ canvas: canvas,
+ audioWorkletSupport: audioWorkletSupport
}).then(() => {
loadModule(selectedMod, false);
}).catch((err) => {
@@ -211,7 +193,7 @@ function loadModule(moduleIndex, hideDrawer = true) {
document.querySelector('a.mdl-navigation__link.selected').classList.toggle('selected');
document.querySelector(`a.mdl-navigation__link.mod_${moduleIndex}`).classList.add('selected');
- ModPlayer.loadModule(prefix + moduleName)
+ ModPlayer.loadModule(moduleName.match(/^http/) ? moduleName : prefix + moduleName)
.catch(err => {
toast.show(`Error loading module: ${err}`);
});
diff --git a/js/mod-processor-es5.js b/js/mod-processor-es5.js
index 81f5f32..fbe4c99 100644
--- a/js/mod-processor-es5.js
+++ b/js/mod-processor-es5.js
@@ -1,2 +1,2 @@
-var t=function(t,e,s){void 0===s&&(s=0);for(var i=new Uint8Array(t),o="",a=!1,n=0;!a;++n){var r=i[s+n];(a=0===r)||(o+=String.fromCharCode(r))}return o},e=function(t,e,s){return void 0===e&&(e=0),void 0===s&&(s=!1),new DataView(t).getUint16(e,s)},s=new Array(4);s[0]=new Float32Array(64);for(var i=0;i-1&&!a.done&&this.ticks>=a.delay){var n=this.samples[a.sample];t[i][s]+=n.data[Math.floor(a.samplePos)]*a.volume/64,a.samplePos+=7093789.2/(2*a.period*this.mixingRate),a.done||(n.repeatLength||n.repeatStart?a.samplePos>=n.repeatStart+n.repeatLength&&(a.samplePos=n.repeatStart):a.samplePos>n.length&&(a.samplePos=0,a.done=!0))}}this.filledSamples++,this.newTick=!1}},i.prototype.tick=function(){this.filledSamples>this.samplesPerTick&&(this.newTick=!0,this.ticks++,this.filledSamples=0,this.ticks>this.speed-1&&(this.ticks=0,this.rowRepeat<=0&&this.row++,(this.row>63||this.skipPattern)&&(this.skipPattern=!1,this.jumpPattern>-1?(this.position=this.jumpPattern,this.jumpPattern=-1,this.getNextPattern()):this.getNextPattern(!0)),this.rowJump>-1&&(this.row=this.rowJump,this.rowJump=-1),this.row>63&&(this.row=0),this.decodeRow(),console.log("** next row !",this.row.toString(16).padStart(2,"0"))))},i.prototype.getNextPattern=function(t){t&&this.position++,this.position>this.positions.length-1&&(console.log("Warning: last position reached, going back to 0"),this.position=0);for(var e=0;e>4)-1,a=15&t[2+s],n=t[3+s],r=this.channels[e];r.delay=0,a?14===a?(r.cmd=224+(n>>4),r.data=15&n):(r.cmd=a,r.data=n):r.cmd=0,o>-1&&(3!==r.cmd&&5!==r.cmd&&(r.samplePos=0),r.done=!1,r.sample=o,r.volume=this.samples[o].volume),i&&(r.done=!1,3!==r.cmd&&5!==r.cmd?(r.period=i,r.samplePos=0):r.slideTo=i)}},i.prototype.executeEffect=function(t){try{a[t.cmd](this,t)}catch(e){console.warn("effect not implemented: "+t.cmd.toString(16).padStart(2,"0")+"/"+t.data.toString(16).padStart(2,"0"))}},i.prototype.getInstruments=function(){this.detectMaxSamples(),this.samples=new Array;for(var s=20,i=new Uint8Array(this.buffer),o=0;oa.length&&(a.repeatLength=0,a.repeatStart=0),this.samples.push(a),s+=30}},i.prototype.getPatternData=function(){var t=new Uint8Array(this.buffer,950);this.songLength=t[0];for(var e=2,s=0,i=0;is&&(s=o)}e=this.patternOffset;for(var a=0;a<=s;++a)this.patterns.push(this.buffer.slice(e,e+this.patternLength)),e+=this.patternLength},i.prototype.getSampleData=function(){for(var t=this.patternOffset+this.patterns.length*this.patternLength,e=0;e856&&(e.period=856))},3:function(t,e,s){t.ticks?e.slideTo&&t.ticks?e.periode.slideTo&&(e.period=e.slideTo)):e.period>e.slideTo&&(e.period-=e.slideSpeed,e.period>4;o&&i&&(e.vdepth=i,e.vspeed=o),e.vform>3&&(e.vpos=0)}e.period+=e.vdepth*s[3&e.vform][e.vpos]/63,e.vpos+=e.vspeed,e.vpos>63&&(e.vpos=e.vpos-64)},5:function(t,e){this[3](t,e),this[10](t,e)},6:function(t,e){this[4](t,e),this[10](t,e)},9:function(t,e){t.ticks||(e.samplePos=256*e.data)},10:function(t,e){if(t.ticks){var s=e.data>>4,i=15&e.data;i?s||(e.volume-=i):e.volume+=s,e.volume>63?e.volume=63:e.volume<0&&(e.volume=0)}},11:function(t,e){e.data>=0&&e.data<=t.patterns.length-1&&(t.skipPattern=!0,t.jumpPattern=e.data,t.rowJump=0)},12:function(t,e){t.ticks||(e.volume=e.data,e.volume>63&&(e.volume=63))},13:function(t,e){t.ticks||(t.rowJump=10*((240&e.data)>>4)+(15&e.data),t.skipPattern=!0)},224:function(t,e){t.ticks||(console.log("need to toggle lowPass",!!e.data),t.toggleLowPass(!!e.data))},228:function(t,e){},230:function(t,e){t.ticks||(0===e.data?e.loopInitiated?e.loops===e.loopCount&&(e.loopInitiated=!1):(e.loopInitiated=!0,e.loopStart=t.row,e.loopCount=0):e.loopInitiated&&(t.rowJump=e.loopStart,e.loopCount?e.loops++:(e.loopCount=e.data,e.loops=1)))},233:function(t,e){(t.ticks+1)%e.data||(console.log("retriggering note!",t.ticks+1),e.samplePos=0,e.done=!1)},234:function(t,e){t.ticks||(e.volume+=e.data,e.volume>63&&(e.volume=63))},235:function(t,e){t.ticks||(e.volume-=e.data,e.volume<0&&(e.volume=0))},237:function(t,e){t.ticks||(e.delay=e.data)},238:function(t,e){t.ticks||(t.rowRepeat?t.rowRepeat&&t.rowRepeat--:(t.rowRepeat=e.data,console.log("setting repeat to",t.rowRepeat)))},15:function(t,e){t.ticks||(e.data<32?t.speed=e.data:(t.bpm=e.data,t.calcTickSpeed()))},4095:function(){}};console.time("");
+var t=function(t,e,s){void 0===s&&(s=0);for(var i=new Uint8Array(t),o="",a=!1,n=0;!a;++n){var r=i[s+n];(a=0===r)||(o+=String.fromCharCode(r))}return o},e=function(t,e,s){return void 0===e&&(e=0),void 0===s&&(s=!1),new DataView(t).getUint16(e,s)},s=new Array(4);s[0]=new Float32Array(64);for(var i=0;i-1&&!a.done&&this.ticks>=a.delay){var r=this.samples[a.sample];n[s]+=r.data[Math.floor(a.samplePos)]*a.volume/64,a.samplePos+=7093789.2/(2*a.period*this.mixingRate),a.done||(r.repeatLength||r.repeatStart?a.samplePos>=r.repeatStart+r.repeatLength&&(a.samplePos=r.repeatStart):a.samplePos>r.length&&(a.samplePos=0,a.done=!0))}}this.filledSamples++,this.newTick=!1}},i.prototype.tick=function(){this.filledSamples>this.samplesPerTick&&(this.newTick=!0,this.ticks++,this.filledSamples=0,this.ticks>this.speed-1&&(this.ticks=0,this.rowRepeat<=0&&this.row++,(this.row>63||this.skipPattern)&&(this.skipPattern=!1,this.jumpPattern>-1?(this.position=this.jumpPattern,this.jumpPattern=-1,this.getNextPattern()):this.getNextPattern(!0)),this.rowJump>-1&&(this.row=this.rowJump,this.rowJump=-1),this.row>63&&(this.row=0),this.decodeRow()))},i.prototype.getNextPattern=function(t){t&&this.position++,this.position>this.positions.length-1&&(console.log("Warning: last position reached, going back to 0"),this.position=0);for(var e=0;e>4)-1,a=15&t[2+s],n=t[3+s],r=this.channels[e];r.delay=0,a?14===a?(r.cmd=224+(n>>4),r.data=15&n):(r.cmd=a,r.data=n):r.cmd=0,o>-1&&(3!==r.cmd&&5!==r.cmd&&(r.samplePos=0),r.done=!1,r.sample=o,r.volume=this.samples[o].volume),i&&(r.done=!1,3!==r.cmd&&5!==r.cmd?(r.period=i,r.samplePos=0):r.slideTo=i)}},i.prototype.executeEffect=function(t){try{a[t.cmd](this,t)}catch(e){console.warn("effect not implemented: "+t.cmd.toString(16).padStart(2,"0")+"/"+t.data.toString(16).padStart(2,"0"))}},i.prototype.getInstruments=function(){this.detectMaxSamples(),this.samples=new Array;for(var s=20,i=new Uint8Array(this.buffer),o=0;oa.length&&(a.repeatLength=0,a.repeatStart=0),this.samples.push(a),s+=30}},i.prototype.getPatternData=function(){var t=new Uint8Array(this.buffer,950);this.songLength=t[0];for(var e=2,s=0,i=0;is&&(s=o)}e=this.patternOffset;for(var a=0;a<=s;++a)this.patterns.push(this.buffer.slice(e,e+this.patternLength)),e+=this.patternLength},i.prototype.getSampleData=function(){for(var t=this.patternOffset+this.patterns.length*this.patternLength,e=0;e856&&(e.period=856))},3:function(t,e,s){t.ticks?e.slideTo&&t.ticks?e.periode.slideTo&&(e.period=e.slideTo)):e.period>e.slideTo&&(e.period-=e.slideSpeed,e.period>4;o&&i&&(e.vdepth=i,e.vspeed=o),e.vform>3&&(e.vpos=0)}e.period+=e.vdepth*s[3&e.vform][e.vpos]/63,e.vpos+=e.vspeed,e.vpos>63&&(e.vpos=e.vpos-64)},5:function(t,e){this[3](t,e),this[10](t,e)},6:function(t,e){this[4](t,e),this[10](t,e)},9:function(t,e){t.ticks||(e.samplePos=256*e.data)},10:function(t,e){if(t.ticks){var s=e.data>>4,i=15&e.data;i?s||(e.volume-=i):e.volume+=s,e.volume>63?e.volume=63:e.volume<0&&(e.volume=0)}},11:function(t,e){e.data>=0&&e.data<=t.patterns.length-1&&(t.skipPattern=!0,t.jumpPattern=e.data,t.rowJump=0)},12:function(t,e){t.ticks||(e.volume=e.data,e.volume>63&&(e.volume=63))},13:function(t,e){t.ticks||(t.rowJump=10*((240&e.data)>>4)+(15&e.data),t.skipPattern=!0)},224:function(t,e){t.ticks||(console.log("need to toggle lowPass",!!e.data),t.toggleLowPass(!!e.data))},228:function(t,e){},230:function(t,e){t.ticks||(0===e.data?e.loopInitiated?e.loops===e.loopCount&&(e.loopInitiated=!1):(e.loopInitiated=!0,e.loopStart=t.row,e.loopCount=0):e.loopInitiated&&(t.rowJump=e.loopStart,e.loopCount?e.loops++:(e.loopCount=e.data,e.loops=1)))},233:function(t,e){(t.ticks+1)%e.data||(console.log("retriggering note!",t.ticks+1),e.samplePos=0,e.done=!1)},234:function(t,e){t.ticks||(e.volume+=e.data,e.volume>63&&(e.volume=63))},235:function(t,e){t.ticks||(e.volume-=e.data,e.volume<0&&(e.volume=0))},237:function(t,e){t.ticks||(e.delay=e.data)},238:function(t,e){t.ticks||(t.rowRepeat?t.rowRepeat&&t.rowRepeat--:(t.rowRepeat=e.data,console.log("setting repeat to",t.rowRepeat)))},15:function(t,e){t.ticks||(e.data<32?t.speed=e.data:(t.bpm=e.data,t.calcTickSpeed()))}};
//# sourceMappingURL=modplayer-js.js.map
diff --git a/js/mod-processor.js b/js/mod-processor.js
index 07e572d..55d4713 100644
--- a/js/mod-processor.js
+++ b/js/mod-processor.js
@@ -54,6 +54,7 @@ class PTModuleProcessor extends AudioWorkletProcessor{
switch (event.data.message) {
case 'init':
this.mixingRate = event.data.mixingRate;
+ this.audioWorkletSupport = event.data.audioWorkletSupport;
break;
case 'loadModule':
@@ -81,6 +82,12 @@ class PTModuleProcessor extends AudioWorkletProcessor{
}
});
break;
+
+ case 'speedUp':
+ console.log('speed up', event.data.speedUp);
+ this.speedUp = event.data.speedUp;
+ this.calcTickSpeed();
+ break;
}
}
@@ -90,21 +97,31 @@ class PTModuleProcessor extends AudioWorkletProcessor{
process(inputs, outputs, params) {
if (this.ready && this.playing) {
- this.mix(outputs[0]);
+ this.mix(outputs);
} else {
- this.emptyOutputBuffer(outputs[0]);
+ this.emptyOutputBuffer(outputs);
}
return true;
}
- emptyOutputBuffer(buffers) {
- const length = buffers[0].length,
- chans = buffers.length;
+ emptyOutputBuffer(outputs) {
+ if (this.audioWorkletSupport) {
+ const chans = outputs.length,
+ bufLength = outputs[0][0].length;
- for (let i = 0; i < length; ++i) {
for (let chan = 0; chan < chans; ++chan) {
- buffers[chan][i] = 0.0;
+ for (let i = 0; i < bufLength; ++i) {
+ outputs[chan][0][i] = 0.0;
+ }
+ }
+ } else {
+ const chans = outputs[0].length,
+ bufLength = outputs[0][0].length;
+ for (let chan = 0; chan < chans; ++chan) {
+ for (let i = 0; i < bufLength; ++i) {
+ outputs[0][chan][i] = 0.0;
+ }
}
}
}
@@ -123,6 +140,7 @@ class PTModuleProcessor extends AudioWorkletProcessor{
this.bpm = 125;
// number of ticks before playing next pattern row
this.speed = 6;
+ this.speedUp = 1;
this.position = 0;
this.pattern = 0;
this.row = 0;
@@ -228,29 +246,39 @@ class PTModuleProcessor extends AudioWorkletProcessor{
* Calculates the number of samples needed
*/
calcTickSpeed() {
- this.samplesPerTick = ((this.mixingRate * 60) / this.bpm) / 24;
+ this.samplesPerTick = ((this.mixingRate * 60) / (this.bpm * this.speedUp)) / 24;
}
/**
* ProTracker audio mixer
*
- * @param {Float32Array} buffer Output buffer that should be filled with PCM data
+ * @param {Float32Array} outputs Output buffer that should be filled with PCM data
*
* This method is called each time the buffer should be filled with data
*/
- mix(buffers) {
- const length = buffers[0].length;
+ mix(outputs) {
+ const length = this.audioWorkletSupport && outputs[0][0].length || outputs[0][0].length;
for (let i = 0; i < length; ++i) {
- buffers[0][i] = 0.0;
- buffers[1][i] = 0.0;
-
+ // buffers[0][i] = 0.0;
+ // buffers[1][i] = 0.0;
let outputChannel = 0;
+ if (this.audioWorkletSupport) {
+ outputs[0][0][i] = 0.0;
+ outputs[1][0][i] = 0.0;
+ outputs[2][0][i] = 0.0;
+ outputs[3][0][i] = 0.0;
+ } else {
+ outputs[0][0][i] = 0.0;
+ outputs[0][1][i] = 0.0;
+ }
+
// playing speed test
this.tick();
for (let chan = 0; chan < this.channels.length; ++chan) {
const channel = this.channels[chan];
+ const buffer = this.audioWorkletSupport ? outputs[chan][0] : outputs[0][outputChannel];
// select left/right output depending on module channel:
// voices 0,3 go to left channel, 1,2 go to right channel
outputChannel = outputChannel ^ (chan & 1);
@@ -266,7 +294,7 @@ class PTModuleProcessor extends AudioWorkletProcessor{
const sample = this.samples[channel.sample];
// actually mix audio
- buffers[outputChannel][i] += (sample.data[Math.floor(channel.samplePos)] * channel.volume) / 64.0;
+ buffer[i] += (sample.data[Math.floor(channel.samplePos)] * channel.volume) / 64.0;
const sampleSpeed = 7093789.2 / ((channel.period * 2) * this.mixingRate);
channel.samplePos += sampleSpeed;
@@ -327,7 +355,7 @@ class PTModuleProcessor extends AudioWorkletProcessor{
this.decodeRow();
- console.log('** next row !', this.row.toString(16).padStart(2, "0"));
+ // console.log('** next row !', this.row.toString(16).padStart(2, "0"));
}
}
}
@@ -634,10 +662,8 @@ const Effects = {
y = channel.data & 0x0F;
if (!y) {
- // console.log('volume slide', x);
channel.volume += x;
} else if (!x) {
- // console.log('volume slide', -y);
channel.volume -= y;
}
@@ -666,9 +692,7 @@ const Effects = {
channel.volume = channel.data;
if (channel.volume > 63) {
channel.volume = 63;
- }/* else {
- channel.id === 2 && console.log('volume set to', channel.volume);
- }*/
+ }
}
},
/**
@@ -686,7 +710,6 @@ const Effects = {
0xE0(Module, channel) {
if (!Module.ticks) {
console.log('need to toggle lowPass', !!channel.data);
- // TODO: handle this message in modplayer.js to activate the filter
Module.toggleLowPass(!!channel.data);
}
},
@@ -795,6 +818,4 @@ const Effects = {
}
}
}
-}
-
-console.time('')
+}
\ No newline at end of file
diff --git a/js/modplayer.js b/js/modplayer.js
index 3ce7aea..75c1ac5 100644
--- a/js/modplayer.js
+++ b/js/modplayer.js
@@ -12,8 +12,10 @@ const ModPlayer = {
init(options) {
this.canvas = options.canvas;
this.ctx = this.canvas.getContext('2d');
- this.canvasWidth = (this.canvas.width) / 2;
+ this.audioWorkletSupport = options.audioWorkletSupport;
+ this.canvasWidth = (this.canvas.width) / 4;
this.canvasHeight = this.canvas.height;
+ this.channels = [true, true, true, true];
return this.createContext();
},
@@ -59,42 +61,67 @@ const ModPlayer = {
const soundProcessor = this.isXbox && 'mod-processor-es5.js' || 'mod-processor.js';
return this.context.audioWorklet.addModule(`js/${soundProcessor}`).then(() => {
+ const numAnalysers = this.audioWorkletSupport && 4 || 2;
+
+ // apply a filter
+ this.filterNode = this.context.createBiquadFilter();
+ this.filterNode.frequency.value = 22050;
+
+ // Use 4 inputs that will be used to send each track's data to a separate analyser
+ // NOTE: what should we do if we support more channels (and different mod formats)?
this.workletNode = new AudioWorkletNode(this.context, 'mod-processor', {
- outputChannelCount:[2]
+ outputChannelCount: [1, 1, 1, 1],
+ numberOfInputs: 0,
+ numberOfOutputs: 4
});
+
+ if (!this.audioWorkletSupport) {
+ this.splitter = this.context.createChannelSplitter(numAnalysers);
+ this.filterNode.connect(this.splitter);
+ }
+
this.workletNode.port.onmessage = this.handleMessage.bind(this);
this.postMessage({
message: 'init',
- mixingRate: this.mixingRate
+ mixingRate: this.mixingRate,
+ audioWorkletSupport: this.audioWorkletSupport
});
this.workletNode.port.start();
- this.filterNode = this.context.createBiquadFilter();
- this.filterNode.frequency.value = 22050;
-
- this.workletNode.connect(this.filterNode);
- this.filterNode.connect(this.context.destination);
+ // create four analysers and connect each worklet's input to one
+ this.analysers = new Array();
+
+ for (let i = 0; i < numAnalysers; ++i) {
+ const analyser = this.context.createAnalyser();
+ analyser.fftSize = 256;// Math.pow(2, 11);
+ analyser.minDecibels = -90;
+ analyser.maxDecibels = -10;
+ analyser.smoothingTimeConstant = 0.65;
+ if (this.audioWorkletSupport) {
+ this.workletNode.connect(analyser, i, 0);
+ } else {
+ this.splitter.connect(analyser, i);
+ }
+ this.analysers.push(analyser);
+ }
- // split channels and connect each channel's output
- // to a separate analyzer
- this.analysisSplitter = this.context.createChannelSplitter(2);
- this.filterNode.connect(this.analysisSplitter);
+ if (this.audioWorkletSupport) {
+ this.merger = this.context.createChannelMerger(4);
- this.analyserLeft = this.context.createAnalyser();
+ // merge the channel 0+3 in left channel, 1+2 in right channel
+ this.workletNode.connect(this.merger, 0, 0);
+ this.workletNode.connect(this.merger, 1, 1);
+ this.workletNode.connect(this.merger, 2, 1);
+ this.workletNode.connect(this.merger, 3, 0);
- this.analyserLeft.fftSize = Math.pow(2, 11);
- this.analyserLeft.minDecibels = -96;
- this.analyserLeft.maxDecibels = 0;
- this.analyserLeft.smoothingTimeConstant = 0.85;
+ // finally apply the lowpass filter and send audio to destination
+ this.merger.connect(this.filterNode);
+ } else {
+ this.workletNode.connect(this.filterNode);
+ }
- this.analyserRight = this.context.createAnalyser();
- this.analyserRight.fftSize = Math.pow(2, 11);
- this.analyserRight.minDecibels = -96;
- this.analyserRight.maxDecibels = 0;
- this.analyserRight.smoothingTimeConstant = 0.85;
- this.analysisSplitter.connect(this.analyserLeft, 0);
- this.analysisSplitter.connect(this.analyserRight, 1);
+ this.filterNode.connect(this.context.destination);
});
},
@@ -102,6 +129,13 @@ const ModPlayer = {
this.filterNode.frequency.value = activate && 6000 || 22050;
},
+ setSpeed(speedUp) {
+ this.postMessage({
+ message: 'speedUp',
+ speedUp: speedUp
+ });
+ },
+
handleMessage(message) {
switch (message.data.message) {
case 'moduleLoaded':
@@ -110,6 +144,9 @@ const ModPlayer = {
event.data = message.data.data;
event.data.wasPlaying = this.wasPlaying;
document.dispatchEvent(event);
+ if (!this.playing) {
+ this.renderScope();
+ }
break;
case 'toggleLowPass':
@@ -174,6 +211,12 @@ const ModPlayer = {
message: 'setPlayingChannels',
channels: channels
});
+
+ this.channels = channels;
+
+ if (!this.playing) {
+ this.renderScope();
+ }
},
render() {
@@ -187,37 +230,84 @@ const ModPlayer = {
* render adapted from https://github.com/acarabott/audio-dsp-playground (MIT Licence)
*/
renderScope() {
- const toRender = [
- {
- label: "Left",
- analyser: this.analyserLeft,
- style: "rgba(53, 233, 255, 1)",
- edgeThreshold: 0,
- active: true
- },
- {
- label: "Right",
- analyser: this.analyserRight,
- style: "rgba(53, 233, 255, 1)",
- edgeThreshold: 0,
- active: true
- }];
+ let toRender;
+
+ if (this.audioWorkletSupport) {
+ toRender = [
+ {
+ label: "chan 1",
+ analyser: this.analysers[0],
+ style: "rgba(53, 233, 255, 1)",
+ edgeThreshold: 0,
+ pos: 0,
+ },
+ {
+ label: "chan 2",
+ analyser: this.analysers[1],
+ style: "rgba(53, 233, 255, 1)",
+ edgeThreshold: 0,
+ pos: 1
+ },
+ {
+ label: "chan 3",
+ analyser: this.analysers[2],
+ style: "rgba(53, 233, 255, 1)",
+ edgeThreshold: 0,
+ pos: 2
+ },
+ {
+ label: "chan 4",
+ analyser: this.analysers[3],
+ style: "rgba(53, 233, 255, 1)",
+ edgeThreshold: 0,
+ pos: 3
+ }];
+ } else {
+ toRender = [
+ {
+ label: "left",
+ analyser: this.analysers[0],
+ style: "rgba(53, 233, 255, 1)",
+ edgeThreshold: 0,
+ pos: 0
+ },
+ {
+ label: "right",
+ analyser: this.analysers[1],
+ style: "rgba(53, 233, 255, 1)",
+ edgeThreshold: 0,
+ pos: 3
+ }];
+ }
this.ctx.fillStyle = "transparent";
- this.ctx.clearRect(0, 0, this.canvasWidth * 2, this.canvasHeight);
+ this.ctx.clearRect(0, 0, this.canvasWidth * 4, this.canvasHeight);
- toRender.forEach(({ analyser, style = "rgb(43, 156, 212)", edgeThreshold = 0 }, i) => {
+ toRender.forEach(({ analyser, label, style = "rgb(43, 156, 212)", edgeThreshold = 0, pos }, i) => {
if (analyser === undefined) { return; }
+
+ this.ctx.font = "12px Verdana";
+ this.ctx.fillStyle = "rgba(255,255,255,0.8)";
+ this.ctx.textAlign = "left";
+ // Chan number
+ this.ctx.fillText(label, 46 + pos * this.canvasWidth, 15);
+
+ if (!this.channels[i]) {
+ this.ctx.font = "18px Arial";
+ this.ctx.fillText('MUTE', 40 + pos * this.canvasWidth, 70);
+ return;
+ }
+
const timeData = new Float32Array(analyser.frequencyBinCount);
let risingEdge = 0;
analyser.getFloatTimeDomainData(timeData);
- this.ctx.lineWidth = 2;
this.ctx.strokeStyle = style;
+ this.ctx.fillStyle = style;
- this.ctx.beginPath();
+ // this.ctx.beginPath();
while (timeData[risingEdge] > 0 &&
risingEdge <= this.canvasWidth &&
@@ -238,17 +328,12 @@ const ModPlayer = {
for (let x = risingEdge; x < timeData.length && x - risingEdge < this.canvasWidth; x++) {
const y = this.canvasHeight - (((timeData[x] + 1) / 2) * this.canvasHeight);
- this.ctx.lineTo(x - risingEdge + i * this.canvasWidth, y);
+ // this.ctx.moveTo(x - risingEdge + i * this.canvasWidth, y-1);
+ // this.ctx.lineTo(x - risingEdge + i * this.canvasWidth, y);
+ this.ctx.fillRect(x - risingEdge + pos * this.canvasWidth, y, 1, 1);
}
- this.ctx.stroke();
+ // this.ctx.stroke();
});
-
- // L/R
- this.ctx.fillStyle = "rgba(255,255,255,0.7)";
- this.ctx.font = "11px Verdana";
- this.ctx.textAlign = "left";
- this.ctx.fillText("L", 5, 15);
- this.ctx.fillText("R", 496, 15);
}
}
\ No newline at end of file