in

¿Por qué mi teléfono inteligente con Android 11 puede detectar los auriculares con micrófono a través de aplicaciones de Play Store pero no desde Google Chrome o Firefox Mobile?

Tengo un teléfono inteligente que puede detectar y reproducir sonidos de los auriculares con micrófono y el micrófono interno integrado en el teléfono inteligente, pero cuando uso esta aplicación web no detecta los auriculares con micrófono, solo detecta el micrófono interno integrado del teléfono inteligente.

Solo las aplicaciones descargadas de Play Store detectan los auriculares con micrófono, Google Chrome o Firefox Mobile no pueden detectarlo

¿Qué puedo hacer para que el navegador web del móvil detecte los auriculares con micrófono? (Mozilla o Firefox)

Solo puedo hacerlo en mi MacBook 2017 con iRig

El contexto es el siguiente, hice un fork de esta app y se me implementaron 40 amplificadores de guitarra y 10 de bajo y más de 100 efectos de pedal, en conclusión el software original es el 15% de mi fork desarrollado de esta app, el El problema es que no puedo ejecutarlo a través del sonido de la interfaz, solo ejecutarlo a través del micrófono interno de mi teléfono inteligente Android 11, por lo que solo puedo procesar mi voz jaja y necesito ingresar correctamente mi guitarra al teléfono inteligente para usar la aplicación web de efectos de proceso.

¿Cómo puede el Web Mobile Browser detectar mi iRig o los auriculares con micrófono?

Esas eran mis 2 preguntas, no puedo conectar mi iRig a la aplicación móvil, muchas gracias

PD: Mi smartphone con Android 11 solo detecta la interfaz iRig a través de Amplitube o Deplike, pero no la detecta el navegador web móvil PD2: ¿¿¿Puedo resolverlo si lo pongo en un iframe en un proyecto de Android Studio APK construido para preceder al sonido a través de la interfaz correctamente???

El dispositivo de entrada debe ser mi guitarra conectada a través Interfaz de audio iRig y la salida son los altavoces de mi ordenador, los altavoces de los smartphones o el amplificador de guitarra o bajo. El problema es que no reconocí la interfaz de entrada de iRig con el navegador web en mi computadora portátil y móvil, mi iRig solo funciona en Deplike y la aplicación de Android Amplitube 🙁 necesito usar esta aplicación para tocar en mi banda de death metal porque los efectos del pedal son más caros , necesito uno llamado «Metal Zone» del frabricante «Boss».

El código angular del módulo «Stage» en donde se seleccionan los dispositivos de entrada y salida es el siguiente:

import { Injectable, OnDestroy } from '@angular/core';
import { moveItemInArray } from '@angular/cdk/drag-drop';
import { BehaviorSubject } from 'rxjs';
import {
  AudioContext,
  GainNode,
  MediaStreamAudioSourceNode,
  MediaStreamAudioDestinationNode
} from 'standardized-audio-context';

import { Effect } from './effects/effect';
import { clamp } from '@audio/utils';
import { Preset } from './preset-manager.service';
import { CabinetInfo } from './effects/cabinet';
import { LocalStorageService } from '@shared/storage/local-storage.service';
import { AudioIO } from './interfaces/audio-io.interface';

@Injectable()
export class AudioContextManager implements OnDestroy {
  static readonly CURRENT_INPUT_KEY = 'jsr_current_input';
  static readonly CURRENT_OUTPUT_KEY = 'jsr_current_output';

  context: AudioContext;
  private effects: Effect<any>[] = [];
  private lineInSource: MediaStreamAudioSourceNode<AudioContext>;
  private audioElement = new Audio();
  private destination: MediaStreamAudioDestinationNode<AudioContext>;
  private masterGain: GainNode<AudioContext>;
  private masterSub$ = new BehaviorSubject(0);
  private inputSub$ = new BehaviorSubject<string>(null);
  private outputSub$ = new BehaviorSubject<string>(null);
  private inputs: AudioIO[] = [];
  private outputs: AudioIO[] = [];
  private createNewStream = true;

  readonly master$ = this.masterSub$.asObservable();
  readonly input$ = this.inputSub$.asObservable();
  readonly output$ = this.outputSub$.asObservable();

  /**
   * Whether the audio stream source is currently active.
   */
  private active = false;

  set master(value: number) {
    const gain = clamp(0, 1, value);
    this.masterSub$.next(gain);
    this.masterGain.gain.setTargetAtTime(gain, this.context.currentTime, 0.01);
  }

  get inputDevices(): AudioIO[] {
    return this.inputs;
  }

  get outputDevices(): AudioIO[] {
    return this.outputs;
  }

  constructor(private storage: LocalStorageService) {
    if ('enumerateDevices' in navigator.mediaDevices) {
      this.getIODevices();
      navigator.mediaDevices.addEventListener('devicechange', async event => {
        await this.getIODevices();

        // Check if current input/output exist.
        // If previous input/output is not available reset it.
        if (
          this.inputSub$.value &&
          !this.inputs.find(input => input.label === this.inputSub$.value)
        ) {
          await this.changeInputDevice(null);
        }

        if (
          this.outputSub$.value &&
          !this.outputs.find(output => output.label === this.outputSub$.value)
        ) {
          await this.changeOutputDevice(this.outputs[0]?.id);
        }
      });
    }

    this.context = new AudioContext({
      latencyHint: 'interactive'
    });
    this.destination = new MediaStreamAudioDestinationNode(this.context);
    this.masterGain = new GainNode(this.context);
    this.masterGain.connect(this.destination);
    this.masterSub$.next(1);
  }

  /**
   * Starts a stream with audio data. Creates a new stream
   * during the first execution or after input device update.
   */
  async plugLineIn(): Promise<void> {
    try {
      if (this.createNewStream) {
        this.disconnectAll();
        const deviceInfo = this.inputSub$.value
          ? { deviceId: this.inputSub$.value }
          : {};
        const mediaStream = await navigator.mediaDevices.getUserMedia({
          audio: {
            echoCancellation: false,
            autoGainControl: false,
            noiseSuppression: false,
            latency: 0,
            ...deviceInfo
          }
        });

        // Updating the input device again as there is
        // no guarantee the selected device will be used.
        const track = mediaStream.getAudioTracks()[0];
        this.saveInput(track.label);

        this.lineInSource = new MediaStreamAudioSourceNode(this.context, {
          mediaStream
        });
        this.createNewStream = false;

        this.audioElement.srcObject = this.destination.stream;
        this.saveOutput();
        this.audioElement.play();

        this.connectAll();
      }
    } catch (err) {
      // TODO: Show errors to the user.
      console.error(err);
    }

    if (this.context.state === 'suspended') {
      await this.context.resume();
    }

    this.active = true;
  }

  async unplugLineIn(): Promise<void> {
    if (this.context.state === 'running') {
      await this.context.suspend();
    }
    this.audioElement.pause();
    this.active = false;
  }

  addEffect(effect: Effect<any>, post = false): void {
    this.disconnectAll();

    if (post) {
      this.effects.push(effect);
    } else {
      this.effects.splice(-1, 0, effect);
    }

    this.connectAll();
  }

  removeEffect(effect: Effect<any>): void {
    this.disconnectAll();
    this.effects = this.effects.filter(eff => eff !== effect);
    this.connectAll();
  }

  moveEffect(previousIndex: number, currentIndex: number): void {
    this.disconnectAll();
    moveItemInArray(this.effects, previousIndex, currentIndex);
    this.connectAll();
  }

  connectAll(): void {
    if (this.effects.length) {
      if (this.lineInSource) {
        this.lineInSource.connect(this.effects[0].input);
      }

      for (let i = this.effects.length - 1; i > 0; --i) {
        this.effects[i - 1].connect(this.effects[i]);
      }

      this.effects[this.effects.length - 1].output.connect(this.masterGain);
    } else if (this.lineInSource) {
      this.lineInSource.connect(this.masterGain);
    }
  }

