mirror of https://github.com/vector-im/riot-web
Fix float operations to make a little more sense.
parent
a848febd3d
commit
e523ce6036
|
@ -49,16 +49,14 @@ export default class LiveRecordingWaveform extends React.PureComponent<IProps, I
|
||||||
const bars = arrayFastResample(Array.from(update.waveform), DOWNSAMPLE_TARGET);
|
const bars = arrayFastResample(Array.from(update.waveform), DOWNSAMPLE_TARGET);
|
||||||
this.setState({
|
this.setState({
|
||||||
// The incoming data is between zero and one, but typically even screaming into a
|
// The incoming data is between zero and one, but typically even screaming into a
|
||||||
// microphone won't send you over 0.6, so we "cap" the graph at about 0.50 for a
|
// microphone won't send you over 0.6, so we artificially adjust the gain for the
|
||||||
// point where the average user can still see feedback and be perceived as peaking
|
// waveform. This results in a slightly more cinematic/animated waveform for the
|
||||||
// when talking "loudly".
|
// user.
|
||||||
//
|
heights: bars.map(b => percentageOf(b, 0, 0.50)),
|
||||||
// We multiply by 100 because the Waveform component wants values in 0-100 (percentages)
|
|
||||||
heights: bars.map(b => percentageOf(b, 0, 0.50) * 100),
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
return <Waveform heights={this.state.heights} />;
|
return <Waveform relHeights={this.state.heights} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ import React from "react";
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
heights: number[]; // percentages as integers (0-100)
|
relHeights: number[]; // relative heights (0-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
@ -37,8 +37,8 @@ export default class Waveform extends React.PureComponent<IProps, IState> {
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
return <div className='mx_Waveform'>
|
return <div className='mx_Waveform'>
|
||||||
{this.props.heights.map((h, i) => {
|
{this.props.relHeights.map((h, i) => {
|
||||||
return <span key={i} style={{height: h + '%'}} className='mx_Waveform_bar' />;
|
return <span key={i} style={{height: (h * 100) + '%'}} className='mx_Waveform_bar' />;
|
||||||
})}
|
})}
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import encoderPath from 'opus-recorder/dist/encoderWorker.min.js';
|
||||||
import {MatrixClient} from "matrix-js-sdk/src/client";
|
import {MatrixClient} from "matrix-js-sdk/src/client";
|
||||||
import CallMediaHandler from "../CallMediaHandler";
|
import CallMediaHandler from "../CallMediaHandler";
|
||||||
import {SimpleObservable} from "matrix-widget-api";
|
import {SimpleObservable} from "matrix-widget-api";
|
||||||
|
import {percentageOf} from "../utils/numbers";
|
||||||
|
|
||||||
const CHANNELS = 1; // stereo isn't important
|
const CHANNELS = 1; // stereo isn't important
|
||||||
const SAMPLE_RATE = 48000; // 48khz is what WebRTC uses. 12khz is where we lose quality.
|
const SAMPLE_RATE = 48000; // 48khz is what WebRTC uses. 12khz is where we lose quality.
|
||||||
|
@ -133,23 +134,18 @@ export class VoiceRecorder {
|
||||||
// The time domain is the input to the FFT, which means we use an array of the same
|
// The time domain is the input to the FFT, which means we use an array of the same
|
||||||
// size. The time domain is also known as the audio waveform. We're ignoring the
|
// size. The time domain is also known as the audio waveform. We're ignoring the
|
||||||
// output of the FFT here (frequency data) because we're not interested in it.
|
// output of the FFT here (frequency data) because we're not interested in it.
|
||||||
//
|
const data = new Float32Array(this.recorderFFT.fftSize);
|
||||||
// We use bytes out of the analyser because floats have weird precision problems
|
this.recorderFFT.getFloatTimeDomainData(data);
|
||||||
// and are slightly more difficult to work with. The bytes are easy to work with,
|
|
||||||
// which is why we pick them (they're also more precise, but we care less about that).
|
|
||||||
const data = new Uint8Array(this.recorderFFT.fftSize);
|
|
||||||
this.recorderFFT.getByteTimeDomainData(data);
|
|
||||||
|
|
||||||
// Because we're dealing with a uint array we need to do math a bit differently.
|
// We can't just `Array.from()` the array because we're dealing with 32bit floats
|
||||||
// If we just `Array.from()` the uint array, we end up with 1s and 0s, which aren't
|
// and the built-in function won't consider that when converting between numbers.
|
||||||
// what we're after. Instead, we have to use a bit of manual looping to correctly end
|
// However, the runtime will convert the float32 to a float64 during the math operations
|
||||||
// up with the right values
|
// which is why the loop works below. Note that a `.map()` call also doesn't work
|
||||||
|
// and will instead return a Float32Array still.
|
||||||
const translatedData: number[] = [];
|
const translatedData: number[] = [];
|
||||||
for (let i = 0; i < data.length; i++) {
|
for (let i = 0; i < data.length; i++) {
|
||||||
// All we're doing here is inverting the amplitude and putting the metric somewhere
|
// We're clamping the values so we can do that math operation mentioned above.
|
||||||
// between zero and one. Without the inversion, lower values are "louder", which is
|
translatedData.push(percentageOf(data[i], 0, 1));
|
||||||
// not super helpful.
|
|
||||||
translatedData.push(1 - (data[i] / 128.0));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.observable.update({
|
this.observable.update({
|
||||||
|
|
Loading…
Reference in New Issue