Merge pull request #43 from outis1one/claude/fix-linux-pad-registration-62OqF
Fix Linux pad registration, add remap timeout, fix audio clipping
This commit is contained in:
+150
-25
@@ -719,12 +719,13 @@ const NT={C3:130.81,D3:146.83,Eb3:155.56,E3:164.81,F3:174.61,G3:196.00,Ab3:207.6
|
|||||||
|
|
||||||
function playSynth(ac,freq,t,dur,inst,vol,arr){
|
function playSynth(ac,freq,t,dur,inst,vol,arr){
|
||||||
if(t<ac.currentTime-0.1)return;
|
if(t<ac.currentTime-0.1)return;
|
||||||
var v=(vol||0.25)*3*masterVolume;
|
var v=(vol||0.25)*masterVolume;
|
||||||
|
var out=getMasterBus(ac);
|
||||||
if(inst==='strings'){
|
if(inst==='strings'){
|
||||||
var o=ac.createOscillator(),f=ac.createBiquadFilter(),g=ac.createGain();
|
var o=ac.createOscillator(),f=ac.createBiquadFilter(),g=ac.createGain();
|
||||||
o.type='sawtooth';o.frequency.value=freq;
|
o.type='sawtooth';o.frequency.value=freq;
|
||||||
f.type='lowpass';f.frequency.value=Math.min(freq*3,4000);
|
f.type='lowpass';f.frequency.value=Math.min(freq*3,4000);
|
||||||
o.connect(f);f.connect(g);g.connect(ac.destination);
|
o.connect(f);f.connect(g);g.connect(out);
|
||||||
var att=Math.min(0.08,dur*0.15);
|
var att=Math.min(0.08,dur*0.15);
|
||||||
g.gain.setValueAtTime(0.001,t);g.gain.linearRampToValueAtTime(v,t+att);
|
g.gain.setValueAtTime(0.001,t);g.gain.linearRampToValueAtTime(v,t+att);
|
||||||
if(dur>att+0.05)g.gain.setValueAtTime(v*0.8,t+dur*0.7);
|
if(dur>att+0.05)g.gain.setValueAtTime(v*0.8,t+dur*0.7);
|
||||||
@@ -734,7 +735,7 @@ function playSynth(ac,freq,t,dur,inst,vol,arr){
|
|||||||
var o=ac.createOscillator(),f=ac.createBiquadFilter(),g=ac.createGain();
|
var o=ac.createOscillator(),f=ac.createBiquadFilter(),g=ac.createGain();
|
||||||
o.type='square';o.frequency.value=freq;
|
o.type='square';o.frequency.value=freq;
|
||||||
f.type='lowpass';f.frequency.value=Math.min(freq*2,3000);
|
f.type='lowpass';f.frequency.value=Math.min(freq*2,3000);
|
||||||
o.connect(f);f.connect(g);g.connect(ac.destination);
|
o.connect(f);f.connect(g);g.connect(out);
|
||||||
g.gain.setValueAtTime(0.001,t);g.gain.linearRampToValueAtTime(v*0.5,t+0.03);
|
g.gain.setValueAtTime(0.001,t);g.gain.linearRampToValueAtTime(v*0.5,t+0.03);
|
||||||
if(dur>0.08)g.gain.setValueAtTime(v*0.5,t+dur*0.8);
|
if(dur>0.08)g.gain.setValueAtTime(v*0.5,t+dur*0.8);
|
||||||
g.gain.linearRampToValueAtTime(0.001,t+dur);
|
g.gain.linearRampToValueAtTime(0.001,t+dur);
|
||||||
@@ -742,7 +743,7 @@ function playSynth(ac,freq,t,dur,inst,vol,arr){
|
|||||||
}else if(inst==='woodwind'){
|
}else if(inst==='woodwind'){
|
||||||
var o=ac.createOscillator(),g=ac.createGain();
|
var o=ac.createOscillator(),g=ac.createGain();
|
||||||
o.type='triangle';o.frequency.value=freq;
|
o.type='triangle';o.frequency.value=freq;
|
||||||
o.connect(g);g.connect(ac.destination);
|
o.connect(g);g.connect(out);
|
||||||
g.gain.setValueAtTime(0.001,t);g.gain.linearRampToValueAtTime(v*0.45,t+0.05);
|
g.gain.setValueAtTime(0.001,t);g.gain.linearRampToValueAtTime(v*0.45,t+0.05);
|
||||||
if(dur>0.1)g.gain.setValueAtTime(v*0.45,t+dur*0.8);
|
if(dur>0.1)g.gain.setValueAtTime(v*0.45,t+dur*0.8);
|
||||||
g.gain.linearRampToValueAtTime(0.001,t+dur);
|
g.gain.linearRampToValueAtTime(0.001,t+dur);
|
||||||
@@ -750,14 +751,14 @@ function playSynth(ac,freq,t,dur,inst,vol,arr){
|
|||||||
}else if(inst==='bass'){
|
}else if(inst==='bass'){
|
||||||
var o=ac.createOscillator(),g=ac.createGain();
|
var o=ac.createOscillator(),g=ac.createGain();
|
||||||
o.type='sine';o.frequency.value=freq;
|
o.type='sine';o.frequency.value=freq;
|
||||||
o.connect(g);g.connect(ac.destination);
|
o.connect(g);g.connect(out);
|
||||||
g.gain.setValueAtTime(v*0.6,t);
|
g.gain.setValueAtTime(v*0.6,t);
|
||||||
if(dur>0.08)g.gain.setValueAtTime(v*0.6,t+dur*0.8);
|
if(dur>0.08)g.gain.setValueAtTime(v*0.6,t+dur*0.8);
|
||||||
g.gain.linearRampToValueAtTime(0.001,t+dur);
|
g.gain.linearRampToValueAtTime(0.001,t+dur);
|
||||||
o.start(t);o.stop(t+dur+0.02);if(arr)arr.push(o);
|
o.start(t);o.stop(t+dur+0.02);if(arr)arr.push(o);
|
||||||
}else if(inst==='timpani'){
|
}else if(inst==='timpani'){
|
||||||
var o=ac.createOscillator(),g=ac.createGain();
|
var o=ac.createOscillator(),g=ac.createGain();
|
||||||
o.type='sine';o.connect(g);g.connect(ac.destination);
|
o.type='sine';o.connect(g);g.connect(out);
|
||||||
o.frequency.setValueAtTime(freq*1.5,t);
|
o.frequency.setValueAtTime(freq*1.5,t);
|
||||||
o.frequency.exponentialRampToValueAtTime(Math.max(freq*0.5,20),t+dur);
|
o.frequency.exponentialRampToValueAtTime(Math.max(freq*0.5,20),t+dur);
|
||||||
g.gain.setValueAtTime(v*0.8,t);
|
g.gain.setValueAtTime(v*0.8,t);
|
||||||
@@ -767,7 +768,7 @@ function playSynth(ac,freq,t,dur,inst,vol,arr){
|
|||||||
[-6,0,6].forEach(function(d){
|
[-6,0,6].forEach(function(d){
|
||||||
var o=ac.createOscillator(),g=ac.createGain();
|
var o=ac.createOscillator(),g=ac.createGain();
|
||||||
o.type='sine';o.frequency.value=freq+d;
|
o.type='sine';o.frequency.value=freq+d;
|
||||||
o.connect(g);g.connect(ac.destination);
|
o.connect(g);g.connect(out);
|
||||||
var at2=Math.min(0.1,dur*0.2);
|
var at2=Math.min(0.1,dur*0.2);
|
||||||
g.gain.setValueAtTime(0.001,t);g.gain.linearRampToValueAtTime(v*0.3,t+at2);
|
g.gain.setValueAtTime(0.001,t);g.gain.linearRampToValueAtTime(v*0.3,t+at2);
|
||||||
if(dur>at2+0.05)g.gain.setValueAtTime(v*0.3,t+dur*0.8);
|
if(dur>at2+0.05)g.gain.setValueAtTime(v*0.3,t+dur*0.8);
|
||||||
@@ -4884,22 +4885,47 @@ document.getElementById("fi").addEventListener("change",async e=>{
|
|||||||
// Linux/Chrome requires a user gesture before AudioContext will run.
|
// Linux/Chrome requires a user gesture before AudioContext will run.
|
||||||
// We create one shared context and resume it on every user interaction.
|
// We create one shared context and resume it on every user interaction.
|
||||||
let _ac=null;
|
let _ac=null;
|
||||||
|
// Master compressor nodes per context — prevent clipping when many drums hit simultaneously
|
||||||
|
let gameCompressor=null,previewCompressor=null;
|
||||||
|
|
||||||
|
function makeMasterBus(ac){
|
||||||
|
var comp=ac.createDynamicsCompressor();
|
||||||
|
comp.threshold.value=-12; // start compressing at -12dBFS
|
||||||
|
comp.knee.value=6;
|
||||||
|
comp.ratio.value=4;
|
||||||
|
comp.attack.value=0.003;
|
||||||
|
comp.release.value=0.15;
|
||||||
|
comp.connect(ac.destination);
|
||||||
|
return comp;
|
||||||
|
}
|
||||||
|
|
||||||
function getAc(prev=false){
|
function getAc(prev=false){
|
||||||
if(prev){
|
if(prev){
|
||||||
if(!previewAc||previewAc.state==="closed")previewAc=new(window.AudioContext||window.webkitAudioContext)();
|
if(!previewAc||previewAc.state==="closed"){
|
||||||
|
previewAc=new(window.AudioContext||window.webkitAudioContext)();
|
||||||
|
previewCompressor=makeMasterBus(previewAc);
|
||||||
|
}
|
||||||
previewAc.resume();
|
previewAc.resume();
|
||||||
return previewAc;
|
return previewAc;
|
||||||
}
|
}
|
||||||
if(!gameAc||gameAc.state==="closed")gameAc=new(window.AudioContext||window.webkitAudioContext)();
|
if(!gameAc||gameAc.state==="closed"){
|
||||||
|
gameAc=new(window.AudioContext||window.webkitAudioContext)();
|
||||||
|
gameCompressor=makeMasterBus(gameAc);
|
||||||
|
}
|
||||||
gameAc.resume();
|
gameAc.resume();
|
||||||
return gameAc;
|
return gameAc;
|
||||||
}
|
}
|
||||||
|
// Return the master bus (compressor) for a given context so sounds route through it
|
||||||
|
function getMasterBus(ac){
|
||||||
|
if(ac===previewAc)return previewCompressor||ac.destination;
|
||||||
|
return gameCompressor||ac.destination;
|
||||||
|
}
|
||||||
// Unlock audio on ANY user interaction — click, touch, keydown
|
// Unlock audio on ANY user interaction — click, touch, keydown
|
||||||
function unlockAudio(){
|
function unlockAudio(){
|
||||||
[previewAc,gameAc].forEach(ac=>{if(ac&&ac.state==="suspended")ac.resume();});
|
[previewAc,gameAc].forEach(ac=>{if(ac&&ac.state==="suspended")ac.resume();});
|
||||||
// create them early so they're ready
|
// create them early so they're ready
|
||||||
if(!previewAc){previewAc=new(window.AudioContext||window.webkitAudioContext)();previewAc.resume();}
|
if(!previewAc){previewAc=new(window.AudioContext||window.webkitAudioContext)();previewCompressor=makeMasterBus(previewAc);previewAc.resume();}
|
||||||
if(!gameAc){gameAc=new(window.AudioContext||window.webkitAudioContext)();gameAc.resume();}
|
if(!gameAc){gameAc=new(window.AudioContext||window.webkitAudioContext)();gameCompressor=makeMasterBus(gameAc);gameAc.resume();}
|
||||||
}
|
}
|
||||||
["click","touchstart","keydown","mousedown"].forEach(ev=>document.addEventListener(ev,unlockAudio,{once:false}));
|
["click","touchstart","keydown","mousedown"].forEach(ev=>document.addEventListener(ev,unlockAudio,{once:false}));
|
||||||
|
|
||||||
@@ -6768,13 +6794,16 @@ function draw(){
|
|||||||
draw();
|
draw();
|
||||||
|
|
||||||
// ── DRUM SOUNDS ────────────────────────────────────────
|
// ── DRUM SOUNDS ────────────────────────────────────────
|
||||||
|
// Volumes are normalized to 0-1 range; master bus compressor handles peaks.
|
||||||
function playDrumAt(ac,lane,t,vol,arr){
|
function playDrumAt(ac,lane,t,vol,arr){
|
||||||
vol=vol*masterVolume;
|
// Scale raw vol (0-8) down to 0-1 range, then apply masterVolume
|
||||||
|
vol=(vol/8)*masterVolume;
|
||||||
const type=LANES[lane].type;
|
const type=LANES[lane].type;
|
||||||
|
const out=getMasterBus(ac);
|
||||||
function mk(n){if(arr)arr.push(n);}
|
function mk(n){if(arr)arr.push(n);}
|
||||||
if(type==="kick"||type==="bass"){
|
if(type==="kick"||type==="bass"){
|
||||||
const o=ac.createOscillator(),g=ac.createGain();
|
const o=ac.createOscillator(),g=ac.createGain();
|
||||||
o.connect(g);g.connect(ac.destination);
|
o.connect(g);g.connect(out);
|
||||||
o.frequency.setValueAtTime(180,t);o.frequency.exponentialRampToValueAtTime(40,t+0.15);
|
o.frequency.setValueAtTime(180,t);o.frequency.exponentialRampToValueAtTime(40,t+0.15);
|
||||||
g.gain.setValueAtTime(vol,t);g.gain.exponentialRampToValueAtTime(0.001,t+0.3);
|
g.gain.setValueAtTime(vol,t);g.gain.exponentialRampToValueAtTime(0.001,t+0.3);
|
||||||
o.start(t);o.stop(t+0.35);mk(o);
|
o.start(t);o.stop(t+0.35);mk(o);
|
||||||
@@ -6782,18 +6811,18 @@ function playDrumAt(ac,lane,t,vol,arr){
|
|||||||
const sz=ac.sampleRate*0.12,buf=ac.createBuffer(1,sz,ac.sampleRate),d=buf.getChannelData(0);
|
const sz=ac.sampleRate*0.12,buf=ac.createBuffer(1,sz,ac.sampleRate),d=buf.getChannelData(0);
|
||||||
for(let i=0;i<sz;i++)d[i]=Math.random()*2-1;
|
for(let i=0;i<sz;i++)d[i]=Math.random()*2-1;
|
||||||
const s=ac.createBufferSource(),f=ac.createBiquadFilter(),g=ac.createGain();
|
const s=ac.createBufferSource(),f=ac.createBiquadFilter(),g=ac.createGain();
|
||||||
f.type="highpass";f.frequency.value=1500;s.buffer=buf;s.connect(f);f.connect(g);g.connect(ac.destination);
|
f.type="highpass";f.frequency.value=1500;s.buffer=buf;s.connect(f);f.connect(g);g.connect(out);
|
||||||
g.gain.setValueAtTime(vol*.75,t);g.gain.exponentialRampToValueAtTime(0.001,t+0.12);
|
g.gain.setValueAtTime(vol*.75,t);g.gain.exponentialRampToValueAtTime(0.001,t+0.12);
|
||||||
s.start(t);s.stop(t+0.15);mk(s);
|
s.start(t);s.stop(t+0.15);mk(s);
|
||||||
const o=ac.createOscillator(),g2=ac.createGain();
|
const o=ac.createOscillator(),g2=ac.createGain();
|
||||||
o.connect(g2);g2.connect(ac.destination);o.frequency.value=180;
|
o.connect(g2);g2.connect(out);o.frequency.value=180;
|
||||||
g2.gain.setValueAtTime(vol*.35,t);g2.gain.exponentialRampToValueAtTime(0.001,t+0.07);
|
g2.gain.setValueAtTime(vol*.35,t);g2.gain.exponentialRampToValueAtTime(0.001,t+0.07);
|
||||||
o.start(t);o.stop(t+0.08);mk(o);
|
o.start(t);o.stop(t+0.08);mk(o);
|
||||||
} else if(type==="hihat"){
|
} else if(type==="hihat"){
|
||||||
const sz=ac.sampleRate*0.05,buf=ac.createBuffer(1,sz,ac.sampleRate),d=buf.getChannelData(0);
|
const sz=ac.sampleRate*0.05,buf=ac.createBuffer(1,sz,ac.sampleRate),d=buf.getChannelData(0);
|
||||||
for(let i=0;i<sz;i++)d[i]=Math.random()*2-1;
|
for(let i=0;i<sz;i++)d[i]=Math.random()*2-1;
|
||||||
const s=ac.createBufferSource(),f=ac.createBiquadFilter(),g=ac.createGain();
|
const s=ac.createBufferSource(),f=ac.createBiquadFilter(),g=ac.createGain();
|
||||||
f.type="highpass";f.frequency.value=7000;s.buffer=buf;s.connect(f);f.connect(g);g.connect(ac.destination);
|
f.type="highpass";f.frequency.value=7000;s.buffer=buf;s.connect(f);f.connect(g);g.connect(out);
|
||||||
g.gain.setValueAtTime(vol*.5,t);g.gain.exponentialRampToValueAtTime(0.001,t+0.05);
|
g.gain.setValueAtTime(vol*.5,t);g.gain.exponentialRampToValueAtTime(0.001,t+0.05);
|
||||||
s.start(t);s.stop(t+0.06);mk(s);
|
s.start(t);s.stop(t+0.06);mk(s);
|
||||||
} else if(type==="crash"){
|
} else if(type==="crash"){
|
||||||
@@ -6801,7 +6830,7 @@ function playDrumAt(ac,lane,t,vol,arr){
|
|||||||
const sz=ac.sampleRate*0.4,buf=ac.createBuffer(1,sz,ac.sampleRate),d=buf.getChannelData(0);
|
const sz=ac.sampleRate*0.4,buf=ac.createBuffer(1,sz,ac.sampleRate),d=buf.getChannelData(0);
|
||||||
for(let i=0;i<sz;i++)d[i]=Math.random()*2-1;
|
for(let i=0;i<sz;i++)d[i]=Math.random()*2-1;
|
||||||
const s=ac.createBufferSource(),f=ac.createBiquadFilter(),g=ac.createGain();
|
const s=ac.createBufferSource(),f=ac.createBiquadFilter(),g=ac.createGain();
|
||||||
f.type="bandpass";f.frequency.value=4000;f.Q.value=0.5;s.buffer=buf;s.connect(f);f.connect(g);g.connect(ac.destination);
|
f.type="bandpass";f.frequency.value=4000;f.Q.value=0.5;s.buffer=buf;s.connect(f);f.connect(g);g.connect(out);
|
||||||
g.gain.setValueAtTime(vol*.6,t);g.gain.exponentialRampToValueAtTime(0.001,t+0.4);
|
g.gain.setValueAtTime(vol*.6,t);g.gain.exponentialRampToValueAtTime(0.001,t+0.4);
|
||||||
s.start(t);s.stop(t+0.45);mk(s);
|
s.start(t);s.stop(t+0.45);mk(s);
|
||||||
} else if(type==="ride"){
|
} else if(type==="ride"){
|
||||||
@@ -6809,18 +6838,18 @@ function playDrumAt(ac,lane,t,vol,arr){
|
|||||||
const sz=ac.sampleRate*0.15,buf=ac.createBuffer(1,sz,ac.sampleRate),d=buf.getChannelData(0);
|
const sz=ac.sampleRate*0.15,buf=ac.createBuffer(1,sz,ac.sampleRate),d=buf.getChannelData(0);
|
||||||
for(let i=0;i<sz;i++)d[i]=Math.random()*2-1;
|
for(let i=0;i<sz;i++)d[i]=Math.random()*2-1;
|
||||||
const s=ac.createBufferSource(),f=ac.createBiquadFilter(),g=ac.createGain();
|
const s=ac.createBufferSource(),f=ac.createBiquadFilter(),g=ac.createGain();
|
||||||
f.type="bandpass";f.frequency.value=6000;f.Q.value=1.5;s.buffer=buf;s.connect(f);f.connect(g);g.connect(ac.destination);
|
f.type="bandpass";f.frequency.value=6000;f.Q.value=1.5;s.buffer=buf;s.connect(f);f.connect(g);g.connect(out);
|
||||||
g.gain.setValueAtTime(vol*.45,t);g.gain.exponentialRampToValueAtTime(0.001,t+0.12);
|
g.gain.setValueAtTime(vol*.45,t);g.gain.exponentialRampToValueAtTime(0.001,t+0.12);
|
||||||
s.start(t);s.stop(t+0.15);mk(s);
|
s.start(t);s.stop(t+0.15);mk(s);
|
||||||
// Add a subtle pitched ping
|
// Add a subtle pitched ping
|
||||||
const o=ac.createOscillator(),g2=ac.createGain();
|
const o=ac.createOscillator(),g2=ac.createGain();
|
||||||
o.type="triangle";o.connect(g2);g2.connect(ac.destination);o.frequency.value=800;
|
o.type="triangle";o.connect(g2);g2.connect(out);o.frequency.value=800;
|
||||||
g2.gain.setValueAtTime(vol*.15,t);g2.gain.exponentialRampToValueAtTime(0.001,t+0.08);
|
g2.gain.setValueAtTime(vol*.15,t);g2.gain.exponentialRampToValueAtTime(0.001,t+0.08);
|
||||||
o.start(t);o.stop(t+0.1);mk(o);
|
o.start(t);o.stop(t+0.1);mk(o);
|
||||||
} else { // tom / tom2 / tom3
|
} else { // tom / tom2 / tom3
|
||||||
const freq=type==="tom"?160:type==="tom2"?120:90;
|
const freq=type==="tom"?160:type==="tom2"?120:90;
|
||||||
const o=ac.createOscillator(),g=ac.createGain();
|
const o=ac.createOscillator(),g=ac.createGain();
|
||||||
o.connect(g);g.connect(ac.destination);
|
o.connect(g);g.connect(out);
|
||||||
o.frequency.setValueAtTime(freq,t);o.frequency.exponentialRampToValueAtTime(freq*0.4,t+0.2);
|
o.frequency.setValueAtTime(freq,t);o.frequency.exponentialRampToValueAtTime(freq*0.4,t+0.2);
|
||||||
g.gain.setValueAtTime(vol*.85,t);g.gain.exponentialRampToValueAtTime(0.001,t+0.25);
|
g.gain.setValueAtTime(vol*.85,t);g.gain.exponentialRampToValueAtTime(0.001,t+0.25);
|
||||||
o.start(t);o.stop(t+0.28);mk(o);
|
o.start(t);o.stop(t+0.28);mk(o);
|
||||||
@@ -6844,16 +6873,44 @@ function togglePause(){
|
|||||||
|
|
||||||
// ── REMAP ─────────────────────────────────────────────
|
// ── REMAP ─────────────────────────────────────────────
|
||||||
var remapLanes=LANES.map(function(ln,i){return{l:i,name:ln.label.toUpperCase(),color:ln.color};});
|
var remapLanes=LANES.map(function(ln,i){return{l:i,name:ln.label.toUpperCase(),color:ln.color};});
|
||||||
|
var remapTimeoutId=null,remapCountdownId=null,remapSecsLeft=5;
|
||||||
|
|
||||||
|
function remapStartTimer(){
|
||||||
|
clearTimeout(remapTimeoutId);clearInterval(remapCountdownId);
|
||||||
|
remapSecsLeft=5;
|
||||||
|
remapCountdownId=setInterval(function(){
|
||||||
|
remapSecsLeft--;
|
||||||
|
var el=document.getElementById("remap-status");
|
||||||
|
if(el){
|
||||||
|
var rl=remapLanes[remapStep];
|
||||||
|
var hint=remapStep===4?" (or press foot pedal)":"";
|
||||||
|
el.textContent="Step "+(remapStep+1)+" of "+remapLanes.length+hint+" — skipping in "+remapSecsLeft+"s";
|
||||||
|
}
|
||||||
|
if(remapSecsLeft<=0){clearInterval(remapCountdownId);remapSkipStep();}
|
||||||
|
},1000);
|
||||||
|
remapTimeoutId=setTimeout(function(){clearInterval(remapCountdownId);remapSkipStep();},5000);
|
||||||
|
}
|
||||||
|
function remapStopTimer(){
|
||||||
|
clearTimeout(remapTimeoutId);clearInterval(remapCountdownId);
|
||||||
|
}
|
||||||
|
function remapSkipStep(){
|
||||||
|
// No pad pressed in time — skip this lane (leave it unmapped)
|
||||||
|
remapStep++;
|
||||||
|
showRemapStep();
|
||||||
|
}
|
||||||
|
|
||||||
function startRemap(){
|
function startRemap(){
|
||||||
remapping=true;remapStep=0;remapResult={};
|
remapping=true;remapStep=0;remapResult={};
|
||||||
document.getElementById("remap-overlay").classList.add("show");
|
document.getElementById("remap-overlay").classList.add("show");
|
||||||
showRemapStep();
|
showRemapStep();
|
||||||
}
|
}
|
||||||
function cancelRemap(){
|
function cancelRemap(){
|
||||||
|
remapStopTimer();
|
||||||
remapping=false;document.getElementById("remap-overlay").classList.remove("show");
|
remapping=false;document.getElementById("remap-overlay").classList.remove("show");
|
||||||
}
|
}
|
||||||
function showRemapStep(){
|
function showRemapStep(){
|
||||||
if(remapStep>=remapLanes.length){
|
if(remapStep>=remapLanes.length){
|
||||||
|
remapStopTimer();
|
||||||
// Done — save mapping
|
// Done — save mapping
|
||||||
gpMap={};
|
gpMap={};
|
||||||
for(var k in remapResult)gpMap[k]=remapResult[k];
|
for(var k in remapResult)gpMap[k]=remapResult[k];
|
||||||
@@ -6864,12 +6921,15 @@ function showRemapStep(){
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var rl=remapLanes[remapStep];
|
var rl=remapLanes[remapStep];
|
||||||
|
var hint=remapStep===4?" (or press foot pedal)":"";
|
||||||
document.getElementById("remap-prompt").innerHTML="Hit your <span style='color:"+rl.color+";font-size:24px'>"+rl.name+"</span> pad";
|
document.getElementById("remap-prompt").innerHTML="Hit your <span style='color:"+rl.color+";font-size:24px'>"+rl.name+"</span> pad";
|
||||||
document.getElementById("remap-status").textContent="Step "+(remapStep+1)+" of "+remapLanes.length+(remapStep===4?" (or press foot pedal)":"");
|
document.getElementById("remap-status").textContent="Step "+(remapStep+1)+" of "+remapLanes.length+hint+" — skipping in 5s";
|
||||||
|
remapStartTimer();
|
||||||
}
|
}
|
||||||
var remapCooldown=false;
|
var remapCooldown=false;
|
||||||
function handleRemapButton(bi){
|
function handleRemapButton(bi){
|
||||||
if(!remapping||remapCooldown)return false;
|
if(!remapping||remapCooldown)return false;
|
||||||
|
remapStopTimer();
|
||||||
remapResult[bi]=remapLanes[remapStep].l;
|
remapResult[bi]=remapLanes[remapStep].l;
|
||||||
remapStep++;
|
remapStep++;
|
||||||
remapCooldown=true;
|
remapCooldown=true;
|
||||||
@@ -6885,16 +6945,40 @@ var customKeyMap=JSON.parse(localStorage.getItem("drgameKeyMap")||"null");
|
|||||||
if(customKeyMap){
|
if(customKeyMap){
|
||||||
LANES.forEach(function(ln,i){if(customKeyMap[i]!==undefined)ln.key=customKeyMap[i];});
|
LANES.forEach(function(ln,i){if(customKeyMap[i]!==undefined)ln.key=customKeyMap[i];});
|
||||||
}
|
}
|
||||||
|
var keyRemapTimeoutId=null,keyRemapCountdownId=null,keyRemapSecsLeft=5;
|
||||||
|
|
||||||
|
function keyRemapStartTimer(){
|
||||||
|
clearTimeout(keyRemapTimeoutId);clearInterval(keyRemapCountdownId);
|
||||||
|
keyRemapSecsLeft=5;
|
||||||
|
keyRemapCountdownId=setInterval(function(){
|
||||||
|
keyRemapSecsLeft--;
|
||||||
|
var el=document.getElementById("keyremap-status");
|
||||||
|
if(el)el.textContent="Step "+(keyRemapStep+1)+" of "+LANES.length+" — skipping in "+keyRemapSecsLeft+"s";
|
||||||
|
if(keyRemapSecsLeft<=0){clearInterval(keyRemapCountdownId);keyRemapSkipStep();}
|
||||||
|
},1000);
|
||||||
|
keyRemapTimeoutId=setTimeout(function(){clearInterval(keyRemapCountdownId);keyRemapSkipStep();},5000);
|
||||||
|
}
|
||||||
|
function keyRemapStopTimer(){
|
||||||
|
clearTimeout(keyRemapTimeoutId);clearInterval(keyRemapCountdownId);
|
||||||
|
}
|
||||||
|
function keyRemapSkipStep(){
|
||||||
|
// No key pressed in time — skip this lane (keep existing key)
|
||||||
|
keyRemapStep++;
|
||||||
|
showKeyRemapStep();
|
||||||
|
}
|
||||||
|
|
||||||
function startKeyRemap(){
|
function startKeyRemap(){
|
||||||
keyRemapping=true;keyRemapStep=0;keyRemapResult={};
|
keyRemapping=true;keyRemapStep=0;keyRemapResult={};
|
||||||
document.getElementById("keyremap-overlay").classList.add("show");
|
document.getElementById("keyremap-overlay").classList.add("show");
|
||||||
showKeyRemapStep();
|
showKeyRemapStep();
|
||||||
}
|
}
|
||||||
function cancelKeyRemap(){
|
function cancelKeyRemap(){
|
||||||
|
keyRemapStopTimer();
|
||||||
keyRemapping=false;document.getElementById("keyremap-overlay").classList.remove("show");
|
keyRemapping=false;document.getElementById("keyremap-overlay").classList.remove("show");
|
||||||
}
|
}
|
||||||
function showKeyRemapStep(){
|
function showKeyRemapStep(){
|
||||||
if(keyRemapStep>=LANES.length){
|
if(keyRemapStep>=LANES.length){
|
||||||
|
keyRemapStopTimer();
|
||||||
// Done — apply and save
|
// Done — apply and save
|
||||||
LANES.forEach(function(ln,i){if(keyRemapResult[i]!==undefined)ln.key=keyRemapResult[i];});
|
LANES.forEach(function(ln,i){if(keyRemapResult[i]!==undefined)ln.key=keyRemapResult[i];});
|
||||||
var map={};LANES.forEach(function(ln,i){map[i]=ln.key;});
|
var map={};LANES.forEach(function(ln,i){map[i]=ln.key;});
|
||||||
@@ -6907,7 +6991,8 @@ function showKeyRemapStep(){
|
|||||||
}
|
}
|
||||||
var ln=LANES[keyRemapStep];
|
var ln=LANES[keyRemapStep];
|
||||||
document.getElementById("keyremap-prompt").innerHTML="Press key for <span style='color:"+ln.color+";font-size:24px'>"+ln.label.toUpperCase()+"</span>";
|
document.getElementById("keyremap-prompt").innerHTML="Press key for <span style='color:"+ln.color+";font-size:24px'>"+ln.label.toUpperCase()+"</span>";
|
||||||
document.getElementById("keyremap-status").textContent="Step "+(keyRemapStep+1)+" of "+LANES.length+" (press any key)";
|
document.getElementById("keyremap-status").textContent="Step "+(keyRemapStep+1)+" of "+LANES.length+" — skipping in 5s";
|
||||||
|
keyRemapStartTimer();
|
||||||
}
|
}
|
||||||
var keyRemapCooldown=false;
|
var keyRemapCooldown=false;
|
||||||
window.addEventListener("keydown",function(e){
|
window.addEventListener("keydown",function(e){
|
||||||
@@ -6917,6 +7002,7 @@ window.addEventListener("keydown",function(e){
|
|||||||
if(e.key==="Escape"){cancelKeyRemap();e.preventDefault();return;}
|
if(e.key==="Escape"){cancelKeyRemap();e.preventDefault();return;}
|
||||||
if(keyRemapCooldown)return;
|
if(keyRemapCooldown)return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
keyRemapStopTimer();
|
||||||
keyRemapResult[keyRemapStep]=e.key===" "?" ":e.key.toLowerCase();
|
keyRemapResult[keyRemapStep]=e.key===" "?" ":e.key.toLowerCase();
|
||||||
keyRemapStep++;
|
keyRemapStep++;
|
||||||
keyRemapCooldown=true;
|
keyRemapCooldown=true;
|
||||||
@@ -6952,10 +7038,22 @@ function autoDetectGamepad(gp){
|
|||||||
for(var j=5;j<lanesToEnable;j++)activePads[j]=true;
|
for(var j=5;j<lanesToEnable;j++)activePads[j]=true;
|
||||||
}
|
}
|
||||||
buildPadToggles();
|
buildPadToggles();
|
||||||
|
// Initialize prevBtns from actual current button state to avoid spurious triggers on Linux
|
||||||
|
// (on Linux/Chrome, gamepad may connect with buttons already in a non-zero state)
|
||||||
|
gp.buttons.forEach((btn,bi)=>{prevBtns[bi]=btn.pressed||(btn.value>0.5);});
|
||||||
|
// Initialize axis tracking for Linux (some drums report pads as axis changes, not buttons)
|
||||||
|
if(!prevAxes[gp.index])prevAxes[gp.index]=Array.from(gp.axes||[]);
|
||||||
var gpd=document.getElementById("gpd");
|
var gpd=document.getElementById("gpd");
|
||||||
if(gpd)gpd.textContent="🎮 "+gp.id.slice(0,25)+" ("+nButtons+" buttons)";
|
if(gpd)gpd.textContent="🎮 "+gp.id.slice(0,25)+" ("+nButtons+" btns, "+gp.axes.length+" axes)";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Linux: some USB drum kits report pads as axis changes rather than buttons.
|
||||||
|
// Track previous axis values per gamepad index.
|
||||||
|
var prevAxes={};
|
||||||
|
|
||||||
|
// Map axis index to a virtual button index (offset past real buttons)
|
||||||
|
var AXIS_BTN_OFFSET=32;
|
||||||
|
|
||||||
function pollGP(){
|
function pollGP(){
|
||||||
const gps=navigator.getGamepads?navigator.getGamepads():[];
|
const gps=navigator.getGamepads?navigator.getGamepads():[];
|
||||||
let found=false;
|
let found=false;
|
||||||
@@ -6963,20 +7061,47 @@ function pollGP(){
|
|||||||
if(!gp)continue;found=true;
|
if(!gp)continue;found=true;
|
||||||
if(!gpAutoDetected)autoDetectGamepad(gp);
|
if(!gpAutoDetected)autoDetectGamepad(gp);
|
||||||
else document.getElementById("gpd").textContent="🎮 "+gp.id.slice(0,20);
|
else document.getElementById("gpd").textContent="🎮 "+gp.id.slice(0,20);
|
||||||
|
|
||||||
var remapHandled=false;
|
var remapHandled=false;
|
||||||
|
|
||||||
|
// ── Button handling ──────────────────────────────
|
||||||
gp.buttons.forEach((btn,bi)=>{
|
gp.buttons.forEach((btn,bi)=>{
|
||||||
if(btn.pressed&&!prevBtns[bi]){
|
// Use btn.value > 0.5 as fallback for analog buttons (common on Linux)
|
||||||
|
var isPressed=btn.pressed||(btn.value>0.5);
|
||||||
|
if(isPressed&&!prevBtns[bi]){
|
||||||
if(remapping){
|
if(remapping){
|
||||||
if(!remapHandled){remapHandled=true;handleRemapButton(bi);}
|
if(!remapHandled){remapHandled=true;handleRemapButton(bi);}
|
||||||
}
|
}
|
||||||
else if(!paused&&gpMap[bi]!==undefined)triggerPad(gpMap[bi]);
|
else if(!paused&&gpMap[bi]!==undefined)triggerPad(gpMap[bi]);
|
||||||
}
|
}
|
||||||
prevBtns[bi]=btn.pressed;
|
prevBtns[bi]=isPressed;
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Axis handling (Linux: Wii drums may report hits as axis transitions) ──
|
||||||
|
var pa=prevAxes[gp.index];
|
||||||
|
if(!pa)pa=prevAxes[gp.index]=Array.from(gp.axes||[]);
|
||||||
|
gp.axes.forEach((val,ai)=>{
|
||||||
|
var wasActive=(pa[ai]>0.5)||(pa[ai]<-0.5);
|
||||||
|
var isActive=(val>0.5)||(val<-0.5);
|
||||||
|
if(isActive&&!wasActive){
|
||||||
|
var vbi=AXIS_BTN_OFFSET+ai; // virtual button index for this axis
|
||||||
|
if(remapping){
|
||||||
|
if(!remapHandled){remapHandled=true;handleRemapButton(vbi);}
|
||||||
|
}
|
||||||
|
else if(!paused&&gpMap[vbi]!==undefined)triggerPad(gpMap[vbi]);
|
||||||
|
}
|
||||||
|
pa[ai]=val;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if(!found)document.getElementById("gpd").textContent="No gamepad";
|
if(!found)document.getElementById("gpd").textContent="No gamepad";
|
||||||
requestAnimationFrame(pollGP);
|
requestAnimationFrame(pollGP);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Also listen for gamepadconnected to catch late-connecting gamepads on Linux
|
||||||
|
window.addEventListener("gamepadconnected",function(e){
|
||||||
|
unlockAudio();
|
||||||
|
if(!gpAutoDetected)autoDetectGamepad(e.gamepad);
|
||||||
|
});
|
||||||
pollGP();
|
pollGP();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user