  disconnectAll(): void {
    if (this.lineInSource) {
      this.lineInSource.disconnect();
    }

    for (const effect of this.effects) {
      effect.disconnect();
    }
  }

  takeSnapshot(): Preset {
    if (!this.effects.length) {
      return;
    }

    const cabinet = this.effects[
      this.effects.length - 1
    ].takeSnapshot() as CabinetInfo;
    cabinet.params.volume = this.masterSub$.value;

    const snapshot: Preset = {
      cabinet,
      pedals: []
    };

    for (let i = 0; i < this.effects.length - 1; i++) {
      const effectParams = this.effects[i].takeSnapshot();
      snapshot.pedals.push(effectParams);
    }

    return snapshot;
  }

  ngOnDestroy(): void {
    this.masterSub$.complete();
    this.inputSub$.complete();
    this.outputSub$.complete();
  }

  async changeInputDevice(id: string | null): Promise<void> {
    if (this.inputSub$.value !== id) {
      this.inputSub$.next(id);
      this.createNewStream = this.active;

      // If audio is enabled
      if (this.active) {
        await this.unplugLineIn();
        await this.plugLineIn();
      } else {
        await this.plugLineIn();
        await this.unplugLineIn();
      }
    }
  }

  async changeOutputDevice(id: string): Promise<void> {
    if (this.outputSub$.value !== id) {
      this.disconnectAll();
      try {
        await (this.audioElement as any).setSinkId(id);
        this.saveOutput();
      } catch (err) {
        // TODO: Show errors to the user.
        console.error(err);
      }
      this.connectAll();
    }
  }

  private async getIODevices(): Promise<void> {
    const devices = await navigator.mediaDevices.enumerateDevices();
    this.inputs = [];
    this.outputs = [];
    let inputIndex = 0;
    let outputIndex = 0;

    for (const device of devices) {
      if (device.kind === 'audioinput') {
        this.inputs.push({
          id: device.deviceId,
          label: device.label || `Microphone ${++inputIndex}`
        });
      }

      if (device.kind === 'audiooutput') {
        this.outputs.push({
          id: device.deviceId,
          label: device.label || `Speakers ${++outputIndex}`
        });
      }
    }

    const previousInput = this.storage.getItem(
      AudioContextManager.CURRENT_INPUT_KEY
    );

    if (
      previousInput &&
      this.inputs.some(input => input.label === previousInput)
    ) {
      this.inputSub$.next(this.inputDeviceIdByLabel(previousInput));
    }

    const previousOutput = this.storage.getItem(
      AudioContextManager.CURRENT_OUTPUT_KEY
    );

    let outputDeviceId: string;

    if (
      previousOutput &&
      this.outputs.some(output => output.label === previousOutput)
    ) {
      outputDeviceId = this.outputDeviceIdByLabel(previousOutput);
    } else if (this.outputs.length) {
      outputDeviceId = this.outputs[0].id;
    }

    if (!outputDeviceId) {
      return;
    }

    try {
      this.outputSub$.next(outputDeviceId);
      (this.audioElement as any).setSinkId(outputDeviceId);
    } catch (err) {
      // TODO: Show errors to the user.
      console.error(err);
    }
  }

  private inputDeviceIdByLabel(label: string) {
    return this.inputs.find(input => input.label === label)?.id;
  }

  private outputDeviceIdByLabel(label: string) {
    return this.outputs.find(input => input.label === label)?.id;
  }

  private saveInput(label: string) {
    this.inputSub$.next(this.inputDeviceIdByLabel(label));
    this.storage.setItem(AudioContextManager.CURRENT_INPUT_KEY, label);
  }

  private saveOutput() {
    const id = (this.audioElement as any).sinkId;
    const label = this.outputs.find(output => output.id === id)?.label ?? '';
    this.outputSub$.next(id);
    this.storage.setItem(AudioContextManager.CURRENT_OUTPUT_KEY, label);
  }
} 

