@geastack/native-webgl-angle 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +184 -0
- package/geatsc-plugin-batched-probe.mjs +98 -0
- package/geatsc-plugin-instancing.mjs +160 -0
- package/geatsc-plugin-uniforms.mjs +1955 -0
- package/geatsc-plugin-uploads.mjs +29 -0
- package/geatsc-plugin.mjs +10275 -0
- package/native/angle_webgl_host.mm +2687 -0
- package/native/audio_host.mm +857 -0
- package/native/audio_uwp.h +116 -0
- package/package.json +70 -0
- package/src/gea-native-types.d.ts +4 -0
- package/src/nativeAudioHost.ts +296 -0
- package/src/nativeThreeUtils.ts +11 -0
- package/src/nativeWebGL.ts +1211 -0
- package/src/nativeWebGLAnimation.ts +13 -0
- package/src/nativeWebGLHost.ts +333 -0
- package/src/nativeWebXRManager.ts +48 -0
- package/src/text/atlas-sans.ts +195 -0
- package/src/text/atlas-serif-italic.ts +195 -0
- package/src/text/atlas-serif.ts +195 -0
- package/src/text/native-text-types.ts +17 -0
- package/src/text/native-text.ts +203 -0
- package/src/troika-three-text.ts +248 -0
- package/test/auto-instanced-render-scene.ts +66 -0
- package/test/batched-render-scene.ts +58 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#ifndef NOMINMAX
|
|
3
|
+
#define NOMINMAX
|
|
4
|
+
#endif
|
|
5
|
+
#include <windows.h>
|
|
6
|
+
#include <xaudio2.h>
|
|
7
|
+
#include <mfapi.h>
|
|
8
|
+
#include <mfidl.h>
|
|
9
|
+
#include <mfreadwrite.h>
|
|
10
|
+
#include <wrl/client.h>
|
|
11
|
+
#include <array>
|
|
12
|
+
#include <string>
|
|
13
|
+
#include <vector>
|
|
14
|
+
|
|
15
|
+
// The WebAudio graph remains in audio_host.mm. This adapter only delivers its
|
|
16
|
+
// planar float output to XAudio2 and decodes packaged audio through Media Foundation.
|
|
17
|
+
class GeaUwpAudioOutput final : public IXAudio2VoiceCallback {
|
|
18
|
+
// Two 10 ms blocks bound queued audio to 20 ms at 48 kHz. The previous
|
|
19
|
+
// three 1024-frame blocks delayed gain changes and new sounds by up to 64 ms.
|
|
20
|
+
static constexpr size_t frames = 480;
|
|
21
|
+
Microsoft::WRL::ComPtr<IXAudio2> engine;
|
|
22
|
+
IXAudio2MasteringVoice *master = nullptr;
|
|
23
|
+
IXAudio2SourceVoice *voice = nullptr;
|
|
24
|
+
std::array<std::array<float, frames * 2>, 2> buffers{};
|
|
25
|
+
std::array<float, frames> left{}, right{};
|
|
26
|
+
void *context = nullptr;
|
|
27
|
+
void (*render)(void *, size_t, float *, float *) = nullptr;
|
|
28
|
+
|
|
29
|
+
HRESULT submit(float *data) {
|
|
30
|
+
XAUDIO2_BUFFER buffer{};
|
|
31
|
+
buffer.AudioBytes = static_cast<UINT32>(frames * 2 * sizeof(float));
|
|
32
|
+
buffer.pAudioData = reinterpret_cast<const BYTE *>(data);
|
|
33
|
+
buffer.pContext = data;
|
|
34
|
+
return voice->SubmitSourceBuffer(&buffer);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public:
|
|
38
|
+
~GeaUwpAudioOutput() {
|
|
39
|
+
if (voice) voice->DestroyVoice();
|
|
40
|
+
if (master) master->DestroyVoice();
|
|
41
|
+
}
|
|
42
|
+
bool start(void *ctx, void (*pull)(void *, size_t, float *, float *)) {
|
|
43
|
+
context = ctx;
|
|
44
|
+
render = pull;
|
|
45
|
+
if (FAILED(XAudio2Create(engine.GetAddressOf(), 0, XAUDIO2_DEFAULT_PROCESSOR))) return false;
|
|
46
|
+
if (FAILED(engine->CreateMasteringVoice(&master, 2, 48000))) return false;
|
|
47
|
+
WAVEFORMATEX format{};
|
|
48
|
+
format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
|
|
49
|
+
format.nChannels = 2;
|
|
50
|
+
format.nSamplesPerSec = 48000;
|
|
51
|
+
format.wBitsPerSample = 32;
|
|
52
|
+
format.nBlockAlign = 8;
|
|
53
|
+
format.nAvgBytesPerSec = 48000 * 8;
|
|
54
|
+
if (FAILED(engine->CreateSourceVoice(&voice, &format, 0, 1.0f, this))) return false;
|
|
55
|
+
for (auto &buffer : buffers) if (FAILED(submit(buffer.data()))) return false;
|
|
56
|
+
return SUCCEEDED(voice->Start());
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
void STDMETHODCALLTYPE OnBufferEnd(void *data) override {
|
|
60
|
+
render(context, frames, left.data(), right.data());
|
|
61
|
+
auto *output = static_cast<float *>(data);
|
|
62
|
+
for (size_t i = 0; i < frames; ++i) {
|
|
63
|
+
output[i * 2] = left[i];
|
|
64
|
+
output[i * 2 + 1] = right[i];
|
|
65
|
+
}
|
|
66
|
+
if (FAILED(submit(output))) OutputDebugStringA("[gea-audio] XAudio2 buffer submission failed\n");
|
|
67
|
+
}
|
|
68
|
+
void STDMETHODCALLTYPE OnStreamEnd() override {}
|
|
69
|
+
void STDMETHODCALLTYPE OnVoiceProcessingPassEnd() override {}
|
|
70
|
+
void STDMETHODCALLTYPE OnVoiceProcessingPassStart(UINT32) override {}
|
|
71
|
+
void STDMETHODCALLTYPE OnBufferStart(void *) override {}
|
|
72
|
+
void STDMETHODCALLTYPE OnLoopEnd(void *) override {}
|
|
73
|
+
void STDMETHODCALLTYPE OnVoiceError(void *, HRESULT) override {
|
|
74
|
+
OutputDebugStringA("[gea-audio] XAudio2 voice failed\n");
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
inline bool geaUwpDecodeAudio(const std::wstring &path, std::vector<std::vector<float>> &channels, double &rate) {
|
|
79
|
+
using Microsoft::WRL::ComPtr;
|
|
80
|
+
static const HRESULT startup = MFStartup(MF_VERSION);
|
|
81
|
+
if (FAILED(startup)) return false;
|
|
82
|
+
ComPtr<IMFSourceReader> reader;
|
|
83
|
+
if (FAILED(MFCreateSourceReaderFromURL(path.c_str(), nullptr, reader.GetAddressOf()))) return false;
|
|
84
|
+
ComPtr<IMFMediaType> requested;
|
|
85
|
+
if (FAILED(MFCreateMediaType(requested.GetAddressOf()))) return false;
|
|
86
|
+
requested->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio);
|
|
87
|
+
requested->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_Float);
|
|
88
|
+
if (FAILED(reader->SetCurrentMediaType(MF_SOURCE_READER_FIRST_AUDIO_STREAM, nullptr, requested.Get()))) return false;
|
|
89
|
+
ComPtr<IMFMediaType> actual;
|
|
90
|
+
if (FAILED(reader->GetCurrentMediaType(MF_SOURCE_READER_FIRST_AUDIO_STREAM, actual.GetAddressOf()))) return false;
|
|
91
|
+
UINT32 count = 0, sampleRate = 0;
|
|
92
|
+
if (FAILED(actual->GetUINT32(MF_MT_AUDIO_NUM_CHANNELS, &count)) || count == 0 || count > 2) return false;
|
|
93
|
+
if (FAILED(actual->GetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, &sampleRate)) || sampleRate == 0) return false;
|
|
94
|
+
rate = sampleRate;
|
|
95
|
+
channels.resize(count);
|
|
96
|
+
for (;;) {
|
|
97
|
+
DWORD flags = 0;
|
|
98
|
+
ComPtr<IMFSample> sample;
|
|
99
|
+
if (FAILED(reader->ReadSample(MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, nullptr, &flags, nullptr, sample.GetAddressOf()))) return false;
|
|
100
|
+
if (flags & MF_SOURCE_READERF_ERROR) return false;
|
|
101
|
+
if (sample) {
|
|
102
|
+
ComPtr<IMFMediaBuffer> buffer;
|
|
103
|
+
if (FAILED(sample->ConvertToContiguousBuffer(buffer.GetAddressOf()))) return false;
|
|
104
|
+
BYTE *bytes = nullptr;
|
|
105
|
+
DWORD length = 0;
|
|
106
|
+
if (FAILED(buffer->Lock(&bytes, nullptr, &length))) return false;
|
|
107
|
+
const auto *pcm = reinterpret_cast<const float *>(bytes);
|
|
108
|
+
const size_t frames = length / (sizeof(float) * count);
|
|
109
|
+
for (size_t i = 0; i < frames; ++i)
|
|
110
|
+
for (UINT32 ch = 0; ch < count; ++ch) channels[ch].push_back(pcm[i * count + ch]);
|
|
111
|
+
buffer->Unlock();
|
|
112
|
+
}
|
|
113
|
+
if (flags & MF_SOURCE_READERF_ENDOFSTREAM) break;
|
|
114
|
+
}
|
|
115
|
+
return !channels[0].empty();
|
|
116
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@geastack/native-webgl-angle",
|
|
3
|
+
"version": "0.1.6",
|
|
4
|
+
"description": "Typed native WebGL compatibility layer backed by ANGLE/Metal",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/geastack/native-webgl-angle.git"
|
|
9
|
+
},
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=20.19"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": "./src/nativeWebGL.ts",
|
|
16
|
+
"./nativeWebGL": "./src/nativeWebGL.ts",
|
|
17
|
+
"./nativeWebGLHost": "./src/nativeWebGLHost.ts",
|
|
18
|
+
"./nativeAudioHost": "./src/nativeAudioHost.ts",
|
|
19
|
+
"./nativeWebGLAnimation": "./src/nativeWebGLAnimation.ts",
|
|
20
|
+
"./nativeThreeUtils": "./src/nativeThreeUtils.ts",
|
|
21
|
+
"./nativeWebXRManager": "./src/nativeWebXRManager.ts",
|
|
22
|
+
"./troika-three-text": "./src/troika-three-text.ts",
|
|
23
|
+
"./geatsc-plugin": "./geatsc-plugin.mjs",
|
|
24
|
+
"./native/*": "./native/*",
|
|
25
|
+
"./package.json": "./package.json",
|
|
26
|
+
"./native-text-types": "./src/text/native-text-types.ts",
|
|
27
|
+
"./text/native-text-types": "./src/text/native-text-types.ts",
|
|
28
|
+
"./geatsc-plugin-batched-probe": "./geatsc-plugin-batched-probe.mjs",
|
|
29
|
+
"./test/batched-render-scene": "./test/batched-render-scene.ts",
|
|
30
|
+
"./test/auto-instanced-render-scene": "./test/auto-instanced-render-scene.ts"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"src",
|
|
34
|
+
"native",
|
|
35
|
+
"geatsc-plugin.mjs",
|
|
36
|
+
"geatsc-plugin-uploads.mjs",
|
|
37
|
+
"geatsc-plugin-uniforms.mjs",
|
|
38
|
+
"geatsc-plugin-instancing.mjs",
|
|
39
|
+
"geatsc-plugin-batched-probe.mjs",
|
|
40
|
+
"test/batched-render-scene.ts",
|
|
41
|
+
"test/auto-instanced-render-scene.ts"
|
|
42
|
+
],
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public",
|
|
45
|
+
"registry": "https://registry.npmjs.org/"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"test:event-dispatch": "node test/native-event-dispatch-contract.test.mjs",
|
|
49
|
+
"test:host-effects": "node test/native-host-effects-contract.test.mjs",
|
|
50
|
+
"test:allocation-contracts": "node test/render-sort-contract.test.mjs && node test/upload-array-contract.test.mjs && node test/draw-dispatch-contract.test.mjs && node test/native-upload-transport.test.mjs && node test/fixed-uniform-contract.test.mjs && node test/uniform-binding-contract.test.mjs && node test/buffer-streaming-contract.test.mjs",
|
|
51
|
+
"check": "tsc -p tsconfig.json --noEmit",
|
|
52
|
+
"test": "node test/native-blending-contract.test.mjs && node test/native-transform-contract.test.mjs && node test/troika-three-text.test.mjs && node test/typed-void-contract.test.mjs && node test/instancing-transport-contract.test.mjs && node test/automatic-instancing-contract.test.mjs && node test/jsdoc-fold-contract.test.mjs",
|
|
53
|
+
"test:package": "node --experimental-vm-modules test/package.test.mjs"
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"@geastack/apple": ">=0.1.0"
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"three": "^0.185.0",
|
|
60
|
+
"@geastack/compiler": "^1.0.9"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@geastack/compiler": "^1.0.9",
|
|
64
|
+
"@geastack/core": "^0.1.12",
|
|
65
|
+
"@geastack/engine": "^0.1.0",
|
|
66
|
+
"@types/three": "^0.185.0",
|
|
67
|
+
"three": "^0.185.0",
|
|
68
|
+
"typescript": "latest"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
// Native audio host facade — a Web-Audio-shaped wrapper over the
|
|
2
|
+
// `gea_three_audio_*` externs (native/audio_host.mm, registered through
|
|
3
|
+
// geatsc-plugin.mjs). It implements exactly the surface a Web Audio sound
|
|
4
|
+
// design uses: gain / biquad
|
|
5
|
+
// lowpass / buffer source / oscillator nodes, param automation (setValueAtTime,
|
|
6
|
+
// linear/exponential ramps, cancel), synth buffers written via getChannelData,
|
|
7
|
+
// and mp3 decode by asset basename (the native asset stub lowers
|
|
8
|
+
// `import url from './x.mp3'` to the file's basename).
|
|
9
|
+
//
|
|
10
|
+
// Everything here is structural: the classes satisfy the app's
|
|
11
|
+
// AudioContextLike/GainLike/... interfaces without importing them. On the web
|
|
12
|
+
// these classes are never constructed (the real AudioContext wins), so the
|
|
13
|
+
// ambient host externs are never referenced at runtime there.
|
|
14
|
+
|
|
15
|
+
// Node kinds (native/audio_host.mm NodeKind).
|
|
16
|
+
const NODE_GAIN = 1
|
|
17
|
+
const NODE_BIQUAD = 2
|
|
18
|
+
const NODE_BUFFER_SOURCE = 3
|
|
19
|
+
const NODE_OSCILLATOR = 4
|
|
20
|
+
|
|
21
|
+
// Param codes (audio_host.mm paramFor).
|
|
22
|
+
const PARAM_GAIN = 0
|
|
23
|
+
const PARAM_FREQUENCY = 1
|
|
24
|
+
const PARAM_Q = 2
|
|
25
|
+
const PARAM_PLAYBACK_RATE = 3
|
|
26
|
+
|
|
27
|
+
// Param methods (audio_host.mm ParamMethod).
|
|
28
|
+
const METHOD_SET_VALUE = 0
|
|
29
|
+
const METHOD_SET_VALUE_AT_TIME = 1
|
|
30
|
+
const METHOD_LINEAR_RAMP = 2
|
|
31
|
+
const METHOD_EXPONENTIAL_RAMP = 3
|
|
32
|
+
const METHOD_CANCEL = 4
|
|
33
|
+
|
|
34
|
+
export class NativeAudioParam {
|
|
35
|
+
readonly __audioNodeId: number
|
|
36
|
+
readonly __paramCode: number
|
|
37
|
+
|
|
38
|
+
constructor(nodeId: number, paramCode: number) {
|
|
39
|
+
this.__audioNodeId = nodeId
|
|
40
|
+
this.__paramCode = paramCode
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get value(): number {
|
|
44
|
+
return threeAudioParamGet(this.__audioNodeId, this.__paramCode)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
set value(v: number) {
|
|
48
|
+
threeAudioParamEvent(this.__audioNodeId, this.__paramCode, METHOD_SET_VALUE, v, 0)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
setValueAtTime(value: number, time: number): void {
|
|
52
|
+
threeAudioParamEvent(this.__audioNodeId, this.__paramCode, METHOD_SET_VALUE_AT_TIME, value, time)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
linearRampToValueAtTime(value: number, time: number): void {
|
|
56
|
+
threeAudioParamEvent(this.__audioNodeId, this.__paramCode, METHOD_LINEAR_RAMP, value, time)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
exponentialRampToValueAtTime(value: number, time: number): void {
|
|
60
|
+
threeAudioParamEvent(this.__audioNodeId, this.__paramCode, METHOD_EXPONENTIAL_RAMP, value, time)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
cancelScheduledValues(time: number): void {
|
|
64
|
+
threeAudioParamEvent(this.__audioNodeId, this.__paramCode, METHOD_CANCEL, 0, time)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export class NativeAudioNode {
|
|
69
|
+
readonly __audioNodeId: number
|
|
70
|
+
|
|
71
|
+
constructor(nodeId: number) {
|
|
72
|
+
this.__audioNodeId = nodeId
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Param connect targets never occur in the reference graph; node targets are
|
|
76
|
+
// always wrappers from this module, so the id read is direct.
|
|
77
|
+
connect(node: NativeAudioNode): void {
|
|
78
|
+
threeAudioConnect(this.__audioNodeId, node.__audioNodeId)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export class NativeAudioDestination extends NativeAudioNode {}
|
|
83
|
+
|
|
84
|
+
export class NativeGainNode extends NativeAudioNode {
|
|
85
|
+
readonly gain: NativeAudioParam
|
|
86
|
+
|
|
87
|
+
constructor(nodeId: number) {
|
|
88
|
+
super(nodeId)
|
|
89
|
+
this.gain = new NativeAudioParam(nodeId, PARAM_GAIN)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export class NativeBiquadFilterNode extends NativeAudioNode {
|
|
94
|
+
readonly frequency: NativeAudioParam
|
|
95
|
+
readonly Q: NativeAudioParam
|
|
96
|
+
private __typeName = 'lowpass'
|
|
97
|
+
|
|
98
|
+
constructor(nodeId: number) {
|
|
99
|
+
super(nodeId)
|
|
100
|
+
this.frequency = new NativeAudioParam(nodeId, PARAM_FREQUENCY)
|
|
101
|
+
this.Q = new NativeAudioParam(nodeId, PARAM_Q)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
get type(): string {
|
|
105
|
+
return this.__typeName
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
set type(value: string) {
|
|
109
|
+
this.__typeName = value
|
|
110
|
+
threeAudioSetNodeType(this.__audioNodeId, value)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export class NativeOscillatorNode extends NativeAudioNode {
|
|
115
|
+
readonly frequency: NativeAudioParam
|
|
116
|
+
private __typeName = 'sine'
|
|
117
|
+
|
|
118
|
+
constructor(nodeId: number) {
|
|
119
|
+
super(nodeId)
|
|
120
|
+
this.frequency = new NativeAudioParam(nodeId, PARAM_FREQUENCY)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
get type(): string {
|
|
124
|
+
return this.__typeName
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
set type(value: string) {
|
|
128
|
+
this.__typeName = value
|
|
129
|
+
threeAudioSetNodeType(this.__audioNodeId, value)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
start(when = 0): void {
|
|
133
|
+
threeAudioSourceStart(this.__audioNodeId, when, 0)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
stop(when = 0): void {
|
|
137
|
+
threeAudioSourceStop(this.__audioNodeId, when)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export class NativeAudioBuffer {
|
|
142
|
+
__nativeBufferId: number
|
|
143
|
+
readonly __sampleRate: number
|
|
144
|
+
readonly __channels: Float32Array[]
|
|
145
|
+
|
|
146
|
+
constructor(nativeBufferId: number, channels: number, length: number, sampleRate: number) {
|
|
147
|
+
this.__nativeBufferId = nativeBufferId
|
|
148
|
+
this.__sampleRate = sampleRate
|
|
149
|
+
this.__channels = []
|
|
150
|
+
if (nativeBufferId === 0) {
|
|
151
|
+
for (let ch = 0; ch < channels; ch++) this.__channels.push(new Float32Array(length))
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Synth buffers (createBuffer) hand out writable local planes; decoded
|
|
156
|
+
// buffers live natively and are never read back by the game.
|
|
157
|
+
getChannelData(channel: number): Float32Array {
|
|
158
|
+
if (channel >= 0 && channel < this.__channels.length) return this.__channels[channel]
|
|
159
|
+
return new Float32Array(0)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Push locally written synth planes to the host on first playback.
|
|
163
|
+
__ensureUploaded(): number {
|
|
164
|
+
if (this.__nativeBufferId === 0 && this.__channels.length > 0) {
|
|
165
|
+
const length = this.__channels[0].length
|
|
166
|
+
const id = threeAudioCreateBuffer(this.__channels.length, length, this.__sampleRate)
|
|
167
|
+
for (let ch = 0; ch < this.__channels.length; ch++) {
|
|
168
|
+
threeAudioBufferChannelData(id, ch, this.__channels[ch])
|
|
169
|
+
}
|
|
170
|
+
this.__nativeBufferId = id
|
|
171
|
+
}
|
|
172
|
+
return this.__nativeBufferId
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export class NativeBufferSourceNode extends NativeAudioNode {
|
|
177
|
+
buffer: NativeAudioBuffer | null = null
|
|
178
|
+
loop = false
|
|
179
|
+
readonly playbackRate: NativeAudioParam
|
|
180
|
+
|
|
181
|
+
constructor(nodeId: number) {
|
|
182
|
+
super(nodeId)
|
|
183
|
+
this.playbackRate = new NativeAudioParam(nodeId, PARAM_PLAYBACK_RATE)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
start(when = 0): void {
|
|
187
|
+
const buffer = this.buffer
|
|
188
|
+
if (buffer) {
|
|
189
|
+
const bufferId = buffer.__ensureUploaded()
|
|
190
|
+
if (bufferId > 0) threeAudioSourceSetBuffer(this.__audioNodeId, bufferId)
|
|
191
|
+
}
|
|
192
|
+
threeAudioSourceStart(this.__audioNodeId, when, this.loop ? 1 : 0)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
stop(when = 0): void {
|
|
196
|
+
threeAudioSourceStop(this.__audioNodeId, when)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export class NativeAudioContext {
|
|
201
|
+
readonly destination: NativeAudioDestination = new NativeAudioDestination(0)
|
|
202
|
+
readonly state: string = 'running'
|
|
203
|
+
|
|
204
|
+
get currentTime(): number {
|
|
205
|
+
return threeAudioCurrentTime()
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
get sampleRate(): number {
|
|
209
|
+
return threeAudioSampleRate()
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
createGain(): NativeGainNode {
|
|
213
|
+
return new NativeGainNode(threeAudioCreateNode(NODE_GAIN))
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
createBiquadFilter(): NativeBiquadFilterNode {
|
|
217
|
+
return new NativeBiquadFilterNode(threeAudioCreateNode(NODE_BIQUAD))
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
createBufferSource(): NativeBufferSourceNode {
|
|
221
|
+
return new NativeBufferSourceNode(threeAudioCreateNode(NODE_BUFFER_SOURCE))
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
createOscillator(): NativeOscillatorNode {
|
|
225
|
+
return new NativeOscillatorNode(threeAudioCreateNode(NODE_OSCILLATOR))
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
createBuffer(channels: number, length: number, sampleRate: number): NativeAudioBuffer {
|
|
229
|
+
return new NativeAudioBuffer(0, channels, length, sampleRate)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Web-only surface (kept for structural compatibility with the app's
|
|
233
|
+
// AudioContextLike). Native clip loading goes through
|
|
234
|
+
// nativeAudioBufferForFile below; this promise never settles.
|
|
235
|
+
decodeAudioData(_data: ArrayBuffer): Promise<NativeAudioBuffer> {
|
|
236
|
+
return new Promise<NativeAudioBuffer>(() => {})
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
resume(): void {
|
|
240
|
+
threeAudioResume()
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
let activeNativeAudioContext: NativeAudioContext | null = null
|
|
245
|
+
|
|
246
|
+
// Create (or return) the native audio context. Only call this when the web
|
|
247
|
+
// AudioContext is absent — on the web the host externs referenced here do not
|
|
248
|
+
// exist at runtime.
|
|
249
|
+
export function createNativeAudioContext(): NativeAudioContext | null {
|
|
250
|
+
if (activeNativeAudioContext) return activeNativeAudioContext
|
|
251
|
+
if (!threeAudioSupported()) return null
|
|
252
|
+
activeNativeAudioContext = new NativeAudioContext()
|
|
253
|
+
return activeNativeAudioContext
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Decode an audio asset by basename ("coin.mp3" — what the native asset stub
|
|
257
|
+
// lowers an mp3 import to) into a playable buffer. Returns null when no native
|
|
258
|
+
// context is active (web) or the file is missing, letting callers fall through
|
|
259
|
+
// to the fetch + decodeAudioData path.
|
|
260
|
+
export function nativeAudioBufferForFile(name: string): NativeAudioBuffer | null {
|
|
261
|
+
if (!activeNativeAudioContext) return null
|
|
262
|
+
const bufferId = threeAudioLoadBuffer(name)
|
|
263
|
+
if (bufferId <= 0) return null
|
|
264
|
+
return new NativeAudioBuffer(bufferId, 0, 0, threeAudioSampleRate())
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** @gea-host-inert Reads scalar arguments and updates native audio state only; retains no script references or callbacks. */
|
|
268
|
+
declare function threeAudioSupported(): boolean
|
|
269
|
+
/** @gea-host-inert Reads scalar arguments and updates native audio state only; retains no script references or callbacks. */
|
|
270
|
+
declare function threeAudioCurrentTime(): number
|
|
271
|
+
/** @gea-host-inert Reads scalar arguments and updates native audio state only; retains no script references or callbacks. */
|
|
272
|
+
declare function threeAudioSampleRate(): number
|
|
273
|
+
/** @gea-host-inert Reads scalar arguments and updates native audio state only; retains no script references or callbacks. */
|
|
274
|
+
declare function threeAudioResume(): void
|
|
275
|
+
/** @gea-host-inert Reads scalar arguments and updates native audio state only; retains no script references or callbacks. */
|
|
276
|
+
declare function threeAudioCreateNode(kind: number): number
|
|
277
|
+
/** @gea-host-inert Reads scalar arguments and updates native audio state only; retains no script references or callbacks. */
|
|
278
|
+
declare function threeAudioSetNodeType(node: number, type: string): void
|
|
279
|
+
/** @gea-host-inert Reads scalar arguments and updates native audio state only; retains no script references or callbacks. */
|
|
280
|
+
declare function threeAudioConnect(src: number, dst: number): void
|
|
281
|
+
/** @gea-host-inert Reads scalar arguments and updates native audio state only; retains no script references or callbacks. */
|
|
282
|
+
declare function threeAudioParamEvent(node: number, param: number, method: number, value: number, time: number): void
|
|
283
|
+
/** @gea-host-inert Reads scalar arguments and updates native audio state only; retains no script references or callbacks. */
|
|
284
|
+
declare function threeAudioParamGet(node: number, param: number): number
|
|
285
|
+
/** @gea-host-inert Reads scalar arguments and updates native audio state only; retains no script references or callbacks. */
|
|
286
|
+
declare function threeAudioLoadBuffer(name: string): number
|
|
287
|
+
/** @gea-host-inert Reads scalar arguments and updates native audio state only; retains no script references or callbacks. */
|
|
288
|
+
declare function threeAudioCreateBuffer(channels: number, length: number, sampleRate: number): number
|
|
289
|
+
/** @gea-host-inert Synchronously copies the typed-array argument into native audio storage; retains no script references or callbacks. */
|
|
290
|
+
declare function threeAudioBufferChannelData(buffer: number, channel: number, data: Float32Array): void
|
|
291
|
+
/** @gea-host-inert Reads scalar arguments and updates native audio state only; retains no script references or callbacks. */
|
|
292
|
+
declare function threeAudioSourceSetBuffer(node: number, buffer: number): void
|
|
293
|
+
/** @gea-host-inert Reads scalar arguments and updates native audio state only; retains no script references or callbacks. */
|
|
294
|
+
declare function threeAudioSourceStart(node: number, when: number, loop: number): void
|
|
295
|
+
/** @gea-host-inert Reads scalar arguments and updates native audio state only; retains no script references or callbacks. */
|
|
296
|
+
declare function threeAudioSourceStop(node: number, when: number): void
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from 'three/src/utils.js'
|
|
2
|
+
|
|
3
|
+
import { createNativeWebGLCanvas, type NativeWebGLCanvas } from './nativeWebGL'
|
|
4
|
+
|
|
5
|
+
/** Three's XHTML factory, narrowed to the canvas surface used by the native renderer. */
|
|
6
|
+
export function createElementNS(_name: string): NativeWebGLCanvas {
|
|
7
|
+
return createNativeWebGLCanvas(300, 150)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Three's browser fallback, backed by the real native ANGLE canvas. */
|
|
11
|
+
export function createCanvasElement(): NativeWebGLCanvas { return createElementNS('canvas') }
|