Last active
December 20, 2020 16:33
-
-
Save highfiiv/36da3c339c0c42f8ea21cea5e4079d02 to your computer and use it in GitHub Desktop.
The main web audio controller and view for sound eq and sensitivity ranges for each (see other gists for band and input-range components)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { AfterViewInit, Component, OnDestroy, OnInit } from '@angular/core'; | |
import { Subject } from 'rxjs'; | |
@Component({ | |
selector: 'sys-sound', | |
templateUrl: './sys-sound.component.html', | |
styleUrls: ['./sys-sound.component.scss'] | |
}) | |
export class SysSoundComponent implements OnInit, AfterViewInit, OnDestroy { | |
private _ngUnsubscribe: Subject<void> = new Subject<void>(); | |
private _intervalID: any; // AnimationFrame ID | |
private _source: any; // eventual stream source | |
private _fbc: any; // frequencyBinCount | |
private _data: Uint8Array; // final audio data in the standard format | |
private _audioDeviceId: string; // MediaDeviceInfo; | |
private _audioContext = new AudioContext(); | |
private _analyser = this._audioContext.createAnalyser(); | |
public devices: any[]; | |
public bands: { dB?: number, sensitivity?: number }[] = []; // eq band objects for element height etc | |
public freqs: number[]; | |
constructor() { } | |
public ngOnInit(): void {} | |
public ngAfterViewInit(): void { | |
// get and list the devices for selection | |
navigator.mediaDevices.enumerateDevices().then((devices: MediaDeviceInfo[]) => { | |
this.devices = devices.filter(device => device.kind == "audiooutput"); | |
}) | |
.catch(function (err) { | |
console.log(err.name + ": " + err.message); | |
}); | |
} | |
/* ------------------------------------------------------------------------ * | |
When sensitivity scores exist this method: | |
- Makes a request of some kind | |
- Probably want to debounce this | |
* ------------------------------------------------------------------------ */ | |
private sensitivityRequest(amount:number){ | |
console.log(amount); | |
} | |
// Control the view | |
private frameLooper() { | |
// this._intervalID = requestAnimationFrame(() => this.frameLooper()); // doesnt run in background tabs, dont use it | |
// how many values from analyser (the "buffer" size) | |
this._fbc = this._analyser.frequencyBinCount; | |
const time = 1000 / 12; // 12 times per second | |
this._intervalID = setInterval(() => { | |
// frequency data comes as integers on a scale from 0 to 255 | |
this._data = new Uint8Array(this._analyser.frequencyBinCount); | |
this._analyser.getByteFrequencyData(this._data); | |
// DEBUGGING WEBAUDIO | |
// console.log({ analyser: this._analyser }); | |
// console.log({ frequencyBinCount: this._analyser.frequencyBinCount }); | |
// console.log({ fbc: this.fbc }); | |
// console.log({ _data: this._data }); | |
// console.log({ sampleRate: this.audioContext.sampleRate }); | |
this._fbc = this._analyser.frequencyBinCount; | |
// calculate the height of each band element using frequency data | |
for (var i = 0; i < this._fbc; i++) { | |
this.bands[i].dB = this._data[i]; | |
// this.bands[i].dB >= this.bands[i]. | |
// SENSITIVITY SCORE 0 - 255 | |
// current dB is within how much of the sensitivity range | |
const ratio = this.bands[i].dB - this.bands[i].sensitivity; | |
// console.log(ratio); | |
if (ratio >= 0) { | |
this.sensitivityRequest(ratio); | |
} | |
} | |
}, time); | |
} | |
private connectStream(stream: MediaStream) { | |
this._analyser.minDecibels = -90; | |
this._analyser.maxDecibels = -10; | |
this._analyser.fftSize = 32; | |
// https://developer.mozilla.org/en-US/docs/Web/API/AudioContext/createMediaStreamSource | |
this._source = this._audioContext.createMediaStreamSource(stream); | |
// https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode | |
// MDN An AnalyserNode has exactly one input and one output. The node works even if the output is not connected. | |
this._source.connect(this._analyser); | |
// setup the frequency labels for the bands | |
this.freqs = this.calcFreqs(this._analyser.context.sampleRate, this._analyser.fftSize); | |
// set context.status: running | |
this._audioContext.resume(); | |
} | |
// calculate the frequency resolutions being displayed - sampleRate / eq band count | |
private calcFreqs(sampleRate, fftSize) { | |
const bands = fftSize / 2; // bands are half the fftSize | |
let fqRange = sampleRate / 2; | |
const fqBand = sampleRate / fftSize; | |
let allocated = []; | |
// setup eqbands and labels | |
for (let i = 0, j = bands; i < j; i++) { | |
this.bands[i] = { | |
'dB': undefined, | |
'sensitivity': 50 | |
} | |
// frequency labels | |
// sampleRate = Math.round(sampleRate - fqRange); | |
fqRange = Math.round(fqRange - fqBand); | |
allocated.push(fqRange); | |
} | |
return allocated.slice().reverse(); | |
} | |
// Add range values to their band data | |
public updateBandSense(band: { sensitivity: number, id: number }) { | |
this.bands[band.id].sensitivity = band.sensitivity; | |
} | |
// PRESS play button (or select an audio device) | |
public play() { | |
navigator.mediaDevices.getUserMedia({ | |
audio: { deviceId: this._audioDeviceId ? { exact: this._audioDeviceId } : undefined } | |
}).then((stream: MediaStream) => { | |
this.connectStream(stream); | |
this.frameLooper(); | |
}); | |
} | |
public stop() { | |
clearInterval(this._intervalID); | |
} | |
public onSelect(target:any){ | |
const elm = target as HTMLSelectElement; | |
this._audioDeviceId = elm.value; | |
if (this._audioDeviceId) { | |
this.play(); | |
} | |
// console.log(this._audioDeviceId); | |
} | |
// optimize ngFor | |
public trackByFn(index, item) { | |
return index; // or item.id | |
} | |
public ngOnDestroy(): void { | |
this._ngUnsubscribe.next(); | |
this._ngUnsubscribe.complete(); | |
this.stop(); | |
this._intervalID = null; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<nav> | |
<img src="./assets/stop.svg" alt="stop audio feed" (click)="stop()"/> | |
<img src="./assets/play.svg" alt="play audio feed" (click)="play()"/> | |
<select name="devices" (change)="onSelect($event.target)"> | |
<option *ngFor="let device of devices;" [value]="device.deviceId">{{device.label}}</option> | |
</select> | |
</nav> | |
<div class="eq-wrapper"> | |
<div class="eqbands"> | |
<ng-container *ngFor="let band of bands; index as i; trackBy: trackByFn;"> | |
<band [dB]="band.dB" (senseVal)="updateBandSense({sensitivity: $event, id: i})"></band> | |
</ng-container> | |
</div> | |
<div class="freqs"> | |
<div class="freq" *ngFor="let freq of freqs;">{{freq}}+</div> | |
</div> | |
</div> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment