angulartypescriptaudio

Setting up Tone.js on button click still fails with : The AudioContext was not allowed to start


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

enter image description here

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>

Solution

  • 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.