I have a angular component that has a button to start an animation and at the same time play some notes each time a number is highlighted
I'm setting up tone.js synth when the button is pressed see escalaPentatonicaToggle
but I'm still getting an error about The AudioContext was not allowed to start. It must be resumed (or created) after a user gesture on the page
Why am I getting that error even when the sound is trying to be played after a CLICK event ? how can I solve it.
I have seen this similar question but offers a different scenario Tone.js - The AudioContext was not allowed to start
Here is the component logic
import { Component } from '@angular/core';
import { ElementoPentatonica, EscalaPentatonica, NotasEscalaPentatonica } from '../../models/EscalaPentatonica';
import { CommonModule } from '@angular/common';
import { ElementoPentatonicaComponent } from '../elemento-pentatonica/elemento-pentatonica.component';
import * as Tone from 'tone';
@Component({
selector: 'app-escala-pentatonica',
standalone: true,
imports: [CommonModule, ElementoPentatonicaComponent],
templateUrl: './escala-pentatonica.component.html',
styleUrl: './escala-pentatonica.component.css'
})
export class EscalaPentatonicaComponent {
escala: EscalaPentatonica;
highlightedIndex: number = 0;
timer: any;
lapse: number = 600;
running: boolean = false;
soundEnabled: boolean = true;
sonido = "LA";
start = 1;
lineasFlat: ElementoPentatonica[] = [];
synth: Tone.Synth | null = null;
constructor() {
this.escala = new EscalaPentatonica(this.start, NotasEscalaPentatonica.SI_MAYOR, this.sonido);
this.lineasFlat = this.escala.lineas.flat();
}
onSelectionChange(event: Event) {
this.stopAnimation();
this.start = Number((event.target as HTMLSelectElement).value);
this.escala = new EscalaPentatonica(this.start, NotasEscalaPentatonica.SI_MAYOR, this.sonido);
}
ngOnInit() {
}
async escalaPentatonicaToggle() {
await Tone.start();
if (!this.running) {
await this.startAnimation();
} else {
this.stopAnimation();
}
this.running = !this.running;
}
async startAnimation() {
this.synth = new Tone.Synth().toDestination();
this.highlightedIndex = 0;
this.startTimer();
}
stopAnimation() {
if (this.timer) {
clearInterval(this.timer);
}
if (this.synth) {
this.synth.dispose();
this.synth = null;
}
}
ngOnDestroy() {
this.stopAnimation();
}
startTimer() {
this.timer = setInterval(() => {
this.highlightedIndex = (this.highlightedIndex + 1) % this.lineasFlat.length;
this.playSound(this.highlightedIndex);
}, this.lapse);
}
getIndex(i: number, j: number): number {
let index = 0;
for (let x = 0; x < i; x++) {
index += this.escala.lineas[x].length;
}
return index + j;
}
playSound(index: number) {
let note = this.escala.notas[this.lineasFlat[index].numero - this.escala.start];
if (this.synth) {
console.log('Playing note:', note);
this.synth.triggerAttackRelease(note, '2s');
}
}
}
and here is the html
<div class="escala-container">
<select #escalaSelect (change)="onSelectionChange($event)">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
</select>
<button class="nextButton" (click)="escalaPentatonicaToggle()">
{{ running ? 'DETENER' : 'INICIAR' }}
</button>
<div *ngFor="let linea of escala.lineas; let i = index" class="linea" [style.margin-left.px]="(4 - i) * 20">
<app-elemento-pentatonica *ngFor="let elemento of linea; let j = index" [numero]="elemento.numero"
[sonido]="elemento.sonido" [highlight]="highlightedIndex === getIndex(i, j)">
</app-elemento-pentatonica>
</div>
</div>
Problem solved, reason "The tab was muted". Yes is embarresing ;-)
Also the messages in the console are WARNINGS not errors, and the sound plays just fine.