aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/AudioWaveform.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/AudioWaveform.tsx')
-rw-r--r--src/client/views/AudioWaveform.tsx242
1 files changed, 164 insertions, 78 deletions
diff --git a/src/client/views/AudioWaveform.tsx b/src/client/views/AudioWaveform.tsx
index 8f3b7c2cd..8a3c3c319 100644
--- a/src/client/views/AudioWaveform.tsx
+++ b/src/client/views/AudioWaveform.tsx
@@ -1,6 +1,6 @@
import React = require("react");
import axios from "axios";
-import { action, computed } from "mobx";
+import { action, computed, reaction, IReactionDisposer } from "mobx";
import { observer } from "mobx-react";
import Waveform from "react-audio-waveform";
import { Doc } from "../../fields/Doc";
@@ -10,117 +10,203 @@ import { Cast, NumCast } from "../../fields/Types";
import { numberRange } from "../../Utils";
import "./AudioWaveform.scss";
import { Colors } from "./global/globalEnums";
+import Color = require("color");
export interface AudioWaveformProps {
- duration: number;
+ duration: number; // length of media clip
+ rawDuration: number; // length of underlying media data
mediaPath: string;
layoutDoc: Doc;
- trimming: boolean;
- PanelHeight: () => number;
+ clipStart: number;
+ clipEnd: number;
+ zoomFactor: number;
+ PanelHeight: number;
+ PanelWidth: number;
}
@observer
export class AudioWaveform extends React.Component<AudioWaveformProps> {
public static NUMBER_OF_BUCKETS = 100;
- @computed get _waveHeight() {
- return Math.max(50, this.props.PanelHeight());
+ _disposer: IReactionDisposer | undefined;
+ @computed get waveHeight() { return Math.max(50, this.props.PanelHeight); }
+ @computed get clipStart() { return this.props.clipStart; }
+ @computed get clipEnd() { return this.props.clipEnd; }
+ @computed get zoomFactor() { return this.props.zoomFactor; }
+ @computed get audioBuckets() { return Cast(this.props.layoutDoc[this.audioBucketField(this.clipStart, this.clipEnd, this.zoomFactor)], listSpec("number"), []); }
+
+ audioBucketField = (start: number, end: number, zoomFactor: number) => "audioBuckets/" + "/" + start.toFixed(2).replace(".", "_") + "/" + end.toFixed(2).replace(".", "_") + "/" + (zoomFactor * 10);
+
+ componentWillUnmount() {
+ this._disposer?.();
}
componentDidMount() {
- const audioBuckets = Cast(
- this.props.layoutDoc.audioBuckets,
- listSpec("number"),
- []
- );
- if (!audioBuckets.length) {
- this.props.layoutDoc.audioBuckets = new List<number>([0, 0]); /// "lock" to prevent other views from computing the same data
- setTimeout(this.createWaveformBuckets);
- }
+ this._disposer = reaction(() => ({ clipStart: this.clipStart, clipEnd: this.clipEnd, fieldKey: this.audioBucketField(this.clipStart, this.clipEnd, this.zoomFactor), zoomFactor: this.props.zoomFactor }),
+ ({ clipStart, clipEnd, fieldKey, zoomFactor }) => {
+ if (!this.props.layoutDoc[fieldKey]) {
+ // setting these values here serves as a "lock" to prevent multiple attempts to create the waveform at nerly the same time.
+ const waveform = Cast(this.props.layoutDoc[this.audioBucketField(0, this.props.rawDuration, 1)], listSpec("number"));
+ this.props.layoutDoc[fieldKey] = waveform && new List<number>(waveform.slice(clipStart / this.props.rawDuration * waveform.length, clipEnd / this.props.rawDuration * waveform.length));
+ setTimeout(() => this.createWaveformBuckets(fieldKey, clipStart, clipEnd, zoomFactor));
+ }
+ }, { fireImmediately: true });
+
}
// decodes the audio file into peaks for generating the waveform
- createWaveformBuckets = async () => {
+ createWaveformBuckets = async (fieldKey: string, clipStart: number, clipEnd: number, zoomFactor: number) => {
axios({ url: this.props.mediaPath, responseType: "arraybuffer" }).then(
(response) => {
const context = new window.AudioContext();
context.decodeAudioData(
response.data,
action((buffer) => {
- const decodedAudioData = buffer.getChannelData(0);
+ const rawDecodedAudioData = buffer.getChannelData(0);
+ const startInd = clipStart / this.props.rawDuration;
+ const endInd = clipEnd / this.props.rawDuration;
+ const decodedAudioData = rawDecodedAudioData.slice(Math.floor(startInd * rawDecodedAudioData.length), Math.floor(endInd * rawDecodedAudioData.length));
+ const numBuckets = Math.floor(AudioWaveform.NUMBER_OF_BUCKETS * zoomFactor);
const bucketDataSize = Math.floor(
- decodedAudioData.length / AudioWaveform.NUMBER_OF_BUCKETS
+ decodedAudioData.length / numBuckets
);
const brange = Array.from(Array(bucketDataSize));
- this.props.layoutDoc.audioBuckets = new List<number>(
- numberRange(AudioWaveform.NUMBER_OF_BUCKETS).map(
- (i: number) =>
- brange.reduce(
- (p, x, j) =>
- Math.abs(
- Math.max(p, decodedAudioData[i * bucketDataSize + j])
- ),
- 0
- ) / 2
- )
+ const bucketList = numberRange(numBuckets).map(
+ (i: number) =>
+ brange.reduce(
+ (p, x, j) =>
+ Math.abs(
+ Math.max(p, decodedAudioData[i * bucketDataSize + j])
+ ),
+ 0
+ ) / 2
);
+ this.props.layoutDoc[fieldKey] = new List<number>(bucketList);
})
);
}
);
}
-
- @action
- createTrimBuckets = () => {
- const audioBuckets = Cast(
- this.props.layoutDoc.audioBuckets,
- listSpec("number"),
- []
- );
-
- const start = Math.floor(
- (NumCast(this.props.layoutDoc.clipStart) / this.props.duration) * 100
- );
- const end = Math.floor(
- (NumCast(this.props.layoutDoc.clipEnd) / this.props.duration) * 100
- );
- return audioBuckets.slice(start, end);
- }
-
render() {
- const audioBuckets = Cast(
- this.props.layoutDoc.audioBuckets,
- listSpec("number"),
- []
- );
-
return (
<div className="audioWaveform">
- {this.props.trimming || !this.props.layoutDoc.clipEnd ? (
- <Waveform
- color={Colors.MEDIUM_BLUE}
- height={this._waveHeight}
- barWidth={0.1}
- pos={this.props.duration}
- duration={this.props.duration}
- peaks={
- audioBuckets.length === AudioWaveform.NUMBER_OF_BUCKETS
- ? audioBuckets
- : undefined
- }
- progressColor={Colors.MEDIUM_BLUE}
- />
- ) : (
- <Waveform
- color={Colors.MEDIUM_BLUE}
- height={this._waveHeight}
- barWidth={0.1}
- pos={this.props.duration}
- duration={this.props.duration}
- peaks={this.createTrimBuckets()}
- progressColor={Colors.MEDIUM_BLUE}
- />
- )}
+ {/* <Waveform
+ barWidth={2}
+ width={() => this.props.PanelWidth}
+ height={this.props.PanelHeight}
+ peaks={this.audioBuckets}
+ color={Colors.MEDIUM_BLUE}
+ /> */}
+ <Waveform
+ color={Colors.MEDIUM_BLUE_ALT}
+ height={this.waveHeight}
+ barWidth={200 / this.audioBuckets.length}
+ pos={this.props.duration}
+ duration={this.props.duration}
+ peaks={this.audioBuckets}
+ progressColor={Colors.MEDIUM_BLUE_ALT}
+ />
</div>
);
}
}
+
+
+export interface WaveformProps {
+ barWidth: number;
+ width: () => number;
+ height: () => number;
+ peaks: number[];
+ color: string;
+}
+
+// @observer
+// export class Waveform extends React.Component<WaveformProps> {
+// private _canvas: HTMLCanvasElement | null = null;
+
+// get width() { return this.props.width(); }
+// get height() { return this.props.height(); }
+// get peaks() { return this.props.peaks; }
+
+// componentDidMount() {
+// this.drawBars();
+// }
+
+// drawBars() {
+// const waveCanvasCtx = this._canvas?.getContext("2d");
+
+// if (waveCanvasCtx) {
+// const pixelRatio = window.devicePixelRatio;
+// console.log(pixelRatio);
+
+// const displayWidth = Math.round(this.width);
+// const displayHeight = Math.round(this.height);
+// waveCanvasCtx.canvas.width = this.width;
+// waveCanvasCtx.canvas.height = this.height;
+// waveCanvasCtx.canvas.style.width = `${displayWidth}px`;
+// waveCanvasCtx.canvas.style.height = `${displayHeight}px`;
+
+// waveCanvasCtx.clearRect(0, 0, this.width, this.height);
+
+// const hasMinVals = [].some.call(this.peaks, (val) => val < 0);
+// let filteredPeaks = this.peaks;
+// if (hasMinVals) {
+// // If the first value is negative, add 1 to the filtered indices
+// let indexOffset = 0;
+// if (this.peaks[0] < 0) {
+// indexOffset = 1;
+// }
+// filteredPeaks = [].filter.call(
+// this.peaks,
+// (_, index) => (index + indexOffset) % 2 == 0
+// );
+// }
+
+// const $ = 0.5;
+// const height = this.height;
+// const offsetY = 0;
+// const halfH = this.height / 2;
+// const length = filteredPeaks.length;
+// const bar = this.props.barWidth;
+// const gap = 2;
+// const step = bar + gap;
+
+// let absmax = 1;
+// absmax = this.absMax(filteredPeaks);
+
+// const scale = length / this.width;
+
+// waveCanvasCtx.fillStyle = this.props.color;
+
+// for (let i = 0; i < this.width; i += step) {
+// let h = Math.round(filteredPeaks[Math.floor(i * scale)] / absmax * halfH)
+// if (h === 0) {
+// h = 1
+// }
+// waveCanvasCtx.fillRect(i + $, halfH - h + offsetY, bar + $, h * 2)
+// }
+// }
+// }
+
+// absMax = (values: number[]) => {
+// let max = -Infinity;
+// for (const i in values) {
+// const num = Math.abs(values[i]);
+// if (num > max) {
+// max = num;
+// }
+// }
+
+// return max;
+// }
+
+
+// render() {
+// return this.props.peaks ? (
+// <canvas
+// ref={(instance) => {
+// this._canvas = instance;
+// }}
+// />
+// ) : null
+// }
+// } \ No newline at end of file