Die Web Audio API gibt es schon lange und es gibt viele Artikel darüber. Daher werden wir nicht viel über die API selbst sprechen. Wir werden Ihnen sagen, dass Web Audio und Angular die besten Freunde sein können, wenn Sie sie richtig vorstellen. Lass uns das tun!

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