Creo que este código también está involucrado:

import {
  ChangeDetectionStrategy,
  Component,
  ElementRef,
  EventEmitter,
  HostBinding,
  HostListener,
  Input,
  NgModule,
  Output,
  ViewChild
} from '@angular/core';
import { CommonModule } from '@angular/common';

import { LedModule } from '../led/led.component';

@Component({
  selector: 'jsr-small-switch',
  templateUrl: './small-switch.component.html',
  styleUrls: ['./small-switch.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class SmallSwitchComponent {
  @HostBinding('attr.tabindex')
  tabIndex = '0';

  @Input()
  active = false;

  @Input()
  showLed = true;

  @Output()
  switch = new EventEmitter<void>();

  @ViewChild('control', { static: true })
  control: ElementRef;

  @HostListener('focus')
  onFocus() {
    this.control.nativeElement.focus();
  }
}

> Blockquote

@NgModule({
  declarations: [SmallSwitchComponent],
  imports: [CommonModule, LedModule],
  exports: [SmallSwitchComponent]
})
export class SmallSwitchModule {}

y esto

import {
  Component,
  ChangeDetectionStrategy,
  HostListener,
  OnInit,
  OnDestroy,
  ComponentRef
} from '@angular/core';
import { moveItemInArray } from '@angular/cdk/drag-drop';
import { MatDialog } from '@angular/material/dialog';
import { takeUntil } from 'rxjs/operators';
import { NgsgOrderChange } from 'ng-sortgrid';

import { AudioContextManager } from '@audio/audio-context-manager.service';
import {
  PresetManagerService,
  Preset,
  PresetInfo
} from '@audio/preset-manager.service';
import { EffectInfo } from '@audio/effects/effect';
import { PedalComponent, PedalDescriptor } from '../pedal.interface';
import { PresetNameDialogComponent } from '../preset-name-dialog/preset-name-dialog.component';
import { MatSelectChange } from '@angular/material/select';
import { AudioIO } from '@audio/interfaces/audio-io.interface';

@Component({
  selector: 'jsr-stage',
  templateUrl: './stage.component.html',
  styleUrls: ['./stage.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class StageComponent implements OnInit, OnDestroy {
  /**
   * Whether the audio stream source is currently active.
   */
  isLinePlugged = false;

  /**
   * Current stage amp and pedals configurations.
   */
  config: Preset;

  /**
   * Currently selected preset id, empty for default preset.
   */
  selectedPresetId: string;

  /**
   * Previously saved user presets.
   */
  presets: PresetInfo[] = [];

  activeInputDevice$ = this.manager.input$;
  activeOutputDevice$ = this.manager.output$;

  get inputDevices(): AudioIO[] {
    return this.manager.inputDevices;
  }

  get outputDevices(): AudioIO[] {
    return this.manager.outputDevices;
  }

  /**
   * Effect types used to group pedals.
   */
  readonly effectTypes = [
    'Tuner',
    'Compressor',
    'Overdrive',
    'Distortion',
    'Fuzz',
    'Chorus',
    'Phaser',
    'Tremolo',
    'Delay',
    'Reverb'
  ];

  /**
   * All available pedals meta data.
   */
  readonly availablePedals: PedalDescriptor[] = [
    {
      id: 'jtu-3',
      type: 'Tuner',
      brand: 'JOSS',
      name: 'Tuner',
      model: 'JTU-3'
    },
    {
      id: 'jcp-1',
      type: 'Compressor',
      brand: 'JOSS',
      name: 'Lemon Squeeze',
      model: 'JCP-1'
    },
    {
      id: 'jbd-2',
      type: 'Overdrive',
      brand: 'JOSS',
      name: 'Blues Driver',
      model: 'JBD-2'
    },
    {
      id: 'jod-3',
      type: 'Overdrive',
      brand: 'JOSS',
      name: 'OverDrive',
      model: 'JOD-3'
    },
    {
      id: 'jds-1',
      type: 'Distortion',
      brand: 'JOSS',
      name: 'Classic Distortion',
      model: 'JDS-1'
    },
    {
      id: 'jmt-2',
      type: 'Distortion',
      brand: 'JOSS',
      name: 'Metal Area',
      model: 'JMT-2'
    },
    {
      id: 'js-bmf',
      type: 'Fuzz',
      brand: 'Ernesto-Saxophonist',
      name: 'Massive Muff π'
    },
    {
      id: 'jch-1',
      type: 'Chorus',
      brand: 'JOSS',
      name: 'Cool Chorus',
      model: 'JCH-1'
    },
    {
      id: 'js-phase-pi-by-2',
      type: 'Phaser',
      brand: 'TSX',
      name: 'phase π/2'
    },
    {
      id: 'jtr-2',
      type: 'Tremolo',
      brand: 'JOSS',
      name: 'Tremolo',
      model: 'JTR-2'
    },
    {
      id: 'soft-yellow-tremolo',
      type: 'Tremolo',
      brand: 'Crazy Doctor',
      name: 'Soft Yellow Tremolo'
    },
    {
      id: 'jdm-2',
      type: 'Delay',
      brand: 'JOSS',
      name: 'Delay',
      model: 'JDM-2'
    },
    {
      id: 'jrv-6',
      type: 'Reverb',
      brand: 'JOSS',
      name: 'Reverb',
      model: 'JRV-6'
    }
  ];
  private presetKeyMap: string[] = [''];

  constructor(
    public dialog: MatDialog,
    private manager: AudioContextManager,
    private presetsManager: PresetManagerService
  ) {
    this.savePreset = this.savePreset.bind(this);
  }

  ngOnInit(): void {
    this.presets = this.presetsManager.getPresetsInfo();
    this.afterConfigChange();
    this.updatePresetsKeyMap();
  }

  ngOnDestroy(): void {
    this.presetsManager.setCurrentPreset(this.selectedPresetId);
  }

  /**
   * Updates input audio device.
   */
  handleInputDeviceChange(event: MatSelectChange): void {
    this.manager.changeInputDevice(event.value);
  }

  /**
   * Updates output audio device.
   */
  handleOutputDeviceChange(event: MatSelectChange): void {
    this.manager.changeOutputDevice(event.value);
  }

  @HostListener('window:keyup', ['$event'])
  handlePresetShortcut(event: KeyboardEvent): void {
    const id = this.presetKeyMap[event.key];

    if (
      typeof id === 'undefined' ||
      (event.target as HTMLElement).nodeName.toLowerCase() === 'input'
    ) {
      return;
    }

    this.activatePreset(id);
  }

  /**
   * Updates connect button and audio stream states.
   */
  toggleLineConnection(): void {
    this.isLinePlugged = !this.isLinePlugged;

    if (this.isLinePlugged) {
      this.manager.plugLineIn();
    } else {
      this.manager.unplugLineIn();
    }
  }

  /**
   * Reordering pedals on DnD interactions.
   */
  dropPedal(event: NgsgOrderChange<EffectInfo>, pedal: EffectInfo): void {
    const previousIndex = event.previousOrder.indexOf(pedal);
    const currentIndex = event.currentOrder.indexOf(pedal);
    moveItemInArray(this.config.pedals, previousIndex, currentIndex);
    this.manager.moveEffect(previousIndex, currentIndex);
  }

  /**
   * Opens a dialog to define a new preset name.
   */
  openPresetNameDialog(): void {
    const dialogRef = this.dialog.open(PresetNameDialogComponent, {
      width: '320px',
      data: { name: '' }
    });

    dialogRef.afterClosed().subscribe(this.savePreset);
  }

  requestSavePreset(): void {
    if (this.selectedPresetId) {
      this.savePreset();
    } else {
      this.openPresetNameDialog();
    }
  }

  /**
   * Saves preset to the storage.
   */
  savePreset(name?: string): void {
    if (!name && !this.selectedPresetId) {
      return;
    }

    const preset = this.manager.takeSnapshot();

    if (this.selectedPresetId) {
      preset.id = this.selectedPresetId;
      this.presetsManager.updatePreset(preset);
    } else {
      const result = this.presetsManager.addPreset(preset, name);
      this.presets = result.presets;
      this.selectedPresetId = result.id;
      this.presetsManager.setCurrentPreset(result.id);
      this.updatePresetsKeyMap();
    }
  }

  deletePreset(): void {
    this.presets = this.presetsManager.removePreset(this.selectedPresetId);
    this.updatePresetsKeyMap();
    this.afterConfigChange();
  }

  blankPreset(): void {
    this.selectedPresetId = '';
    this.presetsManager.setCurrentPreset('');
    this.afterConfigChange();
  }

  activatePreset(id: string): void {
    this.selectedPresetId = id;
    this.presetsManager.setCurrentPreset(id);
    this.afterConfigChange();
  }

  addPedal(id: string): void {
    const pedalInfo = {
      model: id,
      params: null as null
    };

    this.config.pedals.push(pedalInfo);
  }

  initPedal(
    componentRef: ComponentRef<PedalComponent<unknown>>,
    pedal: EffectInfo,
    id: string
  ): void {
    const component = componentRef.instance;

    component.remove
      .pipe(takeUntil(component.destroy$))
      .subscribe(() => this.removePedal(componentRef, pedal));

    component.info = this.availablePedals.find(
      descriptor => descriptor.id === id
    );

    if (pedal.params) {
      component.params = pedal.params;
    }
  }

  private removePedal(
    componentRef: ComponentRef<PedalComponent<unknown>>,
    pedal: EffectInfo
  ): void {
    this.config.pedals = this.config.pedals.filter(config => config !== pedal);
    componentRef.destroy();
  }

  private afterConfigChange(): void {
    this.config = this.presetsManager.getCurrentPreset();
    this.config.cabinet = { ...this.config.cabinet };
    this.selectedPresetId = this.config.id;
  }

  private updatePresetsKeyMap(): void {
    this.presetKeyMap = this.presets.reduce(
      (map, preset) => {
        map.push(preset.id);

        return map;
      },
      ['']
    );
  }
}

Finalmente encuentra el código involucrado y es este:

<jsr-page-actions>
  <div class="connect-decorator">
    <button
      mat-fab
      class="line-toggle"
      type="button"
      [attr.aria-label]="
        isLinePlugged ? 'Disconnect instrument' : 'Connect instrument'
      "
      [attr.aria-pressed]="isLinePlugged"
      [matTooltip]="
        isLinePlugged ? 'Disconnect instrument' : 'Connect instrument'
      "
      [color]="isLinePlugged ? 'accent' : 'warn'"
      (click)="toggleLineConnection()"
    >
      <mat-icon svgIcon="settings_input_svideo"></mat-icon>
    </button>
  </div>

  <mat-menu #appMenu="matMenu">
    <button
      type="button"
      role="menuitemradio"
      mat-menu-item
      (click)="blankPreset()"
    >
      <mat-icon
        [svgIcon]="
          selectedPresetId === ''
            ? 'radio_button_checked'
            : 'radio_button_unchecked'
        "
      ></mat-icon>
      <span>Default</span>
    </button>
    <button
      type="button"
      mat-menu-item
      role="menuitemradio"
      *ngFor="let preset of presets"
      (click)="activatePreset(preset.id)"
    >
      <mat-icon
        [svgIcon]="
          selectedPresetId === preset.id
            ? 'radio_button_checked'
            : 'radio_button_unchecked'
        "
      ></mat-icon>
      <span>{{ preset.name }}</span>
    </button>
  </mat-menu>

  <button
    type="button"
    aria-label="Presets"
    mat-icon-button
    matTooltip="Presets"
    [matMenuTriggerFor]="appMenu"
  >
    <mat-icon
      [matBadge]="presets.length | number"
      matBadgeColor="accent"
      matBadgePosition="above before"
      [matBadgeHidden]="!presets.length"
      svgIcon="list"
    ></mat-icon>
  </button>

  <button
    type="button"
    aria-label="New preset"
    class="toolbar-btn"
    mat-icon-button
    matTooltip="New preset"
    (click)="blankPreset()"
  >
    <mat-icon svgIcon="blank"></mat-icon>
  </button>

  <button
    type="button"
    aria-label="Save preset"
    class="toolbar-btn"
    mat-icon-button
    matTooltip="Save preset"
    (click)="requestSavePreset()"
  >
    <mat-icon svgIcon="save"></mat-icon>
  </button>

  <button
    *ngIf="selectedPresetId"
    type="button"
    aria-label="Delete preset"
    class="toolbar-btn"
    mat-icon-button
    matTooltip="Delete preset"
    (click)="deletePreset()"
  >
    <mat-icon svgIcon="delete"></mat-icon>
  </button>
</jsr-page-actions>

<jsr-nav-configurations>
  <mat-form-field appearance="fill">
    <mat-label>Input device</mat-label>
    <mat-select
      [value]="activeInputDevice$ | async"
      (selectionChange)="handleInputDeviceChange($event)"
    >
      <mat-option *ngFor="let input of inputDevices" [value]="input.id">{{
        input.label
      }}</mat-option>
    </mat-select>
  </mat-form-field>
  <mat-form-field appearance="fill">
    <mat-label>Output device</mat-label>
    <mat-select
      [value]="activeOutputDevice$ | async"
      (selectionChange)="handleOutputDeviceChange($event)"
    >
      <mat-option *ngFor="let output of outputDevices" [value]="output.id">{{
        output.label
      }}</mat-option>
    </mat-select>
  </mat-form-field>
</jsr-nav-configurations>

<jsr-amp [config]="config.cabinet"></jsr-amp>

<div>
  <ngx-loadable
    *ngFor="let pedal of config?.pedals"
    ngSortgridItem
    [ngSortGridItems]="config?.pedals"
    [autoScroll]="true"
    [module]="pedal.model"
    [show]="true"
    (init)="initPedal($event, pedal, pedal.model)"
    (sorted)="dropPedal($event, pedal)"
  ></ngx-loadable>
</div>

<p *ngIf="!config?.pedals?.length" class="empty-board-message mat-subheading-2">
  No effects in you pedalboard :(<br />
  Use "Add new effect" button below to pick one.
</p>

<button
  mat-fab
  aria-label="Add new effect"
  type="button"
  class="add-button"
  color="accent"
  matTooltip="Add new effect"
  [matMenuTriggerFor]="pedalBoardMenu"
>
  <mat-icon svgIcon="add"></mat-icon>
</button>

<mat-menu #pedalBoardMenu="matMenu">
  <button
    *ngFor="let type of effectTypes"
    type="button"
    mat-menu-item
    [matMenuTriggerFor]="pedalsMenu"
    [matMenuTriggerData]="{ type: type }"
  >
    {{ type }}
  </button>
</mat-menu>

<mat-menu #pedalsMenu="matMenu">
  <ng-template matMenuContent let-type="type">
    <button
      type="button"
      mat-menu-item
      *ngFor="let pedal of availablePedals | pickByProp: 'type':type"
      (click)="addPedal(pedal.id)"
    >
      {{ pedal.brand }} {{ pedal.name }} {{ pedal.model }}
    </button>
  </ng-template>
</mat-menu>

0

¿Te ayudó la respuesta?

Subscribirse
Notificar por
guest
0 Comentarios
Inline Feedbacks
Ver todas las Respuestas

¿Qué debería haber visto para entender mejor «Doctor Strange in the Multiverse of Madness»?

Esto sucede cuando Genymotion intentó iniciar un dispositivo