Audiokontextové demo. Hlasitost je na Chrome/Android poloviční, na Firefoxu praská

Vytvořil jsem čistě CSS/JS klavírní klaviaturu pomocí AudioContext objekt, ale mám dva problémy související s playTone funkce:

  1. V Chrome/androidu (v.89.0.4389.105/Android 10 ) zdá se, že hlasitost se sníží na polovinu při každém stisknutí tlačítka:po několika zahraných tónech už není hlasitost slyšet.

  2. Ve Firefoxu (v.88/MacOS 10.15.7 ) Na konci každé stisknuté klávesy slyším praskání.

Pro srovnání na nejnovějším Chrome/MacOS to zní dobře.

const noteFrequencies = {
   'C1' : 261.63,
   'C1#': 277.18,
   'D1' : 293.66,
   'D1#': 311.13,
   'E1' : 329.63,
   'F1' : 349.23,
   'F1#': 369.99,
   'G1' : 392.00,
   'G1#': 415.30,
   'A1' : 440.00,
   'A1#': 466.16,
   'B1' : 493.88
}


function playTone(note, duration = 1) {

   let ac = new AudioContext();
   let oscl = ac.createOscillator();
   let gain = ac.createGain();

   gain.connect(ac.destination);
   oscl.connect(gain);
   
   oscl.frequency.value = noteFrequencies[note];
   oscl.type = "sine"
   gain.gain.linearRampToValueAtTime(.0001, ac.currentTime + duration);
   oscl.start();
   oscl.stop(ac.currentTime + duration);
}


let notelist = document.querySelector('.piano');

notelist.addEventListener('click', (ev) => {
  let tgt = ev.target;
  let tone;
  if (tgt.matches('[data-tone]')) {
     tone = tgt.getAttribute('data-tone');
     playTone(tone);   
  }
});
* {
   box-sizing: border-box;
   font-family: "Lobster Two";
   color: #555666;
}

h1 {
   text-align: center;
   font-size: 5rem;
   margin-bottom: 4rem;
}

.piano {
   position   : relative;
   display    : flex;
   width      : max-content;
   list-style : none;
   margin     : 0 auto;
   padding    : 0;
   align-items: flex-start;
}

.piano li {      
   --ar   : .2;   
   display: inherit;
   cursor : pointer;
   color  : transparent;
   user-select : none;
   aspect-ratio: var(--ar);
}

@supports (not (aspect-ratio: 1)) {
  .piano li::before {
      content: "";
      padding-top: calc(100% / var(--ar));
   }
}


li:not(.diesis) {
  width         : max(50px, 4vw);
  border-left   : 1px solid #c4c4c8;
  border-bottom : 1px solid #c4c4c8;
  border-radius : 0 0 4px 4px;
  background    : linear-gradient(to bottom, #f2f2f5, #fff);
  box-shadow    : 0 0 5px #ccc inset;
}


li:not(.diesis):active {
  border-top    : 1px solid #bbb;
  border-left   : 1px solid #ccc;
  border-bottom : 1px solid #ccc;
  background    : linear-gradient(to bottom, #fff, #f2f2f5);
  box-shadow    :
     2px 0 3px rgba(0,0,0,0.1) inset,
     -4px 2px 10px rgba(0,0,0,.02) inset; 
}


li.diesis {   
  position      : relative;
  z-index       : 1;
   
  --w           : max(30px, 2.5vw);
  width         : var(--w);
  margin        : 0 calc(var(--w) / -2);
  border        : 1px solid #131313;
  border-radius : 0 0 4px 4px;
  background    : linear-gradient(40deg, #222, #555);
  box-shadow    :
     0 -1px 1px 2px rgba(0,0,0, .5) inset,
     0 1px 2px rgba(0,0,0, .5);
   
}

li.diesis:active {
  background: linear-gradient(100deg, #505050, #131313);
  box-shadow: 
     -1px -1px 2px rgba(255,255,255,0.2) inset,
     0 -2px 2px 3px rgba(0,0,0,0.6) inset,
     0 1px 2px rgba(0,0,0,0.5);
}
<ul class="piano">
   <li data-tone="C1">C1</li>
   <li data-tone="C1#" class="diesis">C1#</li>
   <li data-tone="D1">D1</li>
   <li data-tone="D1#" class="diesis">D1#</li>
   <li data-tone="E1">E1</li>
   <li data-tone="F1">F1</li>
   <li data-tone="F1#" class="diesis">F1#</li>
   <li data-tone="G1">G1</li>
   <li data-tone="G1#" class="diesis">G1#</li>
   <li data-tone="A1">A1</li>
   <li data-tone="A1#" class="diesis">A1#</li>
   <li data-tone="B1">B1</li>
</ul>

K dispozici je také úplné demo na codepen, pokud může pomoci s laděním.

Děkuji

Odpověď

Problém s hlasitostí v Chrome lze vyřešit použitím pouze jednoho globálního AudioContext které je pak třeba obnovit v obslužném programu kliknutí.

Praskání ve Firefoxu lze odstranit přidáním explicitní hodnoty do časové osy automatizace pro začátek.

const currentTime = ac.currentTime;

gain.gain.setValueAtTime(1, currentTime);
gain.gain.linearRampToValueAtTime(.0001, currentTime + duration);

Aktualizovaný kód JavaScript vašeho CodePen by pak vypadal takto:

const ac = new AudioContext();
const noteFrequencies = {
   'C1' : 261.63,
   'C1#': 277.18,
   'D1' : 293.66,
   'D1#': 311.13,
   'E1' : 329.63,
   'F1' : 349.23,
   'F1#': 369.99,
   'G1' : 392.00,
   'G1#': 415.30,
   'A1' : 440.00,
   'A1#': 466.16,
   'B1' : 493.88,
   'C2' : 523.25,
   'C2#': 554.37,
   'D2' : 587.33,
   'D2#': 622.25,
   'E2' : 659.25
}


function playTone(note, duration = 1) {
   const currentTime = ac.currentTime;

   let oscl = ac.createOscillator();
   let gain = ac.createGain();

   gain.connect(ac.destination);
   oscl.connect(gain);

   oscl.frequency.value = noteFrequencies[note];
   oscl.type = "sine"
   gain.gain.setValueAtTime(1, currentTime);
   gain.gain.linearRampToValueAtTime(.0001, currentTime + duration);
   oscl.start();
   oscl.stop(currentTime + duration);
}


let notelist = document.querySelector('.piano');

notelist.addEventListener('click', (ev) => {
  ac.resume();

  let tgt = ev.target;
  let tone;
  if (tgt.matches('[data-tone]')) {
     tone = tgt.getAttribute('data-tone');
     playTone(tone);   
  }
});