Menuliskan Microwave ke Angular

Web Audio API sudah ada sejak lama, dan ada banyak artikel tentangnya. Karena itu, kami tidak akan banyak bicara tentang API itu sendiri. Kami akan memberi tahu Anda bahwa Audio Web dan Angular dapat menjadi teman terbaik jika Anda mengenalkannya dengan benar. Ayo lakukan itu!



Web Audio API , , . , , . . - :


const context = new AudioContext();
const gainNode = context.createGain();

, , . Web Audio API Angular.



Angular β€” , . , , :


const context = new AudioContext();
const gainNode = new GainNode(context);
const delayNode = new DelayNode(context);
const audioSource = new MediaElementAudioSourceNode(context, {
   mediaElement: audioElement.nativeElement,
});

gainNode.gain.value = 0.5;
delayNode.delayTime.value = 0.2;

audioSource.connect(gainNode);
audioSource.connect(context.destination);
gainNode.connect(delayNode);
delayNode.connect(gainNode);
delayNode.connect(context.destination);

, β€” . , , connect. HTML audio , , . . AudioContext Dependency Injection.


GainNode DelayNode β€” . , AudioParam. .


@Directive({
   selector: '[waGainNode]',
   inputs: [
     'channelCount',
     'channelCountMode',
     'channelInterpretation'
   ],
})
export class WebAudioGain extends GainNode {
   @Input('gain')
   set gainSetter(value: number) {
       this.gain.value = value;
   }

   constructor(@Inject(AUDIO_CONTEXT) context: AudioContext) {
       super(context);
   }
}

, : channelCount, channelCountMode channelInterpretation. inputs @Directive β€” . DelayNode . AUDIO_NODE, :


@Directive({
   selector: '[waGainNode]',
   inputs: [
     'channelCount',
     'channelCountMode',
     'channelInterpretation'
   ],
   exportAs: 'AudioNode',
   providers: [{
     provide: AUDIO_NODE,
     useExisting: forwardRef(() => WebAudioGain),
   }],
})
export class WebAudioGain extends GainNode implements OnDestroy {
   @Input('gain')
   set gainSetter(value: number) {
       this.gain.value = value;
   }

   constructor(
     @Inject(AUDIO_CONTEXT) context: BaseAudioContext,
     @SkipSelf() @Inject(AUDIO_NODE) node: AudioNode | null,
   ) {
       super(context);

       if (node) {
           node.connect(this);
       }
   }

   ngOnDestroy() {
       this.disconnect();
   }
}

DI . exportAs β€” template reference variables. :


<ng-container waGainNode>
   <ng-container waDelayNode></ng-container>
</ng-container>

waAudioDestination:


@Directive({
   selector: '[waAudioDestinationNode]',
   exportAs: 'AudioNode',
})
export class WebAudioDestination extends GainNode 
   implements OnDestroy {
   constructor(
       @Inject(AUDIO_CONTEXT) context: AudioContext,
       @Inject(AUDIO_NODE) node: AudioNode | null,
   ) {
       super(context);
       this.connect(context.destination);

       if (node) {
           node.connect(this);
       }
   }

   ngOnDestroy() {
       this.disconnect();
   }
}

, , Dependency Injection. . , :


@Directive({
   selector: '[waOutput]',
})
export class WebAudioOutput extends GainNode implements OnDestroy {
   @Input()
   set waOutput(destination: AudioNode | undefined) {
       this.disconnect();

       if (destination) {
           this.connect(destination);
       }
   }

   constructor(
       @Inject(AUDIO_CONTEXT) context: AudioContext,
       @Inject(AUDIO_NODE) node: AudioNode | null,
   ) {
       super(context);

       if (node) {
           node.connect(this);
       }
   }

   ngOnDestroy() {
       this.disconnect();
   }
}

GainNode, . ngOnDestroy. , , this .



. -, . audio , MediaElementAudioSourceNode:


@Directive({
   selector: 'audio[waMediaElementAudioSourceNode]',
   exportAs: 'AudioNode',
   providers: [
       {
           provide: AUDIO_NODE,
           useExisting: forwardRef(() => WebAudioMediaSource),
       },
   ],
})
export class WebAudioMediaSource extends MediaElementAudioSourceNode
   implements OnDestroy {
   constructor(
       @Inject(AUDIO_CONTEXT) context: AudioContext,
       @Inject(ElementRef) {nativeElement}: ElementRef<HTMLMediaElement>,
   ) {
       super(context, {mediaElement: nativeElement});
   }

   ngOnDestroy() {
       this.disconnect();
   }
}

:


<audio src="/demo.wav" waMediaElementAudioSourceNode>
    <ng-container #node="AudioNode" waDelayNode [delayTime]="0.2">
        <ng-container waGainNode [gain]="0.5">
            <ng-container [waOutput]="node"></ng-container>
            <ng-container waAudioDestinationNode></ng-container>
        </ng-container>
    </ng-container>
    <ng-container waAudioDestinationNode></ng-container>
</audio>

Web Audio API , . - . HTML . AudioBufferSourceNode. β€” , `AudioBuffer. AudioBuffer:


@Injectable({
   providedIn: 'root',
})
export class AudioBufferService {
   private readonly cache = new Map<string, AudioBuffer>();

   constructor(
      @Inject(AUDIO_CONTEXT) private readonly context: AudioContext
   ) {}

   fetch(url: string): Promise<AudioBuffer> {
       return new Promise<AudioBuffer>((resolve, reject) => {
           if (this.cache.has(url)) {
               resolve(this.cache.get(url));

               return;
           }

           const request = new XMLHttpRequest();

           request.open('GET', url, true);
           request.responseType = 'arraybuffer';
           request.onerror = reject;
           request.onabort = reject;
           request.onload = () => {
               this.context.decodeAudioData(
                   request.response,
                   buffer => {
                       this.cache.set(url, buffer);
                       resolve(buffer);
                   },
                   reject,
               );
           };
           request.send();
       });
   }
}

AudioBufferSourceNode, AudioBuffer, :


export class WebAudioBufferSource extends AudioBufferSourceNode 
   implements OnDestroy {
   @Input('buffer')
   set bufferSetter(source: AudioBuffer | null | string) {
       this.buffer$.next(source);
   }

   readonly buffer$ = new Subject<AudioBuffer | null | string>();

   constructor(
       @Inject(AudioBufferService) service: AudioBufferService,
       @Inject(AUDIO_CONTEXT) context: AudioContext,
       @Attribute('autoplay') autoplay: string | null,
   ) {
       super(context);

       this.buffer$
           .pipe(
               switchMap(source =>
                   typeof source === 'string'
                       ? service.fetch(source)
                       : of(source),
               ),
           )
           .subscribe(buffer => {
               this.buffer = buffer;
           });

       if (autoplay !== null) {
           this.start();
       }
   }

   ngOnDestroy() {
       this.buffer$.complete();

       try {
           this.stop();
       } catch (_) {}

       this.disconnect();
   }
}

, autoplay audio, .


AudioParam


β€” AudioParam. GainNode . . . β€” , . , AudioParam . :


@Input('gain')
@audioParam('gain')
gainParam?: AudioParamInput;

:


export type AudioParamDecorator<K extends string> = (
   target: AudioNodeWithParams<K>,
   propertyKey: string,
) => void;

export function audioParam<K extends string>(
   param: K,
): AudioParamDecorator<K> {
   return (target, propertyKey) => {
       Object.defineProperty(target, propertyKey, {
           set(
               this: AudioNode & Record<K, AudioParam>,
               value: AudioParamInput,
           ) {
               processAudioParam(
                   this[param],
                   value,
                   this.context.currentTime,
               );
           },
       });
   };
}

. AudioParamInput? :


export type AudioParamAutomation = Readonly<{
   value: number;
   duration: number;
   mode: 'instant' | 'linear' | 'exponential';
}>;

processAudioParam API. , . 0 , 1 , β€” : {value: 1, duration: 1, mode: β€˜linear’}. .


, duration, . . . , , :


@Pipe({
   name: 'waAudioParam',
})
export class WebAudioParamPipe implements PipeTransform {
   transform(
       value: number,
       duration: number,
       mode: AudioParamAutomationMode = 'exponential',
   ): AudioParamAutomation {
       return {
           value,
           duration,
           mode,
       };
   }
}

AudioParam . 1, LFO β€” Low Frequency Oscillator. . β€” . waOutput, . exportAs :


<ng-container waOscillatorNode frequency="0.2" autoplay>
   <ng-container waGainNode gain="3000">
       <ng-container [waOutput]="filter.frequency"></ng-container>
   </ng-container>
</ng-container>

<ng-container waOscillatorNode autoplay [frequency]="note">
   <ng-container
       #filter="AudioNode"
       waBiquadFilterNode
       type="bandpass"
       frequency="4000"
   >
       <ng-container waAudioDestinationNode></ng-container>
   </ng-container>
</ng-container>


Web Audio API : , . :


https://stackblitz.com/edit/angular-web-audio


β€” -. DI:


export const TICK = new InjectionToken<Observable<number>>('Ticks', {
   factory: () => interval(250, animationFrameScheduler)
      .pipe(share()),
});

4 . beat :


kick$ = this.tick$.pipe(map(tick => tick % 4 < 2));

true false . :


<ng-container
   *ngIf="kick$ | async; else snare"
   waAudioBufferSourceNode
   autoplay
   buffer="kick.wav"
>
   <ng-container waAudioDestinationNode></ng-container>
</ng-container>
<ng-template #snare>
  <ng-container
     waAudioBufferSourceNode
     autoplay
     buffer="snare.wav"
  >
     <ng-container waAudioDestinationNode></ng-container>
  </ng-container>
</ng-template>

. , 69 β€” . . :


const LEAD = [
   70, 70, 70, 70, 70, 70, 70, 68,
   68, 68, 68, 68, 75, 79, 80, 87,
   87, 87, 87, 87, 87, 87, 87, 87,
   87, 87, 87, 87, 84, 80, 79, 75,
   80, 80, 80, 80, 80, 80, 80, 80,
   80, 80, 80, 80, 79, 75, 72, 70,
   70, 70, 70, 70, 70, 70, 70, 68,
   72, 75, 79, 80, 80, 79, 79, 75,
];

:


notes$ = this.tick$.pipe(map(note => toFrequency(LEAD[note % 64])));

. β€” ADSR-. ADSR attack, decay, sustain, release, :



, . :


@Pipe({
   name: 'adsr',
})
export class AdsrPipe implements PipeTransform {
   transform(
       value: number,
       attack: number,
       decay: number,
       sustain: number,
       release: number,
   ): AudioParamInput {
       return [
           {
               value: 0,
               duration: 0,
               mode: 'instant',
           },
           {
               value,
               duration: attack,
               mode: 'linear',
           },
           {
               value: sustain,
               duration: decay,
               mode: 'linear',
           },
           {
               value: 0,
               duration: release,
               mode: 'linear',
           },
       ];
   }
}

:


<ng-container *ngIf="notes$ | async as note">
  <ng-container 
    waOscillatorNode 
    detune="5" 
    autoplay 
    [frequency]="note"
  >
    <ng-container 
      waGainNode
      [gain]="0.1 | adsr : 0 : 0.1 : 0.02 : 0.5"
    >
      <ng-container waAudioDestinationNode></ng-container>
    </ng-container>
  </ng-container>
  <ng-container
    waOscillatorNode
    type="sawtooth"
    autoplay
    [frequency]="note"
  >
    <ng-container
      waGainNode
      [gain]="0.1 | adsr : 0 : 0.1 : 0.02 : 0.5"
    >
      <ng-container
        #feedback="AudioNode"
        waGainNode
        gain="0.7"
      >
        <ng-container waDelayNode delayTime="0.3">
          <ng-container [waOutput]="feedback"></ng-container>
        </ng-container>
        <ng-container waConvolverNode buffer="response.m4a">
          <ng-container waAudioDestinationNode></ng-container>
        </ng-container>
      </ng-container>
      <ng-container waAudioDestinationNode></ng-container>
    </ng-container>
  </ng-container>
</ng-container>

? . β€” ADSR-. , ConvolverNode. . ConvolverNode , . . , LFO waAudioParam.



, . Web Audio API Angular β€” open-source- @ng-web-apis/audio.


Web Audio API canvas , β€” SVG.

Web APIs for Angular, β€” API Angular. , , Payment Request API MIDI- β€” .


All Articles