| Name | Message | Date |
|---|---|---|
| 📄 audioSelector.ts | 1 month ago | |
| 📄 CurveRenderer.ts | 1 month ago | |
| 📄 main.ts | 1 month ago | |
| 📄 style.css | 1 month ago |
📄
CurveRenderer.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import {
Curve,
Group,
LineCurve3,
MathUtils,
Mesh,
MeshPhysicalMaterial,
TubeGeometry,
Vector3,
Vector4,
} from "three";
import { NURBSCurve } from "three/examples/jsm/curves/NURBSCurve.js";
export class CurveRenderer {
public static audioAnalyser: AnalyserNode | null;
private static sampleRate = 50;
private static curveCount = 50;
private static nurbsDegree = 3;
private static width = 10;
private static thickness = 0.05;
private static segments = 8;
private static spacing = 0.2;
private static material = new MeshPhysicalMaterial({
color: 0xbada55,
transparent: true,
opacity: 0.9,
transmission: 1,
depthWrite: false,
});
private readonly group: Group;
private readonly curves: Array<Mesh>;
private previousSampleTime: number | null = null;
public constructor(group: Group) {
this.group = group;
this.curves = CurveRenderer.createStraightLines();
for (const curve of this.curves) {
this.group.add(curve);
}
}
private static createStraightLines(): Array<Mesh> {
const lines = new Array<Mesh>();
const geometry = new TubeGeometry(
new LineCurve3(new Vector3(-this.width / 2, 0, 0), new Vector3(this.width / 2, 0, 0)),
2,
this.thickness,
this.segments,
);
for (let i = 0; i < this.curveCount; i++) {
const mesh = new Mesh(geometry, this.material);
mesh.position.set(0, 0, (this.curveCount / 2) * this.spacing - i * this.spacing);
lines.push(mesh);
}
return lines;
}
public update(): void {
const heights = this.calculateHeights();
if (heights == null) {
return;
}
this.group.remove(this.curves.pop()!);
for (let i = 0; i < this.curves.length; i++) {
this.curves[i].position.setComponent(2, this.curves[i].position.z - CurveRenderer.spacing);
}
const geometry = new TubeGeometry(
this.convertToCurve(heights),
heights.length * 2,
CurveRenderer.thickness,
CurveRenderer.segments,
);
const curve = new Mesh(geometry, CurveRenderer.material);
curve.position.set(0, 0, (CurveRenderer.curveCount / 2) * CurveRenderer.spacing);
this.group.add(curve);
this.curves.unshift(curve);
}
private calculateHeights(): Float32Array | null {
if (CurveRenderer.audioAnalyser == null) {
return null;
}
if (
this.previousSampleTime != null &&
this.previousSampleTime + 1 / CurveRenderer.sampleRate > CurveRenderer.audioAnalyser.context.currentTime
) {
return null;
}
this.previousSampleTime = CurveRenderer.audioAnalyser.context.currentTime;
const frequencyBuffer = new Uint8Array(CurveRenderer.audioAnalyser.frequencyBinCount);
CurveRenderer.audioAnalyser.getByteFrequencyData(frequencyBuffer);
const heights = new Float32Array(frequencyBuffer.length);
for (let i = 0; i < frequencyBuffer.length; i++) {
heights[i] =
(frequencyBuffer[i] - CurveRenderer.audioAnalyser.minDecibels) /
(CurveRenderer.audioAnalyser.maxDecibels - CurveRenderer.audioAnalyser.minDecibels);
}
return heights;
}
private convertToCurve(heights: Float32Array): Curve<Vector3> {
const knots = new Array<number>();
const controlPoints = new Array<Vector4>();
for (let i = 0; i < CurveRenderer.nurbsDegree; i++) {
knots.push(0);
}
for (let i = 0; i < heights.length; i++) {
controlPoints.push(new Vector4((i / heights.length - 0.5) * CurveRenderer.width, heights[i] - 1, 0, 1));
knots.push(MathUtils.clamp((i + 1) / (heights.length - CurveRenderer.nurbsDegree), 0, 1));
}
return new NURBSCurve(CurveRenderer.nurbsDegree, knots, controlPoints);
}
}