From 7a09a2e77af9d236265cd975f24a38cda1735493 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 9 Dec 2020 13:59:31 -0500
Subject: final improvements to audio waveform rendering.

---
 src/client/views/nodes/AudioBox.tsx | 83 +++++++++++++++----------------------
 1 file changed, 34 insertions(+), 49 deletions(-)

(limited to 'src/client/views/nodes/AudioBox.tsx')

diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 785bb5ce8..c89e21312 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -1,34 +1,34 @@
 import React = require("react");
-import { FieldViewProps, FieldView } from './FieldView';
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import axios from "axios";
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import "./AudioBox.scss";
-import { Cast, DateCast, NumCast, FieldValue, ScriptCast } from "../../../fields/Types";
-import { AudioField, nullAudio } from "../../../fields/URLField";
-import { ViewBoxAnnotatableComponent } from "../DocComponent";
-import { makeInterface, createSchema, listSpec } from "../../../fields/Schema";
-import { documentSchema } from "../../../fields/documentSchemas";
-import { Utils, returnTrue, emptyFunction, returnOne, returnTransparent, returnFalse, returnZero, formatTime, setupMoveUpEvents } from "../../../Utils";
-import { runInAction, observable, reaction, IReactionDisposer, computed, action, trace, toJS } from "mobx";
+import Waveform from "react-audio-waveform";
 import { DateField } from "../../../fields/DateField";
-import { SelectionManager } from "../../util/SelectionManager";
 import { Doc, DocListCast, Opt } from "../../../fields/Doc";
-import { ContextMenuProps } from "../ContextMenuItem";
-import { ContextMenu } from "../ContextMenu";
+import { documentSchema } from "../../../fields/documentSchemas";
 import { Id } from "../../../fields/FieldSymbols";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { DocumentView, DocumentViewProps } from "./DocumentView";
-import { Docs, DocUtils } from "../../documents/Documents";
+import { List } from "../../../fields/List";
+import { createSchema, listSpec, makeInterface } from "../../../fields/Schema";
 import { ComputedField, ScriptField } from "../../../fields/ScriptField";
+import { Cast, DateCast, NumCast } from "../../../fields/Types";
+import { AudioField, nullAudio } from "../../../fields/URLField";
+import { emptyFunction, formatTime, returnFalse, returnOne, returnTrue, setupMoveUpEvents, Utils, numberRange } from "../../../Utils";
+import { Docs, DocUtils } from "../../documents/Documents";
 import { Networking } from "../../Network";
-import { LinkAnchorBox } from "./LinkAnchorBox";
-import { List } from "../../../fields/List";
+import { CurrentUserUtils } from "../../util/CurrentUserUtils";
 import { Scripting } from "../../util/Scripting";
-import Waveform from "react-audio-waveform";
-import axios from "axios";
+import { SelectionManager } from "../../util/SelectionManager";
 import { SnappingManager } from "../../util/SnappingManager";
-import { CurrentUserUtils } from "../../util/CurrentUserUtils";
-import { LinkDocPreview } from "./LinkDocPreview";
+import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from "../ContextMenuItem";
+import { ViewBoxAnnotatableComponent } from "../DocComponent";
+import "./AudioBox.scss";
+import { DocumentView, DocumentViewProps } from "./DocumentView";
+import { FieldView, FieldViewProps } from './FieldView';
 import { FormattedTextBoxComment } from "./formattedText/FormattedTextBoxComment";
+import { LinkAnchorBox } from "./LinkAnchorBox";
+import { LinkDocPreview } from "./LinkDocPreview";
 
 declare class MediaRecorder {
     // whatever MediaRecorder has
@@ -43,6 +43,7 @@ const AudioDocument = makeInterface(documentSchema, audioSchema);
 export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioDocument>(AudioDocument) {
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(AudioBox, fieldKey); }
     public static Enabled = false;
+    public static NUMBER_OF_BUCKETS = 100;
 
     static Instance: AudioBox;
     static RangeScript: ScriptField;
@@ -508,7 +509,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
     // returns the audio waveform
     @computed get waveform() {
         const audioBuckets = Cast(this.dataDoc.audioBuckets, listSpec("number"), []);
-        !audioBuckets.length && setTimeout(() => this.buckets());
+        !audioBuckets.length && setTimeout(() => this.createWaveformBuckets());
         return <Waveform
             color={"darkblue"}
             height={this._waveHeight}
@@ -516,39 +517,23 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
             // pos={this.layoutDoc._currentTimecode} need to correctly resize parent to make this work (not very necessary for function)
             pos={this.audioDuration}
             duration={this.audioDuration}
-            peaks={audioBuckets.length === 100 ? audioBuckets : undefined}
+            peaks={audioBuckets.length === AudioBox.NUMBER_OF_BUCKETS ? audioBuckets : undefined}
             progressColor={"blue"} />;
     }
 
     // decodes the audio file into peaks for generating the waveform
-    @action
-    buckets = async () => {
-        const audioCtx = new (window.AudioContext)();
-
+    createWaveformBuckets = async () => {
         axios({ url: this.path, responseType: "arraybuffer" })
-            .then(response => {
-                const audioData = response.data;
-
-                audioCtx.decodeAudioData(audioData, action(buffer => {
+            .then(response => (new (window.AudioContext)()).decodeAudioData(response.data,
+                action(buffer => {
                     const decodedAudioData = buffer.getChannelData(0);
-                    const NUMBER_OF_BUCKETS = 100;
-                    const bucketDataSize = Math.floor(decodedAudioData.length / NUMBER_OF_BUCKETS);
-                    let _buckets: number[] = [];
-                    for (let i = 0; i < NUMBER_OF_BUCKETS; i++) {
-                        const startingPoint = i * bucketDataSize;
-                        const endingPoint = i * bucketDataSize + bucketDataSize;
-                        let max = 0;
-                        for (let j = startingPoint; j < endingPoint; j++) {
-                            if (decodedAudioData[j] > max) {
-                                max = decodedAudioData[j];
-                            }
-                        }
-                        const size = Math.abs(max);
-                        _buckets.push(size / 2);
-                    }
-                    this.dataDoc.audioBuckets = new List<number>(_buckets);
-                }));
-            });
+                    const bucketDataSize = Math.floor(decodedAudioData.length / AudioBox.NUMBER_OF_BUCKETS);
+                    const brange = Array.from(Array(bucketDataSize));
+                    this.dataDoc.audioBuckets = new List<number>(
+                        numberRange(AudioBox.NUMBER_OF_BUCKETS).map(i =>
+                            brange.reduce((p, x, j) => Math.abs(Math.max(p, decodedAudioData[i * bucketDataSize + j])), 0) / 2));
+                }))
+            );
     }
 
     rangeScript = () => AudioBox.RangeScript;
-- 
cgit v1.2.3-70-g09d2

