Labing لوحة مفاتيح MIDI في الزاوي

واجهة برمجة تطبيقات Web MIDI هي وحش مثير للاهتمام. على الرغم من أنها كانت موجودة منذ ما يقرب من خمس سنوات ، إلا أنها لا تزال مدعومة من Chromium فقط. ولكن هذا لا يمنعنا من إنشاء مركب كامل في Angular. حان الوقت لنقل Web Audio API إلى المستوى التالي!





Web Audio API Angular.



, , , ? 80- — MIDI. , Chrome . , , MIDI-, , . , . , - Angular.



Web MIDI API



API, . MIDI- navigator Promise . — — EventTarget. MIDIMessageEvent, Uint8Array . 3 . status byte. , . , , — . MIDI. Angular Observable, Web MIDI API RxJs.



Dependency Injection



, MIDIAccess-, . navigator Promise, RxJs Observable. InjectionToken, NAVIGATOR @ng-web-apis/common. :



export const MIDI_ACCESS = new InjectionToken<Promise<MIDIAccess>>(
   'Promise for MIDIAccess object',
   {
       factory: () => {
           const navigatorRef = inject(NAVIGATOR);

           return navigatorRef.requestMIDIAccess
               ? navigatorRef.requestMIDIAccess()
               : Promise.reject(new Error('Web MIDI API is not supported'));
       },
   },
);


MIDI-. Observable :



  1. , Observable, Geolocation API
  2. , Promise Observable


, . :



export const MIDI_MESSAGES = new InjectionToken<Observable<MIDIMessageEvent>>(
   'All incoming MIDI messages stream',
   {
       factory: () =>
           from(inject(MIDI_ACCESS).catch((e: Error) => e)).pipe(
               switchMap(access =>
                   access instanceof Error
                       ? throwError(access)
                       : merge(
                             ...Array.from(access.inputs).map(([_, input]) =>
                                 fromEvent(
                                     input as FromEventTarget<MIDIMessageEvent>,
                                     'midimessage',
                                 ),
                             ),
                         ),
               ),
               share(),
           ),
   },
);


- , , MIDIAccess. :



export function outputById(id: string): Provider[] {
   return [
       {
           provide: MIDI_OUTPUT_QUERY,
           useValue: id,
       },
       {
           provide: MIDI_OUTPUT,
           deps: [MIDI_ACCESS, MIDI_OUTPUT_QUERY],
           useFactory: outputByIdFactory,
       },
   ];
}

export function outputByIdFactory(
   midiAccess: Promise<MIDIAccess>,
   id: string,
): Promise<MIDIOutput | undefined> {
   return midiAccess.then(access => access.outputs.get(id));
}


, , Provider[], ? providers @Directive , :


providers: [
  outputById(‘someId’),
  ANOTHER_TOKEN,
  SomeService,
]


Angular — .

, .





. , .

:



  • . , . , .
  • . . , , .


:



export function filterByChannel(
   channel: MidiChannel,
): MonoTypeOperatorFunction<MIDIMessageEvent> {
   return source => source.pipe(filter(({data}) => data[0] % 16 === channel));
}


Status byte 16: 128—143 (noteOn) 16 . 144—159 — (noteOff). , 16 — .



, :



export function notes(): MonoTypeOperatorFunction<MIDIMessageEvent> {
   return source =>
       source.pipe(
           filter(({data}) => between(data[0], 128, 159)),
           map(event => {
               if (between(event.data[0], 128, 143)) {
                   event.data[0] += 16;
                   event.data[2] = 0;
               }

               return event;
           }),
       );
}


MIDI- noteOff-, . noteOn . , noteOn. status byte 16, noteOff- noteOn, .

, , :



readonly notes$ = this.messages$.pipe(
  catchError(() => EMPTY),
  notes(),
  toData(),
);

constructor(
  @Inject(MIDI_MESSAGES)
  private readonly messages$: Observable<MIDIMessageEvent>,
) {}


!





Web Audio API, . , .



. , . scan:



readonly notes$ = this.messages$.pipe(
  catchError(() => EMPTY),
  notes(),
  toData(),
  scan(
    (map, [_, note, volume]) => map.set(note, volume), new Map()
  ),
);


, ADSR-. . , ADSR — :





, , , .



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


, , attack. sustain decay. , release.

:



<ng-container
  *ngFor="let note of notes | keyvalue; trackBy: noteKey"
>
  <ng-container
    waOscillatorNode
    detune="5"
    autoplay
    [frequency]="toFrequency(note.key)" 
  >
    <ng-container 
      waGainNode 
      gain="0"
      [gain]="note.value | adsr: 0:0.1:0.02:1"
    >
      <ng-container waAudioDestinationNode></ng-container>
    </ng-container>
  </ng-container> 
  <ng-container
    waOscillatorNode
    type="sawtooth"
    autoplay 
    [frequency]="toFrequency(note.key)"
  >
    <ng-container 
      waGainNode
      gain="0"
      [gain]="note.value | adsr: 0:0.1:0.02:1"
    >
      <ng-container waAudioDestinationNode></ng-container>
      <ng-container [waOutput]="convolver"></ng-container>
    </ng-container>
  </ng-container>
</ng-container>
<ng-container
  #convolver="AudioNode"
  waConvolverNode
  buffer="assets/audio/response.wav"
>
  <ng-container waAudioDestinationNode></ng-container>
</ng-container>


keyvalue , . , . — ConvolverNode. , , . Chrome:



https://ng-web-apis.github.io/midi



MIDI- — .



, MIDI iframe: https://stackblitz.com/edit/angular-midi




Angular RxJs. Web MIDI API DOM-. MIDI Angular . open-source @ng-web-apis/midi. , Web APIs for Angular. — API Angular . , , Payment Request API Intersection Observer — .



, Angular Web MIDI API — Jamigo.app




All Articles